mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:28:00 -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 | import mealie.db.migration_types | ||||||
| from alembic import op | from alembic import op | ||||||
| from mealie.db.models.group.shopping_list import ShoppingList | from mealie.db.models._model_utils.guid import GUID | ||||||
| from mealie.db.models.labels import MultiPurposeLabel |  | ||||||
| 
 | 
 | ||||||
| # revision identifiers, used by Alembic. | # revision identifiers, used by Alembic. | ||||||
| revision = "b04a08da2108" | revision = "b04a08da2108" | ||||||
| @ -23,6 +22,25 @@ branch_labels = None | |||||||
| depends_on = 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( | def populate_shopping_lists_multi_purpose_labels( | ||||||
|     shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session |     shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session | ||||||
| ): | ): | ||||||
|  | |||||||
| @ -12,11 +12,11 @@ from typing import Any | |||||||
| 
 | 
 | ||||||
| import sqlalchemy as sa | import sqlalchemy as sa | ||||||
| from pydantic import UUID4 | from pydantic import UUID4 | ||||||
|  | from sqlalchemy import orm | ||||||
| from sqlalchemy.orm import Session, load_only | from sqlalchemy.orm import Session, load_only | ||||||
| 
 | 
 | ||||||
| from alembic import op | from alembic import op | ||||||
| from mealie.db.models._model_base import SqlAlchemyBase | from mealie.db.models._model_utils.guid import GUID | ||||||
| from mealie.db.models.group.shopping_list import ShoppingListItem |  | ||||||
| from mealie.db.models.labels import MultiPurposeLabel | from mealie.db.models.labels import MultiPurposeLabel | ||||||
| from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel | from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel | ||||||
| 
 | 
 | ||||||
| @ -27,6 +27,27 @@ branch_labels = None | |||||||
| depends_on = 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 | @dataclass | ||||||
| class TableMeta: | class TableMeta: | ||||||
|     tablename: str |     tablename: str | ||||||
| @ -42,7 +63,7 @@ def _is_postgres(): | |||||||
|     return op.get_context().dialect.name == "postgresql" |     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) |     duplicate_map: defaultdict[str, list] = defaultdict(list) | ||||||
| 
 | 
 | ||||||
|     query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}")) |     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. |     kabab case. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     ignore_files = ["DS_Store", ".gitkeep"] |     ignore_files = ["DS_Store", ".gitkeep", "af-ZA.json", "en-US.json"] | ||||||
| 
 | 
 | ||||||
|     ignore_extensions = [".pyc", ".pyo", ".py"] |     ignore_extensions = [".pyc", ".pyo", ".py"] | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import logging | import logging | ||||||
| import re | import re | ||||||
|  | import subprocess | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| 
 | 
 | ||||||
| @ -23,6 +24,11 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict): | |||||||
| 
 | 
 | ||||||
