mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:38:18 -04:00 
			
		
		
		
	feat: Add Households to Mealie (#3970)
This commit is contained in:
		
							parent
							
								
									0c29cef17d
								
							
						
					
					
						commit
						eb170cc7e5
					
				| @ -13,8 +13,7 @@ from sqlalchemy import orm | ||||
| 
 | ||||
| import mealie.db.migration_types | ||||
| from alembic import op | ||||
| from mealie.db.models.group.shopping_list import ShoppingList | ||||
| from mealie.db.models.labels import MultiPurposeLabel | ||||
| from mealie.db.models._model_utils.guid import GUID | ||||
| 
 | ||||
| # revision identifiers, used by Alembic. | ||||
| revision = "b04a08da2108" | ||||
| @ -23,6 +22,25 @@ branch_labels = None | ||||
| depends_on = None | ||||
| 
 | ||||
| 
 | ||||
| # Intermediate table definitions | ||||
| class SqlAlchemyBase(orm.DeclarativeBase): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class ShoppingList(SqlAlchemyBase): | ||||
|     __tablename__ = "shopping_lists" | ||||
| 
 | ||||
|     id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate) | ||||
|     group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True) | ||||
| 
 | ||||
| 
 | ||||
| class MultiPurposeLabel(SqlAlchemyBase): | ||||
|     __tablename__ = "multi_purpose_labels" | ||||
| 
 | ||||
|     id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate) | ||||
|     group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True) | ||||
| 
 | ||||
| 
 | ||||
| def populate_shopping_lists_multi_purpose_labels( | ||||
|     shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session | ||||
| ): | ||||
|  | ||||
| @ -12,11 +12,11 @@ from typing import Any | ||||
| 
 | ||||
| import sqlalchemy as sa | ||||
| from pydantic import UUID4 | ||||
| from sqlalchemy import orm | ||||
| from sqlalchemy.orm import Session, load_only | ||||
| 
 | ||||
| from alembic import op | ||||
| from mealie.db.models._model_base import SqlAlchemyBase | ||||
| from mealie.db.models.group.shopping_list import ShoppingListItem | ||||
| from mealie.db.models._model_utils.guid import GUID | ||||
| from mealie.db.models.labels import MultiPurposeLabel | ||||
| from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel | ||||
| 
 | ||||
| @ -27,6 +27,27 @@ branch_labels = None | ||||
| depends_on = None | ||||
| 
 | ||||
| 
 | ||||
| # Intermediate table definitions | ||||
| class SqlAlchemyBase(orm.DeclarativeBase): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class ShoppingList(SqlAlchemyBase): | ||||
|     __tablename__ = "shopping_lists" | ||||
| 
 | ||||
|     id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate) | ||||
|     group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True) | ||||
| 
 | ||||
| 
 | ||||
| class ShoppingListItem(SqlAlchemyBase): | ||||
|     __tablename__ = "shopping_list_items" | ||||
| 
 | ||||
|     id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate) | ||||
|     food_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_foods.id")) | ||||
|     unit_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_units.id")) | ||||
|     label_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("multi_purpose_labels.id")) | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class TableMeta: | ||||
|     tablename: str | ||||
| @ -42,7 +63,7 @@ def _is_postgres(): | ||||
|     return op.get_context().dialect.name == "postgresql" | ||||
| 
 | ||||
| 
 | ||||
| def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list]: | ||||
| def _get_duplicates(session: Session, model: orm.DeclarativeBase) -> defaultdict[str, list]: | ||||
|     duplicate_map: defaultdict[str, list] = defaultdict(list) | ||||
| 
 | ||||