|     dest.write_text(text) |     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 | @dataclass | ||||||
| class CodeSlicer: | class CodeSlicer: | ||||||
|  | |||||||
| @ -173,7 +173,7 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic | |||||||
|         "dateAdded": "2022-09-03", |         "dateAdded": "2022-09-03", | ||||||
|         "dateUpdated": "2022-09-10T15:18:19.866085", |         "dateUpdated": "2022-09-10T15:18:19.866085", | ||||||
|         "createdAt": "2022-09-03T18:31:17.488118", |         "createdAt": "2022-09-03T18:31:17.488118", | ||||||
|         "updateAt": "2022-09-10T15:18:19.869630", |         "updatedAt": "2022-09-10T15:18:19.869630", | ||||||
|         "recipeInstructions": [ |         "recipeInstructions": [ | ||||||
|             { |             { | ||||||
|                 "id": "60ae53a3-b3ff-40ee-bae3-89fea0b1c637", |                 "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 | ```yaml | ||||||
| - platform: rest | - platform: rest | ||||||
|   resource: "http://mealie:9000/api/groups/mealplans/today" |   resource: "http://mealie:9000/api/households/mealplans/today" | ||||||
|   method: GET |   method: GET | ||||||
|   name: Mealie todays meal |   name: Mealie todays meal | ||||||
|   headers: |   headers: | ||||||
| @ -36,7 +36,7 @@ Make sure the url and port (`http://mealie:9000` ) matches your installation's a | |||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
| - platform: rest | - platform: rest | ||||||
|   resource: "http://mealie:9000/api/groups/mealplans/today" |   resource: "http://mealie:9000/api/households/mealplans/today" | ||||||
|   method: GET |   method: GET | ||||||
|   name: Mealie todays meal ID |   name: Mealie todays meal ID | ||||||
|   headers: |   headers: | ||||||
|  | |||||||
| @ -73,13 +73,13 @@ Mealie uses a calendar like view to help you plan your meals. It shows you the p | |||||||
| !!! tip | !!! 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. |     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 | ### 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.** | 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 | ## Shopping Lists | ||||||
| 
 | 
 | ||||||
| @ -105,13 +105,13 @@ Notifiers use the [Apprise library](https://github.com/caronc/apprise/wiki), whi | |||||||
| - `json` and `jsons` | - `json` and `jsons` | ||||||
| - `xml` and `xmls` | - `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 | ### 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. | 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 | ### Recipe Actions | ||||||
| 
 | 
 | ||||||
|  | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -2,30 +2,11 @@ | |||||||
|   <div v-if="preferences"> |   <div v-if="preferences"> | ||||||
|     <BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle> |     <BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle> | ||||||
|     <v-checkbox v-model="preferences.privateGroup" class="mt-n4" :label="$t('group.private-group')"></v-checkbox> |     <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> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, computed, useContext } from "@nuxtjs/composition-api"; | import { defineComponent, computed } from "@nuxtjs/composition-api"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   props: { |   props: { | ||||||
| @ -35,48 +16,6 @@ export default defineComponent({ | |||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   setup(props, context) { |   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({ |     const preferences = computed({ | ||||||
|       get() { |       get() { | ||||||
|         return props.value; |         return props.value; | ||||||
| @ -87,8 +26,6 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       allDays, |  | ||||||
|       labels, |  | ||||||
|       preferences, |       preferences, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ | |||||||
| import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api"; | import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { Recipe } from "~/lib/api/types/recipe"; | import { Recipe } from "~/lib/api/types/recipe"; | ||||||
| import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue"; | 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"; | import { useUserApi } from "~/composables/api"; | ||||||
| 
 | 
 | ||||||
| export interface ContextMenuItem { | export interface ContextMenuItem { | ||||||
| @ -35,7 +35,7 @@ | |||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, computed, ref } from "@nuxtjs/composition-api"; | 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"; | import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | 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; |           break; | ||||||
|         case EVENTS.updated: |         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; |           break; | ||||||
|         case EVENTS.lastMade: |         case EVENTS.lastMade: | ||||||
|           setter( |           setter( | ||||||
|  | |||||||
| @ -138,11 +138,11 @@ import RecipeDialogShare from "./RecipeDialogShare.vue"; | |||||||
| import { useLoggedInState } from "~/composables/use-logged-in-state"; | import { useLoggedInState } from "~/composables/use-logged-in-state"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions"; | 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 { alert } from "~/composables/use-toast"; | ||||||
| import { usePlanTypeOptions } from "~/composables/use-group-mealplan"; | import { usePlanTypeOptions } from "~/composables/use-group-mealplan"; | ||||||
| import { Recipe } from "~/lib/api/types/recipe"; | 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 { PlanEntryType } from "~/lib/api/types/meal-plan"; | ||||||
| import { useAxiosDownloader } from "~/composables/api/use-axios-download"; | import { useAxiosDownloader } from "~/composables/api/use-axios-download"; | ||||||
| 
 | 
 | ||||||
| @ -254,14 +254,14 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const { i18n, $auth, $globals } = useContext(); |     const { i18n, $auth, $globals } = useContext(); | ||||||
|     const { group } = useGroupSelf(); |     const { household } = useHouseholdSelf(); | ||||||
|     const { isOwnGroup } = useLoggedInState(); |     const { isOwnGroup } = useLoggedInState(); | ||||||
| 
 | 
 | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); |     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); | ||||||
| 
 | 
 | ||||||
|     const firstDayOfWeek = computed(() => { |     const firstDayOfWeek = computed(() => { | ||||||
|       return group.value?.preferences?.firstDayOfWeek || 0; |       return household.value?.preferences?.firstDayOfWeek || 0; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // =========================================================================== |     // =========================================================================== | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ | |||||||
|       </tr> |       </tr> | ||||||
|     </template> |     </template> | ||||||
|     <template #item.name="{ item }"> |     <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> | ||||||
|     <template #item.tags="{ item }"> |     <template #item.tags="{ item }"> | ||||||
|       <RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" /> |       <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 RecipeChip from "./RecipeChips.vue"; | ||||||
| import { Recipe } from "~/lib/api/types/recipe"; | import { Recipe } from "~/lib/api/types/recipe"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { UserOut } from "~/lib/api/types/user"; | import { UserSummary } from "~/lib/api/types/user"; | ||||||
| 
 | 
 | ||||||
| const INPUT_EVENT = "input"; | const INPUT_EVENT = "input"; | ||||||
| 
 | 
 | ||||||
| @ -95,7 +95,8 @@ export default defineComponent({ | |||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   setup(props, context) { |   setup(props, context) { | ||||||
|     const { i18n } = useContext(); |     const { $auth, i18n } = useContext(); | ||||||
|  |     const groupSlug = $auth.user?.groupSlug; | ||||||
| 
 | 
 | ||||||
|     function setValue(value: Recipe[]) { |     function setValue(value: Recipe[]) { | ||||||
|       context.emit(INPUT_EVENT, value); |       context.emit(INPUT_EVENT, value); | ||||||
| @ -134,7 +135,7 @@ export default defineComponent({ | |||||||
|     // ============ |     // ============ | ||||||
|     // Group Members |     // Group Members | ||||||
|     const api = useUserApi(); |     const api = useUserApi(); | ||||||
|     const members = ref<UserOut[]>([]); |     const members = ref<UserSummary[]>([]); | ||||||
| 
 | 
 | ||||||
|     async function refreshMembers() { |     async function refreshMembers() { | ||||||
|       const { data } = await api.groups.fetchMembers(); |       const { data } = await api.groups.fetchMembers(); | ||||||
| @ -149,13 +150,19 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
|     function getMember(id: string) { |     function getMember(id: string) { | ||||||
|       if (members.value[0]) { |       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 i18n.t("general.none"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return { setValue, headers, members, getMember }; |     return { | ||||||
|  |       groupSlug, | ||||||
|  |       setValue, | ||||||
|  |       headers, | ||||||
|  |       members, | ||||||
|  |       getMember, | ||||||
|  |     }; | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   data() { |   data() { | ||||||
|  | |||||||
| @ -127,14 +127,14 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api" | import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api"; | ||||||
|   import { toRefs } from "@vueuse/core" | import { toRefs } from "@vueuse/core"; | ||||||
|   import RecipeIngredientListItem from "./RecipeIngredientListItem.vue" | import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"; | ||||||
|   import { useUserApi } from "~/composables/api" | import { useUserApi } from "~/composables/api"; | ||||||
|   import { alert } from "~/composables/use-toast" | import { alert } from "~/composables/use-toast"; | ||||||
|   import { useShoppingListPreferences } from "~/composables/use-users/preferences" | import { useShoppingListPreferences } from "~/composables/use-users/preferences"; | ||||||
|   import { ShoppingListSummary } from "~/lib/api/types/group" | import { ShoppingListSummary } from "~/lib/api/types/household"; | ||||||
|   import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe" | import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe"; | ||||||
| 
 | 
 | ||||||
| export interface RecipeWithScale extends Recipe { | export interface RecipeWithScale extends Recipe { | ||||||
|   scale: number; |   scale: number; | ||||||
| @ -209,7 +209,8 @@ export default defineComponent({ | |||||||
|     watchEffect( |     watchEffect( | ||||||
|       () => { |       () => { | ||||||
|         if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) { |         if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) { | ||||||
|           openShoppingListIngredientDialog(shoppingListChoices.value[0]); |           selectedShoppingList.value = shoppingListChoices.value[0]; | ||||||
|  |           openShoppingListIngredientDialog(selectedShoppingList.value); | ||||||
|         } else { |         } else { | ||||||
|           ready.value = true; |           ready.value = true; | ||||||
|         } |         } | ||||||
| @ -365,12 +366,8 @@ export default defineComponent({ | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       const successMessage = promises.length === 1 |       success ? alert.success(i18n.tc("recipe.successfully-added-to-list")) | ||||||
|         ? i18n.t("recipe.successfully-added-to-list") as string |       : alert.error(i18n.tc("failed-to-add-recipes-to-list")) | ||||||
|         : 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) |  | ||||||
| 
 | 
 | ||||||
|       state.shoppingListDialog = false; |       state.shoppingListDialog = false; | ||||||
|       state.shoppingListIngredientDialog = false; |       state.shoppingListIngredientDialog = false; | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ import { defineComponent, computed, toRefs, reactive, useContext, useRoute } fro | |||||||
| import { useClipboard, useShare, whenever } from "@vueuse/core"; | import { useClipboard, useShare, whenever } from "@vueuse/core"; | ||||||
| import { RecipeShareToken } from "~/lib/api/types/recipe"; | import { RecipeShareToken } from "~/lib/api/types/recipe"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { useGroupSelf } from "~/composables/use-groups"; | import { useHouseholdSelf } from "~/composables/use-households"; | ||||||
| import { alert } from "~/composables/use-toast"; | import { alert } from "~/composables/use-toast"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| @ -113,12 +113,12 @@ export default defineComponent({ | |||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const { $auth, i18n } = useContext(); |     const { $auth, i18n } = useContext(); | ||||||
|     const { group } = useGroupSelf(); |     const { household } = useHouseholdSelf(); | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); |     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); | ||||||
| 
 | 
 | ||||||
|     const firstDayOfWeek = computed(() => { |     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, |         icon: $globals.icons.update, | ||||||
|         name: i18n.tc("general.updated"), |         name: i18n.tc("general.updated"), | ||||||
|         value: "update_at", |         value: "updated_at", | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         icon: $globals.icons.diceMultiple, |         icon: $globals.icons.diceMultiple, | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ | |||||||
| </template> | </template> | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent } from "@nuxtjs/composition-api"; | 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"; | import { useParsedIngredientText } from "~/composables/recipes"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | |||||||
| @ -114,7 +114,7 @@ import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@n | |||||||
| import { whenever } from "@vueuse/core"; | import { whenever } from "@vueuse/core"; | ||||||
| import { VForm } from "~/types/vuetify"; | import { VForm } from "~/types/vuetify"; | ||||||
| import { useUserApi } from "~/composables/api"; | 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"; | import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| @ -131,7 +131,7 @@ export default defineComponent({ | |||||||
|   setup(props, context) { |   setup(props, context) { | ||||||
|     const madeThisDialog = ref(false); |     const madeThisDialog = ref(false); | ||||||
|     const userApi = useUserApi(); |     const userApi = useUserApi(); | ||||||
|     const { group } = useGroupSelf(); |     const { household } = useHouseholdSelf(); | ||||||
|     const { $auth, i18n } = useContext(); |     const { $auth, i18n } = useContext(); | ||||||
|     const domMadeThisForm = ref<VForm>(); |     const domMadeThisForm = ref<VForm>(); | ||||||
|     const newTimelineEvent = ref<RecipeTimelineEventIn>({ |     const newTimelineEvent = ref<RecipeTimelineEventIn>({ | ||||||
| @ -157,7 +157,7 @@ export default defineComponent({ | |||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const firstDayOfWeek = computed(() => { |     const firstDayOfWeek = computed(() => { | ||||||
|       return group.value?.preferences?.firstDayOfWeek || 0; |       return household.value?.preferences?.firstDayOfWeek || 0; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function clearImage() { |     function clearImage() { | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ | |||||||
| import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api"; | import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api"; | ||||||
| import DOMPurify from "dompurify"; | import DOMPurify from "dompurify"; | ||||||
| import { useFraction } from "~/composables/recipes/use-fraction"; | 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"; | import { RecipeSummary } from "~/lib/api/types/recipe"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ | |||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; | import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/group"; | import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household"; | ||||||
| 
 | 
 | ||||||
| interface actions { | interface actions { | ||||||
|   text: string; |   text: string; | ||||||
|  | |||||||
| @ -75,7 +75,7 @@ | |||||||
|     <v-row v-if="listItem.checked" no-gutters class="mb-2"> |     <v-row v-if="listItem.checked" no-gutters class="mb-2"> | ||||||
|       <v-col cols="auto"> |       <v-col cols="auto"> | ||||||
|         <div class="text-caption font-weight-light font-italic"> |         <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> |         </div> | ||||||
|       </v-col> |       </v-col> | ||||||
|     </v-row> |     </v-row> | ||||||
| @ -99,7 +99,7 @@ import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition- | |||||||
| import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue"; | import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue"; | ||||||
| import ShoppingListItemEditor from "./ShoppingListItemEditor.vue"; | import ShoppingListItemEditor from "./ShoppingListItemEditor.vue"; | ||||||
| import MultiPurposeLabel from "./MultiPurposeLabel.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 { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels"; | ||||||
| import { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe"; | import { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe"; | ||||||
| import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | ||||||
|  | |||||||
| @ -111,7 +111,7 @@ | |||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, computed, watch } from "@nuxtjs/composition-api"; | 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 { MultiPurposeLabelOut } from "~/lib/api/types/labels"; | ||||||
| import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe"; | import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe"; | ||||||
| import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store"; | import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store"; | ||||||
|  | |||||||
| @ -183,7 +183,7 @@ export default defineComponent({ | |||||||
|       { |       { | ||||||
|         icon: $globals.icons.calendarMultiselect, |         icon: $globals.icons.calendarMultiselect, | ||||||
|         title: i18n.tc("meal-plan.meal-planner"), |         title: i18n.tc("meal-plan.meal-planner"), | ||||||
|         to: "/group/mealplan/planner/view", |         to: "/household/mealplan/planner/view", | ||||||
|         restricted: true, |         restricted: true, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <v-toolbar flat> |   <v-toolbar color="transparent" flat> | ||||||
|     <BaseButton color="null" rounded secondary @click="$router.go(-1)"> |     <BaseButton color="null" rounded secondary @click="$router.go(-1)"> | ||||||
|       <template #icon> {{ $globals.icons.arrowLeftBold }}</template> |       <template #icon> {{ $globals.icons.arrowLeftBold }}</template> | ||||||
|       {{ $t('general.back') }} |       {{ $t('general.back') }} | ||||||
|  | |||||||
| @ -85,6 +85,8 @@ | |||||||
|           :label="inputField.label" |           :label="inputField.label" | ||||||
|           :name="inputField.varName" |           :name="inputField.varName" | ||||||
|           :items="inputField.options" |           :items="inputField.options" | ||||||
|  |           :item-text="inputField.itemText" | ||||||
|  |           :item-value="inputField.itemValue" | ||||||
|           :return-object="false" |           :return-object="false" | ||||||
|           :hint="inputField.hint" |           :hint="inputField.hint" | ||||||
|           persistent-hint |           persistent-hint | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; | |||||||
| import { QueryValue } from "~/lib/api/base/route"; | import { QueryValue } from "~/lib/api/base/route"; | ||||||
| 
 | 
 | ||||||
| type BoundT = { | type BoundT = { | ||||||
|   id?: string | number; |   id?: string | number | null; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface PublicStoreActions<T extends BoundT> { | interface PublicStoreActions<T extends BoundT> { | ||||||
|  | |||||||
| @ -160,6 +160,9 @@ export function usePageUser(): { user: UserOut } { | |||||||
|         group: "", |         group: "", | ||||||
|         groupId: "", |         groupId: "", | ||||||
|         groupSlug: "", |         groupSlug: "", | ||||||
|  |         household: "", | ||||||
|  |         householdId: "", | ||||||
|  |         householdSlug: "", | ||||||
|         cacheKey: "", |         cacheKey: "", | ||||||
|         email: "", |         email: "", | ||||||
|       }, |       }, | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const { quantity, food, unit, note } = ingredient; |   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 |   const usePluralFood = (!quantity) || quantity * scale > 1 | ||||||
| 
 | 
 | ||||||
|   let returnQty = ""; |   let returnQty = ""; | ||||||
| @ -69,8 +69,8 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const unitName = useUnitName(unit, usePluralUnit); |   const unitName = useUnitName(unit || undefined, usePluralUnit); | ||||||
|   const foodName = useFoodName(food, usePluralFood); |   const foodName = useFoodName(food || undefined, usePluralFood); | ||||||
| 
 | 
 | ||||||
|   return { |   return { | ||||||
|     quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined, |     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 { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; | ||||||
| import { usePublicExploreApi } from "../api/api-client"; | import { usePublicExploreApi } from "../api/api-client"; | ||||||
| import { useUserApi } from "~/composables/api"; | 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 items: Ref<RecipeTag[]> = ref([]); | ||||||
| const publicStoreLoading = ref(false); | const publicStoreLoading = ref(false); | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { useAsync, ref, Ref, useContext } from "@nuxtjs/composition-api"; | import { useAsync, ref, Ref, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { useAsyncKey } from "./use-utils"; | import { useAsyncKey } from "./use-utils"; | ||||||
| import { usePublicExploreApi } from "./api/api-client"; | import { usePublicExploreApi } from "./api/api-client"; | ||||||
|  | import { useHouseholdSelf } from "./use-households"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook"; | import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook"; | ||||||
| 
 | 
 | ||||||
| @ -67,6 +68,7 @@ export const usePublicCookbooks = function (groupSlug: string) { | |||||||
| 
 | 
 | ||||||
| export const useCookbooks = function () { | export const useCookbooks = function () { | ||||||
|   const api = useUserApi(); |   const api = useUserApi(); | ||||||
|  |   const { household } = useHouseholdSelf(); | ||||||
|   const loading = ref(false); |   const loading = ref(false); | ||||||
| 
 | 
 | ||||||
|   const { i18n } = useContext(); |   const { i18n } = useContext(); | ||||||
| @ -100,7 +102,7 @@ export const useCookbooks = function () { | |||||||
|     async createOne() { |     async createOne() { | ||||||
|       loading.value = true; |       loading.value = true; | ||||||
|       const { data } = await api.cookbooks.createOne({ |       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) { |       if (data && cookbookStore?.value) { | ||||||
|         cookbookStore.value.push(data); |         cookbookStore.value.push(data); | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { computed, reactive, ref } from "@nuxtjs/composition-api"; | import { computed, reactive, ref } from "@nuxtjs/composition-api"; | ||||||
| import { useStoreActions } from "./partials/use-actions-factory"; | import { useStoreActions } from "./partials/use-actions-factory"; | ||||||
| import { useUserApi } from "~/composables/api"; | 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"; | import { Recipe } from "~/lib/api/types/recipe"; | ||||||
| 
 | 
 | ||||||
| const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null); | const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null); | ||||||
| @ -10,7 +10,7 @@ const loading = ref(false); | |||||||
| export function useGroupRecipeActionData() { | export function useGroupRecipeActionData() { | ||||||
|   const data = reactive({ |   const data = reactive({ | ||||||
|     id: "", |     id: "", | ||||||
|     actionType: "link" as RecipeActionType, |     actionType: "link" as GroupRecipeActionType, | ||||||
|     title: "", |     title: "", | ||||||
|     url: "", |     url: "", | ||||||
|   }); |   }); | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { useAsync, ref } from "@nuxtjs/composition-api"; | import { useAsync, ref } from "@nuxtjs/composition-api"; | ||||||
| import { useAsyncKey } from "./use-utils"; | import { useAsyncKey } from "./use-utils"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { ReadWebhook } from "~/lib/api/types/group"; | import { ReadWebhook } from "~/lib/api/types/household"; | ||||||
| 
 | 
 | ||||||
| export const useGroupWebhooks = function () { | export const useGroupWebhooks = function () { | ||||||
|   const api = useUserApi(); |   const api = useUserApi(); | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ export const useGroups = function () { | |||||||
|     loading.value = true; |     loading.value = true; | ||||||
|     const asyncKey = String(Date.now()); |     const asyncKey = String(Date.now()); | ||||||
|     const groups = useAsync(async () => { |     const groups = useAsync(async () => { | ||||||
|       const { data } = await api.groups.getAll(); |       const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});; | ||||||
| 
 | 
 | ||||||
|       if (data) { |       if (data) { | ||||||
|         return data.items; |         return data.items; | ||||||
| @ -66,7 +66,7 @@ export const useGroups = function () { | |||||||
| 
 | 
 | ||||||
|   async function refreshAllGroups() { |   async function refreshAllGroups() { | ||||||
|     loading.value = true; |     loading.value = true; | ||||||
|     const { data } = await api.groups.getAll(); |     const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});; | ||||||
| 
 | 
 | ||||||
|     if (data) { |     if (data) { | ||||||
|       groups.value = data.items; |       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 { computed, reactive, watch } from "@nuxtjs/composition-api"; | ||||||
| import { useLocalStorage } from "@vueuse/core"; | import { useLocalStorage } from "@vueuse/core"; | ||||||
| import { useUserApi } from "~/composables/api"; | 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"; | import { RequestResponse } from "~/lib/api/types/non-generated"; | ||||||
| 
 | 
 | ||||||
| const localStorageKey = "shopping-list-queue"; | const localStorageKey = "shopping-list-queue"; | ||||||
| @ -144,7 +144,7 @@ export function useShoppingListItemActions(shoppingListId: string) { | |||||||
| 
 | 
 | ||||||
|   function checkUpdateState(list: ShoppingListOut) { |   function checkUpdateState(list: ShoppingListOut) { | ||||||
|     const cutoffDate = new Date(queue.lastUpdate + queueTimeout).toISOString(); |     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
 |       // 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"); |       console.log("Out of sync with server; clearing queue"); | ||||||
|       clearQueueItems("all"); |       clearQueueItems("all"); | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ | |||||||
|     "database-type": "Database Type", |     "database-type": "Database Type", | ||||||
|     "database-url": "Database URL", |     "database-url": "Database URL", | ||||||
|     "default-group": "Default Group", |     "default-group": "Default Group", | ||||||
|  |     "default-household": "Default Household", | ||||||
|     "demo": "Demo", |     "demo": "Demo", | ||||||
|     "demo-status": "Demo Status", |     "demo-status": "Demo Status", | ||||||
|     "development": "Development", |     "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." |       "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": "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", |     "manage": "Manage", | ||||||
|     "invite": "Invite", |     "invite": "Invite", | ||||||
|     "looking-to-update-your-profile": "Looking to Update Your Profile?", |     "looking-to-update-your-profile": "Looking to Update Your Profile?", | ||||||
| @ -246,7 +247,7 @@ | |||||||
|     "default-recipe-preferences": "Default Recipe Preferences", |     "default-recipe-preferences": "Default Recipe Preferences", | ||||||
|     "group-preferences": "Group Preferences", |     "group-preferences": "Group Preferences", | ||||||
|     "private-group": "Private Group", |     "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": "Enable Public Access", | ||||||
|     "enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in", |     "enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in", | ||||||
|     "allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes", |     "allow-users-outside-of-your-group-to-see-your-recipes": "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": "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-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": "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", |     "general-preferences": "General Preferences", | ||||||
|     "group-recipe-preferences": "Group Recipe Preferences", |     "group-recipe-preferences": "Group Recipe Preferences", | ||||||
|     "report": "Report", |     "report": "Report", | ||||||
| @ -268,7 +269,28 @@ | |||||||
|     "group-management": "Group Management", |     "group-management": "Group Management", | ||||||
|     "admin-group-management": "Admin Group Management", |     "admin-group-management": "Admin Group Management", | ||||||
|     "admin-group-management-text": "Changes to this group will be reflected immediately.", |     "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": { |   "meal-plan": { | ||||||
|     "create-a-new-meal-plan": "Create a New 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.", |     "account-summary-description": "Here's a summary of your group's information.", | ||||||
|     "group-statistics": "Group Statistics", |     "group-statistics": "Group Statistics", | ||||||
|     "group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.", |     "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": "Storage Capacity", | ||||||
|     "storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.", |     "storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.", | ||||||
|     "personal": "Personal", |     "personal": "Personal", | ||||||
| @ -1239,10 +1263,13 @@ | |||||||
|     "api-tokens-description": "Manage your API Tokens for access from external applications.", |     "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-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": "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.", |     "cookbooks-description": "Manage a collection of recipe categories and generate pages for them.", | ||||||
|     "members": "Members", |     "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.", |     "webhooks-description": "Set up webhooks that trigger on days that you have mealplans scheduled.", | ||||||
|     "notifiers": "Notifiers", |     "notifiers": "Notifiers", | ||||||
|     "notifiers-description": "Set up email and push notifications that trigger on specific events.", |     "notifiers-description": "Set up email and push notifications that trigger on specific events.", | ||||||
| @ -1277,6 +1304,7 @@ | |||||||
|     "require-all-tools": "Require All Tools", |     "require-all-tools": "Require All Tools", | ||||||
|     "cookbook-name": "Cookbook Name", |     "cookbook-name": "Cookbook Name", | ||||||
|     "cookbook-with-name": "Cookbook {0}", |     "cookbook-with-name": "Cookbook {0}", | ||||||
|  |     "household-cookbook-name": "{0} Cookbook {1}", | ||||||
|     "create-a-cookbook": "Create a Cookbook", |     "create-a-cookbook": "Create a Cookbook", | ||||||
|     "cookbook": "Cookbook" |     "cookbook": "Cookbook" | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -64,6 +64,12 @@ export default defineComponent({ | |||||||
|         title: i18n.tc("user.users"), |         title: i18n.tc("user.users"), | ||||||
|         restricted: true, |         restricted: true, | ||||||
|       }, |       }, | ||||||
|  |       { | ||||||
|  |         icon: $globals.icons.household, | ||||||
|  |         to: "/admin/manage/households", | ||||||
|  |         title: i18n.tc("household.households"), | ||||||
|  |         restricted: true, | ||||||
|  |       }, | ||||||
|       { |       { | ||||||
|         icon: $globals.icons.group, |         icon: $globals.icons.group, | ||||||
|         to: "/admin/manage/groups", |         to: "/admin/manage/groups", | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <v-app dark> |   <v-app v-if="ready" dark> | ||||||
|     <v-card-title> |     <v-card-title> | ||||||
|       <slot> |       <slot> | ||||||
|         <h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1> |         <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) { |     if (props.error.statusCode === 404) { | ||||||
|       // see if adding the groupSlug fixes the error |       handle404(); | ||||||
|       insertGroupSlugIntoRoute().then(() => { ready.value = true }); |  | ||||||
|     } else { |     } else { | ||||||
|       ready.value = true; |       ready.value = true; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { RecipeAPI } from "./user/recipes"; | import { RecipeAPI } from "./user/recipes"; | ||||||
| import { UserApi } from "./user/users"; | import { UserApi } from "./user/users"; | ||||||
|  | import { HouseholdAPI } from "./user/households"; | ||||||
| import { GroupAPI } from "./user/groups"; | import { GroupAPI } from "./user/groups"; | ||||||
| import { BackupAPI } from "./user/backups"; | import { BackupAPI } from "./user/backups"; | ||||||
| import { UploadFile } from "./user/upload"; | import { UploadFile } from "./user/upload"; | ||||||
| @ -28,6 +29,7 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | |||||||
| export class UserApiClient { | export class UserApiClient { | ||||||
|   public recipes: RecipeAPI; |   public recipes: RecipeAPI; | ||||||
|   public users: UserApi; |   public users: UserApi; | ||||||
|  |   public households: HouseholdAPI; | ||||||
|   public groups: GroupAPI; |   public groups: GroupAPI; | ||||||
|   public backups: BackupAPI; |   public backups: BackupAPI; | ||||||
|   public categories: CategoriesAPI; |   public categories: CategoriesAPI; | ||||||
| @ -63,6 +65,7 @@ export class UserApiClient { | |||||||
| 
 | 
 | ||||||
|     // Users
 |     // Users
 | ||||||
|     this.users = new UserApi(requests); |     this.users = new UserApi(requests); | ||||||
|  |     this.households = new HouseholdAPI(requests); | ||||||
|     this.groups = new GroupAPI(requests); |     this.groups = new GroupAPI(requests); | ||||||
|     this.cookbooks = new CookbookAPI(requests); |     this.cookbooks = new CookbookAPI(requests); | ||||||
|     this.groupRecipeActions = new GroupRecipeActionsAPI(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"; | import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||||
| 
 | 
 | ||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
|  | const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|     cookbooksGroupSlug: (groupSlug: string | number) => `${prefix}/explore/cookbooks/${groupSlug}`, |     cookbooksGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks`, | ||||||
|     cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${prefix}/explore/cookbooks/${groupSlug}/${cookbookId}`, |     cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks/${cookbookId}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class PublicCookbooksApi extends BaseCRUDAPIReadOnly<RecipeCookBook> { | 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"; | import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||||
| 
 | 
 | ||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
|  | const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|     foodsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/foods/${groupSlug}`, |     foodsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/foods`, | ||||||
|     foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${prefix}/explore/foods/${groupSlug}/${foodId}`, |     foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${exploreGroupSlug(groupSlug)}/foods/${foodId}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class PublicFoodsApi extends BaseCRUDAPIReadOnly<IngredientFood> { | 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"; | import { ApiRequestInstance } from "~/lib/api/types/non-generated"; | ||||||
| 
 | 
 | ||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
|  | const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|     categoriesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories`, |     categoriesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories`, | ||||||
|     categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories/${categoryId}`, |     categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories/${categoryId}`, | ||||||
|     tagsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags`, |     tagsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags`, | ||||||
|     tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags/${tagId}`, |     tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags/${tagId}`, | ||||||
|     toolsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools`, |     toolsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`, | ||||||
|     toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools/${toolId}`, |     toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class PublicCategoriesApi extends BaseCRUDAPIReadOnly<RecipeCategory> { | 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"; | import { RecipeSearchQuery } from "../../user/recipes/recipe"; | ||||||
| 
 | 
 | ||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
|  | const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   recipesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}`, |   recipesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes`, | ||||||
|   recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}/${recipeSlug}`, |   recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes/${recipeSlug}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class PublicRecipeApi extends BaseCRUDAPIReadOnly<Recipe> { | export class PublicRecipeApi extends BaseCRUDAPIReadOnly<Recipe> { | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ export interface AdminAboutInfo { | |||||||
|   version: string; |   version: string; | ||||||
|   demoStatus: boolean; |   demoStatus: boolean; | ||||||
|   allowSignup: boolean; |   allowSignup: boolean; | ||||||
|  |   defaultGroupSlug?: string | null; | ||||||
|  |   defaultHouseholdSlug?: string | null; | ||||||
|   enableOidc: boolean; |   enableOidc: boolean; | ||||||
|   oidcRedirect: boolean; |   oidcRedirect: boolean; | ||||||
|   oidcProviderName: string; |   oidcProviderName: string; | ||||||
| @ -18,8 +20,9 @@ export interface AdminAboutInfo { | |||||||
|   apiPort: number; |   apiPort: number; | ||||||
|   apiDocs: boolean; |   apiDocs: boolean; | ||||||
|   dbType: string; |   dbType: string; | ||||||
|   dbUrl?: string; |   dbUrl?: string | null; | ||||||
|   defaultGroup: string; |   defaultGroup: string; | ||||||
|  |   defaultHousehold: string; | ||||||
|   buildId: string; |   buildId: string; | ||||||
|   recipeScraperVersion: string; |   recipeScraperVersion: string; | ||||||
| } | } | ||||||
| @ -37,7 +40,8 @@ export interface AppInfo { | |||||||
|   version: string; |   version: string; | ||||||
|   demoStatus: boolean; |   demoStatus: boolean; | ||||||
|   allowSignup: boolean; |   allowSignup: boolean; | ||||||
|   defaultGroupSlug?: string; |   defaultGroupSlug?: string | null; | ||||||
|  |   defaultHouseholdSlug?: string | null; | ||||||
|   enableOidc: boolean; |   enableOidc: boolean; | ||||||
|   oidcRedirect: boolean; |   oidcRedirect: boolean; | ||||||
|   oidcProviderName: string; |   oidcProviderName: string; | ||||||
| @ -51,6 +55,7 @@ export interface AppStartupInfo { | |||||||
| export interface AppStatistics { | export interface AppStatistics { | ||||||
|   totalRecipes: number; |   totalRecipes: number; | ||||||
|   totalUsers: number; |   totalUsers: number; | ||||||
|  |   totalHouseholds: number; | ||||||
|   totalGroups: number; |   totalGroups: number; | ||||||
|   uncategorizedRecipes: number; |   uncategorizedRecipes: number; | ||||||
|   untaggedRecipes: number; |   untaggedRecipes: number; | ||||||
| @ -93,16 +98,16 @@ export interface ChowdownURL { | |||||||
| export interface CommentImport { | export interface CommentImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface CreateBackup { | export interface CreateBackup { | ||||||
|   tag?: string; |   tag?: string | null; | ||||||
|   options: BackupOptions; |   options: BackupOptions; | ||||||
|   templates?: string[]; |   templates?: string[] | null; | ||||||
| } | } | ||||||
| export interface CustomPageBase { | export interface CustomPageBase { | ||||||
|   name: string; |   name: string; | ||||||
|   slug?: string; |   slug: string | null; | ||||||
|   position: number; |   position: number; | ||||||
|   categories?: RecipeCategoryResponse[]; |   categories?: RecipeCategoryResponse[]; | ||||||
| } | } | ||||||
| @ -113,38 +118,41 @@ export interface RecipeCategoryResponse { | |||||||
|   recipes?: RecipeSummary[]; |   recipes?: RecipeSummary[]; | ||||||
| } | } | ||||||
| export interface RecipeSummary { | export interface RecipeSummary { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   userId?: string; |   userId?: string; | ||||||
|  |   householdId?: string; | ||||||
|   groupId?: string; |   groupId?: string; | ||||||
|   name?: string; |   name?: string | null; | ||||||
|   slug?: string; |   slug?: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   recipeYield?: string; |   recipeYield?: string | null; | ||||||
|   totalTime?: string; |   totalTime?: string | null; | ||||||
|   prepTime?: string; |   prepTime?: string | null; | ||||||
|   cookTime?: string; |   cookTime?: string | null; | ||||||
|   performTime?: string; |   performTime?: string | null; | ||||||
|   description?: string; |   description?: string | null; | ||||||
|   recipeCategory?: RecipeCategory[]; |   recipeCategory?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
|   tools?: RecipeTool[]; |   tools?: RecipeTool[]; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   orgURL?: string; |   orgURL?: string | null; | ||||||
|   dateAdded?: string; |   dateAdded?: string | null; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string | null; | ||||||
|   createdAt?: string; |   createdAt?: string | null; | ||||||
|   updateAt?: string; |   updatedAt?: string | null; | ||||||
|   lastMade?: string; |   lastMade?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeCategory { | export interface RecipeCategory { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface RecipeTag { | export interface RecipeTag { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface RecipeTool { | export interface RecipeTool { | ||||||
|   id: string; |   id: string; | ||||||
| @ -155,11 +163,11 @@ export interface RecipeTool { | |||||||
| export interface CustomPageImport { | export interface CustomPageImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface CustomPageOut { | export interface CustomPageOut { | ||||||
|   name: string; |   name: string; | ||||||
|   slug?: string; |   slug: string | null; | ||||||
|   position: number; |   position: number; | ||||||
|   categories?: RecipeCategoryResponse[]; |   categories?: RecipeCategoryResponse[]; | ||||||
|   id: number; |   id: number; | ||||||
| @ -169,7 +177,7 @@ export interface EmailReady { | |||||||
| } | } | ||||||
| export interface EmailSuccess { | export interface EmailSuccess { | ||||||
|   success: boolean; |   success: boolean; | ||||||
|   error?: string; |   error?: string | null; | ||||||
| } | } | ||||||
| export interface EmailTest { | export interface EmailTest { | ||||||
|   email: string; |   email: string; | ||||||
| @ -177,12 +185,12 @@ export interface EmailTest { | |||||||
| export interface GroupImport { | export interface GroupImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface ImportBase { | export interface ImportBase { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface ImportJob { | export interface ImportJob { | ||||||
|   recipes?: boolean; |   recipes?: boolean; | ||||||
| @ -217,8 +225,8 @@ export interface MigrationFile { | |||||||
| export interface MigrationImport { | export interface MigrationImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
| } | } | ||||||
| export interface Migrations { | export interface Migrations { | ||||||
|   type: string; |   type: string; | ||||||
| @ -227,25 +235,26 @@ export interface Migrations { | |||||||
| export interface NotificationImport { | export interface NotificationImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface OIDCInfo { | export interface OIDCInfo { | ||||||
|   configurationUrl?: string; |   configurationUrl: string | null; | ||||||
|   clientId?: string; |   clientId: string | null; | ||||||
|  |   groupsClaim: string | null; | ||||||
| } | } | ||||||
| export interface RecipeImport { | export interface RecipeImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
| } | } | ||||||
| export interface SettingsImport { | export interface SettingsImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface UserImport { | export interface UserImport { | ||||||
|   name: string; |   name: string; | ||||||
|   status: boolean; |   status: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ | |||||||
| export interface CreateCookBook { | export interface CreateCookBook { | ||||||
|   name: string; |   name: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
|   position?: number; |   position?: number; | ||||||
|   public?: boolean; |   public?: boolean; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[]; | ||||||
| @ -37,7 +37,7 @@ export interface RecipeTool { | |||||||
| export interface ReadCookBook { | export interface ReadCookBook { | ||||||
|   name: string; |   name: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
|   position?: number; |   position?: number; | ||||||
|   public?: boolean; |   public?: boolean; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[]; | ||||||
| @ -47,12 +47,13 @@ export interface ReadCookBook { | |||||||
|   requireAllTags?: boolean; |   requireAllTags?: boolean; | ||||||
|   requireAllTools?: boolean; |   requireAllTools?: boolean; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
| export interface RecipeCookBook { | export interface RecipeCookBook { | ||||||
|   name: string; |   name: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
|   position?: number; |   position?: number; | ||||||
|   public?: boolean; |   public?: boolean; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[]; | ||||||
| @ -62,47 +63,51 @@ export interface RecipeCookBook { | |||||||
|   requireAllTags?: boolean; |   requireAllTags?: boolean; | ||||||
|   requireAllTools?: boolean; |   requireAllTools?: boolean; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   id: string; |   id: string; | ||||||
|   recipes: RecipeSummary[]; |   recipes: RecipeSummary[]; | ||||||
| } | } | ||||||
| export interface RecipeSummary { | export interface RecipeSummary { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   userId?: string; |   userId?: string; | ||||||
|  |   householdId?: string; | ||||||
|   groupId?: string; |   groupId?: string; | ||||||
|   name?: string; |   name?: string | null; | ||||||
|   slug?: string; |   slug?: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   recipeYield?: string; |   recipeYield?: string | null; | ||||||
|   totalTime?: string; |   totalTime?: string | null; | ||||||
|   prepTime?: string; |   prepTime?: string | null; | ||||||
|   cookTime?: string; |   cookTime?: string | null; | ||||||
|   performTime?: string; |   performTime?: string | null; | ||||||
|   description?: string; |   description?: string | null; | ||||||
|   recipeCategory?: RecipeCategory[]; |   recipeCategory?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
|   tools?: RecipeTool[]; |   tools?: RecipeTool[]; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   orgURL?: string; |   orgURL?: string | null; | ||||||
|   dateAdded?: string; |   dateAdded?: string | null; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string | null; | ||||||
|   createdAt?: string; |   createdAt?: string | null; | ||||||
|   updateAt?: string; |   updatedAt?: string | null; | ||||||
|   lastMade?: string; |   lastMade?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeCategory { | export interface RecipeCategory { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface RecipeTag { | export interface RecipeTag { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface SaveCookBook { | export interface SaveCookBook { | ||||||
|   name: string; |   name: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
|   position?: number; |   position?: number; | ||||||
|   public?: boolean; |   public?: boolean; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[]; | ||||||
| @ -112,11 +117,12 @@ export interface SaveCookBook { | |||||||
|   requireAllTags?: boolean; |   requireAllTags?: boolean; | ||||||
|   requireAllTools?: boolean; |   requireAllTools?: boolean; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
| } | } | ||||||
| export interface UpdateCookBook { | export interface UpdateCookBook { | ||||||
|   name: string; |   name: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   slug?: string; |   slug?: string | null; | ||||||
|   position?: number; |   position?: number; | ||||||
|   public?: boolean; |   public?: boolean; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[]; | ||||||
| @ -126,5 +132,6 @@ export interface UpdateCookBook { | |||||||
|   requireAllTags?: boolean; |   requireAllTags?: boolean; | ||||||
|   requireAllTools?: boolean; |   requireAllTools?: boolean; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,10 +5,6 @@ | |||||||
| /* Do not modify it by hand - just update the pydantic models and then re-run the script | /* Do not modify it by hand - just update the pydantic models and then re-run the script | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| export type RecipeActionType = |  | ||||||
|   | "link" |  | ||||||
|   | "post"; |  | ||||||
| export type WebhookType = "mealplan"; |  | ||||||
| export type SupportedMigrations = | export type SupportedMigrations = | ||||||
|   | "nextcloud" |   | "nextcloud" | ||||||
|   | "chowdown" |   | "chowdown" | ||||||
| @ -17,59 +13,23 @@ export type SupportedMigrations = | |||||||
|   | "mealie_alpha" |   | "mealie_alpha" | ||||||
|   | "tandoor" |   | "tandoor" | ||||||
|   | "plantoeat" |   | "plantoeat" | ||||||
|  |   | "myrecipebox" | ||||||
|   | "recipekeeper"; |   | "recipekeeper"; | ||||||
| 
 | 
 | ||||||
| export interface CreateGroupPreferences { | export interface CreateGroupPreferences { | ||||||
|   privateGroup?: boolean; |   privateGroup?: boolean; | ||||||
|   firstDayOfWeek?: number; |  | ||||||
|   recipePublic?: boolean; |  | ||||||
|   recipeShowNutrition?: boolean; |  | ||||||
|   recipeShowAssets?: boolean; |  | ||||||
|   recipeLandscapeView?: boolean; |  | ||||||
|   recipeDisableComments?: boolean; |  | ||||||
|   recipeDisableAmount?: boolean; |  | ||||||
|   groupId: string; |   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 { | export interface DataMigrationCreate { | ||||||
|   sourceType: SupportedMigrations; |   sourceType: SupportedMigrations; | ||||||
| } | } | ||||||
| export interface EmailInitationResponse { |  | ||||||
|   success: boolean; |  | ||||||
|   error?: string; |  | ||||||
| } |  | ||||||
| export interface EmailInvitation { |  | ||||||
|   email: string; |  | ||||||
|   token: string; |  | ||||||
| } |  | ||||||
| export interface GroupAdminUpdate { | export interface GroupAdminUpdate { | ||||||
|   id: string; |   id: string; | ||||||
|   name: string; |   name: string; | ||||||
|   preferences?: UpdateGroupPreferences; |   preferences?: UpdateGroupPreferences | null; | ||||||
| } | } | ||||||
| export interface UpdateGroupPreferences { | export interface UpdateGroupPreferences { | ||||||
|   privateGroup?: boolean; |   privateGroup?: boolean; | ||||||
|   firstDayOfWeek?: number; |  | ||||||
|   recipePublic?: boolean; |  | ||||||
|   recipeShowNutrition?: boolean; |  | ||||||
|   recipeShowAssets?: boolean; |  | ||||||
|   recipeLandscapeView?: boolean; |  | ||||||
|   recipeDisableComments?: boolean; |  | ||||||
|   recipeDisableAmount?: boolean; |  | ||||||
| } | } | ||||||
| export interface GroupDataExport { | export interface GroupDataExport { | ||||||
|   id: string; |   id: string; | ||||||
| @ -80,140 +40,6 @@ export interface GroupDataExport { | |||||||
|   size: string; |   size: string; | ||||||
|   expires: 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 { | export interface GroupStorage { | ||||||
|   usedStorageBytes: number; |   usedStorageBytes: number; | ||||||
|   usedStorageStr: string; |   usedStorageStr: string; | ||||||
| @ -222,408 +48,9 @@ export interface GroupStorage { | |||||||
| } | } | ||||||
| export interface ReadGroupPreferences { | export interface ReadGroupPreferences { | ||||||
|   privateGroup?: boolean; |   privateGroup?: boolean; | ||||||
|   firstDayOfWeek?: number; |  | ||||||
|   recipePublic?: boolean; |  | ||||||
|   recipeShowNutrition?: boolean; |  | ||||||
|   recipeShowAssets?: boolean; |  | ||||||
|   recipeLandscapeView?: boolean; |  | ||||||
|   recipeDisableComments?: boolean; |  | ||||||
|   recipeDisableAmount?: boolean; |  | ||||||
|   groupId: string; |   groupId: string; | ||||||
|   id: 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 { | export interface SeederConfig { | ||||||
|   locale: string; |   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; |   entryType?: PlanEntryType & string; | ||||||
|   title?: string; |   title?: string; | ||||||
|   text?: string; |   text?: string; | ||||||
|   recipeId?: string; |   recipeId?: string | null; | ||||||
| } | } | ||||||
| export interface CreateRandomEntry { | export interface CreateRandomEntry { | ||||||
|   date: string; |   date: string; | ||||||
|   entryType?: PlanEntryType & string; |   entryType?: PlanEntryType & string; | ||||||
| } | } | ||||||
| export interface ListItem { | export interface ListItem { | ||||||
|   title?: string; |   title?: string | null; | ||||||
|   text?: string; |   text?: string; | ||||||
|   quantity?: number; |   quantity?: number; | ||||||
|   checked?: boolean; |   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 { | export interface PlanRulesCreate { | ||||||
|   day?: PlanRulesDay & string; |   day?: PlanRulesDay & string; | ||||||
|   entryType?: PlanRulesType & string; |   entryType?: PlanRulesType & string; | ||||||
| @ -76,6 +48,7 @@ export interface PlanRulesOut { | |||||||
|   categories?: Category[]; |   categories?: Category[]; | ||||||
|   tags?: Tag[]; |   tags?: Tag[]; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
| export interface PlanRulesSave { | export interface PlanRulesSave { | ||||||
| @ -84,51 +57,56 @@ export interface PlanRulesSave { | |||||||
|   categories?: Category[]; |   categories?: Category[]; | ||||||
|   tags?: Tag[]; |   tags?: Tag[]; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
| } | } | ||||||
| export interface ReadPlanEntry { | export interface ReadPlanEntry { | ||||||
|   date: string; |   date: string; | ||||||
|   entryType?: PlanEntryType & string; |   entryType?: PlanEntryType & string; | ||||||
|   title?: string; |   title?: string; | ||||||
|   text?: string; |   text?: string; | ||||||
|   recipeId?: string; |   recipeId?: string | null; | ||||||
|   id: number; |   id: number; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|   userId?: string; |   userId?: string | null; | ||||||
|   recipe?: RecipeSummary; |   householdId: string; | ||||||
|  |   recipe?: RecipeSummary | null; | ||||||
| } | } | ||||||
| export interface RecipeSummary { | export interface RecipeSummary { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   userId?: string; |   userId?: string; | ||||||
|  |   householdId?: string; | ||||||
|   groupId?: string; |   groupId?: string; | ||||||
|   name?: string; |   name?: string | null; | ||||||
|   slug?: string; |   slug?: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   recipeYield?: string; |   recipeYield?: string | null; | ||||||
|   totalTime?: string; |   totalTime?: string | null; | ||||||
|   prepTime?: string; |   prepTime?: string | null; | ||||||
|   cookTime?: string; |   cookTime?: string | null; | ||||||
|   performTime?: string; |   performTime?: string | null; | ||||||
|   description?: string; |   description?: string | null; | ||||||
|   recipeCategory?: RecipeCategory[]; |   recipeCategory?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
|   tools?: RecipeTool[]; |   tools?: RecipeTool[]; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   orgURL?: string; |   orgURL?: string | null; | ||||||
|   dateAdded?: string; |   dateAdded?: string | null; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string | null; | ||||||
|   createdAt?: string; |   createdAt?: string | null; | ||||||
|   updateAt?: string; |   updatedAt?: string | null; | ||||||
|   lastMade?: string; |   lastMade?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeCategory { | export interface RecipeCategory { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface RecipeTag { | export interface RecipeTag { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|  |   [k: string]: unknown; | ||||||
| } | } | ||||||
| export interface RecipeTool { | export interface RecipeTool { | ||||||
|   id: string; |   id: string; | ||||||
| @ -141,18 +119,18 @@ export interface SavePlanEntry { | |||||||
|   entryType?: PlanEntryType & string; |   entryType?: PlanEntryType & string; | ||||||
|   title?: string; |   title?: string; | ||||||
|   text?: string; |   text?: string; | ||||||
|   recipeId?: string; |   recipeId?: string | null; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|   userId?: string; |   userId?: string | null; | ||||||
| } | } | ||||||
| export interface ShoppingListIn { | export interface ShoppingListIn { | ||||||
|   name: string; |   name: string; | ||||||
|   group?: string; |   group?: string | null; | ||||||
|   items: ListItem[]; |   items: ListItem[]; | ||||||
| } | } | ||||||
| export interface ShoppingListOut { | export interface ShoppingListOut { | ||||||
|   name: string; |   name: string; | ||||||
|   group?: string; |   group?: string | null; | ||||||
|   items: ListItem[]; |   items: ListItem[]; | ||||||
|   id: number; |   id: number; | ||||||
| } | } | ||||||
| @ -161,8 +139,8 @@ export interface UpdatePlanEntry { | |||||||
|   entryType?: PlanEntryType & string; |   entryType?: PlanEntryType & string; | ||||||
|   title?: string; |   title?: string; | ||||||
|   text?: string; |   text?: string; | ||||||
|   recipeId?: string; |   recipeId?: string | null; | ||||||
|   id: number; |   id: number; | ||||||
|   groupId: string; |   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; |   groupId: string; | ||||||
| } | } | ||||||
| export interface CreateIngredientFood { | export interface CreateIngredientFood { | ||||||
|  |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|   labelId?: string; |  | ||||||
|   aliases?: CreateIngredientFoodAlias[]; |  | ||||||
|   onHand?: boolean; |   onHand?: boolean; | ||||||
|  |   labelId?: string | null; | ||||||
|  |   aliases?: CreateIngredientFoodAlias[]; | ||||||
| } | } | ||||||
| export interface CreateIngredientFoodAlias { | export interface CreateIngredientFoodAlias { | ||||||
|   name: string; |   name: string; | ||||||
| } | } | ||||||
| export interface CreateIngredientUnit { | export interface CreateIngredientUnit { | ||||||
|  |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|  |   onHand?: boolean; | ||||||
|   fraction?: boolean; |   fraction?: boolean; | ||||||
|   abbreviation?: string; |   abbreviation?: string; | ||||||
|   pluralAbbreviation?: string; |   pluralAbbreviation?: string | null; | ||||||
|   useAbbreviation?: boolean; |   useAbbreviation?: boolean; | ||||||
|   aliases?: CreateIngredientUnitAlias[]; |   aliases?: CreateIngredientUnitAlias[]; | ||||||
| } | } | ||||||
| @ -89,16 +92,16 @@ export interface CreateRecipe { | |||||||
| } | } | ||||||
| export interface CreateRecipeBulk { | export interface CreateRecipeBulk { | ||||||
|   url: string; |   url: string; | ||||||
|   categories?: RecipeCategory[]; |   categories?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
| } | } | ||||||
| export interface RecipeCategory { | export interface RecipeCategory { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
| } | } | ||||||
| export interface RecipeTag { | export interface RecipeTag { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
| } | } | ||||||
| @ -116,27 +119,27 @@ export interface ExportRecipes { | |||||||
|   exportType?: ExportTypes & string; |   exportType?: ExportTypes & string; | ||||||
| } | } | ||||||
| export interface IngredientConfidence { | export interface IngredientConfidence { | ||||||
|   average?: number; |   average?: number | null; | ||||||
|   comment?: number; |   comment?: number | null; | ||||||
|   name?: number; |   name?: number | null; | ||||||
|   unit?: number; |   unit?: number | null; | ||||||
|   quantity?: number; |   quantity?: number | null; | ||||||
|   food?: number; |   food?: number | null; | ||||||
| } | } | ||||||
| export interface IngredientFood { | export interface IngredientFood { | ||||||
|  |   id: string; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|   labelId?: string; |  | ||||||
|   aliases?: IngredientFoodAlias[]; |  | ||||||
|   id: string; |  | ||||||
|   label?: MultiPurposeLabelSummary; |  | ||||||
|   createdAt?: string; |  | ||||||
|   updateAt?: string; |  | ||||||
|   onHand?: boolean; |   onHand?: boolean; | ||||||
|  |   labelId?: string | null; | ||||||
|  |   aliases?: IngredientFoodAlias[]; | ||||||
|  |   label?: MultiPurposeLabelSummary | null; | ||||||
|  |   createdAt?: string | null; | ||||||
|  |   updatedAt?: string | null; | ||||||
| } | } | ||||||
| export interface IngredientFoodAlias { | export interface IngredientFoodAlias { | ||||||
|   name: string; |   name: string; | ||||||
| @ -151,27 +154,28 @@ export interface MultiPurposeLabelSummary { | |||||||
|  * A list of ingredient references. |  * A list of ingredient references. | ||||||
|  */ |  */ | ||||||
| export interface IngredientReferences { | export interface IngredientReferences { | ||||||
|   referenceId?: string; |   referenceId?: string | null; | ||||||
| } | } | ||||||
| export interface IngredientRequest { | export interface IngredientRequest { | ||||||
|   parser?: RegisteredParser & string; |   parser?: RegisteredParser & string; | ||||||
|   ingredient: string; |   ingredient: string; | ||||||
| } | } | ||||||
| export interface IngredientUnit { | export interface IngredientUnit { | ||||||
|  |   id: string; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|  |   onHand?: boolean; | ||||||
|   fraction?: boolean; |   fraction?: boolean; | ||||||
|   abbreviation?: string; |   abbreviation?: string; | ||||||
|   pluralAbbreviation?: string; |   pluralAbbreviation?: string | null; | ||||||
|   useAbbreviation?: boolean; |   useAbbreviation?: boolean; | ||||||
|   aliases?: IngredientUnitAlias[]; |   aliases?: IngredientUnitAlias[]; | ||||||
|   id: string; |   createdAt?: string | null; | ||||||
|   createdAt?: string; |   updatedAt?: string | null; | ||||||
|   updateAt?: string; |  | ||||||
| } | } | ||||||
| export interface IngredientUnitAlias { | export interface IngredientUnitAlias { | ||||||
|   name: string; |   name: string; | ||||||
| @ -189,64 +193,65 @@ export interface MergeUnit { | |||||||
|   toUnit: string; |   toUnit: string; | ||||||
| } | } | ||||||
| export interface Nutrition { | export interface Nutrition { | ||||||
|   calories?: string; |   calories?: string | null; | ||||||
|   fatContent?: string; |   fatContent?: string | null; | ||||||
|   proteinContent?: string; |   proteinContent?: string | null; | ||||||
|   carbohydrateContent?: string; |   carbohydrateContent?: string | null; | ||||||
|   fiberContent?: string; |   fiberContent?: string | null; | ||||||
|   sodiumContent?: string; |   sodiumContent?: string | null; | ||||||
|   sugarContent?: string; |   sugarContent?: string | null; | ||||||
| } | } | ||||||
| export interface ParsedIngredient { | export interface ParsedIngredient { | ||||||
|   input?: string; |   input?: string | null; | ||||||
|   confidence?: IngredientConfidence; |   confidence?: IngredientConfidence; | ||||||
|   ingredient: RecipeIngredient; |   ingredient: RecipeIngredient; | ||||||
| } | } | ||||||
| export interface RecipeIngredient { | export interface RecipeIngredient { | ||||||
|   quantity?: number; |   quantity?: number | null; | ||||||
|   unit?: IngredientUnit | CreateIngredientUnit; |   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||||
|   food?: IngredientFood | CreateIngredientFood; |   food?: IngredientFood | CreateIngredientFood | null; | ||||||
|   note?: string; |   note?: string | null; | ||||||
|   isFood?: boolean; |   isFood?: boolean | null; | ||||||
|   disableAmount?: boolean; |   disableAmount?: boolean; | ||||||
|   display?: string; |   display?: string; | ||||||
|   title?: string; |   title?: string | null; | ||||||
|   originalText?: string; |   originalText?: string | null; | ||||||
|   referenceId?: string; |   referenceId?: string; | ||||||
| } | } | ||||||
| export interface Recipe { | export interface Recipe { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   userId?: string; |   userId?: string; | ||||||
|  |   householdId?: string; | ||||||
|   groupId?: string; |   groupId?: string; | ||||||
|   name?: string; |   name?: string | null; | ||||||
|   slug?: string; |   slug?: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   recipeYield?: string; |   recipeYield?: string | null; | ||||||
|   totalTime?: string; |   totalTime?: string | null; | ||||||
|   prepTime?: string; |   prepTime?: string | null; | ||||||
|   cookTime?: string; |   cookTime?: string | null; | ||||||
|   performTime?: string; |   performTime?: string | null; | ||||||
|   description?: string; |   description?: string | null; | ||||||
|   recipeCategory?: RecipeCategory[]; |   recipeCategory?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
|   tools?: RecipeTool[]; |   tools?: RecipeTool[]; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   orgURL?: string; |   orgURL?: string | null; | ||||||
|   dateAdded?: string; |   dateAdded?: string | null; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string | null; | ||||||
|   createdAt?: string; |   createdAt?: string | null; | ||||||
|   updateAt?: string; |   updatedAt?: string | null; | ||||||
|   lastMade?: string; |   lastMade?: string | null; | ||||||
|   recipeIngredient?: RecipeIngredient[]; |   recipeIngredient?: RecipeIngredient[]; | ||||||
|   recipeInstructions?: RecipeStep[]; |   recipeInstructions?: RecipeStep[] | null; | ||||||
|   nutrition?: Nutrition; |   nutrition?: Nutrition | null; | ||||||
|   settings?: RecipeSettings; |   settings?: RecipeSettings | null; | ||||||
|   assets?: RecipeAsset[]; |   assets?: RecipeAsset[] | null; | ||||||
|   notes?: RecipeNote[]; |   notes?: RecipeNote[] | null; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|   comments?: RecipeCommentOut[]; |   comments?: RecipeCommentOut[] | null; | ||||||
| } | } | ||||||
| export interface RecipeTool { | export interface RecipeTool { | ||||||
|   id: string; |   id: string; | ||||||
| @ -255,15 +260,15 @@ export interface RecipeTool { | |||||||
|   onHand?: boolean; |   onHand?: boolean; | ||||||
| } | } | ||||||
| export interface RecipeStep { | export interface RecipeStep { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   title?: string; |   title?: string | null; | ||||||
|   text: string; |   text: string; | ||||||
|   ingredientReferences?: IngredientReferences[]; |   ingredientReferences?: IngredientReferences[]; | ||||||
| } | } | ||||||
| export interface RecipeAsset { | export interface RecipeAsset { | ||||||
|   name: string; |   name: string; | ||||||
|   icon: string; |   icon: string; | ||||||
|   fileName?: string; |   fileName?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeNote { | export interface RecipeNote { | ||||||
|   title: string; |   title: string; | ||||||
| @ -274,13 +279,13 @@ export interface RecipeCommentOut { | |||||||
|   text: string; |   text: string; | ||||||
|   id: string; |   id: string; | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   updateAt: string; |   updatedAt: string; | ||||||
|   userId: string; |   userId: string; | ||||||
|   user: UserBase; |   user: UserBase; | ||||||
| } | } | ||||||
| export interface UserBase { | export interface UserBase { | ||||||
|   id: string; |   id: string; | ||||||
|   username?: string; |   username?: string | null; | ||||||
|   admin: boolean; |   admin: boolean; | ||||||
| } | } | ||||||
| export interface RecipeCategoryResponse { | export interface RecipeCategoryResponse { | ||||||
| @ -290,28 +295,29 @@ export interface RecipeCategoryResponse { | |||||||
|   recipes?: RecipeSummary[]; |   recipes?: RecipeSummary[]; | ||||||
| } | } | ||||||
| export interface RecipeSummary { | export interface RecipeSummary { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   userId?: string; |   userId?: string; | ||||||
|  |   householdId?: string; | ||||||
|   groupId?: string; |   groupId?: string; | ||||||
|   name?: string; |   name?: string | null; | ||||||
|   slug?: string; |   slug?: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   recipeYield?: string; |   recipeYield?: string | null; | ||||||
|   totalTime?: string; |   totalTime?: string | null; | ||||||
|   prepTime?: string; |   prepTime?: string | null; | ||||||
|   cookTime?: string; |   cookTime?: string | null; | ||||||
|   performTime?: string; |   performTime?: string | null; | ||||||
|   description?: string; |   description?: string | null; | ||||||
|   recipeCategory?: RecipeCategory[]; |   recipeCategory?: RecipeCategory[] | null; | ||||||
|   tags?: RecipeTag[]; |   tags?: RecipeTag[] | null; | ||||||
|   tools?: RecipeTool[]; |   tools?: RecipeTool[]; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   orgURL?: string; |   orgURL?: string | null; | ||||||
|   dateAdded?: string; |   dateAdded?: string | null; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string | null; | ||||||
|   createdAt?: string; |   createdAt?: string | null; | ||||||
|   updateAt?: string; |   updatedAt?: string | null; | ||||||
|   lastMade?: string; |   lastMade?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeCommentCreate { | export interface RecipeCommentCreate { | ||||||
|   recipeId: string; |   recipeId: string; | ||||||
| @ -327,15 +333,15 @@ export interface RecipeCommentUpdate { | |||||||
|   text: string; |   text: string; | ||||||
| } | } | ||||||
| export interface RecipeDuplicate { | export interface RecipeDuplicate { | ||||||
|   name?: string; |   name?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeIngredientBase { | export interface RecipeIngredientBase { | ||||||
|   quantity?: number; |   quantity?: number | null; | ||||||
|   unit?: IngredientUnit | CreateIngredientUnit; |   unit?: IngredientUnit | CreateIngredientUnit | null; | ||||||
|   food?: IngredientFood | CreateIngredientFood; |   food?: IngredientFood | CreateIngredientFood | null; | ||||||
|   note?: string; |   note?: string | null; | ||||||
|   isFood?: boolean; |   isFood?: boolean | null; | ||||||
|   disableAmount?: boolean; |   disableAmount?: boolean | null; | ||||||
|   display?: string; |   display?: string; | ||||||
| } | } | ||||||
| export interface RecipeLastMade { | export interface RecipeLastMade { | ||||||
| @ -379,17 +385,17 @@ export interface RecipeTimelineEventCreate { | |||||||
|   userId: string; |   userId: string; | ||||||
|   subject: string; |   subject: string; | ||||||
|   eventType: TimelineEventType; |   eventType: TimelineEventType; | ||||||
|   eventMessage?: string; |   eventMessage?: string | null; | ||||||
|   image?: TimelineEventImage & string; |   image?: TimelineEventImage | null; | ||||||
|   timestamp?: string; |   timestamp?: string; | ||||||
| } | } | ||||||
| export interface RecipeTimelineEventIn { | export interface RecipeTimelineEventIn { | ||||||
|   recipeId: string; |   recipeId: string; | ||||||
|   userId?: string; |   userId?: string | null; | ||||||
|   subject: string; |   subject: string; | ||||||
|   eventType: TimelineEventType; |   eventType: TimelineEventType; | ||||||
|   eventMessage?: string; |   eventMessage?: string | null; | ||||||
|   image?: TimelineEventImage & string; |   image?: TimelineEventImage | null; | ||||||
|   timestamp?: string; |   timestamp?: string; | ||||||
| } | } | ||||||
| export interface RecipeTimelineEventOut { | export interface RecipeTimelineEventOut { | ||||||
| @ -397,17 +403,19 @@ export interface RecipeTimelineEventOut { | |||||||
|   userId: string; |   userId: string; | ||||||
|   subject: string; |   subject: string; | ||||||
|   eventType: TimelineEventType; |   eventType: TimelineEventType; | ||||||
|   eventMessage?: string; |   eventMessage?: string | null; | ||||||
|   image?: TimelineEventImage & string; |   image?: TimelineEventImage | null; | ||||||
|   timestamp?: string; |   timestamp?: string; | ||||||
|   id: string; |   id: string; | ||||||
|  |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   updateAt: string; |   updatedAt: string; | ||||||
| } | } | ||||||
| export interface RecipeTimelineEventUpdate { | export interface RecipeTimelineEventUpdate { | ||||||
|   subject: string; |   subject: string; | ||||||
|   eventMessage?: string; |   eventMessage?: string | null; | ||||||
|   image?: TimelineEventImage; |   image?: TimelineEventImage | null; | ||||||
| } | } | ||||||
| export interface RecipeToolCreate { | export interface RecipeToolCreate { | ||||||
|   name: string; |   name: string; | ||||||
| @ -435,26 +443,30 @@ export interface RecipeZipTokenResponse { | |||||||
|   token: string; |   token: string; | ||||||
| } | } | ||||||
| export interface SaveIngredientFood { | export interface SaveIngredientFood { | ||||||
|  |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|   labelId?: string; |   onHand?: boolean; | ||||||
|  |   labelId?: string | null; | ||||||
|   aliases?: CreateIngredientFoodAlias[]; |   aliases?: CreateIngredientFoodAlias[]; | ||||||
|   groupId: string; |   groupId: string; | ||||||
| } | } | ||||||
| export interface SaveIngredientUnit { | export interface SaveIngredientUnit { | ||||||
|  |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|  |   onHand?: boolean; | ||||||
|   fraction?: boolean; |   fraction?: boolean; | ||||||
|   abbreviation?: string; |   abbreviation?: string; | ||||||
|   pluralAbbreviation?: string; |   pluralAbbreviation?: string | null; | ||||||
|   useAbbreviation?: boolean; |   useAbbreviation?: boolean; | ||||||
|   aliases?: CreateIngredientUnitAlias[]; |   aliases?: CreateIngredientUnitAlias[]; | ||||||
|   groupId: string; |   groupId: string; | ||||||
| @ -465,8 +477,9 @@ export interface ScrapeRecipe { | |||||||
| } | } | ||||||
| export interface ScrapeRecipeTest { | export interface ScrapeRecipeTest { | ||||||
|   url: string; |   url: string; | ||||||
|  |   useOpenAI?: boolean; | ||||||
| } | } | ||||||
| export interface SlugResponse { } | export interface SlugResponse {} | ||||||
| export interface TagIn { | export interface TagIn { | ||||||
|   name: string; |   name: string; | ||||||
| } | } | ||||||
| @ -481,12 +494,14 @@ export interface TagSave { | |||||||
|   groupId: string; |   groupId: string; | ||||||
| } | } | ||||||
| export interface UnitFoodBase { | export interface UnitFoodBase { | ||||||
|  |   id?: string | null; | ||||||
|   name: string; |   name: string; | ||||||
|   pluralName?: string; |   pluralName?: string | null; | ||||||
|   description?: string; |   description?: string; | ||||||
|   extras?: { |   extras?: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
|   }; |   } | null; | ||||||
|  |   onHand?: boolean; | ||||||
| } | } | ||||||
| export interface UpdateImageResponse { | export interface UpdateImageResponse { | ||||||
|   image: string; |   image: string; | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ export type OrderDirection = "asc" | "desc"; | |||||||
| export interface ErrorResponse { | export interface ErrorResponse { | ||||||
|   message: string; |   message: string; | ||||||
|   error?: boolean; |   error?: boolean; | ||||||
|   exception?: string; |   exception?: string | null; | ||||||
| } | } | ||||||
| export interface FileTokenResponse { | export interface FileTokenResponse { | ||||||
|   fileToken: string; |   fileToken: string; | ||||||
| @ -19,19 +19,19 @@ export interface FileTokenResponse { | |||||||
| export interface PaginationQuery { | export interface PaginationQuery { | ||||||
|   page?: number; |   page?: number; | ||||||
|   perPage?: number; |   perPage?: number; | ||||||
|   orderBy?: string; |   orderBy?: string | null; | ||||||
|   orderByNullPosition?: OrderByNullPosition; |   orderByNullPosition?: OrderByNullPosition | null; | ||||||
|   orderDirection?: OrderDirection & string; |   orderDirection?: OrderDirection & string; | ||||||
|   queryFilter?: string; |   queryFilter?: string | null; | ||||||
|   paginationSeed?: string; |   paginationSeed?: string | null; | ||||||
| } | } | ||||||
| export interface RecipeSearchQuery { | export interface RecipeSearchQuery { | ||||||
|   cookbook?: string; |   cookbook?: string | null; | ||||||
|   requireAllCategories?: boolean; |   requireAllCategories?: boolean; | ||||||
|   requireAllTags?: boolean; |   requireAllTags?: boolean; | ||||||
|   requireAllTools?: boolean; |   requireAllTools?: boolean; | ||||||
|   requireAllFoods?: boolean; |   requireAllFoods?: boolean; | ||||||
|   search?: string; |   search?: string | null; | ||||||
| } | } | ||||||
| export interface SuccessResponse { | export interface SuccessResponse { | ||||||
|   message: string; |   message: string; | ||||||
|  | |||||||
| @ -19,8 +19,9 @@ export interface CreateToken { | |||||||
|   token: string; |   token: string; | ||||||
| } | } | ||||||
| export interface CreateUserRegistration { | export interface CreateUserRegistration { | ||||||
|   group?: string; |   group?: string | null; | ||||||
|   groupToken?: string; |   household?: string | null; | ||||||
|  |   groupToken?: string | null; | ||||||
|   email: string; |   email: string; | ||||||
|   username: string; |   username: string; | ||||||
|   fullName: string; |   fullName: string; | ||||||
| @ -45,21 +46,19 @@ export interface ForgotPassword { | |||||||
| export interface GroupBase { | export interface GroupBase { | ||||||
|   name: string; |   name: string; | ||||||
| } | } | ||||||
|  | export interface GroupHouseholdSummary { | ||||||
|  |   id: string; | ||||||
|  |   name: string; | ||||||
|  | } | ||||||
| export interface GroupInDB { | export interface GroupInDB { | ||||||
|   name: string; |   name: string; | ||||||
|   id: string; |   id: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[] | null; | ||||||
|   webhooks?: ReadWebhook[]; |   webhooks?: ReadWebhook[]; | ||||||
|   users?: UserOut[]; |   households?: GroupHouseholdSummary[] | null; | ||||||
|   preferences?: ReadGroupPreferences; |   users?: UserSummary[] | null; | ||||||
| } |   preferences?: ReadGroupPreferences | null; | ||||||
| export interface GroupSummary { |  | ||||||
|   name: string; |  | ||||||
|   id: string; |  | ||||||
|   slug: string; |  | ||||||
|   preferences?: ReadGroupPreferences; |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| export interface CategoryBase { | export interface CategoryBase { | ||||||
|   name: string; |   name: string; | ||||||
| @ -73,43 +72,24 @@ export interface ReadWebhook { | |||||||
|   webhookType?: WebhookType & string; |   webhookType?: WebhookType & string; | ||||||
|   scheduledTime: string; |   scheduledTime: string; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|  |   householdId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
| export interface UserOut { | export interface UserSummary { | ||||||
|   id: string; |   id: string; | ||||||
|   username?: string; |   fullName: 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; |  | ||||||
| } | } | ||||||
| export interface ReadGroupPreferences { | export interface ReadGroupPreferences { | ||||||
|   privateGroup?: boolean; |   privateGroup?: boolean; | ||||||
|   firstDayOfWeek?: number; |  | ||||||
|   recipePublic?: boolean; |  | ||||||
|   recipeShowNutrition?: boolean; |  | ||||||
|   recipeShowAssets?: boolean; |  | ||||||
|   recipeLandscapeView?: boolean; |  | ||||||
|   recipeDisableComments?: boolean; |  | ||||||
|   recipeDisableAmount?: boolean; |  | ||||||
|   groupId: string; |   groupId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
|  | export interface GroupSummary { | ||||||
|  |   name: string; | ||||||
|  |   id: string; | ||||||
|  |   slug: string; | ||||||
|  |   preferences?: ReadGroupPreferences | null; | ||||||
|  | } | ||||||
| export interface LongLiveTokenIn { | export interface LongLiveTokenIn { | ||||||
|   name: string; |   name: string; | ||||||
|   integrationId?: string; |   integrationId?: string; | ||||||
| @ -124,23 +104,32 @@ export interface LongLiveTokenInDB { | |||||||
| } | } | ||||||
| export interface PrivateUser { | export interface PrivateUser { | ||||||
|   id: string; |   id: string; | ||||||
|   username?: string; |   username?: string | null; | ||||||
|   fullName?: string; |   fullName?: string | null; | ||||||
|   email: string; |   email: string; | ||||||
|   authMethod?: AuthMethod & string; |   authMethod?: AuthMethod & string; | ||||||
|   admin?: boolean; |   admin?: boolean; | ||||||
|   group: string; |   group: string; | ||||||
|  |   household: string; | ||||||
|   advanced?: boolean; |   advanced?: boolean; | ||||||
|   canInvite?: boolean; |   canInvite?: boolean; | ||||||
|   canManage?: boolean; |   canManage?: boolean; | ||||||
|   canOrganize?: boolean; |   canOrganize?: boolean; | ||||||
|   groupId: string; |   groupId: string; | ||||||
|   groupSlug: string; |   groupSlug: string; | ||||||
|   tokens?: LongLiveTokenOut[]; |   householdId: string; | ||||||
|  |   householdSlug: string; | ||||||
|  |   tokens?: LongLiveTokenOut[] | null; | ||||||
|   cacheKey: string; |   cacheKey: string; | ||||||
|   password: string; |   password: string; | ||||||
|   loginAttemps?: number; |   loginAttemps?: number; | ||||||
|   lockedAt?: string; |   lockedAt?: string | null; | ||||||
|  | } | ||||||
|  | export interface LongLiveTokenOut { | ||||||
|  |   token: string; | ||||||
|  |   name: string; | ||||||
|  |   id: number; | ||||||
|  |   createdAt?: string | null; | ||||||
| } | } | ||||||
| export interface OIDCRequest { | export interface OIDCRequest { | ||||||
|   id_token: string; |   id_token: string; | ||||||
| @ -168,8 +157,8 @@ export interface Token { | |||||||
|   token_type: string; |   token_type: string; | ||||||
| } | } | ||||||
| export interface TokenData { | export interface TokenData { | ||||||
|   user_id?: string; |   user_id?: string | null; | ||||||
|   username?: string; |   username?: string | null; | ||||||
| } | } | ||||||
| export interface UnlockResults { | export interface UnlockResults { | ||||||
|   unlocked?: number; |   unlocked?: number; | ||||||
| @ -178,7 +167,7 @@ export interface UpdateGroup { | |||||||
|   name: string; |   name: string; | ||||||
|   id: string; |   id: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|   categories?: CategoryBase[]; |   categories?: CategoryBase[] | null; | ||||||
|   webhooks?: CreateWebhook[]; |   webhooks?: CreateWebhook[]; | ||||||
| } | } | ||||||
| export interface CreateWebhook { | export interface CreateWebhook { | ||||||
| @ -189,53 +178,75 @@ export interface CreateWebhook { | |||||||
|   scheduledTime: string; |   scheduledTime: string; | ||||||
| } | } | ||||||
| export interface UserBase { | export interface UserBase { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   username?: string; |   username?: string | null; | ||||||
|   fullName?: string; |   fullName?: string | null; | ||||||
|   email: string; |   email: string; | ||||||
|   authMethod?: AuthMethod & string; |   authMethod?: AuthMethod & string; | ||||||
|   admin?: boolean; |   admin?: boolean; | ||||||
|   group?: string; |   group?: string | null; | ||||||
|  |   household?: string | null; | ||||||
|   advanced?: boolean; |   advanced?: boolean; | ||||||
|   canInvite?: boolean; |   canInvite?: boolean; | ||||||
|   canManage?: boolean; |   canManage?: boolean; | ||||||
|   canOrganize?: boolean; |   canOrganize?: boolean; | ||||||
| } | } | ||||||
| export interface UserIn { | export interface UserIn { | ||||||
|   id?: string; |   id?: string | null; | ||||||
|   username?: string; |   username?: string | null; | ||||||
|   fullName?: string; |   fullName?: string | null; | ||||||
|   email: string; |   email: string; | ||||||
|   authMethod?: AuthMethod & string; |   authMethod?: AuthMethod & string; | ||||||
|   admin?: boolean; |   admin?: boolean; | ||||||
|   group?: string; |   group?: string | null; | ||||||
|  |   household?: string | null; | ||||||
|   advanced?: boolean; |   advanced?: boolean; | ||||||
|   canInvite?: boolean; |   canInvite?: boolean; | ||||||
|   canManage?: boolean; |   canManage?: boolean; | ||||||
|   canOrganize?: boolean; |   canOrganize?: boolean; | ||||||
|   password: string; |   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 { | export interface UserRatingCreate { | ||||||
|   recipeId: string; |   recipeId: string; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   isFavorite?: boolean; |   isFavorite?: boolean; | ||||||
|   userId: string; |   userId: string; | ||||||
| } | } | ||||||
| export interface UserRatingOut { | export interface UserRatingOut { | ||||||
|   recipeId: string; |   recipeId: string; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   isFavorite?: boolean; |   isFavorite?: boolean; | ||||||
|   userId: string; |   userId: string; | ||||||
|   id: string; |   id: string; | ||||||
| } | } | ||||||
| export interface UserRatingSummary { | export interface UserRatingSummary { | ||||||
|   recipeId: string; |   recipeId: string; | ||||||
|   rating?: number; |   rating?: number | null; | ||||||
|   isFavorite?: boolean; |   isFavorite?: boolean; | ||||||
| } | } | ||||||
| export interface UserSummary { | export interface UserRatingUpdate { | ||||||
|   id: string; |   rating?: number | null; | ||||||
|   fullName: string; |   isFavorite?: boolean | null; | ||||||
| } | } | ||||||
| export interface ValidateResetToken { | export interface ValidateResetToken { | ||||||
|   token: string; |   token: string; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { BaseAPI } from "../base/base-clients"; | 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 { ForgotPassword } from "~/lib/api/types/user"; | ||||||
| import { EmailTest } from "~/lib/api/types/admin"; | import { EmailTest } from "~/lib/api/types/admin"; | ||||||
| 
 | 
 | ||||||
| @ -7,7 +7,7 @@ const routes = { | |||||||
|   base: "/api/admin/email", |   base: "/api/admin/email", | ||||||
|   forgotPassword: "/api/users/forgot-password", |   forgotPassword: "/api/users/forgot-password", | ||||||
| 
 | 
 | ||||||
|   invitation: "/api/groups/invitations/email", |   invitation: "/api/households/invitations/email", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class EmailAPI extends BaseAPI { | export class EmailAPI extends BaseAPI { | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/ | |||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   cookbooks: `${prefix}/groups/cookbooks`, |   cookbooks: `${prefix}/households/cookbooks`, | ||||||
|   cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`, |   cookbooksId: (id: number) => `${prefix}/households/cookbooks/${id}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> { | export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> { | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| import { BaseCRUDAPI } from "../base/base-clients"; | 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 prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   eventNotifier: `${prefix}/groups/events/notifications`, |   eventNotifier: `${prefix}/households/events/notifications`, | ||||||
|   eventNotifierId: (id: string | number) => `${prefix}/groups/events/notifications/${id}`, |   eventNotifierId: (id: string | number) => `${prefix}/households/events/notifications/${id}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class GroupEventNotifierApi extends BaseCRUDAPI< | export class GroupEventNotifierApi extends BaseCRUDAPI< | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan"; | |||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   rule: `${prefix}/groups/mealplans/rules`, |   rule: `${prefix}/households/mealplans/rules`, | ||||||
|   ruleId: (id: string | number) => `${prefix}/groups/mealplans/rules/${id}`, |   ruleId: (id: string | number) => `${prefix}/households/mealplans/rules/${id}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesCreate, PlanRulesOut> { | export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesCreate, PlanRulesOut> { | ||||||
|  | |||||||
| @ -4,9 +4,9 @@ import { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } fr | |||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   mealplan: `${prefix}/groups/mealplans`, |   mealplan: `${prefix}/households/mealplans`, | ||||||
|   random: `${prefix}/groups/mealplans/random`, |   random: `${prefix}/households/mealplans/random`, | ||||||
|   mealplanId: (id: string | number) => `${prefix}/groups/mealplans/${id}`, |   mealplanId: (id: string | number) => `${prefix}/households/mealplans/${id}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class MealPlanAPI extends BaseCRUDAPI<CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry> { | export class MealPlanAPI extends BaseCRUDAPI<CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry> { | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| import { BaseCRUDAPI } from "../base/base-clients"; | 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 prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|     groupRecipeActions: `${prefix}/groups/recipe-actions`, |     groupRecipeActions: `${prefix}/households/recipe-actions`, | ||||||
|     groupRecipeActionsId: (id: string | number) => `${prefix}/groups/recipe-actions/${id}`, |     groupRecipeActionsId: (id: string | number) => `${prefix}/households/recipe-actions/${id}`, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> { |   export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> { | ||||||
|  | |||||||
| @ -9,20 +9,20 @@ import { | |||||||
|   ShoppingListMultiPurposeLabelUpdate, |   ShoppingListMultiPurposeLabelUpdate, | ||||||
|   ShoppingListOut, |   ShoppingListOut, | ||||||
|   ShoppingListUpdate, |   ShoppingListUpdate, | ||||||
| } from "~/lib/api/types/group"; | } from "~/lib/api/types/household"; | ||||||
| 
 | 
 | ||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   shoppingLists: `${prefix}/groups/shopping/lists`, |   shoppingLists: `${prefix}/households/shopping/lists`, | ||||||
|   shoppingListsId: (id: string) => `${prefix}/groups/shopping/lists/${id}`, |   shoppingListsId: (id: string) => `${prefix}/households/shopping/lists/${id}`, | ||||||
|   shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}`, |   shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}`, | ||||||
|   shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}/delete`, |   shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}/delete`, | ||||||
|   shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/groups/shopping/lists/${id}/label-settings`, |   shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/households/shopping/lists/${id}/label-settings`, | ||||||
| 
 | 
 | ||||||
|   shoppingListItems: `${prefix}/groups/shopping/items`, |   shoppingListItems: `${prefix}/households/shopping/items`, | ||||||
|   shoppingListItemsCreateBulk: `${prefix}/groups/shopping/items/create-bulk`, |   shoppingListItemsCreateBulk: `${prefix}/households/shopping/items/create-bulk`, | ||||||
|   shoppingListItemsId: (id: string) => `${prefix}/groups/shopping/items/${id}`, |   shoppingListItemsId: (id: string) => `${prefix}/households/shopping/items/${id}`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class ShoppingListsApi extends BaseCRUDAPI<ShoppingListCreate, ShoppingListOut, ShoppingListUpdate> { | export class ShoppingListsApi extends BaseCRUDAPI<ShoppingListCreate, ShoppingListOut, ShoppingListUpdate> { | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| import { BaseCRUDAPI } from "../base/base-clients"; | 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 prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   webhooks: `${prefix}/groups/webhooks`, |   webhooks: `${prefix}/households/webhooks`, | ||||||
|   webhooksId: (id: string | number) => `${prefix}/groups/webhooks/${id}`, |   webhooksId: (id: string | number) => `${prefix}/households/webhooks/${id}`, | ||||||
|   webhooksIdTest: (id: string | number) => `${prefix}/groups/webhooks/${id}/test`, |   webhooksIdTest: (id: string | number) => `${prefix}/households/webhooks/${id}/test`, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class WebhooksAPI extends BaseCRUDAPI<CreateWebhook, ReadWebhook> { | export class WebhooksAPI extends BaseCRUDAPI<CreateWebhook, ReadWebhook> { | ||||||
|  | |||||||
| @ -1,13 +1,10 @@ | |||||||
| import { BaseCRUDAPI } from "../base/base-clients"; | 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 { | import { | ||||||
|   CreateInviteToken, |  | ||||||
|   GroupAdminUpdate, |   GroupAdminUpdate, | ||||||
|   GroupStatistics, |  | ||||||
|   GroupStorage, |   GroupStorage, | ||||||
|   ReadGroupPreferences, |   ReadGroupPreferences, | ||||||
|   ReadInviteToken, |  | ||||||
|   SetPermissions, |  | ||||||
|   UpdateGroupPreferences, |   UpdateGroupPreferences, | ||||||
| } from "~/lib/api/types/group"; | } from "~/lib/api/types/group"; | ||||||
| 
 | 
 | ||||||
| @ -16,16 +13,14 @@ const prefix = "/api"; | |||||||
| const routes = { | const routes = { | ||||||
|   groups: `${prefix}/admin/groups`, |   groups: `${prefix}/admin/groups`, | ||||||
|   groupsSelf: `${prefix}/groups/self`, |   groupsSelf: `${prefix}/groups/self`, | ||||||
|   categories: `${prefix}/groups/categories`, |  | ||||||
|   members: `${prefix}/groups/members`, |  | ||||||
|   permissions: `${prefix}/groups/permissions`, |  | ||||||
| 
 |  | ||||||
|   preferences: `${prefix}/groups/preferences`, |   preferences: `${prefix}/groups/preferences`, | ||||||
|   statistics: `${prefix}/groups/statistics`, |  | ||||||
|   storage: `${prefix}/groups/storage`, |   storage: `${prefix}/groups/storage`, | ||||||
| 
 |   households: `${prefix}/households`, | ||||||
|   invitation: `${prefix}/groups/invitations`, |   membersHouseholdId: (householdId: string | number | null) => { | ||||||
| 
 |     return householdId ? | ||||||
|  |       `${prefix}/households/members?householdId=${householdId}` : | ||||||
|  |       `${prefix}/groups/members`; | ||||||
|  |   }, | ||||||
|   groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`, |   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); |     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() { |   async getPreferences() { | ||||||
|     return await this.requests.get<ReadGroupPreferences>(routes.preferences); |     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); |     return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async createInvitation(payload: CreateInviteToken) { |   async fetchMembers(householdId: string | number | null = null) { | ||||||
|     return await this.requests.post<ReadInviteToken>(routes.invitation, payload); |     return await this.requests.get<UserSummary[]>(routes.membersHouseholdId(householdId)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async fetchMembers() { |   async fetchHouseholds() { | ||||||
|     return await this.requests.get<UserOut[]>(routes.members); |     return await this.requests.get<HouseholdSummary[]>(routes.households); | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   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 storage() { |   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 { BaseCRUDAPI } from "../base/base-clients"; | ||||||
| import { QueryValue, route } from "~/lib/api/base/route"; |  | ||||||
| import { PaginationData } from "~/lib/api/types/non-generated"; |  | ||||||
| import { | import { | ||||||
|   ChangePassword, |   ChangePassword, | ||||||
|   DeleteTokenResponse, |   DeleteTokenResponse, | ||||||
| @ -12,7 +10,6 @@ import { | |||||||
|   UserOut, |   UserOut, | ||||||
|   UserRatingOut, |   UserRatingOut, | ||||||
|   UserRatingSummary, |   UserRatingSummary, | ||||||
|   UserSummary, |  | ||||||
| } from "~/lib/api/types/user"; | } from "~/lib/api/types/user"; | ||||||
| 
 | 
 | ||||||
| export interface UserRatingsSummaries { | export interface UserRatingsSummaries { | ||||||
| @ -26,7 +23,6 @@ export interface UserRatingsOut { | |||||||
| const prefix = "/api"; | const prefix = "/api"; | ||||||
| 
 | 
 | ||||||
| const routes = { | const routes = { | ||||||
|   groupUsers: `${prefix}/users/group-users`, |  | ||||||
|   usersSelf: `${prefix}/users/self`, |   usersSelf: `${prefix}/users/self`, | ||||||
|   ratingsSelf: `${prefix}/users/self/ratings`, |   ratingsSelf: `${prefix}/users/self/ratings`, | ||||||
|   passwordReset: `${prefix}/users/reset-password`, |   passwordReset: `${prefix}/users/reset-password`, | ||||||
| @ -51,10 +47,6 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> { | |||||||
|   baseRoute: string = routes.users; |   baseRoute: string = routes.users; | ||||||
|   itemRoute = (itemid: string) => routes.usersId(itemid); |   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) { |   async addFavorite(id: string, slug: string) { | ||||||
|     return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {}); |     return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {}); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import { | |||||||
|   mdiAccountGroup, |   mdiAccountGroup, | ||||||
|   mdiSlotMachine, |   mdiSlotMachine, | ||||||
|   mdiHome, |   mdiHome, | ||||||
|  |   mdiHomeAccount, | ||||||
|   mdiMagnify, |   mdiMagnify, | ||||||
|   mdiPotSteamOutline, |   mdiPotSteamOutline, | ||||||
|   mdiTranslate, |   mdiTranslate, | ||||||
| @ -226,6 +227,7 @@ export const icons = { | |||||||
|   heart: mdiHeart, |   heart: mdiHeart, | ||||||
|   heartOutline: mdiHeartOutline, |   heartOutline: mdiHeartOutline, | ||||||
|   home: mdiHome, |   home: mdiHome, | ||||||
|  |   household: mdiHomeAccount, | ||||||
|   import: mdiImport, |   import: mdiImport, | ||||||
|   information: mdiInformation, |   information: mdiInformation, | ||||||
|   informationVariant: mdiInformationVariant, |   informationVariant: mdiInformationVariant, | ||||||
|  | |||||||
| @ -477,7 +477,7 @@ export default { | |||||||
|           "name": "Meal Planner", |           "name": "Meal Planner", | ||||||
|           "short_name": "Meal Planner", |           "short_name": "Meal Planner", | ||||||
|           "description": "Open the meal planner", |           "description": "Open the meal planner", | ||||||
|           "url": "/group/mealplan/planner/view", |           "url": "/household/mealplan/planner/view", | ||||||
|           "icons": [ |           "icons": [ | ||||||
|             { |             { | ||||||
|               "src": "/icons/mdiCalendarMultiselect-192x192.png", |               "src": "/icons/mdiCalendarMultiselect-192x192.png", | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ | |||||||
|           <i18n path="settings.backup.experimental-description" /> |           <i18n path="settings.backup.experimental-description" /> | ||||||
|           </v-card-text> |           </v-card-text> | ||||||
|         </BaseCardSectionTitle> |         </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> |         <BaseButton class="mr-2" @click="createBackup"> {{ $t("settings.backup.create-heading") }} </BaseButton> | ||||||
|         <AppButtonUpload |         <AppButtonUpload | ||||||
|                 :text-btn="false" |                 :text-btn="false" | ||||||
|  | |||||||
| @ -1,15 +1,14 @@ | |||||||
| // TODO: Edit Group |  | ||||||
| <template> | <template> | ||||||
|   <v-container fluid> |   <v-container fluid> | ||||||
|     <BaseDialog |     <BaseDialog | ||||||
|       v-model="createDialog" |       v-model="createDialog" | ||||||
|       :title="$t('group.create-group')" |       :title="$t('group.create-group')" | ||||||
|       :icon="$globals.icons.group" |       :icon="$globals.icons.group" | ||||||
|       @submit="createGroup(createUserForm.data)" |       @submit="createGroup(createGroupForm.data)" | ||||||
|     > |     > | ||||||
|       <template #activator> </template> |       <template #activator> </template> | ||||||
|       <v-card-text> |       <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> |       </v-card-text> | ||||||
|     </BaseDialog> |     </BaseDialog> | ||||||
| 
 | 
 | ||||||
| @ -27,7 +26,7 @@ | |||||||
| 
 | 
 | ||||||
|     <BaseCardSectionTitle :title="$tc('group.group-management')"> </BaseCardSectionTitle> |     <BaseCardSectionTitle :title="$tc('group.group-management')"> </BaseCardSectionTitle> | ||||||
|     <section> |     <section> | ||||||
|       <v-toolbar flat color="background" class="justify-between"> |       <v-toolbar flat color="transparent" class="justify-between"> | ||||||
|         <BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton> |         <BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton> | ||||||
|       </v-toolbar> |       </v-toolbar> | ||||||
| 
 | 
 | ||||||
| @ -41,15 +40,15 @@ | |||||||
|         :search="search" |         :search="search" | ||||||
|         @click:row="handleRowClick" |         @click:row="handleRowClick" | ||||||
|       > |       > | ||||||
|  |         <template #item.households="{ item }"> | ||||||
|  |           {{ item.households.length }} | ||||||
|  |         </template> | ||||||
|         <template #item.users="{ item }"> |         <template #item.users="{ item }"> | ||||||
|           {{ item.users.length }} |           {{ item.users.length }} | ||||||
|         </template> |         </template> | ||||||
|         <template #item.webhookEnable="{ item }"> |  | ||||||
|           {{ item.webhooks.length > 0 ? $t("general.yes") : $t("general.no") }} |  | ||||||
|         </template> |  | ||||||
|         <template #item.actions="{ item }"> |         <template #item.actions="{ item }"> | ||||||
|           <v-btn |           <v-btn | ||||||
|             :disabled="item && item.users.length > 0" |             :disabled="item && (item.households.length > 0 || item.users.length > 0)" | ||||||
|             class="mr-1" |             class="mr-1" | ||||||
|             icon |             icon | ||||||
|             color="error" |             color="error" | ||||||
| @ -94,12 +93,12 @@ export default defineComponent({ | |||||||
|           value: "id", |           value: "id", | ||||||
|         }, |         }, | ||||||
|         { text: i18n.t("general.name"), value: "name" }, |         { 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.total-users"), value: "users" }, | ||||||
|         { text: i18n.t("user.webhooks-enabled"), value: "webhookEnable" }, |  | ||||||
|         { text: i18n.t("general.delete"), value: "actions" }, |         { text: i18n.t("general.delete"), value: "actions" }, | ||||||
|       ], |       ], | ||||||
|       updateMode: false, |       updateMode: false, | ||||||
|       createUserForm: { |       createGroupForm: { | ||||||
|         items: [ |         items: [ | ||||||
|           { |           { | ||||||
|             label: i18n.t("group.group-name"), |             label: i18n.t("group.group-name"), | ||||||
| @ -116,7 +115,7 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
|     function openDialog() { |     function openDialog() { | ||||||
|       state.createDialog = true; |       state.createDialog = true; | ||||||
|       state.createUserForm.data.name = ""; |       state.createGroupForm.data.name = ""; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const router = useRouter(); |     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"> |           <div class="d-flex"> | ||||||
|             <p> {{ $t("user.user-id-with-value", {id: user.id} ) }}</p> |             <p> {{ $t("user.user-id-with-value", {id: user.id} ) }}</p> | ||||||
|           </div> |           </div> | ||||||
|  |           <!-- This is disabled since we can't properly handle changing the user's group in most scenarios --> | ||||||
|           <v-select |           <v-select | ||||||
|             v-if="groups" |             v-if="groups" | ||||||
|             v-model="user.group" |             v-model="user.group" | ||||||
|  |             disabled | ||||||
|             :items="groups" |             :items="groups" | ||||||
|             rounded |             rounded | ||||||
|             class="rounded-lg" |             class="rounded-lg" | ||||||
| @ -26,7 +28,20 @@ | |||||||
|             filled |             filled | ||||||
|             :label="$tc('group.user-group')" |             :label="$tc('group.user-group')" | ||||||
|             :rules="[validators.required]" |             :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"> |           <div class="d-flex py-2 pr-2"> | ||||||
|             <BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset"> |             <BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset"> | ||||||
|               {{ $t("user.generate-password-reset-link") }} |               {{ $t("user.generate-password-reset-link") }} | ||||||
| @ -65,6 +80,7 @@ | |||||||
| import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; | import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { useAdminApi, useUserApi } from "~/composables/api"; | import { useAdminApi, useUserApi } from "~/composables/api"; | ||||||
| import { useGroups } from "~/composables/use-groups"; | import { useGroups } from "~/composables/use-groups"; | ||||||
|  | import { useHouseholds } from "~/composables/use-households"; | ||||||
| import { alert } from "~/composables/use-toast"; | import { alert } from "~/composables/use-toast"; | ||||||
| import { useUserForm } from "~/composables/use-users"; | import { useUserForm } from "~/composables/use-users"; | ||||||
| import { validators } from "~/composables/use-validators"; | import { validators } from "~/composables/use-validators"; | ||||||
| @ -76,6 +92,7 @@ export default defineComponent({ | |||||||
|   setup() { |   setup() { | ||||||
|     const { userForm } = useUserForm(); |     const { userForm } = useUserForm(); | ||||||
|     const { groups } = useGroups(); |     const { groups } = useGroups(); | ||||||
|  |     const { useHouseholdsInGroup } = useHouseholds(); | ||||||
|     const { i18n } = useContext(); |     const { i18n } = useContext(); | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
| 
 | 
 | ||||||
| @ -89,6 +106,8 @@ export default defineComponent({ | |||||||
|     const adminApi = useAdminApi(); |     const adminApi = useAdminApi(); | ||||||
| 
 | 
 | ||||||
|     const user = ref<UserOut | null>(null); |     const user = ref<UserOut | null>(null); | ||||||
|  |     const households = useHouseholdsInGroup(computed(() => user.value?.groupId || "")); | ||||||
|  | 
 | ||||||
|     const disabledFields = computed(() => { |     const disabledFields = computed(() => { | ||||||
|       return user.value?.authMethod !== "Mealie" ? ["admin"] : []; |       return user.value?.authMethod !== "Mealie" ? ["admin"] : []; | ||||||
|     }) |     }) | ||||||
| @ -154,6 +173,7 @@ export default defineComponent({ | |||||||
|       refNewUserForm, |       refNewUserForm, | ||||||
|       handleSubmit, |       handleSubmit, | ||||||
|       groups, |       groups, | ||||||
|  |       households, | ||||||
|       validators, |       validators, | ||||||
|       handlePasswordReset, |       handlePasswordReset, | ||||||
|       resetUrl, |       resetUrl, | ||||||
|  | |||||||
| @ -12,17 +12,30 @@ | |||||||
|         <v-card-text> |         <v-card-text> | ||||||
|           <v-select |           <v-select | ||||||
|             v-if="groups" |             v-if="groups" | ||||||
|             v-model="newUserData.group" |             v-model="selectedGroupId" | ||||||
|             :items="groups" |             :items="groups" | ||||||
|             rounded |             rounded | ||||||
|             class="rounded-lg" |             class="rounded-lg" | ||||||
|             item-text="name" |             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" |             item-value="name" | ||||||
|             :return-object="false" |             :return-object="false" | ||||||
|             filled |             filled | ||||||
|             :label="$t('group.user-group')" |             :label="$t('household.user-household')" | ||||||
|             :rules="[validators.required]" |             :rules="[validators.required]" | ||||||
|           ></v-select> |           /> | ||||||
|           <AutoForm v-model="newUserData" :items="userForm" /> |           <AutoForm v-model="newUserData" :items="userForm" /> | ||||||
|         </v-card-text> |         </v-card-text> | ||||||
|       </v-card> |       </v-card> | ||||||
| @ -34,9 +47,10 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <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 { useAdminApi } from "~/composables/api"; | ||||||
| import { useGroups } from "~/composables/use-groups"; | import { useGroups } from "~/composables/use-groups"; | ||||||
|  | import { useHouseholds } from "~/composables/use-households"; | ||||||
| import { useUserForm } from "~/composables/use-users"; | import { useUserForm } from "~/composables/use-users"; | ||||||
| import { validators } from "~/composables/use-validators"; | import { validators } from "~/composables/use-validators"; | ||||||
| import { VForm } from "~/types/vuetify"; | import { VForm } from "~/types/vuetify"; | ||||||
| @ -46,6 +60,7 @@ export default defineComponent({ | |||||||
|   setup() { |   setup() { | ||||||
|     const { userForm } = useUserForm(); |     const { userForm } = useUserForm(); | ||||||
|     const { groups } = useGroups(); |     const { groups } = useGroups(); | ||||||
|  |     const { useHouseholdsInGroup } = useHouseholds(); | ||||||
|     const router = useRouter(); |     const router = useRouter(); | ||||||
| 
 | 
 | ||||||
|     // ============================================== |     // ============================================== | ||||||
| @ -55,13 +70,20 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
|     const adminApi = useAdminApi(); |     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({ |     const state = reactive({ | ||||||
|       newUserData: { |       newUserData: { | ||||||
|         username: "", |         username: "", | ||||||
|         fullName: "", |         fullName: "", | ||||||
|         email: "", |         email: "", | ||||||
|         admin: false, |         admin: false, | ||||||
|         group: "", |         group: selectedGroup.value?.name || "", | ||||||
|  |         household: "", | ||||||
|         advanced: false, |         advanced: false, | ||||||
|         canInvite: false, |         canInvite: false, | ||||||
|         canManage: false, |         canManage: false, | ||||||
| @ -70,6 +92,10 @@ export default defineComponent({ | |||||||
|         authMethod: "Mealie", |         authMethod: "Mealie", | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|  |     watch(selectedGroup, (newGroup) => { | ||||||
|  |       state.newUserData.group = newGroup?.name || ""; | ||||||
|  |       state.newUserData.household = ""; | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     async function handleSubmit() { |     async function handleSubmit() { | ||||||
|       if (!refNewUserForm.value?.validate()) return; |       if (!refNewUserForm.value?.validate()) return; | ||||||
| @ -87,6 +113,8 @@ export default defineComponent({ | |||||||
|       refNewUserForm, |       refNewUserForm, | ||||||
|       handleSubmit, |       handleSubmit, | ||||||
|       groups, |       groups, | ||||||
|  |       selectedGroupId, | ||||||
|  |       households, | ||||||
|       validators, |       validators, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ | |||||||
| 
 | 
 | ||||||
|     <BaseCardSectionTitle :title="$tc('user.user-management')"> </BaseCardSectionTitle> |     <BaseCardSectionTitle :title="$tc('user.user-management')"> </BaseCardSectionTitle> | ||||||
|     <section> |     <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"> |         <BaseButton to="/admin/manage/users/create" class="mr-2"> | ||||||
|           {{ $t("general.create") }} |           {{ $t("general.create") }} | ||||||
|         </BaseButton> |         </BaseButton> | ||||||
| @ -129,6 +129,7 @@ export default defineComponent({ | |||||||
|       { text: i18n.t("user.full-name"), value: "fullName" }, |       { text: i18n.t("user.full-name"), value: "fullName" }, | ||||||
|       { text: i18n.t("user.email"), value: "email" }, |       { text: i18n.t("user.email"), value: "email" }, | ||||||
|       { text: i18n.t("group.group"), value: "group" }, |       { 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.auth-method"), value: "authMethod" }, | ||||||
|       { text: i18n.t("user.admin"), value: "admin" }, |       { text: i18n.t("user.admin"), value: "admin" }, | ||||||
|       { text: i18n.t("general.delete"), value: "actions", sortable: false, align: "center" }, |       { text: i18n.t("general.delete"), value: "actions", sortable: false, align: "center" }, | ||||||
|  | |||||||
| @ -312,7 +312,6 @@ export default defineComponent({ | |||||||
|       const preferences = { |       const preferences = { | ||||||
|         ...data.preferences, |         ...data.preferences, | ||||||
|         privateGroup: !commonSettings.value.makeGroupRecipesPublic, |         privateGroup: !commonSettings.value.makeGroupRecipesPublic, | ||||||
|         recipePublic: commonSettings.value.makeGroupRecipesPublic, |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const payload = { |       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() { |     async function seedFoods() { | ||||||
|       const { response } = await api.seeders.foods({ locale: locale.value }) |       const { response } = await api.seeders.foods({ locale: locale.value }) | ||||||
|       if (!response || response.status !== 200) { |       if (!response || response.status !== 200) { | ||||||
| @ -365,6 +390,7 @@ export default defineComponent({ | |||||||
|     async function submitCommonSettings() { |     async function submitCommonSettings() { | ||||||
|       const tasks = [ |       const tasks = [ | ||||||
|         updateGroup(), |         updateGroup(), | ||||||
|  |         updateHousehold(), | ||||||
|         seedData(), |         seedData(), | ||||||
|       ] |       ] | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -367,6 +367,11 @@ export default defineComponent({ | |||||||
|                             icon: $globals.icons.group, |                             icon: $globals.icons.group, | ||||||
|                             value: data.defaultGroup, |                             value: data.defaultGroup, | ||||||
|                         }, |                         }, | ||||||
|  |                         { | ||||||
|  |                             name: i18n.t("about.default-household"), | ||||||
|  |                             icon: $globals.icons.household, | ||||||
|  |                             value: data.defaultHousehold, | ||||||
|  |                         }, | ||||||
|                         { |                         { | ||||||
|                             slot: "recipe-scraper", |                             slot: "recipe-scraper", | ||||||
|                             name: i18n.t("settings.recipe-scraper-version"), |                             name: i18n.t("settings.recipe-scraper-version"), | ||||||
|  | |||||||
| @ -24,17 +24,18 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
|     const groupName = ref<string>(""); |     const groupName = ref<string>(""); | ||||||
|     const queryFilter = ref<string>(""); |     const queryFilter = ref<string>(""); | ||||||
|     async function fetchGroup() { |     async function fetchHousehold() { | ||||||
|       const { data } = await api.groups.getCurrentUserGroup(); |       const { data } = await api.households.getCurrentUserHousehold(); | ||||||
|       if (data) { |       if (data) { | ||||||
|         queryFilter.value = `recipe.group_id="${data.id}"`; |         // TODO: once users are able to fetch other households' recipes, remove the household filter | ||||||
|         groupName.value = data.name; |         queryFilter.value = `recipe.group_id="${data.groupId}" AND recipe.household_id="${data.id}"`; | ||||||
|  |         groupName.value = data.group; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       ready.value = true; |       ready.value = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fetchGroup(); |     fetchHousehold(); | ||||||
|     return { |     return { | ||||||
|       groupName, |       groupName, | ||||||
|       queryFilter, |       queryFilter, | ||||||
|  | |||||||
| @ -121,7 +121,7 @@ | |||||||
| import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api"; | import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { validators } from "~/composables/use-validators"; | import { validators } from "~/composables/use-validators"; | ||||||
| import { useGroupRecipeActions, useGroupRecipeActionData } from "~/composables/use-group-recipe-actions"; | 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({ | export default defineComponent({ | ||||||
|   setup() { |   setup() { | ||||||
|  | |||||||
| @ -25,139 +25,22 @@ | |||||||
|           <DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" /> |           <DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" /> | ||||||
|         </div> |         </div> | ||||||
|       </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> |     </section> | ||||||
|   </v-container> |   </v-container> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | import { defineComponent } from "@nuxtjs/composition-api"; | ||||||
| import { useGroupSelf } from "~/composables/use-groups"; | import { useGroupSelf } from "~/composables/use-groups"; | ||||||
| import { ReadGroupPreferences } from "~/lib/api/types/group"; |  | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   middleware: ["auth", "can-manage-only"], |   middleware: ["auth", "can-manage-only"], | ||||||
|   setup() { |   setup() { | ||||||
|     const { group, actions: groupActions } = useGroupSelf(); |     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 { |     return { | ||||||
|       group, |       group, | ||||||
|       groupActions, |       groupActions, | ||||||
|       allDays, |  | ||||||
|       preferencesEditor, |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   head() { |   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"> |     <div class="d-flex flex-wrap align-center justify-space-between mb-2"> | ||||||
|       <v-tabs style="width: fit-content;"> |       <v-tabs style="width: fit-content;"> | ||||||
|         <v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab> |         <v-tab :to="`/household/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/edit`">{{ $t('general.edit') }}</v-tab> | ||||||
|       </v-tabs> |       </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> | ||||||
| 
 | 
 | ||||||
|     <div> |     <div> | ||||||
| @ -56,7 +56,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent, ref, useRoute, useRouter, watch } from "@nuxtjs/composition-api"; | import { computed, defineComponent, ref, useRoute, useRouter, watch } from "@nuxtjs/composition-api"; | ||||||
| import { isSameDay, addDays, parseISO } from "date-fns"; | 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 { useMealplans } from "~/composables/use-group-mealplan"; | ||||||
| import { useUserMealPlanPreferences } from "~/composables/use-users/preferences"; | import { useUserMealPlanPreferences } from "~/composables/use-users/preferences"; | ||||||
| 
 | 
 | ||||||
| @ -65,7 +65,7 @@ export default defineComponent({ | |||||||
|   setup() { |   setup() { | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const router = useRouter(); |     const router = useRouter(); | ||||||
|     const { group } = useGroupSelf(); |     const { household } = useHouseholdSelf(); | ||||||
| 
 | 
 | ||||||
|     const mealPlanPreferences = useUserMealPlanPreferences(); |     const mealPlanPreferences = useUserMealPlanPreferences(); | ||||||
|     const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7); |     const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7); | ||||||
| @ -74,8 +74,8 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Force to /view if current route is /planner |     // Force to /view if current route is /planner | ||||||
|     if (route.value.path === "/group/mealplan/planner") { |     if (route.value.path === "/household/mealplan/planner") { | ||||||
|       router.push("/group/mealplan/planner/view"); |       router.push("/household/mealplan/planner/view"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function fmtYYYYMMDD(date: Date) { |     function fmtYYYYMMDD(date: Date) { | ||||||
| @ -95,7 +95,7 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const firstDayOfWeek = computed(() => { |     const firstDayOfWeek = computed(() => { | ||||||
|       return group.value?.preferences?.firstDayOfWeek || 0; |       return household.value?.preferences?.firstDayOfWeek || 0; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const weekRange = computed(() => { |     const weekRange = computed(() => { | ||||||
| @ -229,7 +229,7 @@ import { useMealplans, usePlanTypeOptions, getEntryTypeText } from "~/composable | |||||||
| import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue"; | import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue"; | ||||||
| import { PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan"; | import { PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan"; | ||||||
| import { useUserApi } from "~/composables/api"; | 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"; | import { useRecipeSearch } from "~/composables/recipes/use-recipe-search"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| @ -249,7 +249,7 @@ export default defineComponent({ | |||||||
|   }, |   }, | ||||||
|   setup(props) { |   setup(props) { | ||||||
|     const api = useUserApi(); |     const api = useUserApi(); | ||||||
|     const { group } = useGroupSelf(); |     const { household } = useHouseholdSelf(); | ||||||
| 
 | 
 | ||||||
|     const state = ref({ |     const state = ref({ | ||||||
|       dialog: false, |       dialog: false, | ||||||
| @ -257,7 +257,7 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const firstDayOfWeek = computed(() => { |     const firstDayOfWeek = computed(() => { | ||||||
|       return group.value?.preferences?.firstDayOfWeek || 0; |       return household.value?.preferences?.firstDayOfWeek || 0; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function onMoveCallback(evt: SortableEvent) { |     function onMoveCallback(evt: SortableEvent) { | ||||||
| @ -308,7 +308,7 @@ export default defineComponent({ | |||||||
|       entryType: "dinner" as PlanEntryType, |       entryType: "dinner" as PlanEntryType, | ||||||
|       existing: false, |       existing: false, | ||||||
|       id: 0, |       id: 0, | ||||||
|       groupId: "" |       groupId: "", | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function openDialog(date: Date) { |     function openDialog(date: Date) { | ||||||
| @ -54,7 +54,7 @@ | |||||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||||
| import { MealsByDate } from "./types"; | import { MealsByDate } from "./types"; | ||||||
| import { ReadPlanEntry } from "~/lib/api/types/meal-plan"; | 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 RecipeCardMobile from "~/components/Domain/Recipe/RecipeCardMobile.vue"; | ||||||
| import { RecipeSummary } from "~/lib/api/types/recipe"; | import { RecipeSummary } from "~/lib/api/types/recipe"; | ||||||
| 
 | 
 | ||||||
| @ -89,7 +89,7 @@ | |||||||
| import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api"; | import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan"; | 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 { useAsyncKey } from "~/composables/use-utils"; | ||||||
| import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue"; | import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue"; | ||||||
| 
 | 
 | ||||||
| @ -97,7 +97,7 @@ export default defineComponent({ | |||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     async function refreshMembers() { |     async function refreshMembers() { | ||||||
|       const { data } = await api.groups.fetchMembers(); |       const { data } = await api.households.fetchMembers(); | ||||||
|       if (data) { |       if (data) { | ||||||
|         members.value = data; |         members.value = data; | ||||||
|       } |       } | ||||||
| @ -111,7 +111,7 @@ export default defineComponent({ | |||||||
|         canOrganize: user.canOrganize, |         canOrganize: user.canOrganize, | ||||||
|       }; |       }; | ||||||
| 
 | 
 | ||||||
|       await api.groups.setMemberPermissions(payload); |       await api.households.setMemberPermissions(payload); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     onMounted(async () => { |     onMounted(async () => { | ||||||
| @ -109,7 +109,7 @@ | |||||||
| import { defineComponent, useAsync, reactive, useContext, toRefs } from "@nuxtjs/composition-api"; | import { defineComponent, useAsync, reactive, useContext, toRefs } from "@nuxtjs/composition-api"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import { useAsyncKey } from "~/composables/use-utils"; | import { useAsyncKey } from "~/composables/use-utils"; | ||||||
| import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/group"; | import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/household"; | ||||||
| 
 | 
 | ||||||
| interface OptionKey { | interface OptionKey { | ||||||
|   text: string; |   text: string; | ||||||
| @ -45,7 +45,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from "@nuxtjs/composition-api"; | import { defineComponent } from "@nuxtjs/composition-api"; | ||||||
| import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks"; | 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"; | import { alert } from "~/composables/use-toast"; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| @ -294,8 +294,8 @@ import { useCopyList } from "~/composables/use-copy"; | |||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" | import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" | ||||||
| import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; | import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; | ||||||
| import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group"; | import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household"; | ||||||
| import { UserSummary } from "~/lib/api/types/user"; | import { UserOut } from "~/lib/api/types/user"; | ||||||
| import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; | ||||||
| import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; | import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; | ||||||
| import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; | import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; | ||||||
| @ -444,7 +444,7 @@ export default defineComponent({ | |||||||
|         unchecked: shoppingList.value?.listItems?.filter((item) => !item.checked) ?? [], |         unchecked: shoppingList.value?.listItems?.filter((item) => !item.checked) ?? [], | ||||||
|         checked: shoppingList.value?.listItems |         checked: shoppingList.value?.listItems | ||||||
|           ?.filter((item) => item.checked) |           ?.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; |         item.position = shoppingList.value.listItems.length; | ||||||
| 
 | 
 | ||||||
|         // set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items |         // 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 |       // make updates reflect immediately | ||||||
| @ -934,7 +934,7 @@ export default defineComponent({ | |||||||
|         : 0; |         : 0; | ||||||
| 
 | 
 | ||||||
|       createListItemData.value.createdAt = new Date().toISOString(); |       createListItemData.value.createdAt = new Date().toISOString(); | ||||||
|       createListItemData.value.updateAt = createListItemData.value.createdAt; |       createListItemData.value.updatedAt = createListItemData.value.createdAt; | ||||||
| 
 | 
 | ||||||
|       updateListItemOrder(); |       updateListItemOrder(); | ||||||
| 
 | 
 | ||||||
| @ -1020,16 +1020,16 @@ export default defineComponent({ | |||||||
|     // =============================================================== |     // =============================================================== | ||||||
|     // Shopping List Settings |     // Shopping List Settings | ||||||
| 
 | 
 | ||||||
|     const allUsers = ref<UserSummary[]>([]); |     const allUsers = ref<UserOut[]>([]); | ||||||
|     const currentUserId = ref<string | undefined>(); |     const currentUserId = ref<string | undefined>(); | ||||||
|     async function fetchAllUsers() { |     async function fetchAllUsers() { | ||||||
|       const { data } = await userApi.users.getGroupUsers(1, -1, { orderBy: "full_name", orderDirection: "asc" }); |       const { data } = await userApi.households.fetchMembers(); | ||||||
|       if (!data) { |       if (!data) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // update current user |       // update current user | ||||||
|       allUsers.value = data.items; |       allUsers.value = data.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1)); | ||||||
|       currentUserId.value = shoppingList.value?.userId; |       currentUserId.value = shoppingList.value?.userId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ | |||||||
|       <p class="subtitle-1 mb-0 text-center"> |       <p class="subtitle-1 mb-0 text-center"> | ||||||
|        {{ $t('profile.description') }} |        {{ $t('profile.description') }} | ||||||
|       </p> |       </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-card-actions class="d-flex justify-center my-4"> | ||||||
|           <v-btn v-if="$auth.user.canInvite"  outlined rounded @click="getSignupLink()"> |           <v-btn v-if="$auth.user.canInvite"  outlined rounded @click="getSignupLink()"> | ||||||
|             <v-icon left> |             <v-icon left> | ||||||
| @ -57,9 +57,9 @@ | |||||||
|       <v-row tag="section"> |       <v-row tag="section"> | ||||||
|         <v-col cols="12" sm="12" md="12"> |         <v-col cols="12" sm="12" md="12"> | ||||||
|           <v-card outlined> |           <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"> |             <v-card-text class="py-0"> | ||||||
|               {{ $t('profile.group-statistics-description') }} |               {{ $t('profile.household-statistics-description') }} | ||||||
|             </v-card-text> |             </v-card-text> | ||||||
|             <v-card-text class="d-flex flex-wrap justify-center align-center" style="gap: 0.8rem"> |             <v-card-text class="d-flex flex-wrap justify-center align-center" style="gap: 0.8rem"> | ||||||
|               <StatsCards |               <StatsCards | ||||||
| @ -106,8 +106,66 @@ | |||||||
|         </AdvancedOnly> |         </AdvancedOnly> | ||||||
|       </v-row> |       </v-row> | ||||||
|     </section> |     </section> | ||||||
|     <v-divider class="my-7"></v-divider> |     <v-divider class="my-7" /> | ||||||
|     <section> |     <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> |       <div> | ||||||
|         <h3 class="headline">{{ $t('group.group') }}</h3> |         <h3 class="headline">{{ $t('group.group') }}</h3> | ||||||
|         <p>{{ $t('profile.group-description') }}</p> |         <p>{{ $t('profile.group-description') }}</p> | ||||||
| @ -122,46 +180,6 @@ | |||||||
|             {{ $t('profile.group-settings-description') }} |             {{ $t('profile.group-settings-description') }} | ||||||
|           </UserProfileLinkCard> |           </UserProfileLinkCard> | ||||||
|         </v-col> |         </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 --> |         <!-- $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"> |         <v-col v-if="$auth.user.canOrganize" cols="12" sm="12" md="6"> | ||||||
|           <UserProfileLinkCard |           <UserProfileLinkCard | ||||||
| @ -224,7 +242,7 @@ export default defineComponent({ | |||||||
|     const api = useUserApi(); |     const api = useUserApi(); | ||||||
| 
 | 
 | ||||||
|     async function getSignupLink() { |     async function getSignupLink() { | ||||||
|       const { data } = await api.groups.createInvitation({ uses: 1 }); |       const { data } = await api.households.createInvitation({ uses: 1 }); | ||||||
|       if (data) { |       if (data) { | ||||||
|         token.value = data.token; |         token.value = data.token; | ||||||
|         generatedSignupLink.value = constructLink(data.token); |         generatedSignupLink.value = constructLink(data.token); | ||||||
| @ -272,7 +290,7 @@ export default defineComponent({ | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const stats = useAsync(async () => { |     const stats = useAsync(async () => { | ||||||
|       const { data } = await api.groups.statistics(); |       const { data } = await api.households.statistics(); | ||||||
| 
 | 
 | ||||||
|       if (data) { |       if (data) { | ||||||
|         return data; |         return data; | ||||||
| @ -306,7 +324,7 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
|     const statsTo = computed<{ [key: string]: string }>(() => { return { |     const statsTo = computed<{ [key: string]: string }>(() => { return { | ||||||
|       totalRecipes: `/g/${groupSlug.value}/`, |       totalRecipes: `/g/${groupSlug.value}/`, | ||||||
|       totalUsers: "/group/members", |       totalUsers: "/household/members", | ||||||
|       totalCategories: `/g/${groupSlug.value}/recipes/categories`, |       totalCategories: `/g/${groupSlug.value}/recipes/categories`, | ||||||
|       totalTags: `/g/${groupSlug.value}/recipes/tags`, |       totalTags: `/g/${groupSlug.value}/recipes/tags`, | ||||||
|       totalTools: `/g/${groupSlug.value}/recipes/tools`, |       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) |     repos = get_repositories(session) | ||||||
|     group = repos.groups.get_by_slug_or_id(group_slug) |     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") |         raise HTTPException(404, "group not found") | ||||||
|     else: |     else: | ||||||
|         return group |         return group | ||||||
| @ -111,7 +111,7 @@ async def get_current_user( | |||||||
|     except PyJWTError as e: |     except PyJWTError as e: | ||||||
|         raise credentials_exception from 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) |     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: | 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}) |     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: |         if self.__has_tried_user: | ||||||
|             return self.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) |         user = user = db.users.get_one(username, "username", any_case=True) | ||||||
|         if not user: |         if not user: | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ class CredentialsProvider(AuthProvider[CredentialsRequest]): | |||||||
|     async def authenticate(self) -> tuple[str, timedelta] | None: |     async def authenticate(self) -> tuple[str, timedelta] | None: | ||||||
|         """Attempt to authenticate a user given a username and password""" |         """Attempt to authenticate a user given a username and password""" | ||||||
|         settings = get_app_settings() |         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) |         user = self.try_get_user(self.data.username) | ||||||
| 
 | 
 | ||||||
|         if not user: |         if not user: | ||||||
|  | |||||||
| @ -95,7 +95,7 @@ class LDAPProvider(CredentialsProvider): | |||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         settings = get_app_settings() |         settings = get_app_settings() | ||||||
|         db = get_repositories(self.session) |         db = get_repositories(self.session, group_id=None, household_id=None) | ||||||
|         if not self.data: |         if not self.data: | ||||||
|             return None |             return None | ||||||
|         data = self.data |         data = self.data | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ class OpenIDProvider(AuthProvider[OIDCRequest]): | |||||||
|         if not claims: |         if not claims: | ||||||
|             return None |             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)) |         user = self.try_get_user(claims.get(settings.OIDC_USER_CLAIM)) | ||||||
|         is_admin = False |         is_admin = False | ||||||
|  | |||||||
| @ -179,6 +179,7 @@ class AppSettings(AppLoggingSettings): | |||||||
|         return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None |         return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None | ||||||
| 
 | 
 | ||||||
|     DEFAULT_GROUP: str = "Home" |     DEFAULT_GROUP: str = "Home" | ||||||
|  |     DEFAULT_HOUSEHOLD: str = "Family" | ||||||
| 
 | 
 | ||||||
|     _DEFAULT_EMAIL: str = "changeme@example.com" |     _DEFAULT_EMAIL: str = "changeme@example.com" | ||||||
|     """ |     """ | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ SessionLocal, engine = sql_global_init(settings.DB_URL)  # type: ignore | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @contextmanager | @contextmanager | ||||||
| def session_context() -> Session: | def session_context() -> Generator[Session, None, None]: | ||||||
|     """ |     """ | ||||||
|     session_context() provides a managed session to the database that is automatically |     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 |     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.core import root_logger | ||||||
| from mealie.db.models.group.group import Group | 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.labels import MultiPurposeLabel | ||||||
| from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel | from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel | ||||||
| from mealie.db.models.recipe.recipe import RecipeModel | 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