|     query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}")) | ||||
|  | ||||
| @ -0,0 +1,321 @@ | ||||
| """add households | ||||
| 
 | ||||
| Revision ID: feecc8ffb956 | ||||
| Revises: 32d69327997b | ||||
| Create Date: 2024-07-12 16:16:29.973929 | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| from datetime import datetime, timezone | ||||
| from textwrap import dedent | ||||
| from typing import Any | ||||
| from uuid import uuid4 | ||||
| 
 | ||||
| import sqlalchemy as sa | ||||
| from slugify import slugify | ||||
| from sqlalchemy import orm | ||||
| 
 | ||||
| import mealie.db.migration_types | ||||
| from alembic import op | ||||
| from mealie.core.config import get_app_settings | ||||
| 
 | ||||
| # revision identifiers, used by Alembic. | ||||
| revision = "feecc8ffb956" | ||||
| down_revision = "32d69327997b" | ||||
| branch_labels = None  # type: ignore | ||||
| depends_on = None  # type: ignore | ||||
| 
 | ||||
| settings = get_app_settings() | ||||
| 
 | ||||
| 
 | ||||
| def is_postgres(): | ||||
|     return op.get_context().dialect.name == "postgresql" | ||||
| 
 | ||||
| 
 | ||||
| def generate_id() -> str: | ||||
|     """See GUID.convert_value_to_guid""" | ||||
|     val = uuid4() | ||||
|     if is_postgres(): | ||||
|         return str(val) | ||||
|     else: | ||||
|         return f"{val.int:032x}" | ||||
| 
 | ||||
| 
 | ||||
| def dedupe_cookbook_slugs(): | ||||
|     bind = op.get_bind() | ||||
|     session = orm.Session(bind=bind) | ||||
|     with session: | ||||
|         sql = sa.text( | ||||
|             dedent( | ||||
|                 """ | ||||
|                 SELECT slug, group_id, COUNT(*) | ||||
|                 FROM cookbooks | ||||
|                 GROUP BY slug, group_id | ||||
|                 HAVING COUNT(*) > 1 | ||||
|                 """ | ||||
|             ) | ||||
|         ) | ||||
|         rows = session.execute(sql).fetchall() | ||||
| 
 | ||||
|         for slug, group_id, _ in rows: | ||||
|             sql = sa.text( | ||||
|                 dedent( | ||||
|                     """ | ||||
|                     SELECT id | ||||
|                     FROM cookbooks | ||||
|                     WHERE slug = :slug AND group_id = :group_id | ||||
|                     ORDER BY id | ||||
|                     """ | ||||
|                 ) | ||||
|             ) | ||||
|             cookbook_ids = session.execute(sql, {"slug": slug, "group_id": group_id}).fetchall() | ||||
| 
 | ||||
|             for i, (cookbook_id,) in enumerate(cookbook_ids): | ||||
|                 if i == 0: | ||||
|                     continue | ||||
| 
 | ||||
|                 sql = sa.text( | ||||
|                     dedent( | ||||
|                         """ | ||||
|                         UPDATE cookbooks | ||||
|                         SET slug = :slug || '-' || :i | ||||
|                         WHERE id = :id | ||||
|                         """ | ||||
|                     ) | ||||
|                 ) | ||||
|                 session.execute(sql, {"slug": slug, "i": i, "id": cookbook_id}) | ||||
| 
 | ||||
| 
 | ||||
| def create_household(session: orm.Session, group_id: str) -> str: | ||||
|     # create/insert household | ||||
|     household_id = generate_id() | ||||
|     timestamp = datetime.now(timezone.utc).isoformat() | ||||
|     household_data = { | ||||
|         "id": household_id, | ||||
|         "name": settings.DEFAULT_HOUSEHOLD, | ||||
|         "slug": slugify(settings.DEFAULT_HOUSEHOLD), | ||||
|         "group_id": group_id, | ||||
|         "created_at": timestamp, | ||||
|         "update_at": timestamp, | ||||
|     } | ||||
|     columns = ", ".join(household_data.keys()) | ||||
|     placeholders = ", ".join(f":{key}" for key in household_data.keys()) | ||||
|     sql_statement = f"INSERT INTO households ({columns}) VALUES ({placeholders})" | ||||
| 
 | ||||
|     session.execute(sa.text(sql_statement), household_data) | ||||
| 
 | ||||
|     # fetch group preferences so we can copy them over to household preferences | ||||
|     migrated_field_defaults = { | ||||
|         "private_group": True,  # this is renamed later | ||||
|         "first_day_of_week": 0, | ||||
|         "recipe_public": True, | ||||
|         "recipe_show_nutrition": False, | ||||
|         "recipe_show_assets": False, | ||||
|         "recipe_landscape_view": False, | ||||
|         "recipe_disable_comments": False, | ||||
|         "recipe_disable_amount": True, | ||||
|     } | ||||
|     sql_statement = ( | ||||
|         f"SELECT {', '.join(migrated_field_defaults.keys())} FROM group_preferences WHERE group_id = :group_id" | ||||
|     ) | ||||
|     group_preferences = session.execute(sa.text(sql_statement), {"group_id": group_id}).fetchone() | ||||
| 
 | ||||
|     # build preferences data | ||||
|     if group_preferences: | ||||
|         preferences_data: dict[str, Any] = {} | ||||
|         for i, (field, default_value) in enumerate(migrated_field_defaults.items()): | ||||
|             value = group_preferences[i] | ||||
|             preferences_data[field] = value if value is not None else default_value | ||||
|     else: | ||||
|         preferences_data = migrated_field_defaults | ||||
| 
 | ||||
|     preferences_data["id"] = generate_id() | ||||
|     preferences_data["household_id"] = household_id | ||||
|     preferences_data["created_at"] = timestamp | ||||
|     preferences_data["update_at"] = timestamp | ||||
|     preferences_data["private_household"] = preferences_data.pop("private_group") | ||||
| 
 | ||||
|     # insert preferences data | ||||
|     columns = ", ".join(preferences_data.keys()) | ||||
|     placeholders = ", ".join(f":{key}" for key in preferences_data.keys()) | ||||
|     sql_statement = f"INSERT INTO household_preferences ({columns}) VALUES ({placeholders})" | ||||
| 
 | ||||
|     session.execute(sa.text(sql_statement), preferences_data) | ||||
| 
 | ||||
|     return household_id | ||||
| 
 | ||||
| 
 | ||||
| def create_households_for_groups() -> dict[str, str]: | ||||
|     bind = op.get_bind() | ||||
|     session = orm.Session(bind=bind) | ||||
|     group_id_household_id_map: dict[str, str] = {} | ||||
|     with session: | ||||
|         rows = session.execute(sa.text("SELECT id FROM groups")).fetchall() | ||||
|         for row in rows: | ||||
|             group_id = row[0] | ||||
|             group_id_household_id_map[group_id] = create_household(session, group_id) | ||||
| 
 | ||||
|     return group_id_household_id_map | ||||
| 
 | ||||
| 
 | ||||
| def _do_assignment(session: orm.Session, table: str, group_id: str, household_id: str): | ||||
|     sql = sa.text( | ||||
|         dedent( | ||||
|             f""" | ||||
|             UPDATE {table} | ||||
|             SET household_id = :household_id | ||||
|             WHERE group_id = :group_id | ||||
|             """, | ||||
|         ) | ||||
|     ) | ||||
|     session.execute(sql, {"group_id": group_id, "household_id": household_id}) | ||||
| 
 | ||||
| 
 | ||||
| def assign_households(group_id_household_id_map: dict[str, str]): | ||||
|     tables = [ | ||||
|         "cookbooks", | ||||
|         "group_events_notifiers", | ||||
|         "group_meal_plan_rules", | ||||
|         "invite_tokens", | ||||
|         "recipe_actions", | ||||
|         "users", | ||||
|         "webhook_urls", | ||||
|     ] | ||||
| 
 | ||||
|     bind = op.get_bind() | ||||
|     session = orm.Session(bind=bind) | ||||
|     with session: | ||||
|         for table in tables: | ||||
|             for group_id, household_id in group_id_household_id_map.items(): | ||||
|                 _do_assignment(session, table, group_id, household_id) | ||||
| 
 | ||||
| 
 | ||||
| def populate_household_data(): | ||||
|     group_id_household_id_map = create_households_for_groups() | ||||
|     assign_households(group_id_household_id_map) | ||||
| 
 | ||||
| 
 | ||||
| def upgrade(): | ||||
|     dedupe_cookbook_slugs() | ||||
| 
 | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.create_table( | ||||
|         "households", | ||||
|         sa.Column("id", mealie.db.migration_types.GUID(), nullable=False), | ||||
|         sa.Column("name", sa.String(), nullable=False), | ||||
|         sa.Column("slug", sa.String(), nullable=True), | ||||
|         sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False), | ||||
|         sa.Column("created_at", sa.DateTime(), nullable=True), | ||||
|         sa.Column("update_at", sa.DateTime(), nullable=True), | ||||
|         sa.ForeignKeyConstraint( | ||||
|             ["group_id"], | ||||
|             ["groups.id"], | ||||
|         ), | ||||
|         sa.PrimaryKeyConstraint("id"), | ||||
|         sa.UniqueConstraint("group_id", "name", name="household_name_group_id_key"), | ||||
|         sa.UniqueConstraint("group_id", "slug", name="household_slug_group_id_key"), | ||||
|     ) | ||||
|     op.create_index(op.f("ix_households_created_at"), "households", ["created_at"], unique=False) | ||||
|     op.create_index(op.f("ix_households_group_id"), "households", ["group_id"], unique=False) | ||||
|     op.create_index(op.f("ix_households_name"), "households", ["name"], unique=False) | ||||
|     op.create_index(op.f("ix_households_slug"), "households", ["slug"], unique=False) | ||||
|     op.create_table( | ||||
|         "household_preferences", | ||||
|         sa.Column("id", mealie.db.migration_types.GUID(), nullable=False), | ||||
|         sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=False), | ||||
|         sa.Column("private_household", sa.Boolean(), nullable=True), | ||||
|         sa.Column("first_day_of_week", sa.Integer(), nullable=True), | ||||
|         sa.Column("recipe_public", sa.Boolean(), nullable=True), | ||||
|         sa.Column("recipe_show_nutrition", sa.Boolean(), nullable=True), | ||||
|         sa.Column("recipe_show_assets", sa.Boolean(), nullable=True), | ||||
|         sa.Column("recipe_landscape_view", sa.Boolean(), nullable=True), | ||||
|         sa.Column("recipe_disable_comments", sa.Boolean(), nullable=True), | ||||
|         sa.Column("recipe_disable_amount", sa.Boolean(), nullable=True), | ||||
|         sa.Column("created_at", sa.DateTime(), nullable=True), | ||||
|         sa.Column("update_at", sa.DateTime(), nullable=True), | ||||
|         sa.ForeignKeyConstraint( | ||||
|             ["household_id"], | ||||
|             ["households.id"], | ||||
|         ), | ||||
|         sa.PrimaryKeyConstraint("id"), | ||||
|     ) | ||||
|     op.create_index(op.f("ix_household_preferences_created_at"), "household_preferences", ["created_at"], unique=False) | ||||
|     op.create_index( | ||||
|         op.f("ix_household_preferences_household_id"), "household_preferences", ["household_id"], unique=False | ||||
|     ) | ||||
| 
 | ||||
|     with op.batch_alter_table("cookbooks") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_cookbooks_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_cookbooks_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|         # not directly related to households, but important for frontend routes | ||||
|         batch_op.create_unique_constraint("cookbook_slug_group_id_key", ["slug", "group_id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("group_events_notifiers") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_group_events_notifiers_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_group_events_notifiers_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("group_meal_plan_rules") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_group_meal_plan_rules_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_group_meal_plan_rules_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("invite_tokens") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_invite_tokens_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_invite_tokens_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("recipe_actions") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_recipe_actions_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_recipe_actions_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("users") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_users_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_users_household_id", "households", ["household_id"], ["id"]) | ||||
| 
 | ||||
|     with op.batch_alter_table("webhook_urls") as batch_op: | ||||
|         batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True)) | ||||
|         batch_op.create_index(op.f("ix_webhook_urls_household_id"), ["household_id"], unique=False) | ||||
|         batch_op.create_foreign_key("fk_webhook_urls_household_id", "households", ["household_id"], ["id"]) | ||||
|     # ### end Alembic commands ### | ||||
| 
 | ||||
|     populate_household_data() | ||||
| 
 | ||||
| 
 | ||||
| def downgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_constraint(None, "webhook_urls", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_webhook_urls_household_id"), table_name="webhook_urls") | ||||
|     op.drop_column("webhook_urls", "household_id") | ||||
|     op.drop_constraint(None, "users", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_users_household_id"), table_name="users") | ||||
|     op.drop_column("users", "household_id") | ||||
|     op.drop_constraint(None, "recipe_actions", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_recipe_actions_household_id"), table_name="recipe_actions") | ||||
|     op.drop_column("recipe_actions", "household_id") | ||||
|     op.drop_constraint(None, "invite_tokens", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_invite_tokens_household_id"), table_name="invite_tokens") | ||||
|     op.drop_column("invite_tokens", "household_id") | ||||
|     op.drop_constraint(None, "group_meal_plan_rules", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_group_meal_plan_rules_household_id"), table_name="group_meal_plan_rules") | ||||
|     op.drop_column("group_meal_plan_rules", "household_id") | ||||
|     op.drop_constraint(None, "group_events_notifiers", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_group_events_notifiers_household_id"), table_name="group_events_notifiers") | ||||
|     op.drop_column("group_events_notifiers", "household_id") | ||||
|     op.drop_constraint(None, "cookbooks", type_="foreignkey") | ||||
|     op.drop_index(op.f("ix_cookbooks_household_id"), table_name="cookbooks") | ||||
|     op.drop_column("cookbooks", "household_id") | ||||
|     op.drop_constraint("cookbook_slug_group_id_key", "cookbooks", type_="unique") | ||||
|     op.drop_index(op.f("ix_household_preferences_household_id"), table_name="household_preferences") | ||||
|     op.drop_index(op.f("ix_household_preferences_created_at"), table_name="household_preferences") | ||||
|     op.drop_table("household_preferences") | ||||
|     op.drop_index(op.f("ix_households_slug"), table_name="households") | ||||
|     op.drop_index(op.f("ix_households_name"), table_name="households") | ||||
|     op.drop_index(op.f("ix_households_group_id"), table_name="households") | ||||
|     op.drop_index(op.f("ix_households_created_at"), table_name="households") | ||||
|     op.drop_table("households") | ||||
|     # ### end Alembic commands ### | ||||
| @ -67,7 +67,7 @@ def rename_non_compliant_paths(): | ||||
|     kabab case. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_files = ["DS_Store", ".gitkeep"] | ||||
|     ignore_files = ["DS_Store", ".gitkeep", "af-ZA.json", "en-US.json"] | ||||
| 
 | ||||
|     ignore_extensions = [".pyc", ".pyo", ".py"] | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import logging | ||||
| import re | ||||
| import subprocess | ||||
| from dataclasses import dataclass | ||||
| from pathlib import Path | ||||
| 
 | ||||
| @ -23,6 +24,11 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict): | ||||
| 
 | ||||
|     dest.write_text(text) | ||||
| 
 | ||||
|     # lint/format file with Ruff | ||||
|     log.info(f"Formatting {dest}") | ||||
|     subprocess.run(["poetry", "run", "ruff", "check", str(dest), "--fix"]) | ||||
|     subprocess.run(["poetry", "run", "ruff", "format", str(dest)]) | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class CodeSlicer: | ||||
|  | ||||
| @ -173,7 +173,7 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic | ||||
|         "dateAdded": "2022-09-03", | ||||
|         "dateUpdated": "2022-09-10T15:18:19.866085", | ||||
|         "createdAt": "2022-09-03T18:31:17.488118", | ||||
|         "updateAt": "2022-09-10T15:18:19.869630", | ||||
|         "updatedAt": "2022-09-10T15:18:19.869630", | ||||
|         "recipeInstructions": [ | ||||
|             { | ||||
|                 "id": "60ae53a3-b3ff-40ee-bae3-89fea0b1c637", | ||||
|  | ||||
| @ -24,7 +24,7 @@ Make sure the url and port (`http://mealie:9000` ) matches your installation's a | ||||
| 
 | ||||
| ```yaml | ||||
| - platform: rest | ||||
|   resource: "http://mealie:9000/api/groups/mealplans/today" | ||||
|   resource: "http://mealie:9000/api/households/mealplans/today" | ||||
|   method: GET | ||||
|   name: Mealie todays meal | ||||
|   headers: | ||||
| @ -36,7 +36,7 @@ Make sure the url and port (`http://mealie:9000` ) matches your installation's a | ||||
| 
 | ||||
| ```yaml | ||||
| - platform: rest | ||||
|   resource: "http://mealie:9000/api/groups/mealplans/today" | ||||
|   resource: "http://mealie:9000/api/households/mealplans/today" | ||||
|   method: GET | ||||
|   name: Mealie todays meal ID | ||||
|   headers: | ||||
|  | ||||
| @ -73,13 +73,13 @@ Mealie uses a calendar like view to help you plan your meals. It shows you the p | ||||
| !!! tip | ||||
|     You can also add a "Note" type entry to your meal-plan when you want to include something that might not have a specific recipes. This is great for leftovers, or for ordering out. | ||||
| 
 | ||||
| [Mealplanner Demo](https://demo.mealie.io/group/mealplan/planner/view){ .md-button .md-button--primary } | ||||
| [Mealplanner Demo](https://demo.mealie.io/household/mealplan/planner/view){ .md-button .md-button--primary } | ||||
| 
 | ||||
| ### Planner Rules | ||||
| 
 | ||||
| The meal planner has the concept of plan rules. These offer a flexible way to use your organizers to customize how a random recipe is inserted into your meal plan. You can set rules to restrict the pool of recipes based on the Tags and/or Categories of a recipe. Additionally, since meal plans have a Breakfast, Lunch, Dinner, and Snack labels, you can specifically set a rule to be active for a **specific meal type** or even a **specific day of the week.** | ||||
| 
 | ||||
| [Planner Settings Demo](https://demo.mealie.io/group/mealplan/settings){ .md-button .md-button--primary } | ||||
| [Planner Settings Demo](https://demo.mealie.io/household/mealplan/settings){ .md-button .md-button--primary } | ||||
| 
 | ||||
| ## Shopping Lists | ||||
| 
 | ||||
| @ -105,13 +105,13 @@ Notifiers use the [Apprise library](https://github.com/caronc/apprise/wiki), whi | ||||
| - `json` and `jsons` | ||||
| - `xml` and `xmls` | ||||
| 
 | ||||
| [Notifiers Demo](https://demo.mealie.io/group/notifiers){ .md-button .md-button--primary } | ||||
| [Notifiers Demo](https://demo.mealie.io/household/notifiers){ .md-button .md-button--primary } | ||||
| 
 | ||||
| ### Webhooks | ||||
| 
 | ||||
| Unlike notifiers, which are event-driven notifications, Webhooks allow you to send scheduled notifications to your desired endpoint. Webhooks are sent on the day of a scheduled mealplan, at the specified time, and contain the mealplan data in the request. | ||||
| 
 | ||||
| [Webhooks Demo](https://demo.mealie.io/group/webhooks){ .md-button .md-button--primary } | ||||
| [Webhooks Demo](https://demo.mealie.io/household/webhooks){ .md-button .md-button--primary } | ||||
| 
 | ||||
| ### Recipe Actions | ||||
| 
 | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -2,30 +2,11 @@ | ||||
|   <div v-if="preferences"> | ||||
|     <BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle> | ||||
|     <v-checkbox v-model="preferences.privateGroup" class="mt-n4" :label="$t('group.private-group')"></v-checkbox> | ||||
|     <v-select | ||||
|       v-model="preferences.firstDayOfWeek" | ||||
|       :prepend-icon="$globals.icons.calendarWeekBegin" | ||||
|       :items="allDays" | ||||
|       item-text="name" | ||||
|       item-value="value" | ||||
|       :label="$t('settings.first-day-of-week')" | ||||
|     /> | ||||
| 
 | ||||
|     <BaseCardSectionTitle class="mt-5" :title="$tc('group.group-recipe-preferences')"></BaseCardSectionTitle> | ||||
|     <template v-for="(_, key) in preferences"> | ||||
|       <v-checkbox | ||||
|         v-if="labels[key]" | ||||
|         :key="key" | ||||
|         v-model="preferences[key]" | ||||
|         class="mt-n4" | ||||
|         :label="labels[key]" | ||||
|       ></v-checkbox> | ||||
|     </template> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, computed, useContext } from "@nuxtjs/composition-api"; | ||||
| import { defineComponent, computed } from "@nuxtjs/composition-api"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   props: { | ||||
| @ -35,48 +16,6 @@ export default defineComponent({ | ||||
|     }, | ||||
|   }, | ||||
|   setup(props, context) { | ||||
|     const { i18n } = useContext(); | ||||
| 
 | ||||
|     const labels = { | ||||
|       recipePublic: i18n.tc("group.allow-users-outside-of-your-group-to-see-your-recipes"), | ||||
|       recipeShowNutrition: i18n.tc("group.show-nutrition-information"), | ||||
|       recipeShowAssets: i18n.tc("group.show-recipe-assets"), | ||||
|       recipeLandscapeView: i18n.tc("group.default-to-landscape-view"), | ||||
|       recipeDisableComments: i18n.tc("group.disable-users-from-commenting-on-recipes"), | ||||
|       recipeDisableAmount: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"), | ||||
|     }; | ||||
| 
 | ||||
|     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, | ||||
|       }, | ||||
|     ]; | ||||
| 
 | ||||
|     const preferences = computed({ | ||||
|       get() { | ||||
|         return props.value; | ||||
| @ -87,8 +26,6 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|       allDays, | ||||
|       labels, | ||||
|       preferences, | ||||
|     }; | ||||
|   }, | ||||
|  | ||||
| @ -39,7 +39,7 @@ | ||||
| import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api"; | ||||
| import { Recipe } from "~/lib/api/types/recipe"; | ||||
| import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue"; | ||||
| import { ShoppingListSummary } from "~/lib/api/types/group"; | ||||
| import { ShoppingListSummary } from "~/lib/api/types/household"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| 
 | ||||
| export interface ContextMenuItem { | ||||
| @ -35,7 +35,7 @@ | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, computed, ref } from "@nuxtjs/composition-api"; | ||||
| import { ReadWebhook } from "~/lib/api/types/group"; | ||||
| import { ReadWebhook } from "~/lib/api/types/household"; | ||||
| import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| @ -0,0 +1,99 @@ | ||||
| <template> | ||||
|   <div v-if="preferences"> | ||||
|     <BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle> | ||||
|     <v-checkbox v-model="preferences.privateHousehold" class="mt-n4" :label="$t('household.private-household')"></v-checkbox> | ||||
|     <v-select | ||||
|       v-model="preferences.firstDayOfWeek" | ||||
|       :prepend-icon="$globals.icons.calendarWeekBegin" | ||||
|       :items="allDays" | ||||
|       item-text="name" | ||||
|       item-value="value" | ||||
|       :label="$t('settings.first-day-of-week')" | ||||
|     /> | ||||
| 
 | ||||
|     <BaseCardSectionTitle class="mt-5" :title="$tc('household.household-recipe-preferences')"></BaseCardSectionTitle> | ||||
|     <template v-for="(_, key) in preferences"> | ||||
|       <v-checkbox | ||||
|         v-if="labels[key]" | ||||
|         :key="key" | ||||
|         v-model="preferences[key]" | ||||
|         class="mt-n4" | ||||
|         :label="labels[key]" | ||||
|       ></v-checkbox> | ||||
|     </template> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, computed, useContext } from "@nuxtjs/composition-api"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   props: { | ||||
|     value: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|   setup(props, context) { | ||||
|     const { i18n } = useContext(); | ||||
| 
 | ||||
|     const labels = { | ||||
|       recipePublic: i18n.tc("household.allow-users-outside-of-your-household-to-see-your-recipes"), | ||||
|       recipeShowNutrition: i18n.tc("group.show-nutrition-information"), | ||||
|       recipeShowAssets: i18n.tc("group.show-recipe-assets"), | ||||
|       recipeLandscapeView: i18n.tc("group.default-to-landscape-view"), | ||||
|       recipeDisableComments: i18n.tc("group.disable-users-from-commenting-on-recipes"), | ||||
|       recipeDisableAmount: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"), | ||||
|     }; | ||||
| 
 | ||||
|     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, | ||||
|       }, | ||||
|     ]; | ||||
| 
 | ||||
|     const preferences = computed({ | ||||
|       get() { | ||||
|         return props.value; | ||||
|       }, | ||||
|       set(val) { | ||||
|         context.emit("input", val); | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|       allDays, | ||||
|       labels, | ||||
|       preferences, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| </style> | ||||
| @ -337,7 +337,7 @@ export default defineComponent({ | ||||
|           ); | ||||
|           break; | ||||
|         case EVENTS.updated: | ||||
|           setter("update_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false); | ||||
|           setter("updated_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false); | ||||
|           break; | ||||
|         case EVENTS.lastMade: | ||||
|           setter( | ||||
|  | ||||
| @ -138,11 +138,11 @@ import RecipeDialogShare from "./RecipeDialogShare.vue"; | ||||
| import { useLoggedInState } from "~/composables/use-logged-in-state"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| import { usePlanTypeOptions } from "~/composables/use-group-mealplan"; | ||||
| import { Recipe } from "~/lib/api/types/recipe"; | ||||
| import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group"; | ||||
| import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/household"; | ||||
| import { PlanEntryType } from "~/lib/api/types/meal-plan"; | ||||
| import { useAxiosDownloader } from "~/composables/api/use-axios-download"; | ||||
| 
 | ||||
| @ -254,14 +254,14 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     const { i18n, $auth, $globals } = useContext(); | ||||
|     const { group } = useGroupSelf(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
|     const { isOwnGroup } = useLoggedInState(); | ||||
| 
 | ||||
|     const route = useRoute(); | ||||
|     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); | ||||
| 
 | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return group.value?.preferences?.firstDayOfWeek || 0; | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
| 
 | ||||
|     // =========================================================================== | ||||
|  | ||||
| @ -18,7 +18,7 @@ | ||||
|       </tr> | ||||
|     </template> | ||||
|     <template #item.name="{ item }"> | ||||
|       <a :href="`/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a> | ||||
|       <a :href="`/g/${groupSlug}/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a> | ||||
|     </template> | ||||
|     <template #item.tags="{ item }"> | ||||
|       <RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" /> | ||||
| @ -48,7 +48,7 @@ import UserAvatar from "../User/UserAvatar.vue"; | ||||
| import RecipeChip from "./RecipeChips.vue"; | ||||
| import { Recipe } from "~/lib/api/types/recipe"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { UserOut } from "~/lib/api/types/user"; | ||||
| import { UserSummary } from "~/lib/api/types/user"; | ||||
| 
 | ||||
| const INPUT_EVENT = "input"; | ||||
| 
 | ||||
| @ -95,7 +95,8 @@ export default defineComponent({ | ||||
|     }, | ||||
|   }, | ||||
|   setup(props, context) { | ||||
|     const { i18n } = useContext(); | ||||
|     const { $auth, i18n } = useContext(); | ||||
|     const groupSlug = $auth.user?.groupSlug; | ||||
| 
 | ||||
|     function setValue(value: Recipe[]) { | ||||
|       context.emit(INPUT_EVENT, value); | ||||
| @ -134,7 +135,7 @@ export default defineComponent({ | ||||
|     // ============ | ||||
|     // Group Members | ||||
|     const api = useUserApi(); | ||||
|     const members = ref<UserOut[]>([]); | ||||
|     const members = ref<UserSummary[]>([]); | ||||
| 
 | ||||
|     async function refreshMembers() { | ||||
|       const { data } = await api.groups.fetchMembers(); | ||||
| @ -149,13 +150,19 @@ export default defineComponent({ | ||||
| 
 | ||||
|     function getMember(id: string) { | ||||
|       if (members.value[0]) { | ||||
|         return members.value.find((m) => m.id === id)?.username; | ||||
|         return members.value.find((m) => m.id === id)?.fullName; | ||||
|       } | ||||
| 
 | ||||
|       return i18n.t("general.none"); | ||||
|     } | ||||
| 
 | ||||
|     return { setValue, headers, members, getMember }; | ||||
|     return { | ||||
|       groupSlug, | ||||
|       setValue, | ||||
|       headers, | ||||
|       members, | ||||
|       getMember, | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   data() { | ||||
|  | ||||
| @ -127,14 +127,14 @@ | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|   import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api" | ||||
|   import { toRefs } from "@vueuse/core" | ||||
|   import RecipeIngredientListItem from "./RecipeIngredientListItem.vue" | ||||
|   import { useUserApi } from "~/composables/api" | ||||
|   import { alert } from "~/composables/use-toast" | ||||
|   import { useShoppingListPreferences } from "~/composables/use-users/preferences" | ||||
|   import { ShoppingListSummary } from "~/lib/api/types/group" | ||||
|   import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe" | ||||
| import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api"; | ||||
| import { toRefs } from "@vueuse/core"; | ||||
| import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| import { useShoppingListPreferences } from "~/composables/use-users/preferences"; | ||||
| import { ShoppingListSummary } from "~/lib/api/types/household"; | ||||
| import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| export interface RecipeWithScale extends Recipe { | ||||
|   scale: number; | ||||
| @ -209,7 +209,8 @@ export default defineComponent({ | ||||
|     watchEffect( | ||||
|       () => { | ||||
|         if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) { | ||||
|           openShoppingListIngredientDialog(shoppingListChoices.value[0]); | ||||
|           selectedShoppingList.value = shoppingListChoices.value[0]; | ||||
|           openShoppingListIngredientDialog(selectedShoppingList.value); | ||||
|         } else { | ||||
|           ready.value = true; | ||||
|         } | ||||
| @ -365,12 +366,8 @@ export default defineComponent({ | ||||
|         } | ||||
|       }) | ||||
| 
 | ||||
|       const successMessage = promises.length === 1 | ||||
|         ? i18n.t("recipe.successfully-added-to-list") as string | ||||
|         : i18n.t("recipe.failed-to-add-to-list") as string; | ||||
| 
 | ||||
|       success ? alert.success(successMessage) | ||||
|       : alert.error(i18n.t("failed-to-add-recipes-to-list") as string) | ||||
|       success ? alert.success(i18n.tc("recipe.successfully-added-to-list")) | ||||
|       : alert.error(i18n.tc("failed-to-add-recipes-to-list")) | ||||
| 
 | ||||
|       state.shoppingListDialog = false; | ||||
|       state.shoppingListIngredientDialog = false; | ||||
|  | ||||
| @ -66,7 +66,7 @@ import { defineComponent, computed, toRefs, reactive, useContext, useRoute } fro | ||||
| import { useClipboard, useShare, whenever } from "@vueuse/core"; | ||||
| import { RecipeShareToken } from "~/lib/api/types/recipe"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| @ -113,12 +113,12 @@ export default defineComponent({ | ||||
|     ); | ||||
| 
 | ||||
|     const { $auth, i18n } = useContext(); | ||||
|     const { group } = useGroupSelf(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
|     const route = useRoute(); | ||||
|     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); | ||||
| 
 | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return group.value?.preferences?.firstDayOfWeek || 0; | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
| 
 | ||||
|     // ============================================================ | ||||
|  | ||||
| @ -349,7 +349,7 @@ export default defineComponent({ | ||||
|       { | ||||
|         icon: $globals.icons.update, | ||||
|         name: i18n.tc("general.updated"), | ||||
|         value: "update_at", | ||||
|         value: "updated_at", | ||||
|       }, | ||||
|       { | ||||
|         icon: $globals.icons.diceMultiple, | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
| </template> | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent } from "@nuxtjs/composition-api"; | ||||
| import { RecipeIngredient } from "~/lib/api/types/group"; | ||||
| import { RecipeIngredient } from "~/lib/api/types/household"; | ||||
| import { useParsedIngredientText } from "~/composables/recipes"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|  | ||||
| @ -114,7 +114,7 @@ import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@n | ||||
| import { whenever } from "@vueuse/core"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| @ -131,7 +131,7 @@ export default defineComponent({ | ||||
|   setup(props, context) { | ||||
|     const madeThisDialog = ref(false); | ||||
|     const userApi = useUserApi(); | ||||
|     const { group } = useGroupSelf(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
|     const { $auth, i18n } = useContext(); | ||||
|     const domMadeThisForm = ref<VForm>(); | ||||
|     const newTimelineEvent = ref<RecipeTimelineEventIn>({ | ||||
| @ -157,7 +157,7 @@ export default defineComponent({ | ||||
|     ); | ||||
| 
 | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return group.value?.preferences?.firstDayOfWeek || 0; | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
| 
 | ||||
|     function clearImage() { | ||||
|  | ||||
| @ -31,7 +31,7 @@ | ||||
| import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api"; | ||||
| import DOMPurify from "dompurify"; | ||||
| import { useFraction } from "~/composables/recipes/use-fraction"; | ||||
| import { ShoppingListItemOut } from "~/lib/api/types/group"; | ||||
| import { ShoppingListItemOut } from "~/lib/api/types/household"; | ||||
| import { RecipeSummary } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|  | ||||
| @ -29,7 +29,7 @@ | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; | ||||
| import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/group"; | ||||
| import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| interface actions { | ||||
|   text: string; | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|     <v-row v-if="listItem.checked" no-gutters class="mb-2"> | ||||
|       <v-col cols="auto"> | ||||
|         <div class="text-caption font-weight-light font-italic"> | ||||
|           {{ $t("shopping-list.completed-on", {date: new Date(listItem.updateAt || "").toLocaleDateString($i18n.locale)}) }} | ||||
|           {{ $t("shopping-list.completed-on", {date: new Date(listItem.updatedAt || "").toLocaleDateString($i18n.locale)}) }} | ||||
|         </div> | ||||
|       </v-col> | ||||
|     </v-row> | ||||
| @ -99,7 +99,7 @@ import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition- | ||||
| import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue"; | ||||
| import ShoppingListItemEditor from "./ShoppingListItemEditor.vue"; | ||||
| import MultiPurposeLabel from "./MultiPurposeLabel.vue"; | ||||
| import { ShoppingListItemOut } from "~/lib/api/types/group"; | ||||
| import { ShoppingListItemOut } from "~/lib/api/types/household"; | ||||
| import { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels"; | ||||
| import { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe"; | ||||
| import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | ||||
|  | ||||
| @ -111,7 +111,7 @@ | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, computed, watch } from "@nuxtjs/composition-api"; | ||||
| import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group"; | ||||
| import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/household"; | ||||
| import { MultiPurposeLabelOut } from "~/lib/api/types/labels"; | ||||
| import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe"; | ||||
| import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store"; | ||||
|  | ||||
| @ -183,7 +183,7 @@ export default defineComponent({ | ||||
|       { | ||||
|         icon: $globals.icons.calendarMultiselect, | ||||
|         title: i18n.tc("meal-plan.meal-planner"), | ||||
|         to: "/group/mealplan/planner/view", | ||||
|         to: "/household/mealplan/planner/view", | ||||
|         restricted: true, | ||||
|       }, | ||||
|       { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <v-toolbar flat> | ||||
|   <v-toolbar color="transparent" flat> | ||||
|     <BaseButton color="null" rounded secondary @click="$router.go(-1)"> | ||||
|       <template #icon> {{ $globals.icons.arrowLeftBold }}</template> | ||||
|       {{ $t('general.back') }} | ||||
|  | ||||
| @ -85,6 +85,8 @@ | ||||
|           :label="inputField.label" | ||||
|           :name="inputField.varName" | ||||
|           :items="inputField.options" | ||||
|           :item-text="inputField.itemText" | ||||
|           :item-value="inputField.itemValue" | ||||
|           :return-object="false" | ||||
|           :hint="inputField.hint" | ||||
|           persistent-hint | ||||
|  | ||||
| @ -4,7 +4,7 @@ import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; | ||||
| import { QueryValue } from "~/lib/api/base/route"; | ||||
| 
 | ||||
| type BoundT = { | ||||
|   id?: string | number; | ||||
|   id?: string | number | null; | ||||
| }; | ||||
| 
 | ||||
| interface PublicStoreActions<T extends BoundT> { | ||||
|  | ||||
| @ -160,6 +160,9 @@ export function usePageUser(): { user: UserOut } { | ||||
|         group: "", | ||||
|         groupId: "", | ||||
|         groupSlug: "", | ||||
|         household: "", | ||||
|         householdId: "", | ||||
|         householdSlug: "", | ||||
|         cacheKey: "", | ||||
|         email: "", | ||||
|       }, | ||||
|  | ||||
| @ -46,7 +46,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo | ||||
|   } | ||||
| 
 | ||||
|   const { quantity, food, unit, note } = ingredient; | ||||
|   const usePluralUnit = quantity !== undefined && (quantity * scale > 1 || quantity * scale === 0); | ||||
|   const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0); | ||||
|   const usePluralFood = (!quantity) || quantity * scale > 1 | ||||
| 
 | ||||
|   let returnQty = ""; | ||||
| @ -69,8 +69,8 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const unitName = useUnitName(unit, usePluralUnit); | ||||
|   const foodName = useFoodName(food, usePluralFood); | ||||
|   const unitName = useUnitName(unit || undefined, usePluralUnit); | ||||
|   const foodName = useFoodName(food || undefined, usePluralFood); | ||||
| 
 | ||||
|   return { | ||||
|     quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined, | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { reactive, ref, Ref } from "@nuxtjs/composition-api"; | ||||
| import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; | ||||
| import { usePublicExploreApi } from "../api/api-client"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { RecipeTag } from "~/lib/api/types/admin"; | ||||
| import { RecipeTag } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| const items: Ref<RecipeTag[]> = ref([]); | ||||
| const publicStoreLoading = ref(false); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { useAsync, ref, Ref, useContext } from "@nuxtjs/composition-api"; | ||||
| import { useAsyncKey } from "./use-utils"; | ||||
| import { usePublicExploreApi } from "./api/api-client"; | ||||
| import { useHouseholdSelf } from "./use-households"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook"; | ||||
| 
 | ||||
| @ -67,6 +68,7 @@ export const usePublicCookbooks = function (groupSlug: string) { | ||||
| 
 | ||||
| export const useCookbooks = function () { | ||||
|   const api = useUserApi(); | ||||
|   const { household } = useHouseholdSelf(); | ||||
|   const loading = ref(false); | ||||
| 
 | ||||
|   const { i18n } = useContext(); | ||||
| @ -100,7 +102,7 @@ export const useCookbooks = function () { | ||||
|     async createOne() { | ||||
|       loading.value = true; | ||||
|       const { data } = await api.cookbooks.createOne({ | ||||
|         name: i18n.t("cookbook.cookbook-with-name", [String((cookbookStore?.value?.length ?? 0) + 1)]) as string, | ||||
|         name: i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((cookbookStore?.value?.length ?? 0) + 1)]) as string, | ||||
|       }); | ||||
|       if (data && cookbookStore?.value) { | ||||
|         cookbookStore.value.push(data); | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { computed, reactive, ref } from "@nuxtjs/composition-api"; | ||||
| import { useStoreActions } from "./partials/use-actions-factory"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { GroupRecipeActionOut, RecipeActionType } from "~/lib/api/types/group"; | ||||
| import { GroupRecipeActionOut, GroupRecipeActionType } from "~/lib/api/types/household"; | ||||
| import { Recipe } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null); | ||||
| @ -10,7 +10,7 @@ const loading = ref(false); | ||||
| export function useGroupRecipeActionData() { | ||||
|   const data = reactive({ | ||||
|     id: "", | ||||
|     actionType: "link" as RecipeActionType, | ||||
|     actionType: "link" as GroupRecipeActionType, | ||||
|     title: "", | ||||
|     url: "", | ||||
|   }); | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { useAsync, ref } from "@nuxtjs/composition-api"; | ||||
| import { useAsyncKey } from "./use-utils"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { ReadWebhook } from "~/lib/api/types/group"; | ||||
| import { ReadWebhook } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| export const useGroupWebhooks = function () { | ||||
|   const api = useUserApi(); | ||||
|  | ||||
| @ -51,7 +51,7 @@ export const useGroups = function () { | ||||
|     loading.value = true; | ||||
|     const asyncKey = String(Date.now()); | ||||
|     const groups = useAsync(async () => { | ||||
|       const { data } = await api.groups.getAll(); | ||||
|       const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});; | ||||
| 
 | ||||
|       if (data) { | ||||
|         return data.items; | ||||
| @ -66,7 +66,7 @@ export const useGroups = function () { | ||||
| 
 | ||||
|   async function refreshAllGroups() { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.groups.getAll(); | ||||
|     const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});; | ||||
| 
 | ||||
|     if (data) { | ||||
|       groups.value = data.items; | ||||
|  | ||||
							
								
								
									
										117
									
								
								frontend/composables/use-households.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								frontend/composables/use-households.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| import { computed, ref, Ref, useAsync } from "@nuxtjs/composition-api"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const householdSelfRef = ref<HouseholdInDB | null>(null); | ||||
| const loading = ref(false); | ||||
| 
 | ||||
| export const useHouseholdSelf = function () { | ||||
|   const api = useUserApi(); | ||||
| 
 | ||||
|   async function refreshHouseholdSelf() { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.households.getCurrentUserHousehold(); | ||||
|     householdSelfRef.value = data; | ||||
|     loading.value = false; | ||||
|   } | ||||
| 
 | ||||
|   const actions = { | ||||
|     get() { | ||||
|       if (!(householdSelfRef.value || loading.value)) { | ||||
|         refreshHouseholdSelf(); | ||||
|       } | ||||
| 
 | ||||
|       return householdSelfRef; | ||||
|     }, | ||||
|     async updatePreferences() { | ||||
|       if (!householdSelfRef.value) { | ||||
|         await refreshHouseholdSelf(); | ||||
|       } | ||||
|       if (!householdSelfRef.value?.preferences) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const { data } = await api.households.setPreferences(householdSelfRef.value.preferences); | ||||
| 
 | ||||
|       if (data) { | ||||
|         householdSelfRef.value.preferences = data; | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   const household = actions.get(); | ||||
| 
 | ||||
|   return { actions, household }; | ||||
| }; | ||||
| 
 | ||||
| export const useHouseholds = function () { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
| 
 | ||||
|   function getAllHouseholds() { | ||||
|     loading.value = true; | ||||
|     const asyncKey = String(Date.now()); | ||||
|     const households = useAsync(async () => { | ||||
|       const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"}); | ||||
| 
 | ||||
|       if (data) { | ||||
|         return data.items; | ||||
|       } else { | ||||
|         return null; | ||||
|       } | ||||
|     }, asyncKey); | ||||
| 
 | ||||
|     loading.value = false; | ||||
|     return households; | ||||
|   } | ||||
| 
 | ||||
|   async function refreshAllHouseholds() { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"});; | ||||
| 
 | ||||
|     if (data) { | ||||
|       households.value = data.items; | ||||
|     } else { | ||||
|         households.value = null; | ||||
|     } | ||||
| 
 | ||||
|     loading.value = false; | ||||
|   } | ||||
| 
 | ||||
|   async function deleteHousehold(id: string | number) { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.households.deleteOne(id); | ||||
|     loading.value = false; | ||||
|     refreshAllHouseholds(); | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   async function createHousehold(payload: HouseholdCreate) { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.households.createOne(payload); | ||||
| 
 | ||||
|     if (data && households.value) { | ||||
|         households.value.push(data); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const households = getAllHouseholds(); | ||||
|   function useHouseholdsInGroup(groupIdRef: Ref<string>) { | ||||
|     return computed( | ||||
|       () => { | ||||
|         return (households.value && groupIdRef.value) | ||||
|         ? households.value.filter((h) => h.groupId === groupIdRef.value) | ||||
|         : []; | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     households, | ||||
|     useHouseholdsInGroup, | ||||
|     getAllHouseholds, | ||||
|     refreshAllHouseholds, | ||||
|     deleteHousehold, | ||||
|     createHousehold, | ||||
|   }; | ||||
| }; | ||||
| @ -1,7 +1,7 @@ | ||||
| import { computed, reactive, watch } from "@nuxtjs/composition-api"; | ||||
| import { useLocalStorage } from "@vueuse/core"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/group"; | ||||
| import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/household"; | ||||
| import { RequestResponse } from "~/lib/api/types/non-generated"; | ||||
| 
 | ||||
| const localStorageKey = "shopping-list-queue"; | ||||
| @ -144,7 +144,7 @@ export function useShoppingListItemActions(shoppingListId: string) { | ||||
| 
 | ||||
|   function checkUpdateState(list: ShoppingListOut) { | ||||
|     const cutoffDate = new Date(queue.lastUpdate + queueTimeout).toISOString(); | ||||
|     if (list.updateAt && list.updateAt > cutoffDate) { | ||||
|     if (list.updatedAt && list.updatedAt > cutoffDate) { | ||||
|       // If the queue is too far behind the shopping list to reliably do updates, we clear the queue
 | ||||
|       console.log("Out of sync with server; clearing queue"); | ||||
|       clearQueueItems("all"); | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
|     "database-type": "Database Type", | ||||
|     "database-url": "Database URL", | ||||
|     "default-group": "Default Group", | ||||
|     "default-household": "Default Household", | ||||
|     "demo": "Demo", | ||||
|     "demo-status": "Demo Status", | ||||
|     "development": "Development", | ||||
| @ -238,7 +239,7 @@ | ||||
|       "keep-my-recipes-private-description": "Sets your group and all recipes defaults to private. You can always change this later." | ||||
|     }, | ||||
|     "manage-members": "Manage Members", | ||||
|     "manage-members-description": "Manage the permissions of the members in your groups. {manage} allows the user to access the data-management page {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.", | ||||
|     "manage-members-description": "Manage the permissions of the members in your household. {manage} allows the user to access the data-management page, and {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.", | ||||
|     "manage": "Manage", | ||||
|     "invite": "Invite", | ||||
|     "looking-to-update-your-profile": "Looking to Update Your Profile?", | ||||
| @ -246,7 +247,7 @@ | ||||
|     "default-recipe-preferences": "Default Recipe Preferences", | ||||
|     "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.", | ||||
|     "private-group-description": "Setting your group to private will default all public view options to default. This overrides any individual households or 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", | ||||
| @ -260,7 +261,7 @@ | ||||
|     "disable-users-from-commenting-on-recipes": "Disable users from commenting on recipes", | ||||
|     "disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting", | ||||
|     "disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food", | ||||
|     "disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields.", | ||||
|     "disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields", | ||||
|     "general-preferences": "General Preferences", | ||||
|     "group-recipe-preferences": "Group Recipe Preferences", | ||||
|     "report": "Report", | ||||
| @ -268,7 +269,28 @@ | ||||
|     "group-management": "Group Management", | ||||
|     "admin-group-management": "Admin Group Management", | ||||
|     "admin-group-management-text": "Changes to this group will be reflected immediately.", | ||||
|     "group-id-value": "Group Id: {0}" | ||||
|     "group-id-value": "Group Id: {0}", | ||||
|     "total-households": "Total Households" | ||||
|   }, | ||||
|   "household": { | ||||
|     "household": "Household", | ||||
|     "households": "Households", | ||||
|     "user-household": "User Household", | ||||
|     "create-household": "Create Household", | ||||
|     "household-name": "Household Name", | ||||
|     "household-group": "Household Group", | ||||
|     "household-management": "Household Management", | ||||
|     "manage-households": "Manage Households", | ||||
|     "admin-household-management": "Admin Household Management", | ||||
|     "admin-household-management-text": "Changes to this household will be reflected immediately.", | ||||
|     "household-id-value": "Household Id: {0}", | ||||
|     "private-household": "Private Household", | ||||
|     "private-household-description": "Setting your household to private will default all public view options to default. This overrides any individual recipes public view settings.", | ||||
|     "household-recipe-preferences": "Household Recipe Preferences", | ||||
|     "default-recipe-preferences-description": "These are the default settings when a new recipe is created in your household. These can be changed for individual recipes in the recipe settings menu.", | ||||
|     "allow-users-outside-of-your-household-to-see-your-recipes": "Allow users outside of your household to see your recipes", | ||||
|     "allow-users-outside-of-your-household-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 household or with a pre-generated private link", | ||||
|     "household-preferences": "Household Preferences" | ||||
|   }, | ||||
|   "meal-plan": { | ||||
|     "create-a-new-meal-plan": "Create a New Meal Plan", | ||||
| @ -1230,6 +1252,8 @@ | ||||
|     "account-summary-description": "Here's a summary of your group's information.", | ||||
|     "group-statistics": "Group Statistics", | ||||
|     "group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.", | ||||
|     "household-statistics": "Household Statistics", | ||||
|     "household-statistics-description": "Your Household Statistics provide some insight how you're using Mealie.", | ||||
|     "storage-capacity": "Storage Capacity", | ||||
|     "storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.", | ||||
|     "personal": "Personal", | ||||
| @ -1239,10 +1263,13 @@ | ||||
|     "api-tokens-description": "Manage your API Tokens for access from external applications.", | ||||
|     "group-description": "These items are shared within your group. Editing one of them will change it for the whole group!", | ||||
|     "group-settings": "Group Settings", | ||||
|     "group-settings-description": "Manage your common group settings like mealplan and privacy settings.", | ||||
|     "group-settings-description": "Manage your common group settings, like privacy settings.", | ||||
|     "household-description": "These items are shared within your household. Editing one of them will change it for the whole household!", | ||||
|     "household-settings": "Household Settings", | ||||
|     "household-settings-description": "Manage your household settings, like mealplan and privacy settings.", | ||||
|     "cookbooks-description": "Manage a collection of recipe categories and generate pages for them.", | ||||
|     "members": "Members", | ||||
|     "members-description": "See who's in your group and manage their permissions.", | ||||
|     "members-description": "See who's in your household and manage their permissions.", | ||||
|     "webhooks-description": "Set up webhooks that trigger on days that you have mealplans scheduled.", | ||||
|     "notifiers": "Notifiers", | ||||
|     "notifiers-description": "Set up email and push notifications that trigger on specific events.", | ||||
| @ -1277,6 +1304,7 @@ | ||||
|     "require-all-tools": "Require All Tools", | ||||
|     "cookbook-name": "Cookbook Name", | ||||
|     "cookbook-with-name": "Cookbook {0}", | ||||
|     "household-cookbook-name": "{0} Cookbook {1}", | ||||
|     "create-a-cookbook": "Create a Cookbook", | ||||
|     "cookbook": "Cookbook" | ||||
|   } | ||||
|  | ||||
| @ -64,6 +64,12 @@ export default defineComponent({ | ||||
|         title: i18n.tc("user.users"), | ||||
|         restricted: true, | ||||
|       }, | ||||
|       { | ||||
|         icon: $globals.icons.household, | ||||
|         to: "/admin/manage/households", | ||||
|         title: i18n.tc("household.households"), | ||||
|         restricted: true, | ||||
|       }, | ||||
|       { | ||||
|         icon: $globals.icons.group, | ||||
|         to: "/admin/manage/groups", | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <v-app dark> | ||||
|   <v-app v-if="ready" dark> | ||||
|     <v-card-title> | ||||
|       <slot> | ||||
|         <h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1> | ||||
| @ -75,9 +75,21 @@ export default defineComponent({ | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     async function handle404() { | ||||
|       const normalizedRoute = route.value.fullPath.replace(/\/$/, ""); | ||||
|       const newRoute = normalizedRoute.replace(/^\/group\/(mealplan|members|notifiers|webhooks)(\/.*)?$/, "/household/$1$2"); | ||||
| 
 | ||||
|       if (newRoute !== normalizedRoute) { | ||||
|         await router.replace(newRoute); | ||||
|       } else { | ||||
|         await insertGroupSlugIntoRoute(); | ||||
|       } | ||||
| 
 | ||||
|       ready.value = true; | ||||
|     } | ||||
| 
 | ||||
|     if (props.error.statusCode === 404) { | ||||
|       // see if adding the groupSlug fixes the error | ||||
|       insertGroupSlugIntoRoute().then(() => { ready.value = true }); | ||||
|       handle404(); | ||||
|     } else { | ||||
|       ready.value = true; | ||||
|     } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { RecipeAPI } from "./user/recipes"; | ||||
| import { UserApi } from "./user/users"; | ||||
| import { HouseholdAPI } from "./user/households"; | ||||
| import { GroupAPI } from "./user/groups"; | ||||
| import { BackupAPI } from "./user/backups"; | ||||
| import { UploadFile } from "./user/upload"; | ||||
| @ -28,6 +29,7 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||
| export class UserApiClient { | ||||
|   public recipes: RecipeAPI; | ||||
|   public users: UserApi; | ||||
|   public households: HouseholdAPI; | ||||
|   public groups: GroupAPI; | ||||
|   public backups: BackupAPI; | ||||
|   public categories: CategoriesAPI; | ||||
| @ -63,6 +65,7 @@ export class UserApiClient { | ||||
| 
 | ||||
|     // Users
 | ||||
|     this.users = new UserApi(requests); | ||||
|     this.households = new HouseholdAPI(requests); | ||||
|     this.groups = new GroupAPI(requests); | ||||
|     this.cookbooks = new CookbookAPI(requests); | ||||
|     this.groupRecipeActions = new GroupRecipeActionsAPI(requests); | ||||
|  | ||||
| @ -3,10 +3,11 @@ import { RecipeCookBook } from "~/lib/api/types/cookbook"; | ||||
| import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||
| 
 | ||||
| const routes = { | ||||
|     cookbooksGroupSlug: (groupSlug: string | number) => `${prefix}/explore/cookbooks/${groupSlug}`, | ||||
|     cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${prefix}/explore/cookbooks/${groupSlug}/${cookbookId}`, | ||||
|     cookbooksGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks`, | ||||
|     cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks/${cookbookId}`, | ||||
| }; | ||||
| 
 | ||||
| export class PublicCookbooksApi extends BaseCRUDAPIReadOnly<RecipeCookBook> { | ||||
|  | ||||
| @ -3,10 +3,11 @@ import { IngredientFood } from "~/lib/api/types/recipe"; | ||||
| import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||
| 
 | ||||
| const routes = { | ||||
|     foodsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/foods/${groupSlug}`, | ||||
|     foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${prefix}/explore/foods/${groupSlug}/${foodId}`, | ||||
|     foodsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/foods`, | ||||
|     foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${exploreGroupSlug(groupSlug)}/foods/${foodId}`, | ||||
| }; | ||||
| 
 | ||||
| export class PublicFoodsApi extends BaseCRUDAPIReadOnly<IngredientFood> { | ||||
|  | ||||
| @ -3,14 +3,15 @@ import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe"; | ||||
| import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||
| 
 | ||||
| const routes = { | ||||
|     categoriesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories`, | ||||
|     categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories/${categoryId}`, | ||||
|     tagsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags`, | ||||
|     tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags/${tagId}`, | ||||
|     toolsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools`, | ||||
|     toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools/${toolId}`, | ||||
|     categoriesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories`, | ||||
|     categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories/${categoryId}`, | ||||
|     tagsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags`, | ||||
|     tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags/${tagId}`, | ||||
|     toolsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`, | ||||
|     toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`, | ||||
| }; | ||||
| 
 | ||||
| export class PublicCategoriesApi extends BaseCRUDAPIReadOnly<RecipeCategory> { | ||||
|  | ||||
| @ -5,10 +5,11 @@ import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generate | ||||
| import { RecipeSearchQuery } from "../../user/recipes/recipe"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||
| 
 | ||||
| const routes = { | ||||
|   recipesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}`, | ||||
|   recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}/${recipeSlug}`, | ||||
|   recipesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes`, | ||||
|   recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes/${recipeSlug}`, | ||||
| }; | ||||
| 
 | ||||
| export class PublicRecipeApi extends BaseCRUDAPIReadOnly<Recipe> { | ||||
|  | ||||
| @ -10,6 +10,8 @@ export interface AdminAboutInfo { | ||||
|   version: string; | ||||
|   demoStatus: boolean; | ||||
|   allowSignup: boolean; | ||||
|   defaultGroupSlug?: string | null; | ||||
|   defaultHouseholdSlug?: string | null; | ||||
|   enableOidc: boolean; | ||||
|   oidcRedirect: boolean; | ||||
|   oidcProviderName: string; | ||||
| @ -18,8 +20,9 @@ export interface AdminAboutInfo { | ||||
|   apiPort: number; | ||||
|   apiDocs: boolean; | ||||
|   dbType: string; | ||||
|   dbUrl?: string; | ||||
|   dbUrl?: string | null; | ||||
|   defaultGroup: string; | ||||
|   defaultHousehold: string; | ||||
|   buildId: string; | ||||
|   recipeScraperVersion: string; | ||||
| } | ||||
| @ -37,7 +40,8 @@ export interface AppInfo { | ||||
|   version: string; | ||||
|   demoStatus: boolean; | ||||
|   allowSignup: boolean; | ||||
|   defaultGroupSlug?: string; | ||||
|   defaultGroupSlug?: string | null; | ||||
|   defaultHouseholdSlug?: string | null; | ||||
|   enableOidc: boolean; | ||||
|   oidcRedirect: boolean; | ||||
|   oidcProviderName: string; | ||||
| @ -51,6 +55,7 @@ export interface AppStartupInfo { | ||||
| export interface AppStatistics { | ||||
|   totalRecipes: number; | ||||
|   totalUsers: number; | ||||
|   totalHouseholds: number; | ||||
|   totalGroups: number; | ||||
|   uncategorizedRecipes: number; | ||||
|   untaggedRecipes: number; | ||||
| @ -93,16 +98,16 @@ export interface ChowdownURL { | ||||
| export interface CommentImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface CreateBackup { | ||||
|   tag?: string; | ||||
|   tag?: string | null; | ||||
|   options: BackupOptions; | ||||
|   templates?: string[]; | ||||
|   templates?: string[] | null; | ||||
| } | ||||
| export interface CustomPageBase { | ||||
|   name: string; | ||||
|   slug?: string; | ||||
|   slug: string | null; | ||||
|   position: number; | ||||
|   categories?: RecipeCategoryResponse[]; | ||||
| } | ||||
| @ -113,38 +118,41 @@ export interface RecipeCategoryResponse { | ||||
|   recipes?: RecipeSummary[]; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTool { | ||||
|   id: string; | ||||
| @ -155,11 +163,11 @@ export interface RecipeTool { | ||||
| export interface CustomPageImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface CustomPageOut { | ||||
|   name: string; | ||||
|   slug?: string; | ||||
|   slug: string | null; | ||||
|   position: number; | ||||
|   categories?: RecipeCategoryResponse[]; | ||||
|   id: number; | ||||
| @ -169,7 +177,7 @@ export interface EmailReady { | ||||
| } | ||||
| export interface EmailSuccess { | ||||
|   success: boolean; | ||||
|   error?: string; | ||||
|   error?: string | null; | ||||
| } | ||||
| export interface EmailTest { | ||||
|   email: string; | ||||
| @ -177,12 +185,12 @@ export interface EmailTest { | ||||
| export interface GroupImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface ImportBase { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface ImportJob { | ||||
|   recipes?: boolean; | ||||
| @ -217,8 +225,8 @@ export interface MigrationFile { | ||||
| export interface MigrationImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   slug?: string; | ||||
|   exception?: string | null; | ||||
|   slug?: string | null; | ||||
| } | ||||
| export interface Migrations { | ||||
|   type: string; | ||||
| @ -227,25 +235,26 @@ export interface Migrations { | ||||
| export interface NotificationImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface OIDCInfo { | ||||
|   configurationUrl?: string; | ||||
|   clientId?: string; | ||||
|   configurationUrl: string | null; | ||||
|   clientId: string | null; | ||||
|   groupsClaim: string | null; | ||||
| } | ||||
| export interface RecipeImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   slug?: string; | ||||
|   exception?: string | null; | ||||
|   slug?: string | null; | ||||
| } | ||||
| export interface SettingsImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface UserImport { | ||||
|   name: string; | ||||
|   status: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
| export interface CreateCookBook { | ||||
|   name: string; | ||||
|   description?: string; | ||||
|   slug?: string; | ||||
|   slug?: string | null; | ||||
|   position?: number; | ||||
|   public?: boolean; | ||||
|   categories?: CategoryBase[]; | ||||
| @ -37,7 +37,7 @@ export interface RecipeTool { | ||||
| export interface ReadCookBook { | ||||
|   name: string; | ||||
|   description?: string; | ||||
|   slug?: string; | ||||
|   slug?: string | null; | ||||
|   position?: number; | ||||
|   public?: boolean; | ||||
|   categories?: CategoryBase[]; | ||||
| @ -47,12 +47,13 @@ export interface ReadCookBook { | ||||
|   requireAllTags?: boolean; | ||||
|   requireAllTools?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface RecipeCookBook { | ||||
|   name: string; | ||||
|   description?: string; | ||||
|   slug?: string; | ||||
|   slug?: string | null; | ||||
|   position?: number; | ||||
|   public?: boolean; | ||||
|   categories?: CategoryBase[]; | ||||
| @ -62,47 +63,51 @@ export interface RecipeCookBook { | ||||
|   requireAllTags?: boolean; | ||||
|   requireAllTools?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
|   recipes: RecipeSummary[]; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface SaveCookBook { | ||||
|   name: string; | ||||
|   description?: string; | ||||
|   slug?: string; | ||||
|   slug?: string | null; | ||||
|   position?: number; | ||||
|   public?: boolean; | ||||
|   categories?: CategoryBase[]; | ||||
| @ -112,11 +117,12 @@ export interface SaveCookBook { | ||||
|   requireAllTags?: boolean; | ||||
|   requireAllTools?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface UpdateCookBook { | ||||
|   name: string; | ||||
|   description?: string; | ||||
|   slug?: string; | ||||
|   slug?: string | null; | ||||
|   position?: number; | ||||
|   public?: boolean; | ||||
|   categories?: CategoryBase[]; | ||||
| @ -126,5 +132,6 @@ export interface UpdateCookBook { | ||||
|   requireAllTags?: boolean; | ||||
|   requireAllTools?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
|  | ||||
| @ -5,10 +5,6 @@ | ||||
| /* Do not modify it by hand - just update the pydantic models and then re-run the script | ||||
| */ | ||||
| 
 | ||||
| export type RecipeActionType = | ||||
|   | "link" | ||||
|   | "post"; | ||||
| export type WebhookType = "mealplan"; | ||||
| export type SupportedMigrations = | ||||
|   | "nextcloud" | ||||
|   | "chowdown" | ||||
| @ -17,59 +13,23 @@ export type SupportedMigrations = | ||||
|   | "mealie_alpha" | ||||
|   | "tandoor" | ||||
|   | "plantoeat" | ||||
|   | "myrecipebox" | ||||
|   | "recipekeeper"; | ||||
| 
 | ||||
| export interface CreateGroupPreferences { | ||||
|   privateGroup?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
|   groupId: string; | ||||
| } | ||||
| export interface CreateGroupRecipeAction { | ||||
|   actionType: RecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
| } | ||||
| export interface CreateInviteToken { | ||||
|   uses: number; | ||||
| } | ||||
| export interface CreateWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
| } | ||||
| export interface DataMigrationCreate { | ||||
|   sourceType: SupportedMigrations; | ||||
| } | ||||
| export interface EmailInitationResponse { | ||||
|   success: boolean; | ||||
|   error?: string; | ||||
| } | ||||
| export interface EmailInvitation { | ||||
|   email: string; | ||||
|   token: string; | ||||
| } | ||||
| export interface GroupAdminUpdate { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   preferences?: UpdateGroupPreferences; | ||||
|   preferences?: UpdateGroupPreferences | null; | ||||
| } | ||||
| export interface UpdateGroupPreferences { | ||||
|   privateGroup?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
| } | ||||
| export interface GroupDataExport { | ||||
|   id: string; | ||||
| @ -80,140 +40,6 @@ export interface GroupDataExport { | ||||
|   size: string; | ||||
|   expires: string; | ||||
| } | ||||
| export interface GroupEventNotifierCreate { | ||||
|   name: string; | ||||
|   appriseUrl: string; | ||||
| } | ||||
| /** | ||||
|  * These events are in-sync with the EventTypes found in the EventBusService. | ||||
|  * If you modify this, make sure to update the EventBusService as well. | ||||
|  */ | ||||
| export interface GroupEventNotifierOptions { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
| } | ||||
| /** | ||||
|  * These events are in-sync with the EventTypes found in the EventBusService. | ||||
|  * If you modify this, make sure to update the EventBusService as well. | ||||
|  */ | ||||
| export interface GroupEventNotifierOptionsOut { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
|   id: string; | ||||
| } | ||||
| /** | ||||
|  * These events are in-sync with the EventTypes found in the EventBusService. | ||||
|  * If you modify this, make sure to update the EventBusService as well. | ||||
|  */ | ||||
| export interface GroupEventNotifierOptionsSave { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
|   notifierId: string; | ||||
| } | ||||
| export interface GroupEventNotifierOut { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   enabled: boolean; | ||||
|   groupId: string; | ||||
|   options: GroupEventNotifierOptionsOut; | ||||
| } | ||||
| export interface GroupEventNotifierPrivate { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   enabled: boolean; | ||||
|   groupId: string; | ||||
|   options: GroupEventNotifierOptionsOut; | ||||
|   appriseUrl: string; | ||||
| } | ||||
| export interface GroupEventNotifierSave { | ||||
|   name: string; | ||||
|   appriseUrl: string; | ||||
|   enabled?: boolean; | ||||
|   groupId: string; | ||||
|   options?: GroupEventNotifierOptions; | ||||
| } | ||||
| export interface GroupEventNotifierUpdate { | ||||
|   name: string; | ||||
|   appriseUrl?: string; | ||||
|   enabled?: boolean; | ||||
|   groupId: string; | ||||
|   options?: GroupEventNotifierOptions; | ||||
|   id: string; | ||||
| } | ||||
| export interface GroupRecipeActionOut { | ||||
|   actionType: RecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface GroupStatistics { | ||||
|   totalRecipes: number; | ||||
|   totalUsers: number; | ||||
|   totalCategories: number; | ||||
|   totalTags: number; | ||||
|   totalTools: number; | ||||
| } | ||||
| export interface GroupStorage { | ||||
|   usedStorageBytes: number; | ||||
|   usedStorageStr: string; | ||||
| @ -222,408 +48,9 @@ export interface GroupStorage { | ||||
| } | ||||
| export interface ReadGroupPreferences { | ||||
|   privateGroup?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface ReadInviteToken { | ||||
|   token: string; | ||||
|   usesLeft: number; | ||||
|   groupId: string; | ||||
| } | ||||
| export interface ReadWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface SaveGroupRecipeAction { | ||||
|   actionType: RecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
|   groupId: string; | ||||
| } | ||||
| export interface SaveInviteToken { | ||||
|   usesLeft: number; | ||||
|   groupId: string; | ||||
|   token: string; | ||||
| } | ||||
| export interface SaveWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
|   groupId: string; | ||||
| } | ||||
| export interface SeederConfig { | ||||
|   locale: string; | ||||
| } | ||||
| export interface SetPermissions { | ||||
|   userId: string; | ||||
|   canManage?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canOrganize?: boolean; | ||||
| } | ||||
| export interface ShoppingListAddRecipeParams { | ||||
|   recipeIncrementQuantity?: number; | ||||
|   recipeIngredients?: RecipeIngredient[]; | ||||
| } | ||||
| export interface RecipeIngredient { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   title?: string; | ||||
|   originalText?: string; | ||||
|   referenceId?: string; | ||||
| } | ||||
| export interface IngredientUnit { | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: IngredientUnitAlias[]; | ||||
|   id: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
| } | ||||
| export interface IngredientUnitAlias { | ||||
|   name: string; | ||||
| } | ||||
| export interface CreateIngredientUnit { | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: CreateIngredientUnitAlias[]; | ||||
| } | ||||
| export interface CreateIngredientUnitAlias { | ||||
|   name: string; | ||||
| } | ||||
| export interface IngredientFood { | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   labelId?: string; | ||||
|   aliases?: IngredientFoodAlias[]; | ||||
|   id: string; | ||||
|   label?: MultiPurposeLabelSummary; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
| } | ||||
| export interface IngredientFoodAlias { | ||||
|   name: string; | ||||
| } | ||||
| export interface MultiPurposeLabelSummary { | ||||
|   name: string; | ||||
|   color?: string; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface CreateIngredientFood { | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   labelId?: string; | ||||
|   aliases?: CreateIngredientFoodAlias[]; | ||||
| } | ||||
| export interface CreateIngredientFoodAlias { | ||||
|   name: string; | ||||
| } | ||||
| export interface ShoppingListCreate { | ||||
|   name?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
| } | ||||
| export interface ShoppingListItemBase { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string; | ||||
|   labelId?: string; | ||||
|   unitId?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
| } | ||||
| export interface ShoppingListItemCreate { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string; | ||||
|   labelId?: string; | ||||
|   unitId?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   recipeReferences?: ShoppingListItemRecipeRefCreate[]; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefCreate { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number; | ||||
|   recipeNote?: string; | ||||
| } | ||||
| export interface ShoppingListItemOut { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit; | ||||
|   food?: IngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string; | ||||
|   labelId?: string; | ||||
|   unitId?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   id: string; | ||||
|   label?: MultiPurposeLabelSummary; | ||||
|   recipeReferences?: ShoppingListItemRecipeRefOut[]; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefOut { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number; | ||||
|   recipeNote?: string; | ||||
|   id: string; | ||||
|   shoppingListItemId: string; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefUpdate { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number; | ||||
|   recipeNote?: string; | ||||
|   id: string; | ||||
|   shoppingListItemId: string; | ||||
| } | ||||
| export interface ShoppingListItemUpdate { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string; | ||||
|   labelId?: string; | ||||
|   unitId?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[]; | ||||
| } | ||||
| /** | ||||
|  * Only used for bulk update operations where the shopping list item id isn't already supplied | ||||
|  */ | ||||
| export interface ShoppingListItemUpdateBulk { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string; | ||||
|   labelId?: string; | ||||
|   unitId?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[]; | ||||
|   id: string; | ||||
| } | ||||
| /** | ||||
|  * Container for bulk shopping list item changes | ||||
|  */ | ||||
| export interface ShoppingListItemsCollectionOut { | ||||
|   createdItems?: ShoppingListItemOut[]; | ||||
|   updatedItems?: ShoppingListItemOut[]; | ||||
|   deletedItems?: ShoppingListItemOut[]; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelCreate { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelOut { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
|   id: string; | ||||
|   label: MultiPurposeLabelSummary; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelUpdate { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
|   id: string; | ||||
| } | ||||
| export interface ShoppingListOut { | ||||
|   name?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   listItems?: ShoppingListItemOut[]; | ||||
|   recipeReferences: ShoppingListRecipeRefOut[]; | ||||
|   labelSettings: ShoppingListMultiPurposeLabelOut[]; | ||||
| } | ||||
| export interface ShoppingListRecipeRefOut { | ||||
|   id: string; | ||||
|   shoppingListId: string; | ||||
|   recipeId: string; | ||||
|   recipeQuantity: number; | ||||
|   recipe: RecipeSummary; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string; | ||||
|   userId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string; | ||||
|   name: string; | ||||
|   slug: string; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string; | ||||
|   name: string; | ||||
|   slug: string; | ||||
| } | ||||
| export interface RecipeTool { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   onHand?: boolean; | ||||
| } | ||||
| export interface ShoppingListRemoveRecipeParams { | ||||
|   recipeDecrementQuantity?: number; | ||||
| } | ||||
| export interface ShoppingListSave { | ||||
|   name?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
| } | ||||
| export interface ShoppingListSummary { | ||||
|   name?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   recipeReferences: ShoppingListRecipeRefOut[]; | ||||
|   labelSettings: ShoppingListMultiPurposeLabelOut[]; | ||||
| } | ||||
| export interface ShoppingListUpdate { | ||||
|   name?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   listItems?: ShoppingListItemOut[]; | ||||
| } | ||||
| export interface RecipeIngredientBase { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
| } | ||||
|  | ||||
							
								
								
									
										664
									
								
								frontend/lib/api/types/household.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										664
									
								
								frontend/lib/api/types/household.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,664 @@ | ||||
| /* tslint:disable */ | ||||
| /* eslint-disable */ | ||||
| /** | ||||
| /* This file was automatically generated from pydantic models by running pydantic2ts. | ||||
| /* Do not modify it by hand - just update the pydantic models and then re-run the script | ||||
| */ | ||||
| 
 | ||||
| export type GroupRecipeActionType = "link" | "post"; | ||||
| export type WebhookType = "mealplan"; | ||||
| 
 | ||||
| export interface CreateGroupRecipeAction { | ||||
|   actionType: GroupRecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
| } | ||||
| export interface CreateHouseholdPreferences { | ||||
|   privateHousehold?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
| } | ||||
| export interface CreateInviteToken { | ||||
|   uses: number; | ||||
| } | ||||
| export interface CreateWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
| } | ||||
| export interface EmailInitationResponse { | ||||
|   success: boolean; | ||||
|   error?: string | null; | ||||
| } | ||||
| export interface EmailInvitation { | ||||
|   email: string; | ||||
|   token: string; | ||||
| } | ||||
| export interface GroupEventNotifierCreate { | ||||
|   name: string; | ||||
|   appriseUrl?: string | null; | ||||
| } | ||||
| /** | ||||
|  * These events are in-sync with the EventTypes found in the EventBusService. | ||||
|  * If you modify this, make sure to update the EventBusService as well. | ||||
|  */ | ||||
| export interface GroupEventNotifierOptions { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
| } | ||||
| export interface GroupEventNotifierOptionsOut { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
|   id: string; | ||||
| } | ||||
| export interface GroupEventNotifierOptionsSave { | ||||
|   testMessage?: boolean; | ||||
|   webhookTask?: boolean; | ||||
|   recipeCreated?: boolean; | ||||
|   recipeUpdated?: boolean; | ||||
|   recipeDeleted?: boolean; | ||||
|   userSignup?: boolean; | ||||
|   dataMigrations?: boolean; | ||||
|   dataExport?: boolean; | ||||
|   dataImport?: boolean; | ||||
|   mealplanEntryCreated?: boolean; | ||||
|   shoppingListCreated?: boolean; | ||||
|   shoppingListUpdated?: boolean; | ||||
|   shoppingListDeleted?: boolean; | ||||
|   cookbookCreated?: boolean; | ||||
|   cookbookUpdated?: boolean; | ||||
|   cookbookDeleted?: boolean; | ||||
|   tagCreated?: boolean; | ||||
|   tagUpdated?: boolean; | ||||
|   tagDeleted?: boolean; | ||||
|   categoryCreated?: boolean; | ||||
|   categoryUpdated?: boolean; | ||||
|   categoryDeleted?: boolean; | ||||
|   notifierId: string; | ||||
| } | ||||
| export interface GroupEventNotifierOut { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   enabled: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   options: GroupEventNotifierOptionsOut; | ||||
| } | ||||
| export interface GroupEventNotifierPrivate { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   enabled: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   options: GroupEventNotifierOptionsOut; | ||||
|   appriseUrl: string; | ||||
| } | ||||
| export interface GroupEventNotifierSave { | ||||
|   name: string; | ||||
|   appriseUrl?: string | null; | ||||
|   enabled?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   options?: GroupEventNotifierOptions; | ||||
| } | ||||
| export interface GroupEventNotifierUpdate { | ||||
|   name: string; | ||||
|   appriseUrl?: string | null; | ||||
|   enabled?: boolean; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   options?: GroupEventNotifierOptions; | ||||
|   id: string; | ||||
| } | ||||
| export interface GroupRecipeActionOut { | ||||
|   actionType: GroupRecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface HouseholdCreate { | ||||
|   groupId?: string | null; | ||||
|   name: string; | ||||
| } | ||||
| export interface HouseholdInDB { | ||||
|   groupId: string; | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   preferences?: ReadHouseholdPreferences | null; | ||||
|   group: string; | ||||
|   users?: HouseholdUserSummary[] | null; | ||||
|   webhooks?: ReadWebhook[]; | ||||
| } | ||||
| export interface ReadHouseholdPreferences { | ||||
|   privateHousehold?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
|   id: string; | ||||
| } | ||||
| export interface HouseholdUserSummary { | ||||
|   id: string; | ||||
|   fullName: string; | ||||
| } | ||||
| export interface ReadWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface HouseholdSave { | ||||
|   groupId: string; | ||||
|   name: string; | ||||
| } | ||||
| export interface HouseholdStatistics { | ||||
|   totalRecipes: number; | ||||
|   totalUsers: number; | ||||
|   totalCategories: number; | ||||
|   totalTags: number; | ||||
|   totalTools: number; | ||||
| } | ||||
| export interface HouseholdSummary { | ||||
|   groupId: string; | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   preferences?: ReadHouseholdPreferences | null; | ||||
| } | ||||
| export interface ReadInviteToken { | ||||
|   token: string; | ||||
|   usesLeft: number; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface SaveGroupRecipeAction { | ||||
|   actionType: GroupRecipeActionType; | ||||
|   title: string; | ||||
|   url: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface SaveHouseholdPreferences { | ||||
|   privateHousehold?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface SaveInviteToken { | ||||
|   usesLeft: number; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   token: string; | ||||
| } | ||||
| export interface SaveWebhook { | ||||
|   enabled?: boolean; | ||||
|   name?: string; | ||||
|   url?: string; | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface SetPermissions { | ||||
|   userId: string; | ||||
|   canManage?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canOrganize?: boolean; | ||||
| } | ||||
| export interface ShoppingListAddRecipeParams { | ||||
|   recipeIncrementQuantity?: number; | ||||
|   recipeIngredients?: RecipeIngredient[] | null; | ||||
| } | ||||
| export interface RecipeIngredient { | ||||
|   quantity?: number | null; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean | null; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   title?: string | null; | ||||
|   originalText?: string | null; | ||||
|   referenceId?: string; | ||||
| } | ||||
| export interface IngredientUnit { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string | null; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: IngredientUnitAlias[]; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface IngredientUnitAlias { | ||||
|   name: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface CreateIngredientUnit { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string | null; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: CreateIngredientUnitAlias[]; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface CreateIngredientUnitAlias { | ||||
|   name: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface IngredientFood { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   labelId?: string | null; | ||||
|   aliases?: IngredientFoodAlias[]; | ||||
|   label?: MultiPurposeLabelSummary | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface IngredientFoodAlias { | ||||
|   name: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface MultiPurposeLabelSummary { | ||||
|   name: string; | ||||
|   color?: string; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface CreateIngredientFood { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   labelId?: string | null; | ||||
|   aliases?: CreateIngredientFoodAlias[]; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface CreateIngredientFoodAlias { | ||||
|   name: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface ShoppingListCreate { | ||||
|   name?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface ShoppingListItemBase { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string | null; | ||||
|   labelId?: string | null; | ||||
|   unitId?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
| } | ||||
| export interface ShoppingListItemCreate { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string | null; | ||||
|   labelId?: string | null; | ||||
|   unitId?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   id?: string | null; | ||||
|   recipeReferences?: ShoppingListItemRecipeRefCreate[]; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefCreate { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number | null; | ||||
|   recipeNote?: string | null; | ||||
| } | ||||
| export interface ShoppingListItemOut { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | null; | ||||
|   food?: IngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string | null; | ||||
|   labelId?: string | null; | ||||
|   unitId?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   id: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   label?: MultiPurposeLabelSummary | null; | ||||
|   recipeReferences?: ShoppingListItemRecipeRefOut[]; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefOut { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number | null; | ||||
|   recipeNote?: string | null; | ||||
|   id: string; | ||||
|   shoppingListItemId: string; | ||||
| } | ||||
| export interface ShoppingListItemRecipeRefUpdate { | ||||
|   recipeId: string; | ||||
|   recipeQuantity?: number; | ||||
|   recipeScale?: number | null; | ||||
|   recipeNote?: string | null; | ||||
|   id: string; | ||||
|   shoppingListItemId: string; | ||||
| } | ||||
| export interface ShoppingListItemUpdate { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string | null; | ||||
|   labelId?: string | null; | ||||
|   unitId?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[]; | ||||
| } | ||||
| /** | ||||
|  * Only used for bulk update operations where the shopping list item id isn't already supplied | ||||
|  */ | ||||
| export interface ShoppingListItemUpdateBulk { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
|   shoppingListId: string; | ||||
|   checked?: boolean; | ||||
|   position?: number; | ||||
|   foodId?: string | null; | ||||
|   labelId?: string | null; | ||||
|   unitId?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[]; | ||||
|   id: string; | ||||
| } | ||||
| /** | ||||
|  * Container for bulk shopping list item changes | ||||
|  */ | ||||
| export interface ShoppingListItemsCollectionOut { | ||||
|   createdItems?: ShoppingListItemOut[]; | ||||
|   updatedItems?: ShoppingListItemOut[]; | ||||
|   deletedItems?: ShoppingListItemOut[]; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelCreate { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelOut { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
|   id: string; | ||||
|   label: MultiPurposeLabelSummary; | ||||
| } | ||||
| export interface ShoppingListMultiPurposeLabelUpdate { | ||||
|   shoppingListId: string; | ||||
|   labelId: string; | ||||
|   position?: number; | ||||
|   id: string; | ||||
| } | ||||
| export interface ShoppingListOut { | ||||
|   name?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   listItems?: ShoppingListItemOut[]; | ||||
|   householdId: string; | ||||
|   recipeReferences?: ShoppingListRecipeRefOut[]; | ||||
|   labelSettings?: ShoppingListMultiPurposeLabelOut[]; | ||||
| } | ||||
| export interface ShoppingListRecipeRefOut { | ||||
|   id: string; | ||||
|   shoppingListId: string; | ||||
|   recipeId: string; | ||||
|   recipeQuantity: number; | ||||
|   recipe: RecipeSummary; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTool { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   onHand?: boolean; | ||||
| } | ||||
| export interface ShoppingListRemoveRecipeParams { | ||||
|   recipeDecrementQuantity?: number; | ||||
| } | ||||
| export interface ShoppingListSave { | ||||
|   name?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
| } | ||||
| export interface ShoppingListSummary { | ||||
|   name?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   householdId: string; | ||||
|   recipeReferences: ShoppingListRecipeRefOut[]; | ||||
|   labelSettings: ShoppingListMultiPurposeLabelOut[]; | ||||
| } | ||||
| export interface ShoppingListUpdate { | ||||
|   name?: string | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   } | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   groupId: string; | ||||
|   userId: string; | ||||
|   id: string; | ||||
|   listItems?: ShoppingListItemOut[]; | ||||
| } | ||||
| export interface UpdateHousehold { | ||||
|   groupId: string; | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
| } | ||||
| export interface UpdateHouseholdAdmin { | ||||
|   groupId: string; | ||||
|   name: string; | ||||
|   id: string; | ||||
|   preferences?: UpdateHouseholdPreferences | null; | ||||
| } | ||||
| export interface UpdateHouseholdPreferences { | ||||
|   privateHousehold?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
| } | ||||
| export interface RecipeIngredientBase { | ||||
|   quantity?: number | null; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean | null; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
| } | ||||
| @ -19,46 +19,18 @@ export interface CreatePlanEntry { | ||||
|   entryType?: PlanEntryType & string; | ||||
|   title?: string; | ||||
|   text?: string; | ||||
|   recipeId?: string; | ||||
|   recipeId?: string | null; | ||||
| } | ||||
| export interface CreateRandomEntry { | ||||
|   date: string; | ||||
|   entryType?: PlanEntryType & string; | ||||
| } | ||||
| export interface ListItem { | ||||
|   title?: string; | ||||
|   title?: string | null; | ||||
|   text?: string; | ||||
|   quantity?: number; | ||||
|   checked?: boolean; | ||||
| } | ||||
| export interface MealDayIn { | ||||
|   date?: string; | ||||
|   meals: MealIn[]; | ||||
| } | ||||
| export interface MealIn { | ||||
|   slug?: string; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
| } | ||||
| export interface MealDayOut { | ||||
|   date?: string; | ||||
|   meals: MealIn[]; | ||||
|   id: number; | ||||
| } | ||||
| export interface MealPlanIn { | ||||
|   group: string; | ||||
|   startDate: string; | ||||
|   endDate: string; | ||||
|   planDays: MealDayIn[]; | ||||
| } | ||||
| export interface MealPlanOut { | ||||
|   group: string; | ||||
|   startDate: string; | ||||
|   endDate: string; | ||||
|   planDays: MealDayIn[]; | ||||
|   id: number; | ||||
|   shoppingList?: number; | ||||
| } | ||||
| export interface PlanRulesCreate { | ||||
|   day?: PlanRulesDay & string; | ||||
|   entryType?: PlanRulesType & string; | ||||
| @ -76,6 +48,7 @@ export interface PlanRulesOut { | ||||
|   categories?: Category[]; | ||||
|   tags?: Tag[]; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface PlanRulesSave { | ||||
| @ -84,51 +57,56 @@ export interface PlanRulesSave { | ||||
|   categories?: Category[]; | ||||
|   tags?: Tag[]; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
| } | ||||
| export interface ReadPlanEntry { | ||||
|   date: string; | ||||
|   entryType?: PlanEntryType & string; | ||||
|   title?: string; | ||||
|   text?: string; | ||||
|   recipeId?: string; | ||||
|   recipeId?: string | null; | ||||
|   id: number; | ||||
|   groupId: string; | ||||
|   userId?: string; | ||||
|   recipe?: RecipeSummary; | ||||
|   userId?: string | null; | ||||
|   householdId: string; | ||||
|   recipe?: RecipeSummary | null; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
|   [k: string]: unknown; | ||||
| } | ||||
| export interface RecipeTool { | ||||
|   id: string; | ||||
| @ -141,18 +119,18 @@ export interface SavePlanEntry { | ||||
|   entryType?: PlanEntryType & string; | ||||
|   title?: string; | ||||
|   text?: string; | ||||
|   recipeId?: string; | ||||
|   recipeId?: string | null; | ||||
|   groupId: string; | ||||
|   userId?: string; | ||||
|   userId?: string | null; | ||||
| } | ||||
| export interface ShoppingListIn { | ||||
|   name: string; | ||||
|   group?: string; | ||||
|   group?: string | null; | ||||
|   items: ListItem[]; | ||||
| } | ||||
| export interface ShoppingListOut { | ||||
|   name: string; | ||||
|   group?: string; | ||||
|   group?: string | null; | ||||
|   items: ListItem[]; | ||||
|   id: number; | ||||
| } | ||||
| @ -161,8 +139,8 @@ export interface UpdatePlanEntry { | ||||
|   entryType?: PlanEntryType & string; | ||||
|   title?: string; | ||||
|   text?: string; | ||||
|   recipeId?: string; | ||||
|   recipeId?: string | null; | ||||
|   id: number; | ||||
|   groupId: string; | ||||
|   userId?: string; | ||||
|   userId?: string | null; | ||||
| } | ||||
|  | ||||
							
								
								
									
										65
									
								
								frontend/lib/api/types/openai.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								frontend/lib/api/types/openai.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| /* tslint:disable */ | ||||
| /* eslint-disable */ | ||||
| /** | ||||
| /* This file was automatically generated from pydantic models by running pydantic2ts. | ||||
| /* Do not modify it by hand - just update the pydantic models and then re-run the script | ||||
| */ | ||||
| 
 | ||||
| export interface OpenAIIngredient { | ||||
|   /** | ||||
|    * | ||||
|    * The input is simply the ingredient string you are processing as-is. It is forbidden to | ||||
|    * modify this at all, you must provide the input exactly as you received it. | ||||
|    * | ||||
|    */ | ||||
|   input: string; | ||||
|   /** | ||||
|    * | ||||
|    * This value is a float between 0 - 100, where 100 is full confidence that the result is correct, | ||||
|    * and 0 is no confidence that the result is correct. If you're unable to parse anything, | ||||
|    * and you put the entire string in the notes, you should return 0 confidence. If you can easily | ||||
|    * parse the string into each component, then you should return a confidence of 100. If you have to | ||||
|    * guess which part is the unit and which part is the food, your confidence should be lower, such as 60. | ||||
|    * Even if there is no unit or note, if you're able to determine the food, you may use a higher confidence. | ||||
|    * If the entire ingredient consists of only a food, you can use a confidence of 100. | ||||
|    * | ||||
|    */ | ||||
|   confidence?: number | null; | ||||
|   /** | ||||
|    * | ||||
|    * The numerical representation of how much of this ingredient. For instance, if you receive | ||||
|    * "3 1/2 grams of minced garlic", the quantity is "3 1/2". Quantity may be represented as a whole number | ||||
|    * (integer), a float or decimal, or a fraction. You should output quantity in only whole numbers or | ||||
|    * floats, converting fractions into floats. Floats longer than 10 decimal places should be | ||||
|    * rounded to 10 decimal places. | ||||
|    * | ||||
|    */ | ||||
|   quantity?: number | null; | ||||
|   /** | ||||
|    * | ||||
|    * The unit of measurement for this ingredient. For instance, if you receive | ||||
|    * "2 lbs chicken breast", the unit is "lbs" (short for "pounds"). | ||||
|    * | ||||
|    */ | ||||
|   unit?: string | null; | ||||
|   /** | ||||
|    * | ||||
|    * The actual physical ingredient used in the recipe. For instance, if you receive | ||||
|    * "3 cups of onions, chopped", the food is "onions". | ||||
|    * | ||||
|    */ | ||||
|   food?: string | null; | ||||
|   /** | ||||
|    * | ||||
|    * The rest of the text that represents more detail on how to prepare the ingredient. | ||||
|    * Anything that is not one of the above should be the note. For instance, if you receive | ||||
|    * "one can of butter beans, drained" the note would be "drained". If you receive | ||||
|    * "3 cloves of garlic peeled and finely chopped", the note would be "peeled and finely chopped". | ||||
|    * | ||||
|    */ | ||||
|   note?: string | null; | ||||
| } | ||||
| export interface OpenAIIngredients { | ||||
|   ingredients?: OpenAIIngredient[]; | ||||
| } | ||||
| export interface OpenAIBase {} | ||||
| @ -55,29 +55,32 @@ export interface CategorySave { | ||||
|   groupId: string; | ||||
| } | ||||
| export interface CreateIngredientFood { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   labelId?: string; | ||||
|   aliases?: CreateIngredientFoodAlias[]; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   labelId?: string | null; | ||||
|   aliases?: CreateIngredientFoodAlias[]; | ||||
| } | ||||
| export interface CreateIngredientFoodAlias { | ||||
|   name: string; | ||||
| } | ||||
| export interface CreateIngredientUnit { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string; | ||||
|   pluralAbbreviation?: string | null; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: CreateIngredientUnitAlias[]; | ||||
| } | ||||
| @ -89,16 +92,16 @@ export interface CreateRecipe { | ||||
| } | ||||
| export interface CreateRecipeBulk { | ||||
|   url: string; | ||||
|   categories?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   categories?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
| } | ||||
| export interface RecipeCategory { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
| } | ||||
| export interface RecipeTag { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   slug: string; | ||||
| } | ||||
| @ -116,27 +119,27 @@ export interface ExportRecipes { | ||||
|   exportType?: ExportTypes & string; | ||||
| } | ||||
| export interface IngredientConfidence { | ||||
|   average?: number; | ||||
|   comment?: number; | ||||
|   name?: number; | ||||
|   unit?: number; | ||||
|   quantity?: number; | ||||
|   food?: number; | ||||
|   average?: number | null; | ||||
|   comment?: number | null; | ||||
|   name?: number | null; | ||||
|   unit?: number | null; | ||||
|   quantity?: number | null; | ||||
|   food?: number | null; | ||||
| } | ||||
| export interface IngredientFood { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   labelId?: string; | ||||
|   aliases?: IngredientFoodAlias[]; | ||||
|   id: string; | ||||
|   label?: MultiPurposeLabelSummary; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   labelId?: string | null; | ||||
|   aliases?: IngredientFoodAlias[]; | ||||
|   label?: MultiPurposeLabelSummary | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface IngredientFoodAlias { | ||||
|   name: string; | ||||
| @ -151,27 +154,28 @@ export interface MultiPurposeLabelSummary { | ||||
|  * A list of ingredient references. | ||||
|  */ | ||||
| export interface IngredientReferences { | ||||
|   referenceId?: string; | ||||
|   referenceId?: string | null; | ||||
| } | ||||
| export interface IngredientRequest { | ||||
|   parser?: RegisteredParser & string; | ||||
|   ingredient: string; | ||||
| } | ||||
| export interface IngredientUnit { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string; | ||||
|   pluralAbbreviation?: string | null; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: IngredientUnitAlias[]; | ||||
|   id: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
| } | ||||
| export interface IngredientUnitAlias { | ||||
|   name: string; | ||||
| @ -189,64 +193,65 @@ export interface MergeUnit { | ||||
|   toUnit: string; | ||||
| } | ||||
| export interface Nutrition { | ||||
|   calories?: string; | ||||
|   fatContent?: string; | ||||
|   proteinContent?: string; | ||||
|   carbohydrateContent?: string; | ||||
|   fiberContent?: string; | ||||
|   sodiumContent?: string; | ||||
|   sugarContent?: string; | ||||
|   calories?: string | null; | ||||
|   fatContent?: string | null; | ||||
|   proteinContent?: string | null; | ||||
|   carbohydrateContent?: string | null; | ||||
|   fiberContent?: string | null; | ||||
|   sodiumContent?: string | null; | ||||
|   sugarContent?: string | null; | ||||
| } | ||||
| export interface ParsedIngredient { | ||||
|   input?: string; | ||||
|   input?: string | null; | ||||
|   confidence?: IngredientConfidence; | ||||
|   ingredient: RecipeIngredient; | ||||
| } | ||||
| export interface RecipeIngredient { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   quantity?: number | null; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean | null; | ||||
|   disableAmount?: boolean; | ||||
|   display?: string; | ||||
|   title?: string; | ||||
|   originalText?: string; | ||||
|   title?: string | null; | ||||
|   originalText?: string | null; | ||||
|   referenceId?: string; | ||||
| } | ||||
| export interface Recipe { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
|   recipeIngredient?: RecipeIngredient[]; | ||||
|   recipeInstructions?: RecipeStep[]; | ||||
|   nutrition?: Nutrition; | ||||
|   settings?: RecipeSettings; | ||||
|   assets?: RecipeAsset[]; | ||||
|   notes?: RecipeNote[]; | ||||
|   recipeInstructions?: RecipeStep[] | null; | ||||
|   nutrition?: Nutrition | null; | ||||
|   settings?: RecipeSettings | null; | ||||
|   assets?: RecipeAsset[] | null; | ||||
|   notes?: RecipeNote[] | null; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   comments?: RecipeCommentOut[]; | ||||
|   } | null; | ||||
|   comments?: RecipeCommentOut[] | null; | ||||
| } | ||||
| export interface RecipeTool { | ||||
|   id: string; | ||||
| @ -255,15 +260,15 @@ export interface RecipeTool { | ||||
|   onHand?: boolean; | ||||
| } | ||||
| export interface RecipeStep { | ||||
|   id?: string; | ||||
|   title?: string; | ||||
|   id?: string | null; | ||||
|   title?: string | null; | ||||
|   text: string; | ||||
|   ingredientReferences?: IngredientReferences[]; | ||||
| } | ||||
| export interface RecipeAsset { | ||||
|   name: string; | ||||
|   icon: string; | ||||
|   fileName?: string; | ||||
|   fileName?: string | null; | ||||
| } | ||||
| export interface RecipeNote { | ||||
|   title: string; | ||||
| @ -274,13 +279,13 @@ export interface RecipeCommentOut { | ||||
|   text: string; | ||||
|   id: string; | ||||
|   createdAt: string; | ||||
|   updateAt: string; | ||||
|   updatedAt: string; | ||||
|   userId: string; | ||||
|   user: UserBase; | ||||
| } | ||||
| export interface UserBase { | ||||
|   id: string; | ||||
|   username?: string; | ||||
|   username?: string | null; | ||||
|   admin: boolean; | ||||
| } | ||||
| export interface RecipeCategoryResponse { | ||||
| @ -290,28 +295,29 @@ export interface RecipeCategoryResponse { | ||||
|   recipes?: RecipeSummary[]; | ||||
| } | ||||
| export interface RecipeSummary { | ||||
|   id?: string; | ||||
|   id?: string | null; | ||||
|   userId?: string; | ||||
|   householdId?: string; | ||||
|   groupId?: string; | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
|   slug?: string; | ||||
|   image?: unknown; | ||||
|   recipeYield?: string; | ||||
|   totalTime?: string; | ||||
|   prepTime?: string; | ||||
|   cookTime?: string; | ||||
|   performTime?: string; | ||||
|   description?: string; | ||||
|   recipeCategory?: RecipeCategory[]; | ||||
|   tags?: RecipeTag[]; | ||||
|   recipeYield?: string | null; | ||||
|   totalTime?: string | null; | ||||
|   prepTime?: string | null; | ||||
|   cookTime?: string | null; | ||||
|   performTime?: string | null; | ||||
|   description?: string | null; | ||||
|   recipeCategory?: RecipeCategory[] | null; | ||||
|   tags?: RecipeTag[] | null; | ||||
|   tools?: RecipeTool[]; | ||||
|   rating?: number; | ||||
|   orgURL?: string; | ||||
|   dateAdded?: string; | ||||
|   dateUpdated?: string; | ||||
|   createdAt?: string; | ||||
|   updateAt?: string; | ||||
|   lastMade?: string; | ||||
|   rating?: number | null; | ||||
|   orgURL?: string | null; | ||||
|   dateAdded?: string | null; | ||||
|   dateUpdated?: string | null; | ||||
|   createdAt?: string | null; | ||||
|   updatedAt?: string | null; | ||||
|   lastMade?: string | null; | ||||
| } | ||||
| export interface RecipeCommentCreate { | ||||
|   recipeId: string; | ||||
| @ -327,15 +333,15 @@ export interface RecipeCommentUpdate { | ||||
|   text: string; | ||||
| } | ||||
| export interface RecipeDuplicate { | ||||
|   name?: string; | ||||
|   name?: string | null; | ||||
| } | ||||
| export interface RecipeIngredientBase { | ||||
|   quantity?: number; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit; | ||||
|   food?: IngredientFood | CreateIngredientFood; | ||||
|   note?: string; | ||||
|   isFood?: boolean; | ||||
|   disableAmount?: boolean; | ||||
|   quantity?: number | null; | ||||
|   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||
|   food?: IngredientFood | CreateIngredientFood | null; | ||||
|   note?: string | null; | ||||
|   isFood?: boolean | null; | ||||
|   disableAmount?: boolean | null; | ||||
|   display?: string; | ||||
| } | ||||
| export interface RecipeLastMade { | ||||
| @ -379,17 +385,17 @@ export interface RecipeTimelineEventCreate { | ||||
|   userId: string; | ||||
|   subject: string; | ||||
|   eventType: TimelineEventType; | ||||
|   eventMessage?: string; | ||||
|   image?: TimelineEventImage & string; | ||||
|   eventMessage?: string | null; | ||||
|   image?: TimelineEventImage | null; | ||||
|   timestamp?: string; | ||||
| } | ||||
| export interface RecipeTimelineEventIn { | ||||
|   recipeId: string; | ||||
|   userId?: string; | ||||
|   userId?: string | null; | ||||
|   subject: string; | ||||
|   eventType: TimelineEventType; | ||||
|   eventMessage?: string; | ||||
|   image?: TimelineEventImage & string; | ||||
|   eventMessage?: string | null; | ||||
|   image?: TimelineEventImage | null; | ||||
|   timestamp?: string; | ||||
| } | ||||
| export interface RecipeTimelineEventOut { | ||||
| @ -397,17 +403,19 @@ export interface RecipeTimelineEventOut { | ||||
|   userId: string; | ||||
|   subject: string; | ||||
|   eventType: TimelineEventType; | ||||
|   eventMessage?: string; | ||||
|   image?: TimelineEventImage & string; | ||||
|   eventMessage?: string | null; | ||||
|   image?: TimelineEventImage | null; | ||||
|   timestamp?: string; | ||||
|   id: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   createdAt: string; | ||||
|   updateAt: string; | ||||
|   updatedAt: string; | ||||
| } | ||||
| export interface RecipeTimelineEventUpdate { | ||||
|   subject: string; | ||||
|   eventMessage?: string; | ||||
|   image?: TimelineEventImage; | ||||
|   eventMessage?: string | null; | ||||
|   image?: TimelineEventImage | null; | ||||
| } | ||||
| export interface RecipeToolCreate { | ||||
|   name: string; | ||||
| @ -435,26 +443,30 @@ export interface RecipeZipTokenResponse { | ||||
|   token: string; | ||||
| } | ||||
| export interface SaveIngredientFood { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   labelId?: string; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   labelId?: string | null; | ||||
|   aliases?: CreateIngredientFoodAlias[]; | ||||
|   groupId: string; | ||||
| } | ||||
| export interface SaveIngredientUnit { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
|   fraction?: boolean; | ||||
|   abbreviation?: string; | ||||
|   pluralAbbreviation?: string; | ||||
|   pluralAbbreviation?: string | null; | ||||
|   useAbbreviation?: boolean; | ||||
|   aliases?: CreateIngredientUnitAlias[]; | ||||
|   groupId: string; | ||||
| @ -465,6 +477,7 @@ export interface ScrapeRecipe { | ||||
| } | ||||
| export interface ScrapeRecipeTest { | ||||
|   url: string; | ||||
|   useOpenAI?: boolean; | ||||
| } | ||||
| export interface SlugResponse {} | ||||
| export interface TagIn { | ||||
| @ -481,12 +494,14 @@ export interface TagSave { | ||||
|   groupId: string; | ||||
| } | ||||
| export interface UnitFoodBase { | ||||
|   id?: string | null; | ||||
|   name: string; | ||||
|   pluralName?: string; | ||||
|   pluralName?: string | null; | ||||
|   description?: string; | ||||
|   extras?: { | ||||
|     [k: string]: unknown; | ||||
|   }; | ||||
|   } | null; | ||||
|   onHand?: boolean; | ||||
| } | ||||
| export interface UpdateImageResponse { | ||||
|   image: string; | ||||
|  | ||||
| @ -11,7 +11,7 @@ export type OrderDirection = "asc" | "desc"; | ||||
| export interface ErrorResponse { | ||||
|   message: string; | ||||
|   error?: boolean; | ||||
|   exception?: string; | ||||
|   exception?: string | null; | ||||
| } | ||||
| export interface FileTokenResponse { | ||||
|   fileToken: string; | ||||
| @ -19,19 +19,19 @@ export interface FileTokenResponse { | ||||
| export interface PaginationQuery { | ||||
|   page?: number; | ||||
|   perPage?: number; | ||||
|   orderBy?: string; | ||||
|   orderByNullPosition?: OrderByNullPosition; | ||||
|   orderBy?: string | null; | ||||
|   orderByNullPosition?: OrderByNullPosition | null; | ||||
|   orderDirection?: OrderDirection & string; | ||||
|   queryFilter?: string; | ||||
|   paginationSeed?: string; | ||||
|   queryFilter?: string | null; | ||||
|   paginationSeed?: string | null; | ||||
| } | ||||
| export interface RecipeSearchQuery { | ||||
|   cookbook?: string; | ||||
|   cookbook?: string | null; | ||||
|   requireAllCategories?: boolean; | ||||
|   requireAllTags?: boolean; | ||||
|   requireAllTools?: boolean; | ||||
|   requireAllFoods?: boolean; | ||||
|   search?: string; | ||||
|   search?: string | null; | ||||
| } | ||||
| export interface SuccessResponse { | ||||
|   message: string; | ||||
|  | ||||
| @ -19,8 +19,9 @@ export interface CreateToken { | ||||
|   token: string; | ||||
| } | ||||
| export interface CreateUserRegistration { | ||||
|   group?: string; | ||||
|   groupToken?: string; | ||||
|   group?: string | null; | ||||
|   household?: string | null; | ||||
|   groupToken?: string | null; | ||||
|   email: string; | ||||
|   username: string; | ||||
|   fullName: string; | ||||
| @ -45,21 +46,19 @@ export interface ForgotPassword { | ||||
| export interface GroupBase { | ||||
|   name: string; | ||||
| } | ||||
| export interface GroupHouseholdSummary { | ||||
|   id: string; | ||||
|   name: string; | ||||
| } | ||||
| export interface GroupInDB { | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   categories?: CategoryBase[]; | ||||
|   categories?: CategoryBase[] | null; | ||||
|   webhooks?: ReadWebhook[]; | ||||
|   users?: UserOut[]; | ||||
|   preferences?: ReadGroupPreferences; | ||||
| } | ||||
| export interface GroupSummary { | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   preferences?: ReadGroupPreferences; | ||||
| 
 | ||||
|   households?: GroupHouseholdSummary[] | null; | ||||
|   users?: UserSummary[] | null; | ||||
|   preferences?: ReadGroupPreferences | null; | ||||
| } | ||||
| export interface CategoryBase { | ||||
|   name: string; | ||||
| @ -73,43 +72,24 @@ export interface ReadWebhook { | ||||
|   webhookType?: WebhookType & string; | ||||
|   scheduledTime: string; | ||||
|   groupId: string; | ||||
|   householdId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface UserOut { | ||||
| export interface UserSummary { | ||||
|   id: string; | ||||
|   username?: string; | ||||
|   fullName?: string; | ||||
|   email: string; | ||||
|   authMethod?: AuthMethod & string; | ||||
|   admin?: boolean; | ||||
|   group: string; | ||||
|   advanced?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canManage?: boolean; | ||||
|   canOrganize?: boolean; | ||||
|   groupId: string; | ||||
|   groupSlug: string; | ||||
|   tokens?: LongLiveTokenOut[]; | ||||
|   cacheKey: string; | ||||
| } | ||||
| export interface LongLiveTokenOut { | ||||
|   token: string; | ||||
|   name: string; | ||||
|   id: number; | ||||
|   createdAt?: string; | ||||
|   fullName: string; | ||||
| } | ||||
| export interface ReadGroupPreferences { | ||||
|   privateGroup?: boolean; | ||||
|   firstDayOfWeek?: number; | ||||
|   recipePublic?: boolean; | ||||
|   recipeShowNutrition?: boolean; | ||||
|   recipeShowAssets?: boolean; | ||||
|   recipeLandscapeView?: boolean; | ||||
|   recipeDisableComments?: boolean; | ||||
|   recipeDisableAmount?: boolean; | ||||
|   groupId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface GroupSummary { | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   preferences?: ReadGroupPreferences | null; | ||||
| } | ||||
| export interface LongLiveTokenIn { | ||||
|   name: string; | ||||
|   integrationId?: string; | ||||
| @ -124,23 +104,32 @@ export interface LongLiveTokenInDB { | ||||
| } | ||||
| export interface PrivateUser { | ||||
|   id: string; | ||||
|   username?: string; | ||||
|   fullName?: string; | ||||
|   username?: string | null; | ||||
|   fullName?: string | null; | ||||
|   email: string; | ||||
|   authMethod?: AuthMethod & string; | ||||
|   admin?: boolean; | ||||
|   group: string; | ||||
|   household: string; | ||||
|   advanced?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canManage?: boolean; | ||||
|   canOrganize?: boolean; | ||||
|   groupId: string; | ||||
|   groupSlug: string; | ||||
|   tokens?: LongLiveTokenOut[]; | ||||
|   householdId: string; | ||||
|   householdSlug: string; | ||||
|   tokens?: LongLiveTokenOut[] | null; | ||||
|   cacheKey: string; | ||||
|   password: string; | ||||
|   loginAttemps?: number; | ||||
|   lockedAt?: string; | ||||
|   lockedAt?: string | null; | ||||
| } | ||||
| export interface LongLiveTokenOut { | ||||
|   token: string; | ||||
|   name: string; | ||||
|   id: number; | ||||
|   createdAt?: string | null; | ||||
| } | ||||
| export interface OIDCRequest { | ||||
|   id_token: string; | ||||
| @ -168,8 +157,8 @@ export interface Token { | ||||
|   token_type: string; | ||||
| } | ||||
| export interface TokenData { | ||||
|   user_id?: string; | ||||
|   username?: string; | ||||
|   user_id?: string | null; | ||||
|   username?: string | null; | ||||
| } | ||||
| export interface UnlockResults { | ||||
|   unlocked?: number; | ||||
| @ -178,7 +167,7 @@ export interface UpdateGroup { | ||||
|   name: string; | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   categories?: CategoryBase[]; | ||||
|   categories?: CategoryBase[] | null; | ||||
|   webhooks?: CreateWebhook[]; | ||||
| } | ||||
| export interface CreateWebhook { | ||||
| @ -189,53 +178,75 @@ export interface CreateWebhook { | ||||
|   scheduledTime: string; | ||||
| } | ||||
| export interface UserBase { | ||||
|   id?: string; | ||||
|   username?: string; | ||||
|   fullName?: string; | ||||
|   id?: string | null; | ||||
|   username?: string | null; | ||||
|   fullName?: string | null; | ||||
|   email: string; | ||||
|   authMethod?: AuthMethod & string; | ||||
|   admin?: boolean; | ||||
|   group?: string; | ||||
|   group?: string | null; | ||||
|   household?: string | null; | ||||
|   advanced?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canManage?: boolean; | ||||
|   canOrganize?: boolean; | ||||
| } | ||||
| export interface UserIn { | ||||
|   id?: string; | ||||
|   username?: string; | ||||
|   fullName?: string; | ||||
|   id?: string | null; | ||||
|   username?: string | null; | ||||
|   fullName?: string | null; | ||||
|   email: string; | ||||
|   authMethod?: AuthMethod & string; | ||||
|   admin?: boolean; | ||||
|   group?: string; | ||||
|   group?: string | null; | ||||
|   household?: string | null; | ||||
|   advanced?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canManage?: boolean; | ||||
|   canOrganize?: boolean; | ||||
|   password: string; | ||||
| } | ||||
| export interface UserOut { | ||||
|   id: string; | ||||
|   username?: string | null; | ||||
|   fullName?: string | null; | ||||
|   email: string; | ||||
|   authMethod?: AuthMethod & string; | ||||
|   admin?: boolean; | ||||
|   group: string; | ||||
|   household: string; | ||||
|   advanced?: boolean; | ||||
|   canInvite?: boolean; | ||||
|   canManage?: boolean; | ||||
|   canOrganize?: boolean; | ||||
|   groupId: string; | ||||
|   groupSlug: string; | ||||
|   householdId: string; | ||||
|   householdSlug: string; | ||||
|   tokens?: LongLiveTokenOut[] | null; | ||||
|   cacheKey: string; | ||||
| } | ||||
| export interface UserRatingCreate { | ||||
|   recipeId: string; | ||||
|   rating?: number; | ||||
|   rating?: number | null; | ||||
|   isFavorite?: boolean; | ||||
|   userId: string; | ||||
| } | ||||
| export interface UserRatingOut { | ||||
|   recipeId: string; | ||||
|   rating?: number; | ||||
|   rating?: number | null; | ||||
|   isFavorite?: boolean; | ||||
|   userId: string; | ||||
|   id: string; | ||||
| } | ||||
| export interface UserRatingSummary { | ||||
|   recipeId: string; | ||||
|   rating?: number; | ||||
|   rating?: number | null; | ||||
|   isFavorite?: boolean; | ||||
| } | ||||
| export interface UserSummary { | ||||
|   id: string; | ||||
|   fullName: string; | ||||
| export interface UserRatingUpdate { | ||||
|   rating?: number | null; | ||||
|   isFavorite?: boolean | null; | ||||
| } | ||||
| export interface ValidateResetToken { | ||||
|   token: string; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { BaseAPI } from "../base/base-clients"; | ||||
| import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/group"; | ||||
| import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/household"; | ||||
| import { ForgotPassword } from "~/lib/api/types/user"; | ||||
| import { EmailTest } from "~/lib/api/types/admin"; | ||||
| 
 | ||||
| @ -7,7 +7,7 @@ const routes = { | ||||
|   base: "/api/admin/email", | ||||
|   forgotPassword: "/api/users/forgot-password", | ||||
| 
 | ||||
|   invitation: "/api/groups/invitations/email", | ||||
|   invitation: "/api/households/invitations/email", | ||||
| }; | ||||
| 
 | ||||
| export class EmailAPI extends BaseAPI { | ||||
|  | ||||
| @ -4,8 +4,8 @@ import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/ | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   cookbooks: `${prefix}/groups/cookbooks`, | ||||
|   cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`, | ||||
|   cookbooks: `${prefix}/households/cookbooks`, | ||||
|   cookbooksId: (id: number) => `${prefix}/households/cookbooks/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> { | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/group"; | ||||
| import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   eventNotifier: `${prefix}/groups/events/notifications`, | ||||
|   eventNotifierId: (id: string | number) => `${prefix}/groups/events/notifications/${id}`, | ||||
|   eventNotifier: `${prefix}/households/events/notifications`, | ||||
|   eventNotifierId: (id: string | number) => `${prefix}/households/events/notifications/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class GroupEventNotifierApi extends BaseCRUDAPI< | ||||
|  | ||||
| @ -4,8 +4,8 @@ import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan"; | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   rule: `${prefix}/groups/mealplans/rules`, | ||||
|   ruleId: (id: string | number) => `${prefix}/groups/mealplans/rules/${id}`, | ||||
|   rule: `${prefix}/households/mealplans/rules`, | ||||
|   ruleId: (id: string | number) => `${prefix}/households/mealplans/rules/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesCreate, PlanRulesOut> { | ||||
|  | ||||
| @ -4,9 +4,9 @@ import { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } fr | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   mealplan: `${prefix}/groups/mealplans`, | ||||
|   random: `${prefix}/groups/mealplans/random`, | ||||
|   mealplanId: (id: string | number) => `${prefix}/groups/mealplans/${id}`, | ||||
|   mealplan: `${prefix}/households/mealplans`, | ||||
|   random: `${prefix}/households/mealplans/random`, | ||||
|   mealplanId: (id: string | number) => `${prefix}/households/mealplans/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class MealPlanAPI extends BaseCRUDAPI<CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry> { | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/group"; | ||||
| import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|     groupRecipeActions: `${prefix}/groups/recipe-actions`, | ||||
|     groupRecipeActionsId: (id: string | number) => `${prefix}/groups/recipe-actions/${id}`, | ||||
|     groupRecipeActions: `${prefix}/households/recipe-actions`, | ||||
|     groupRecipeActionsId: (id: string | number) => `${prefix}/households/recipe-actions/${id}`, | ||||
|   }; | ||||
| 
 | ||||
|   export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> { | ||||
|  | ||||
| @ -9,20 +9,20 @@ import { | ||||
|   ShoppingListMultiPurposeLabelUpdate, | ||||
|   ShoppingListOut, | ||||
|   ShoppingListUpdate, | ||||
| } from "~/lib/api/types/group"; | ||||
| } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   shoppingLists: `${prefix}/groups/shopping/lists`, | ||||
|   shoppingListsId: (id: string) => `${prefix}/groups/shopping/lists/${id}`, | ||||
|   shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}`, | ||||
|   shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}/delete`, | ||||
|   shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/groups/shopping/lists/${id}/label-settings`, | ||||
|   shoppingLists: `${prefix}/households/shopping/lists`, | ||||
|   shoppingListsId: (id: string) => `${prefix}/households/shopping/lists/${id}`, | ||||
|   shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}`, | ||||
|   shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}/delete`, | ||||
|   shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/households/shopping/lists/${id}/label-settings`, | ||||
| 
 | ||||
|   shoppingListItems: `${prefix}/groups/shopping/items`, | ||||
|   shoppingListItemsCreateBulk: `${prefix}/groups/shopping/items/create-bulk`, | ||||
|   shoppingListItemsId: (id: string) => `${prefix}/groups/shopping/items/${id}`, | ||||
|   shoppingListItems: `${prefix}/households/shopping/items`, | ||||
|   shoppingListItemsCreateBulk: `${prefix}/households/shopping/items/create-bulk`, | ||||
|   shoppingListItemsId: (id: string) => `${prefix}/households/shopping/items/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class ShoppingListsApi extends BaseCRUDAPI<ShoppingListCreate, ShoppingListOut, ShoppingListUpdate> { | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { CreateWebhook, ReadWebhook } from "~/lib/api/types/group"; | ||||
| import { CreateWebhook, ReadWebhook } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   webhooks: `${prefix}/groups/webhooks`, | ||||
|   webhooksId: (id: string | number) => `${prefix}/groups/webhooks/${id}`, | ||||
|   webhooksIdTest: (id: string | number) => `${prefix}/groups/webhooks/${id}/test`, | ||||
|   webhooks: `${prefix}/households/webhooks`, | ||||
|   webhooksId: (id: string | number) => `${prefix}/households/webhooks/${id}`, | ||||
|   webhooksIdTest: (id: string | number) => `${prefix}/households/webhooks/${id}/test`, | ||||
| }; | ||||
| 
 | ||||
| export class WebhooksAPI extends BaseCRUDAPI<CreateWebhook, ReadWebhook> { | ||||
|  | ||||
| @ -1,13 +1,10 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { CategoryBase, GroupBase, GroupInDB, GroupSummary, UserOut } from "~/lib/api/types/user"; | ||||
| import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user"; | ||||
| import { HouseholdSummary } from "~/lib/api/types/household"; | ||||
| import { | ||||
|   CreateInviteToken, | ||||
|   GroupAdminUpdate, | ||||
|   GroupStatistics, | ||||
|   GroupStorage, | ||||
|   ReadGroupPreferences, | ||||
|   ReadInviteToken, | ||||
|   SetPermissions, | ||||
|   UpdateGroupPreferences, | ||||
| } from "~/lib/api/types/group"; | ||||
| 
 | ||||
| @ -16,16 +13,14 @@ const prefix = "/api"; | ||||
| const routes = { | ||||
|   groups: `${prefix}/admin/groups`, | ||||
|   groupsSelf: `${prefix}/groups/self`, | ||||
|   categories: `${prefix}/groups/categories`, | ||||
|   members: `${prefix}/groups/members`, | ||||
|   permissions: `${prefix}/groups/permissions`, | ||||
| 
 | ||||
|   preferences: `${prefix}/groups/preferences`, | ||||
|   statistics: `${prefix}/groups/statistics`, | ||||
|   storage: `${prefix}/groups/storage`, | ||||
| 
 | ||||
|   invitation: `${prefix}/groups/invitations`, | ||||
| 
 | ||||
|   households: `${prefix}/households`, | ||||
|   membersHouseholdId: (householdId: string | number | null) => { | ||||
|     return householdId ? | ||||
|       `${prefix}/households/members?householdId=${householdId}` : | ||||
|       `${prefix}/groups/members`; | ||||
|   }, | ||||
|   groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`, | ||||
| }; | ||||
| 
 | ||||
| @ -38,14 +33,6 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate | ||||
|     return await this.requests.get<GroupSummary>(routes.groupsSelf); | ||||
|   } | ||||
| 
 | ||||
|   async getCategories() { | ||||
|     return await this.requests.get<CategoryBase[]>(routes.categories); | ||||
|   } | ||||
| 
 | ||||
|   async setCategories(payload: CategoryBase[]) { | ||||
|     return await this.requests.put<CategoryBase[]>(routes.categories, payload); | ||||
|   } | ||||
| 
 | ||||
|   async getPreferences() { | ||||
|     return await this.requests.get<ReadGroupPreferences>(routes.preferences); | ||||
|   } | ||||
| @ -55,21 +42,12 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate | ||||
|     return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload); | ||||
|   } | ||||
| 
 | ||||
|   async createInvitation(payload: CreateInviteToken) { | ||||
|     return await this.requests.post<ReadInviteToken>(routes.invitation, payload); | ||||
|   async fetchMembers(householdId: string | number | null = null) { | ||||
|     return await this.requests.get<UserSummary[]>(routes.membersHouseholdId(householdId)); | ||||
|   } | ||||
| 
 | ||||
|   async fetchMembers() { | ||||
|     return await this.requests.get<UserOut[]>(routes.members); | ||||
|   } | ||||
| 
 | ||||
|   async setMemberPermissions(payload: SetPermissions) { | ||||
|     // TODO: This should probably be a patch request, which isn't offered by the API currently
 | ||||
|     return await this.requests.put<UserOut, SetPermissions>(routes.permissions, payload); | ||||
|   } | ||||
| 
 | ||||
|   async statistics() { | ||||
|     return await this.requests.get<GroupStatistics>(routes.statistics); | ||||
|   async fetchHouseholds() { | ||||
|     return await this.requests.get<HouseholdSummary[]>(routes.households); | ||||
|   } | ||||
| 
 | ||||
|   async storage() { | ||||
|  | ||||
							
								
								
									
										64
									
								
								frontend/lib/api/user/households.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/api/user/households.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { UserOut } from "~/lib/api/types/user"; | ||||
| import { | ||||
|   HouseholdCreate, | ||||
|   HouseholdInDB, | ||||
|   UpdateHouseholdAdmin, | ||||
|   HouseholdStatistics, | ||||
|   ReadHouseholdPreferences, | ||||
|   SetPermissions, | ||||
|   UpdateHouseholdPreferences, | ||||
|   CreateInviteToken, | ||||
|   ReadInviteToken, | ||||
| } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   households: `${prefix}/admin/households`, | ||||
|   householdsSelf: `${prefix}/households/self`, | ||||
|   members: `${prefix}/households/members`, | ||||
|   permissions: `${prefix}/households/permissions`, | ||||
| 
 | ||||
|   preferences: `${prefix}/households/preferences`, | ||||
|   statistics: `${prefix}/households/statistics`, | ||||
|   invitation: `${prefix}/households/invitations`, | ||||
| 
 | ||||
|   householdsId: (id: string | number) => `${prefix}/admin/households/${id}`, | ||||
| }; | ||||
| 
 | ||||
| export class HouseholdAPI extends BaseCRUDAPI<HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin> { | ||||
|   baseRoute = routes.households; | ||||
|   itemRoute = routes.householdsId; | ||||
|   /** Returns the Group Data for the Current User | ||||
|    */ | ||||
|   async getCurrentUserHousehold() { | ||||
|     return await this.requests.get<HouseholdInDB>(routes.householdsSelf); | ||||
|   } | ||||
| 
 | ||||
|   async getPreferences() { | ||||
|     return await this.requests.get<ReadHouseholdPreferences>(routes.preferences); | ||||
|   } | ||||
| 
 | ||||
|   async setPreferences(payload: UpdateHouseholdPreferences) { | ||||
|     // TODO: This should probably be a patch request, which isn't offered by the API currently
 | ||||
|     return await this.requests.put<ReadHouseholdPreferences, UpdateHouseholdPreferences>(routes.preferences, payload); | ||||
|   } | ||||
| 
 | ||||
|   async createInvitation(payload: CreateInviteToken) { | ||||
|     return await this.requests.post<ReadInviteToken>(routes.invitation, payload); | ||||
|   } | ||||
| 
 | ||||
|   async fetchMembers() { | ||||
|     return await this.requests.get<UserOut[]>(routes.members); | ||||
|   } | ||||
| 
 | ||||
|   async setMemberPermissions(payload: SetPermissions) { | ||||
|     // TODO: This should probably be a patch request, which isn't offered by the API currently
 | ||||
|     return await this.requests.put<UserOut, SetPermissions>(routes.permissions, payload); | ||||
|   } | ||||
| 
 | ||||
|   async statistics() { | ||||
|     return await this.requests.get<HouseholdStatistics>(routes.statistics); | ||||
|   } | ||||
| } | ||||
| @ -1,6 +1,4 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { QueryValue, route } from "~/lib/api/base/route"; | ||||
| import { PaginationData } from "~/lib/api/types/non-generated"; | ||||
| import { | ||||
|   ChangePassword, | ||||
|   DeleteTokenResponse, | ||||
| @ -12,7 +10,6 @@ import { | ||||
|   UserOut, | ||||
|   UserRatingOut, | ||||
|   UserRatingSummary, | ||||
|   UserSummary, | ||||
| } from "~/lib/api/types/user"; | ||||
| 
 | ||||
| export interface UserRatingsSummaries { | ||||
| @ -26,7 +23,6 @@ export interface UserRatingsOut { | ||||
| const prefix = "/api"; | ||||
| 
 | ||||
| const routes = { | ||||
|   groupUsers: `${prefix}/users/group-users`, | ||||
|   usersSelf: `${prefix}/users/self`, | ||||
|   ratingsSelf: `${prefix}/users/self/ratings`, | ||||
|   passwordReset: `${prefix}/users/reset-password`, | ||||
| @ -51,10 +47,6 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> { | ||||
|   baseRoute: string = routes.users; | ||||
|   itemRoute = (itemid: string) => routes.usersId(itemid); | ||||
| 
 | ||||
|   async getGroupUsers(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) { | ||||
|     return await this.requests.get<PaginationData<UserSummary>>(route(routes.groupUsers, { page, perPage, ...params })); | ||||
|   } | ||||
| 
 | ||||
|   async addFavorite(id: string, slug: string) { | ||||
|     return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {}); | ||||
|   } | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { | ||||
|   mdiAccountGroup, | ||||
|   mdiSlotMachine, | ||||
|   mdiHome, | ||||
|   mdiHomeAccount, | ||||
|   mdiMagnify, | ||||
|   mdiPotSteamOutline, | ||||
|   mdiTranslate, | ||||
| @ -226,6 +227,7 @@ export const icons = { | ||||
|   heart: mdiHeart, | ||||
|   heartOutline: mdiHeartOutline, | ||||
|   home: mdiHome, | ||||
|   household: mdiHomeAccount, | ||||
|   import: mdiImport, | ||||
|   information: mdiInformation, | ||||
|   informationVariant: mdiInformationVariant, | ||||
|  | ||||
| @ -477,7 +477,7 @@ export default { | ||||
|           "name": "Meal Planner", | ||||
|           "short_name": "Meal Planner", | ||||
|           "description": "Open the meal planner", | ||||
|           "url": "/group/mealplan/planner/view", | ||||
|           "url": "/household/mealplan/planner/view", | ||||
|           "icons": [ | ||||
|             { | ||||
|               "src": "/icons/mdiCalendarMultiselect-192x192.png", | ||||
|  | ||||
| @ -60,7 +60,7 @@ | ||||
|           <i18n path="settings.backup.experimental-description" /> | ||||
|           </v-card-text> | ||||
|         </BaseCardSectionTitle> | ||||
|         <v-toolbar color="background" flat class="justify-between"> | ||||
|         <v-toolbar color="transparent" flat class="justify-between"> | ||||
|         <BaseButton class="mr-2" @click="createBackup"> {{ $t("settings.backup.create-heading") }} </BaseButton> | ||||
|         <AppButtonUpload | ||||
|                 :text-btn="false" | ||||
|  | ||||
| @ -1,15 +1,14 @@ | ||||
| // TODO: Edit Group | ||||
| <template> | ||||
|   <v-container fluid> | ||||
|     <BaseDialog | ||||
|       v-model="createDialog" | ||||
|       :title="$t('group.create-group')" | ||||
|       :icon="$globals.icons.group" | ||||
|       @submit="createGroup(createUserForm.data)" | ||||
|       @submit="createGroup(createGroupForm.data)" | ||||
|     > | ||||
|       <template #activator> </template> | ||||
|       <v-card-text> | ||||
|         <AutoForm v-model="createUserForm.data" :update-mode="updateMode" :items="createUserForm.items" /> | ||||
|         <AutoForm v-model="createGroupForm.data" :update-mode="updateMode" :items="createGroupForm.items" /> | ||||
|       </v-card-text> | ||||
|     </BaseDialog> | ||||
| 
 | ||||
| @ -27,7 +26,7 @@ | ||||
| 
 | ||||
|     <BaseCardSectionTitle :title="$tc('group.group-management')"> </BaseCardSectionTitle> | ||||
|     <section> | ||||
|       <v-toolbar flat color="background" class="justify-between"> | ||||
|       <v-toolbar flat color="transparent" class="justify-between"> | ||||
|         <BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton> | ||||
|       </v-toolbar> | ||||
| 
 | ||||
| @ -41,15 +40,15 @@ | ||||
|         :search="search" | ||||
|         @click:row="handleRowClick" | ||||
|       > | ||||
|         <template #item.households="{ item }"> | ||||
|           {{ item.households.length }} | ||||
|         </template> | ||||
|         <template #item.users="{ item }"> | ||||
|           {{ item.users.length }} | ||||
|         </template> | ||||
|         <template #item.webhookEnable="{ item }"> | ||||
|           {{ item.webhooks.length > 0 ? $t("general.yes") : $t("general.no") }} | ||||
|         </template> | ||||
|         <template #item.actions="{ item }"> | ||||
|           <v-btn | ||||
|             :disabled="item && item.users.length > 0" | ||||
|             :disabled="item && (item.households.length > 0 || item.users.length > 0)" | ||||
|             class="mr-1" | ||||
|             icon | ||||
|             color="error" | ||||
| @ -94,12 +93,12 @@ export default defineComponent({ | ||||
|           value: "id", | ||||
|         }, | ||||
|         { text: i18n.t("general.name"), value: "name" }, | ||||
|         { text: i18n.t("group.total-households"), value: "households" }, | ||||
|         { text: i18n.t("user.total-users"), value: "users" }, | ||||
|         { text: i18n.t("user.webhooks-enabled"), value: "webhookEnable" }, | ||||
|         { text: i18n.t("general.delete"), value: "actions" }, | ||||
|       ], | ||||
|       updateMode: false, | ||||
|       createUserForm: { | ||||
|       createGroupForm: { | ||||
|         items: [ | ||||
|           { | ||||
|             label: i18n.t("group.group-name"), | ||||
| @ -116,7 +115,7 @@ export default defineComponent({ | ||||
| 
 | ||||
|     function openDialog() { | ||||
|       state.createDialog = true; | ||||
|       state.createUserForm.data.name = ""; | ||||
|       state.createGroupForm.data.name = ""; | ||||
|     } | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
|  | ||||
							
								
								
									
										117
									
								
								frontend/pages/admin/manage/households/_id.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								frontend/pages/admin/manage/households/_id.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| <template> | ||||
|   <v-container v-if="household" class="narrow-container"> | ||||
|     <BasePageTitle> | ||||
|       <template #header> | ||||
|         <v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img> | ||||
|       </template> | ||||
|       <template #title> {{ $t('household.admin-household-management') }} </template> | ||||
|       {{ $t('household.admin-household-management-text') }} | ||||
|     </BasePageTitle> | ||||
|     <AppToolbar back> </AppToolbar> | ||||
|     <v-card-text> {{ $t('household.household-id-value', [household.id]) }} </v-card-text> | ||||
|     <v-form v-if="!userError" ref="refHouseholdEditForm" @submit.prevent="handleSubmit"> | ||||
|       <v-card outlined> | ||||
|         <v-card-text> | ||||
|           <v-select | ||||
|             v-if="groups" | ||||
|             v-model="household.groupId" | ||||
|             disabled | ||||
|             :items="groups" | ||||
|             rounded | ||||
|             class="rounded-lg" | ||||
|             item-text="name" | ||||
|             item-value="id" | ||||
|             :return-object="false" | ||||
|             filled | ||||
|             :label="$tc('group.user-group')" | ||||
|             :rules="[validators.required]" | ||||
|           /> | ||||
|           <v-text-field | ||||
|             v-model="household.name" | ||||
|             :label="$t('household.household-name')" | ||||
|             :rules="[validators.required]" | ||||
|           /> | ||||
|           <HouseholdPreferencesEditor v-if="household.preferences" v-model="household.preferences" /> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
|       <div class="d-flex pa-2"> | ||||
|         <BaseButton type="submit" edit class="ml-auto"> {{ $t("general.update") }}</BaseButton> | ||||
|       </div> | ||||
|     </v-form> | ||||
|   </v-container> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; | ||||
| import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue"; | ||||
| import { useGroups } from "~/composables/use-groups"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { HouseholdInDB } from "~/lib/api/types/household"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   components: { | ||||
|       HouseholdPreferencesEditor, | ||||
|   }, | ||||
|   layout: "admin", | ||||
|   setup() { | ||||
|     const route = useRoute(); | ||||
|     const { i18n } = useContext(); | ||||
| 
 | ||||
|     const { groups } = useGroups(); | ||||
|     const householdId = route.value.params.id; | ||||
| 
 | ||||
|     // ============================================== | ||||
|     // New User Form | ||||
| 
 | ||||
|     const refHouseholdEditForm = ref<VForm | null>(null); | ||||
| 
 | ||||
|     const userApi = useUserApi(); | ||||
| 
 | ||||
|     const household = ref<HouseholdInDB | null>(null); | ||||
| 
 | ||||
|     const userError = ref(false); | ||||
| 
 | ||||
|     onMounted(async () => { | ||||
|       const { data, error } = await userApi.households.getOne(householdId); | ||||
| 
 | ||||
|       if (error?.response?.status === 404) { | ||||
|         alert.error(i18n.tc("user.user-not-found")); | ||||
|         userError.value = true; | ||||
|       } | ||||
| 
 | ||||
|       if (data) { | ||||
|           household.value = data; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     async function handleSubmit() { | ||||
|       if (!refHouseholdEditForm.value?.validate() || household.value === null) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const { response, data } = await userApi.households.updateOne(household.value.id, household.value); | ||||
|       if (response?.status === 200 && data) { | ||||
|         if (household.value.slug !== data.slug) { | ||||
|           // the slug updated, which invalidates the nav URLs | ||||
|           window.location.reload(); | ||||
|         } | ||||
|         household.value = data; | ||||
|       } else { | ||||
|         alert.error(i18n.tc("settings.settings-update-failed")); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       groups, | ||||
|       household, | ||||
|       validators, | ||||
|       userError, | ||||
|       refHouseholdEditForm, | ||||
|       handleSubmit, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
							
								
								
									
										167
									
								
								frontend/pages/admin/manage/households/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								frontend/pages/admin/manage/households/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,167 @@ | ||||
| <template> | ||||
|   <v-container fluid> | ||||
|     <BaseDialog | ||||
|       v-model="createDialog" | ||||
|       :title="$t('household.create-household')" | ||||
|       :icon="$globals.icons.household" | ||||
|       @submit="createHousehold(createHouseholdForm.data)" | ||||
|     > | ||||
|       <template #activator> </template> | ||||
|       <v-card-text> | ||||
|         <v-select | ||||
|           v-if="groups" | ||||
|           v-model="createHouseholdForm.data.groupId" | ||||
|           :items="groups" | ||||
|           rounded | ||||
|           class="rounded-lg" | ||||
|           item-text="name" | ||||
|           item-value="id" | ||||
|           :return-object="false" | ||||
|           filled | ||||
|           :label="$tc('household.household-group')" | ||||
|           :rules="[validators.required]" | ||||
|         /> | ||||
|         <AutoForm v-model="createHouseholdForm.data" :update-mode="updateMode" :items="createHouseholdForm.items" /> | ||||
|       </v-card-text> | ||||
|     </BaseDialog> | ||||
| 
 | ||||
|     <BaseDialog | ||||
|       v-model="confirmDialog" | ||||
|       :title="$t('general.confirm')" | ||||
|       color="error" | ||||
|       @confirm="deleteHousehold(deleteTarget)" | ||||
|     > | ||||
|       <template #activator> </template> | ||||
|       <v-card-text> | ||||
|         {{ $t("general.confirm-delete-generic") }} | ||||
|       </v-card-text> | ||||
|     </BaseDialog> | ||||
| 
 | ||||
|     <BaseCardSectionTitle :title="$tc('household.household-management')"> </BaseCardSectionTitle> | ||||
|     <section> | ||||
|       <v-toolbar flat color="transparent" class="justify-between"> | ||||
|         <BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton> | ||||
|       </v-toolbar> | ||||
| 
 | ||||
|       <v-data-table | ||||
|         :headers="headers" | ||||
|         :items="households || []" | ||||
|         item-key="id" | ||||
|         class="elevation-0" | ||||
|         hide-default-footer | ||||
|         disable-pagination | ||||
|         :search="search" | ||||
|         @click:row="handleRowClick" | ||||
|       > | ||||
|         <template #item.users="{ item }"> | ||||
|           {{ item.users.length }} | ||||
|         </template> | ||||
|         <template #item.group="{ item }"> | ||||
|           {{ item.group }} | ||||
|         </template> | ||||
|         <template #item.webhookEnable="{ item }"> | ||||
|           {{ item.webhooks.length > 0 ? $t("general.yes") : $t("general.no") }} | ||||
|         </template> | ||||
|         <template #item.actions="{ item }"> | ||||
|           <v-btn | ||||
|             :disabled="item && item.users.length > 0" | ||||
|             class="mr-1" | ||||
|             icon | ||||
|             color="error" | ||||
|             @click.stop=" | ||||
|               confirmDialog = true; | ||||
|               deleteTarget = item.id; | ||||
|             " | ||||
|           > | ||||
|             <v-icon> | ||||
|               {{ $globals.icons.delete }} | ||||
|             </v-icon> | ||||
|           </v-btn> | ||||
|         </template> | ||||
|       </v-data-table> | ||||
|       <v-divider></v-divider> | ||||
|     </section> | ||||
|   </v-container> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api"; | ||||
| import { fieldTypes } from "~/composables/forms"; | ||||
| import { useGroups } from "~/composables/use-groups"; | ||||
| import { useHouseholds } from "~/composables/use-households"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { HouseholdInDB } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   layout: "admin", | ||||
|   setup() { | ||||
|     const { i18n } = useContext(); | ||||
|     const { groups } = useGroups(); | ||||
|     const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useHouseholds(); | ||||
| 
 | ||||
|     const state = reactive({ | ||||
|       createDialog: false, | ||||
|       confirmDialog: false, | ||||
|       deleteTarget: 0, | ||||
|       search: "", | ||||
|       headers: [ | ||||
|         { | ||||
|           text: i18n.t("household.household"), | ||||
|           align: "start", | ||||
|           sortable: false, | ||||
|           value: "id", | ||||
|         }, | ||||
|         { text: i18n.t("general.name"), value: "name" }, | ||||
|         { text: i18n.t("group.group"), value: "group" }, | ||||
|         { text: i18n.t("user.total-users"), value: "users" }, | ||||
|         { text: i18n.t("user.webhooks-enabled"), value: "webhookEnable" }, | ||||
|         { text: i18n.t("general.delete"), value: "actions" }, | ||||
|       ], | ||||
|       updateMode: false, | ||||
|       createHouseholdForm: { | ||||
|         items: [ | ||||
|           { | ||||
|             label: i18n.t("household.household-name"), | ||||
|             varName: "name", | ||||
|             type: fieldTypes.TEXT, | ||||
|             rules: ["required"], | ||||
|           }, | ||||
|         ], | ||||
|         data: { | ||||
|           groupId: "", | ||||
|           name: "", | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     function openDialog() { | ||||
|       state.createDialog = true; | ||||
|       state.createHouseholdForm.data.name = ""; | ||||
|       state.createHouseholdForm.data.groupId = ""; | ||||
|     } | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
|     function handleRowClick(item: HouseholdInDB) { | ||||
|       router.push(`/admin/manage/households/${item.id}`); | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         ...toRefs(state), | ||||
|         groups, | ||||
|         households, | ||||
|         validators, | ||||
|         refreshAllHouseholds, | ||||
|         deleteHousehold, | ||||
|         createHousehold, | ||||
|         openDialog, | ||||
|         handleRowClick, | ||||
|     }; | ||||
|   }, | ||||
|   head() { | ||||
|     return { | ||||
|       title: this.$t("household.manage-households") as string, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| @ -14,9 +14,11 @@ | ||||
|           <div class="d-flex"> | ||||
|             <p> {{ $t("user.user-id-with-value", {id: user.id} ) }}</p> | ||||
|           </div> | ||||
|           <!-- This is disabled since we can't properly handle changing the user's group in most scenarios --> | ||||
|           <v-select | ||||
|             v-if="groups" | ||||
|             v-model="user.group" | ||||
|             disabled | ||||
|             :items="groups" | ||||
|             rounded | ||||
|             class="rounded-lg" | ||||
| @ -26,7 +28,20 @@ | ||||
|             filled | ||||
|             :label="$tc('group.user-group')" | ||||
|             :rules="[validators.required]" | ||||
|           ></v-select> | ||||
|           /> | ||||
|           <v-select | ||||
|             v-if="households" | ||||
|             v-model="user.household" | ||||
|             :items="households" | ||||
|             rounded | ||||
|             class="rounded-lg" | ||||
|             item-text="name" | ||||
|             item-value="name" | ||||
|             :return-object="false" | ||||
|             filled | ||||
|             :label="$tc('household.user-household')" | ||||
|             :rules="[validators.required]" | ||||
|           /> | ||||
|           <div class="d-flex py-2 pr-2"> | ||||
|             <BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset"> | ||||
|               {{ $t("user.generate-password-reset-link") }} | ||||
| @ -65,6 +80,7 @@ | ||||
| import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; | ||||
| import { useAdminApi, useUserApi } from "~/composables/api"; | ||||
| import { useGroups } from "~/composables/use-groups"; | ||||
| import { useHouseholds } from "~/composables/use-households"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| import { useUserForm } from "~/composables/use-users"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| @ -76,6 +92,7 @@ export default defineComponent({ | ||||
|   setup() { | ||||
|     const { userForm } = useUserForm(); | ||||
|     const { groups } = useGroups(); | ||||
|     const { useHouseholdsInGroup } = useHouseholds(); | ||||
|     const { i18n } = useContext(); | ||||
|     const route = useRoute(); | ||||
| 
 | ||||
| @ -89,6 +106,8 @@ export default defineComponent({ | ||||
|     const adminApi = useAdminApi(); | ||||
| 
 | ||||
|     const user = ref<UserOut | null>(null); | ||||
|     const households = useHouseholdsInGroup(computed(() => user.value?.groupId || "")); | ||||
| 
 | ||||
|     const disabledFields = computed(() => { | ||||
|       return user.value?.authMethod !== "Mealie" ? ["admin"] : []; | ||||
|     }) | ||||
| @ -154,6 +173,7 @@ export default defineComponent({ | ||||
|       refNewUserForm, | ||||
|       handleSubmit, | ||||
|       groups, | ||||
|       households, | ||||
|       validators, | ||||
|       handlePasswordReset, | ||||
|       resetUrl, | ||||
|  | ||||
| @ -12,17 +12,30 @@ | ||||
|         <v-card-text> | ||||
|           <v-select | ||||
|             v-if="groups" | ||||
|             v-model="newUserData.group" | ||||
|             v-model="selectedGroupId" | ||||
|             :items="groups" | ||||
|             rounded | ||||
|             class="rounded-lg" | ||||
|             item-text="name" | ||||
|             item-value="id" | ||||
|             :return-object="false" | ||||
|             filled | ||||
|             :label="$t('group.user-group')" | ||||
|             :rules="[validators.required]" | ||||
|           /> | ||||
|           <v-select | ||||
|             v-if="households" | ||||
|             v-model="newUserData.household" | ||||
|             :items="households" | ||||
|             rounded | ||||
|             class="rounded-lg" | ||||
|             item-text="name" | ||||
|             item-value="name" | ||||
|             :return-object="false" | ||||
|             filled | ||||
|             :label="$t('group.user-group')" | ||||
|             :label="$t('household.user-household')" | ||||
|             :rules="[validators.required]" | ||||
|           ></v-select> | ||||
|           /> | ||||
|           <AutoForm v-model="newUserData" :items="userForm" /> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
| @ -34,9 +47,10 @@ | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, useRouter, reactive, ref, toRefs } from "@nuxtjs/composition-api"; | ||||
| import { computed, defineComponent, useRouter, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api"; | ||||
| import { useAdminApi } from "~/composables/api"; | ||||
| import { useGroups } from "~/composables/use-groups"; | ||||
| import { useHouseholds } from "~/composables/use-households"; | ||||
| import { useUserForm } from "~/composables/use-users"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| @ -46,6 +60,7 @@ export default defineComponent({ | ||||
|   setup() { | ||||
|     const { userForm } = useUserForm(); | ||||
|     const { groups } = useGroups(); | ||||
|     const { useHouseholdsInGroup } = useHouseholds(); | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
|     // ============================================== | ||||
| @ -55,13 +70,20 @@ export default defineComponent({ | ||||
| 
 | ||||
|     const adminApi = useAdminApi(); | ||||
| 
 | ||||
|     const selectedGroupId = ref<string>(""); | ||||
|     const households = useHouseholdsInGroup(selectedGroupId); | ||||
| 
 | ||||
|     const selectedGroup = computed(() => { | ||||
|       return groups.value?.find((group) => group.id === selectedGroupId.value); | ||||
|     }); | ||||
|     const state = reactive({ | ||||
|       newUserData: { | ||||
|         username: "", | ||||
|         fullName: "", | ||||
|         email: "", | ||||
|         admin: false, | ||||
|         group: "", | ||||
|         group: selectedGroup.value?.name || "", | ||||
|         household: "", | ||||
|         advanced: false, | ||||
|         canInvite: false, | ||||
|         canManage: false, | ||||
| @ -70,6 +92,10 @@ export default defineComponent({ | ||||
|         authMethod: "Mealie", | ||||
|       }, | ||||
|     }); | ||||
|     watch(selectedGroup, (newGroup) => { | ||||
|       state.newUserData.group = newGroup?.name || ""; | ||||
|       state.newUserData.household = ""; | ||||
|     }); | ||||
| 
 | ||||
|     async function handleSubmit() { | ||||
|       if (!refNewUserForm.value?.validate()) return; | ||||
| @ -87,6 +113,8 @@ export default defineComponent({ | ||||
|       refNewUserForm, | ||||
|       handleSubmit, | ||||
|       groups, | ||||
|       selectedGroupId, | ||||
|       households, | ||||
|       validators, | ||||
|     }; | ||||
|   }, | ||||
|  | ||||
| @ -18,7 +18,7 @@ | ||||
| 
 | ||||
|     <BaseCardSectionTitle :title="$tc('user.user-management')"> </BaseCardSectionTitle> | ||||
|     <section> | ||||
|       <v-toolbar color="background" flat class="justify-between"> | ||||
|       <v-toolbar color="transparent" flat class="justify-between"> | ||||
|         <BaseButton to="/admin/manage/users/create" class="mr-2"> | ||||
|           {{ $t("general.create") }} | ||||
|         </BaseButton> | ||||
| @ -129,6 +129,7 @@ export default defineComponent({ | ||||
|       { text: i18n.t("user.full-name"), value: "fullName" }, | ||||
|       { text: i18n.t("user.email"), value: "email" }, | ||||
|       { text: i18n.t("group.group"), value: "group" }, | ||||
|       { text: i18n.t("household.household"), value: "household" }, | ||||
|       { text: i18n.t("user.auth-method"), value: "authMethod" }, | ||||
|       { text: i18n.t("user.admin"), value: "admin" }, | ||||
|       { text: i18n.t("general.delete"), value: "actions", sortable: false, align: "center" }, | ||||
|  | ||||
| @ -312,7 +312,6 @@ export default defineComponent({ | ||||
|       const preferences = { | ||||
|         ...data.preferences, | ||||
|         privateGroup: !commonSettings.value.makeGroupRecipesPublic, | ||||
|         recipePublic: commonSettings.value.makeGroupRecipesPublic, | ||||
|       } | ||||
| 
 | ||||
|       const payload = { | ||||
| @ -327,6 +326,32 @@ export default defineComponent({ | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     async function updateHousehold() { | ||||
|       // @ts-ignore-next-line user will never be null here | ||||
|       const { data } = await api.households.getOne($auth.user?.householdId); | ||||
|       if (!data || !data.preferences) { | ||||
|         alert.error(i18n.tc("events.something-went-wrong")); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const preferences = { | ||||
|         ...data.preferences, | ||||
|         privateHousehold: !commonSettings.value.makeGroupRecipesPublic, | ||||
|         recipePublic: commonSettings.value.makeGroupRecipesPublic, | ||||
|       } | ||||
| 
 | ||||
|       const payload = { | ||||
|         ...data, | ||||
|         preferences, | ||||
|       } | ||||
| 
 | ||||
|       // @ts-ignore-next-line user will never be null here | ||||
|       const { response } = await api.households.updateOne($auth.user?.householdId, payload); | ||||
|       if (!response || response.status !== 200) { | ||||
|         alert.error(i18n.tc("events.something-went-wrong")); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     async function seedFoods() { | ||||
|       const { response } = await api.seeders.foods({ locale: locale.value }) | ||||
|       if (!response || response.status !== 200) { | ||||
| @ -365,6 +390,7 @@ export default defineComponent({ | ||||
|     async function submitCommonSettings() { | ||||
|       const tasks = [ | ||||
|         updateGroup(), | ||||
|         updateHousehold(), | ||||
|         seedData(), | ||||
|       ] | ||||
| 
 | ||||
|  | ||||
| @ -367,6 +367,11 @@ export default defineComponent({ | ||||
|                             icon: $globals.icons.group, | ||||
|                             value: data.defaultGroup, | ||||
|                         }, | ||||
|                         { | ||||
|                             name: i18n.t("about.default-household"), | ||||
|                             icon: $globals.icons.household, | ||||
|                             value: data.defaultHousehold, | ||||
|                         }, | ||||
|                         { | ||||
|                             slot: "recipe-scraper", | ||||
|                             name: i18n.t("settings.recipe-scraper-version"), | ||||
|  | ||||
| @ -24,17 +24,18 @@ export default defineComponent({ | ||||
| 
 | ||||
|     const groupName = ref<string>(""); | ||||
|     const queryFilter = ref<string>(""); | ||||
|     async function fetchGroup() { | ||||
|       const { data } = await api.groups.getCurrentUserGroup(); | ||||
|     async function fetchHousehold() { | ||||
|       const { data } = await api.households.getCurrentUserHousehold(); | ||||
|       if (data) { | ||||
|         queryFilter.value = `recipe.group_id="${data.id}"`; | ||||
|         groupName.value = data.name; | ||||
|         // TODO: once users are able to fetch other households' recipes, remove the household filter | ||||
|         queryFilter.value = `recipe.group_id="${data.groupId}" AND recipe.household_id="${data.id}"`; | ||||
|         groupName.value = data.group; | ||||
|       } | ||||
| 
 | ||||
|       ready.value = true; | ||||
|     } | ||||
| 
 | ||||
|     fetchGroup(); | ||||
|     fetchHousehold(); | ||||
|     return { | ||||
|       groupName, | ||||
|       queryFilter, | ||||
|  | ||||
| @ -121,7 +121,7 @@ | ||||
| import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { useGroupRecipeActions, useGroupRecipeActionData } from "~/composables/use-group-recipe-actions"; | ||||
| import { GroupRecipeActionOut } from "~/lib/api/types/group"; | ||||
| import { GroupRecipeActionOut } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   setup() { | ||||
|  | ||||
| @ -25,139 +25,22 @@ | ||||
|           <DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <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="$tc('group.default-recipe-preferences')"> | ||||
|         {{ $t("group.default-recipe-preferences-description") }} | ||||
|       </BaseCardSectionTitle> | ||||
| 
 | ||||
|       <div class="preference-container"> | ||||
|         <div v-for="p in preferencesEditor" :key="p.key"> | ||||
|           <v-checkbox | ||||
|             v-model="group.preferences[p.key]" | ||||
|             hide-details | ||||
|             dense | ||||
|             :label="p.label" | ||||
|             @change="groupActions.updatePreferences()" | ||||
|           /> | ||||
|           <p class="ml-8 text-subtitle-2 my-0 py-0"> | ||||
|             {{ p.description }} | ||||
|           </p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
|   </v-container> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||
| import { defineComponent } from "@nuxtjs/composition-api"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { ReadGroupPreferences } from "~/lib/api/types/group"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   middleware: ["auth", "can-manage-only"], | ||||
|   setup() { | ||||
|     const { group, actions: groupActions } = useGroupSelf(); | ||||
| 
 | ||||
|     const { i18n } = useContext(); | ||||
| 
 | ||||
|     type Preference = { | ||||
|       key: keyof ReadGroupPreferences; | ||||
|       value: boolean; | ||||
|       label: string; | ||||
|       description: string; | ||||
|     }; | ||||
| 
 | ||||
|     const preferencesEditor = computed<Preference[]>(() => { | ||||
|       if (!group.value || !group.value.preferences) { | ||||
|         return []; | ||||
|       } | ||||
|       return [ | ||||
|         { | ||||
|           key: "recipePublic", | ||||
|           value: group.value.preferences.recipePublic || false, | ||||
|           label: i18n.t("group.allow-users-outside-of-your-group-to-see-your-recipes"), | ||||
|           description: i18n.t("group.allow-users-outside-of-your-group-to-see-your-recipes-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeShowNutrition", | ||||
|           value: group.value.preferences.recipeShowNutrition || false, | ||||
|           label: i18n.t("group.show-nutrition-information"), | ||||
|           description: i18n.t("group.show-nutrition-information-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeShowAssets", | ||||
|           value: group.value.preferences.recipeShowAssets || false, | ||||
|           label: i18n.t("group.show-recipe-assets"), | ||||
|           description: i18n.t("group.show-recipe-assets-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeLandscapeView", | ||||
|           value: group.value.preferences.recipeLandscapeView || false, | ||||
|           label: i18n.t("group.default-to-landscape-view"), | ||||
|           description: i18n.t("group.default-to-landscape-view-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeDisableComments", | ||||
|           value: group.value.preferences.recipeDisableComments || false, | ||||
|           label: i18n.t("group.disable-users-from-commenting-on-recipes"), | ||||
|           description: i18n.t("group.disable-users-from-commenting-on-recipes-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeDisableAmount", | ||||
|           value: group.value.preferences.recipeDisableAmount || false, | ||||
|           label: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food"), | ||||
|           description: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food-description"), | ||||
|         } as Preference, | ||||
|       ]; | ||||
|     }); | ||||
| 
 | ||||
|     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 { | ||||
|       group, | ||||
|       groupActions, | ||||
|       allDays, | ||||
|       preferencesEditor, | ||||
|     }; | ||||
|   }, | ||||
|   head() { | ||||
|  | ||||
							
								
								
									
										178
									
								
								frontend/pages/household/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								frontend/pages/household/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | ||||
| <template> | ||||
|   <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> {{ $t("profile.household-settings") }} </template> | ||||
|       {{ $t("profile.household-description") }} | ||||
|     </BasePageTitle> | ||||
| 
 | ||||
|     <section v-if="household"> | ||||
|       <BaseCardSectionTitle class="mt-10" :title="$tc('household.household-preferences')"></BaseCardSectionTitle> | ||||
|       <div class="mb-6"> | ||||
|         <v-checkbox | ||||
|           v-model="household.preferences.privateHousehold" | ||||
|           hide-details | ||||
|           dense | ||||
|           :label="$t('household.private-household')" | ||||
|           @change="householdActions.updatePreferences()" | ||||
|         /> | ||||
|         <div class="ml-8"> | ||||
|           <p class="text-subtitle-2 my-0 py-0"> | ||||
|             {{ $t("household.private-household-description") }} | ||||
|           </p> | ||||
|           <DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <v-select | ||||
|         v-model="household.preferences.firstDayOfWeek" | ||||
|         :prepend-icon="$globals.icons.calendarWeekBegin" | ||||
|         :items="allDays" | ||||
|         item-text="name" | ||||
|         item-value="value" | ||||
|         :label="$t('settings.first-day-of-week')" | ||||
|         @change="householdActions.updatePreferences()" | ||||
|       /> | ||||
|     </section> | ||||
| 
 | ||||
|     <section v-if="household"> | ||||
|       <BaseCardSectionTitle class="mt-10" :title="$tc('group.default-recipe-preferences')"> | ||||
|         {{ $t("household.default-recipe-preferences-description") }} | ||||
|       </BaseCardSectionTitle> | ||||
| 
 | ||||
|       <div class="preference-container"> | ||||
|         <div v-for="p in preferencesEditor" :key="p.key"> | ||||
|           <v-checkbox | ||||
|             v-model="household.preferences[p.key]" | ||||
|             hide-details | ||||
|             dense | ||||
|             :label="p.label" | ||||
|             @change="householdActions.updatePreferences()" | ||||
|           /> | ||||
|           <p class="ml-8 text-subtitle-2 my-0 py-0"> | ||||
|             {{ p.description }} | ||||
|           </p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
|   </v-container> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { ReadHouseholdPreferences } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   middleware: ["auth", "can-manage-only"], | ||||
|   setup() { | ||||
|     const { household, actions: householdActions } = useHouseholdSelf(); | ||||
| 
 | ||||
|     const { i18n } = useContext(); | ||||
| 
 | ||||
|     type Preference = { | ||||
|       key: keyof ReadHouseholdPreferences; | ||||
|       value: boolean; | ||||
|       label: string; | ||||
|       description: string; | ||||
|     }; | ||||
| 
 | ||||
|     const preferencesEditor = computed<Preference[]>(() => { | ||||
|       if (!household.value || !household.value.preferences) { | ||||
|         return []; | ||||
|       } | ||||
|       return [ | ||||
|         { | ||||
|           key: "recipePublic", | ||||
|           value: household.value.preferences.recipePublic || false, | ||||
|           label: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes"), | ||||
|           description: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeShowNutrition", | ||||
|           value: household.value.preferences.recipeShowNutrition || false, | ||||
|           label: i18n.t("group.show-nutrition-information"), | ||||
|           description: i18n.t("group.show-nutrition-information-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeShowAssets", | ||||
|           value: household.value.preferences.recipeShowAssets || false, | ||||
|           label: i18n.t("group.show-recipe-assets"), | ||||
|           description: i18n.t("group.show-recipe-assets-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeLandscapeView", | ||||
|           value: household.value.preferences.recipeLandscapeView || false, | ||||
|           label: i18n.t("group.default-to-landscape-view"), | ||||
|           description: i18n.t("group.default-to-landscape-view-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeDisableComments", | ||||
|           value: household.value.preferences.recipeDisableComments || false, | ||||
|           label: i18n.t("group.disable-users-from-commenting-on-recipes"), | ||||
|           description: i18n.t("group.disable-users-from-commenting-on-recipes-description"), | ||||
|         } as Preference, | ||||
|         { | ||||
|           key: "recipeDisableAmount", | ||||
|           value: household.value.preferences.recipeDisableAmount || false, | ||||
|           label: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food"), | ||||
|           description: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food-description"), | ||||
|         } as Preference, | ||||
|       ]; | ||||
|     }); | ||||
| 
 | ||||
|     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 { | ||||
|       household, | ||||
|       householdActions, | ||||
|       allDays, | ||||
|       preferencesEditor, | ||||
|     }; | ||||
|   }, | ||||
|   head() { | ||||
|     return { | ||||
|       title: this.$t("household.household") as string, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="css"> | ||||
| .preference-container { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 0.5rem; | ||||
|   max-width: 600px; | ||||
| } | ||||
| </style> | ||||
| @ -39,10 +39,10 @@ | ||||
| 
 | ||||
|     <div class="d-flex flex-wrap align-center justify-space-between mb-2"> | ||||
|       <v-tabs style="width: fit-content;"> | ||||
|         <v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab> | ||||
|         <v-tab :to="`/group/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab> | ||||
|         <v-tab :to="`/household/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab> | ||||
|         <v-tab :to="`/household/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab> | ||||
|       </v-tabs> | ||||
|       <ButtonLink :icon="$globals.icons.calendar" :to="`/group/mealplan/settings`" :text="$tc('general.settings')" /> | ||||
|       <ButtonLink :icon="$globals.icons.calendar" :to="`/household/mealplan/settings`" :text="$tc('general.settings')" /> | ||||
|     </div> | ||||
| 
 | ||||
|     <div> | ||||
| @ -56,7 +56,7 @@ | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, ref, useRoute, useRouter, watch } from "@nuxtjs/composition-api"; | ||||
| import { isSameDay, addDays, parseISO } from "date-fns"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { useMealplans } from "~/composables/use-group-mealplan"; | ||||
| import { useUserMealPlanPreferences } from "~/composables/use-users/preferences"; | ||||
| 
 | ||||
| @ -65,7 +65,7 @@ export default defineComponent({ | ||||
|   setup() { | ||||
|     const route = useRoute(); | ||||
|     const router = useRouter(); | ||||
|     const { group } = useGroupSelf(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
| 
 | ||||
|     const mealPlanPreferences = useUserMealPlanPreferences(); | ||||
|     const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7); | ||||
| @ -74,8 +74,8 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     // Force to /view if current route is /planner | ||||
|     if (route.value.path === "/group/mealplan/planner") { | ||||
|       router.push("/group/mealplan/planner/view"); | ||||
|     if (route.value.path === "/household/mealplan/planner") { | ||||
|       router.push("/household/mealplan/planner/view"); | ||||
|     } | ||||
| 
 | ||||
|     function fmtYYYYMMDD(date: Date) { | ||||
| @ -95,7 +95,7 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return group.value?.preferences?.firstDayOfWeek || 0; | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
| 
 | ||||
|     const weekRange = computed(() => { | ||||
| @ -229,7 +229,7 @@ import { useMealplans, usePlanTypeOptions, getEntryTypeText } from "~/composable | ||||
| import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue"; | ||||
| import { PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useGroupSelf } from "~/composables/use-groups"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { useRecipeSearch } from "~/composables/recipes/use-recipe-search"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| @ -249,7 +249,7 @@ export default defineComponent({ | ||||
|   }, | ||||
|   setup(props) { | ||||
|     const api = useUserApi(); | ||||
|     const { group } = useGroupSelf(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
| 
 | ||||
|     const state = ref({ | ||||
|       dialog: false, | ||||
| @ -257,7 +257,7 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return group.value?.preferences?.firstDayOfWeek || 0; | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
| 
 | ||||
|     function onMoveCallback(evt: SortableEvent) { | ||||
| @ -308,7 +308,7 @@ export default defineComponent({ | ||||
|       entryType: "dinner" as PlanEntryType, | ||||
|       existing: false, | ||||
|       id: 0, | ||||
|       groupId: "" | ||||
|       groupId: "", | ||||
|     }); | ||||
| 
 | ||||
|     function openDialog(date: Date) { | ||||
| @ -54,7 +54,7 @@ | ||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||
| import { MealsByDate } from "./types"; | ||||
| import { ReadPlanEntry } from "~/lib/api/types/meal-plan"; | ||||
| import GroupMealPlanDayContextMenu from "~/components/Domain/Group/GroupMealPlanDayContextMenu.vue"; | ||||
| import GroupMealPlanDayContextMenu from "~/components/Domain/Household/GroupMealPlanDayContextMenu.vue"; | ||||
| import RecipeCardMobile from "~/components/Domain/Recipe/RecipeCardMobile.vue"; | ||||
| import { RecipeSummary } from "~/lib/api/types/recipe"; | ||||
| 
 | ||||
| @ -89,7 +89,7 @@ | ||||
| import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan"; | ||||
| import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue"; | ||||
| import GroupMealPlanRuleForm from "~/components/Domain/Household/GroupMealPlanRuleForm.vue"; | ||||
| import { useAsyncKey } from "~/composables/use-utils"; | ||||
| import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue"; | ||||
| 
 | ||||
| @ -97,7 +97,7 @@ export default defineComponent({ | ||||
|     ]; | ||||
| 
 | ||||
|     async function refreshMembers() { | ||||
|       const { data } = await api.groups.fetchMembers(); | ||||
|       const { data } = await api.households.fetchMembers(); | ||||
|       if (data) { | ||||
|         members.value = data; | ||||
|       } | ||||
| @ -111,7 +111,7 @@ export default defineComponent({ | ||||
|         canOrganize: user.canOrganize, | ||||
|       }; | ||||
| 
 | ||||
|       await api.groups.setMemberPermissions(payload); | ||||
|       await api.households.setMemberPermissions(payload); | ||||
|     } | ||||
| 
 | ||||
|     onMounted(async () => { | ||||
| @ -109,7 +109,7 @@ | ||||
| import { defineComponent, useAsync, reactive, useContext, toRefs } from "@nuxtjs/composition-api"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useAsyncKey } from "~/composables/use-utils"; | ||||
| import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/group"; | ||||
| import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/household"; | ||||
| 
 | ||||
| interface OptionKey { | ||||
|   text: string; | ||||
| @ -45,7 +45,7 @@ | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from "@nuxtjs/composition-api"; | ||||
| import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks"; | ||||
| import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue"; | ||||
| import GroupWebhookEditor from "~/components/Domain/Household/GroupWebhookEditor.vue"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| @ -294,8 +294,8 @@ import { useCopyList } from "~/composables/use-copy"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" | ||||
| import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; | ||||
| import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group"; | ||||
| import { UserSummary } from "~/lib/api/types/user"; | ||||
| import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household"; | ||||
| import { UserOut } from "~/lib/api/types/user"; | ||||
| import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | ||||
| import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; | ||||
| import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; | ||||
| @ -444,7 +444,7 @@ export default defineComponent({ | ||||
|         unchecked: shoppingList.value?.listItems?.filter((item) => !item.checked) ?? [], | ||||
|         checked: shoppingList.value?.listItems | ||||
|           ?.filter((item) => item.checked) | ||||
|           .sort((a, b) => (a.updateAt < b.updateAt ? 1 : -1)) | ||||
|           .sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1)) | ||||
|           ?? [], | ||||
|       }; | ||||
|     }); | ||||
| @ -863,7 +863,7 @@ export default defineComponent({ | ||||
|         item.position = shoppingList.value.listItems.length; | ||||
| 
 | ||||
|         // set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items | ||||
|         item.updateAt = new Date().toISOString(); | ||||
|         item.updatedAt = new Date().toISOString(); | ||||
|       } | ||||
| 
 | ||||
|       // make updates reflect immediately | ||||
| @ -934,7 +934,7 @@ export default defineComponent({ | ||||
|         : 0; | ||||
| 
 | ||||
|       createListItemData.value.createdAt = new Date().toISOString(); | ||||
|       createListItemData.value.updateAt = createListItemData.value.createdAt; | ||||
|       createListItemData.value.updatedAt = createListItemData.value.createdAt; | ||||
| 
 | ||||
|       updateListItemOrder(); | ||||
| 
 | ||||
| @ -1020,16 +1020,16 @@ export default defineComponent({ | ||||
|     // =============================================================== | ||||
|     // Shopping List Settings | ||||
| 
 | ||||
|     const allUsers = ref<UserSummary[]>([]); | ||||
|     const allUsers = ref<UserOut[]>([]); | ||||
|     const currentUserId = ref<string | undefined>(); | ||||
|     async function fetchAllUsers() { | ||||
|       const { data } = await userApi.users.getGroupUsers(1, -1, { orderBy: "full_name", orderDirection: "asc" }); | ||||
|       const { data } = await userApi.households.fetchMembers(); | ||||
|       if (!data) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // update current user | ||||
|       allUsers.value = data.items; | ||||
|       allUsers.value = data.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1)); | ||||
|       currentUserId.value = shoppingList.value?.userId; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|       <p class="subtitle-1 mb-0 text-center"> | ||||
|        {{ $t('profile.description') }} | ||||
|       </p> | ||||
|       <v-card flat color="background" width="100%" max-width="600px"> | ||||
|       <v-card flat color="transparent" width="100%" max-width="600px"> | ||||
|         <v-card-actions class="d-flex justify-center my-4"> | ||||
|           <v-btn v-if="$auth.user.canInvite"  outlined rounded @click="getSignupLink()"> | ||||
|             <v-icon left> | ||||
| @ -57,9 +57,9 @@ | ||||
|       <v-row tag="section"> | ||||
|         <v-col cols="12" sm="12" md="12"> | ||||
|           <v-card outlined> | ||||
|             <v-card-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title> | ||||
|             <v-card-title class="headline pb-0"> {{ $t('profile.household-statistics') }} </v-card-title> | ||||
|             <v-card-text class="py-0"> | ||||
|               {{ $t('profile.group-statistics-description') }} | ||||
|               {{ $t('profile.household-statistics-description') }} | ||||
|             </v-card-text> | ||||
|             <v-card-text class="d-flex flex-wrap justify-center align-center" style="gap: 0.8rem"> | ||||
|               <StatsCards | ||||
| @ -106,8 +106,66 @@ | ||||
|         </AdvancedOnly> | ||||
|       </v-row> | ||||
|     </section> | ||||
|     <v-divider class="my-7"></v-divider> | ||||
|     <v-divider class="my-7" /> | ||||
|     <section> | ||||
|       <div> | ||||
|         <h3 class="headline">{{ $t('household.household') }}</h3> | ||||
|         <p>{{ $t('profile.household-description') }}</p> | ||||
|       </div> | ||||
|       <v-row tag="section"> | ||||
|         <v-col v-if="$auth.user.canManage" cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
|             :link="{ text: $tc('profile.household-settings'), to: `/household` }" | ||||
|             :image="require('~/static/svgs/manage-group-settings.svg')" | ||||
|           > | ||||
|             <template #title> {{ $t('profile.household-settings') }} </template> | ||||
|             {{ $t('profile.household-settings-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <v-col cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
|             :link="{ text: $tc('profile.manage-cookbooks'), to: `/g/${groupSlug}/cookbooks` }" | ||||
|             :image="require('~/static/svgs/manage-cookbooks.svg')" | ||||
|           > | ||||
|             <template #title> {{ $t('sidebar.cookbooks') }} </template> | ||||
|             {{ $t('profile.cookbooks-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <v-col v-if="user.canManage" cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
|             :link="{ text: $tc('profile.manage-members'), to: `/household/members` }" | ||||
|             :image="require('~/static/svgs/manage-members.svg')" | ||||
|           > | ||||
|             <template #title> {{ $t('profile.members') }} </template> | ||||
|             {{ $t('profile.members-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <AdvancedOnly> | ||||
|           <v-col v-if="user.advanced" cols="12" sm="12" md="6"> | ||||
|             <UserProfileLinkCard | ||||
|               :link="{ text: $tc('profile.manage-webhooks'), to: `/household/webhooks` }" | ||||
|               :image="require('~/static/svgs/manage-webhooks.svg')" | ||||
|             > | ||||
|               <template #title> {{ $t('settings.webhooks.webhooks') }} </template> | ||||
|               {{ $t('profile.webhooks-description') }} | ||||
|             </UserProfileLinkCard> | ||||
|           </v-col> | ||||
|         </AdvancedOnly> | ||||
|         <AdvancedOnly> | ||||
|           <v-col cols="12" sm="12" md="6"> | ||||
|             <UserProfileLinkCard | ||||
|               :link="{ text: $tc('profile.manage-notifiers'), to: `/household/notifiers` }" | ||||
|               :image="require('~/static/svgs/manage-notifiers.svg')" | ||||
|             > | ||||
|               <template #title> {{ $t('profile.notifiers') }} </template> | ||||
|               {{ $t('profile.notifiers-description') }} | ||||
|             </UserProfileLinkCard> | ||||
|           </v-col> | ||||
|         </AdvancedOnly> | ||||
|       </v-row> | ||||
|     </section> | ||||
|     <v-divider class="my-7" /> | ||||
|     <section v-if="$auth.user.canManage || $auth.user.canOrganize || $auth.user.advanced"> | ||||
|       <div> | ||||
|         <h3 class="headline">{{ $t('group.group') }}</h3> | ||||
|         <p>{{ $t('profile.group-description') }}</p> | ||||
| @ -122,46 +180,6 @@ | ||||
|             {{ $t('profile.group-settings-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <v-col cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
|             :link="{ text: $tc('profile.manage-cookbooks'), to: `/g/${groupSlug}/cookbooks` }" | ||||
|             :image="require('~/static/svgs/manage-cookbooks.svg')" | ||||
|           > | ||||
|             <template #title> {{ $t('sidebar.cookbooks') }} </template> | ||||
|             {{ $t('profile.cookbooks-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <v-col v-if="user.canManage" cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
|             :link="{ text: $tc('profile.manage-members'), to: `/group/members` }" | ||||
|             :image="require('~/static/svgs/manage-members.svg')" | ||||
|           > | ||||
|             <template #title> {{ $t('profile.members') }} </template> | ||||
|             {{ $t('profile.members-description') }} | ||||
|           </UserProfileLinkCard> | ||||
|         </v-col> | ||||
|         <AdvancedOnly> | ||||
|           <v-col v-if="user.advanced" cols="12" sm="12" md="6"> | ||||
|             <UserProfileLinkCard | ||||
|               :link="{ text: $tc('profile.manage-webhooks'), to: `/group/webhooks` }" | ||||
|               :image="require('~/static/svgs/manage-webhooks.svg')" | ||||
|             > | ||||
|               <template #title> {{ $t('settings.webhooks.webhooks') }} </template> | ||||
|               {{ $t('profile.webhooks-description') }} | ||||
|             </UserProfileLinkCard> | ||||
|           </v-col> | ||||
|         </AdvancedOnly> | ||||
|         <AdvancedOnly> | ||||
|           <v-col cols="12" sm="12" md="6"> | ||||
|             <UserProfileLinkCard | ||||
|               :link="{ text: $tc('profile.manage-notifiers'), to: `/group/notifiers` }" | ||||
|               :image="require('~/static/svgs/manage-notifiers.svg')" | ||||
|             > | ||||
|               <template #title> {{ $t('profile.notifiers') }} </template> | ||||
|               {{ $t('profile.notifiers-description') }} | ||||
|             </UserProfileLinkCard> | ||||
|           </v-col> | ||||
|         </AdvancedOnly> | ||||
|         <!-- $auth.user.canOrganize should not be null because of the auth middleware --> | ||||
|         <v-col v-if="$auth.user.canOrganize" cols="12" sm="12" md="6"> | ||||
|           <UserProfileLinkCard | ||||
| @ -224,7 +242,7 @@ export default defineComponent({ | ||||
|     const api = useUserApi(); | ||||
| 
 | ||||
|     async function getSignupLink() { | ||||
|       const { data } = await api.groups.createInvitation({ uses: 1 }); | ||||
|       const { data } = await api.households.createInvitation({ uses: 1 }); | ||||
|       if (data) { | ||||
|         token.value = data.token; | ||||
|         generatedSignupLink.value = constructLink(data.token); | ||||
| @ -272,7 +290,7 @@ export default defineComponent({ | ||||
|     }); | ||||
| 
 | ||||
|     const stats = useAsync(async () => { | ||||
|       const { data } = await api.groups.statistics(); | ||||
|       const { data } = await api.households.statistics(); | ||||
| 
 | ||||
|       if (data) { | ||||
|         return data; | ||||
| @ -306,7 +324,7 @@ export default defineComponent({ | ||||
| 
 | ||||
|     const statsTo = computed<{ [key: string]: string }>(() => { return { | ||||
|       totalRecipes: `/g/${groupSlug.value}/`, | ||||
|       totalUsers: "/group/members", | ||||
|       totalUsers: "/household/members", | ||||
|       totalCategories: `/g/${groupSlug.value}/recipes/categories`, | ||||
|       totalTags: `/g/${groupSlug.value}/recipes/tags`, | ||||
|       totalTools: `/g/${groupSlug.value}/recipes/tools`, | ||||
|  | ||||
| @ -68,7 +68,7 @@ async def get_public_group(group_slug: str = fastapi.Path(...), session=Depends( | ||||
|     repos = get_repositories(session) | ||||
|     group = repos.groups.get_by_slug_or_id(group_slug) | ||||
| 
 | ||||
|     if not group or group.preferences.private_group or not group.preferences.recipe_public: | ||||
|     if not group or group.preferences.private_group: | ||||
|         raise HTTPException(404, "group not found") | ||||
|     else: | ||||
|         return group | ||||
| @ -111,7 +111,7 @@ async def get_current_user( | ||||
|     except PyJWTError as e: | ||||
|         raise credentials_exception from e | ||||
| 
 | ||||
|     repos = get_repositories(session) | ||||
|     repos = get_repositories(session, group_id=None, household_id=None) | ||||
| 
 | ||||
|     user = repos.users.get_one(token_data.user_id, "id", any_case=False) | ||||
| 
 | ||||
| @ -139,7 +139,7 @@ async def get_admin_user(current_user: PrivateUser = Depends(get_current_user)) | ||||
| 
 | ||||
| 
 | ||||
| def validate_long_live_token(session: Session, client_token: str, user_id: str) -> PrivateUser: | ||||
|     repos = get_repositories(session) | ||||
|     repos = get_repositories(session, group_id=None, household_id=None) | ||||
| 
 | ||||
|     token = repos.api_tokens.multi_query({"token": client_token, "user_id": user_id}) | ||||
| 
 | ||||
|  | ||||
| @ -56,7 +56,7 @@ class AuthProvider(Generic[T], metaclass=abc.ABCMeta): | ||||
|         if self.__has_tried_user: | ||||
|             return self.user | ||||
| 
 | ||||
|         db = get_repositories(self.session) | ||||
|         db = get_repositories(self.session, group_id=None, household_id=None) | ||||
| 
 | ||||
|         user = user = db.users.get_one(username, "username", any_case=True) | ||||
|         if not user: | ||||
|  | ||||
| @ -23,7 +23,7 @@ class CredentialsProvider(AuthProvider[CredentialsRequest]): | ||||
|     async def authenticate(self) -> tuple[str, timedelta] | None: | ||||
|         """Attempt to authenticate a user given a username and password""" | ||||
|         settings = get_app_settings() | ||||
|         db = get_repositories(self.session) | ||||
|         db = get_repositories(self.session, group_id=None, household_id=None) | ||||
|         user = self.try_get_user(self.data.username) | ||||
| 
 | ||||
|         if not user: | ||||
|  | ||||
| @ -95,7 +95,7 @@ class LDAPProvider(CredentialsProvider): | ||||
|         """ | ||||
| 
 | ||||
|         settings = get_app_settings() | ||||
|         db = get_repositories(self.session) | ||||
|         db = get_repositories(self.session, group_id=None, household_id=None) | ||||
|         if not self.data: | ||||
|             return None | ||||
|         data = self.data | ||||
|  | ||||
| @ -33,7 +33,7 @@ class OpenIDProvider(AuthProvider[OIDCRequest]): | ||||
|         if not claims: | ||||
|             return None | ||||
| 
 | ||||
|         repos = get_repositories(self.session) | ||||
|         repos = get_repositories(self.session, group_id=None, household_id=None) | ||||
| 
 | ||||
|         user = self.try_get_user(claims.get(settings.OIDC_USER_CLAIM)) | ||||
|         is_admin = False | ||||
|  | ||||
| @ -179,6 +179,7 @@ class AppSettings(AppLoggingSettings): | ||||
|         return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None | ||||
| 
 | ||||
|     DEFAULT_GROUP: str = "Home" | ||||
|     DEFAULT_HOUSEHOLD: str = "Family" | ||||
| 
 | ||||
|     _DEFAULT_EMAIL: str = "changeme@example.com" | ||||
|     """ | ||||
|  | ||||
| @ -26,7 +26,7 @@ SessionLocal, engine = sql_global_init(settings.DB_URL)  # type: ignore | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def session_context() -> Session: | ||||
| def session_context() -> Generator[Session, None, None]: | ||||
|     """ | ||||
|     session_context() provides a managed session to the database that is automatically | ||||
|     closed when the context is exited. This is the preferred method of accessing the | ||||
|  | ||||
| @ -5,7 +5,7 @@ from sqlalchemy.orm import Session | ||||
| 
 | ||||
| from mealie.core import root_logger | ||||
| from mealie.db.models.group.group import Group | ||||
| from mealie.db.models.group.shopping_list import ShoppingList, ShoppingListMultiPurposeLabel | ||||
| from mealie.db.models.household.shopping_list import ShoppingList, ShoppingListMultiPurposeLabel | ||||
| from mealie.db.models.labels import MultiPurposeLabel | ||||
| from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel | ||||
| from mealie.db.models.recipe.recipe import RecipeModel | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user