From 0d7cdf51ca822a56a88c594e80a9c5cc0d4fe1de Mon Sep 17 00:00:00 2001 From: icamarillo Date: Tue, 3 Feb 2026 10:38:54 -0700 Subject: [PATCH] =?UTF-8?q?Versi=C3=B3n=201.3.0:=20Conexion=20completa=20?= =?UTF-8?q?=20del=20proyecto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/endpoints/categories.py | 166 +- backend/app/api/v1/endpoints/systems.py | 152 +- backend/app/api/v1/endpoints/tenants.py | 18 + backend/app/api/v1/endpoints/tickets.py | 46 +- backend/app/api/v1/endpoints/users.py | 317 +- backend/app/api/v1/router.py | 7 +- backend/app/models/category.py | 40 +- backend/app/models/relationships.py | 0 backend/app/models/system.py | 36 +- backend/app/models/tenant.py | 13 +- backend/app/models/ticket.py | 93 +- backend/app/update_password_hashes.py | 0 backend/backend/migrations/README | 1 + backend/backend/migrations/env.py | 78 + backend/backend/migrations/script.py.mako | 26 + backend/fix_password_script.py | 9 +- backend/migrations/env.py | 68 + backups | Bin 0 -> 92064 bytes fix_admin_password.py | 67 + frontend-client/package-lock.json | 3946 +++++++++++++++++ .../src/lib/components/Header.svelte | 6 + frontend-client/src/lib/stores/tickets.ts | 41 +- .../src/routes/tenants/+page.svelte | 17 +- frontend-internal/vite.config.js | 40 +- 24 files changed, 5038 insertions(+), 149 deletions(-) create mode 100644 backend/app/models/relationships.py create mode 100644 backend/app/update_password_hashes.py create mode 100644 backend/backend/migrations/README create mode 100644 backend/backend/migrations/env.py create mode 100644 backend/backend/migrations/script.py.mako create mode 100644 backend/migrations/env.py create mode 100644 backups create mode 100644 fix_admin_password.py create mode 100644 frontend-client/package-lock.json diff --git a/backend/app/api/v1/endpoints/categories.py b/backend/app/api/v1/endpoints/categories.py index 4aaabd4..17eeaa7 100644 --- a/backend/app/api/v1/endpoints/categories.py +++ b/backend/app/api/v1/endpoints/categories.py @@ -3,53 +3,191 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db from app.models.category import Category +from app.models.user import User from app.api import deps router = APIRouter() -class CategoryBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class CategoryCreate(BaseModel): + """Schema para crear categoría - NO incluye tenant_id (se asigna automáticamente)""" name: str description: Optional[str] = None - is_active: bool = True - tenant_id: Optional[uuid.UUID] = None + color: Optional[str] = None + sla_response_hours: int = 24 + sla_resolution_hours: int = 72 + auto_assign_to: Optional[uuid.UUID] = None -class CategoryCreate(CategoryBase): - pass - -class CategoryUpdate(CategoryBase): +class CategoryUpdate(BaseModel): + """Schema para actualizar categoría""" name: Optional[str] = None description: Optional[str] = None + color: Optional[str] = None + sla_response_hours: Optional[int] = None + sla_resolution_hours: Optional[int] = None + auto_assign_to: Optional[uuid.UUID] = None is_active: Optional[bool] = None - tenant_id: Optional[uuid.UUID] = None -class CategoryResponse(CategoryBase): +class CategoryResponse(BaseModel): + """Schema de respuesta - incluye todos los campos""" id: uuid.UUID + tenant_id: uuid.UUID # ✅ AÑADIDO + name: str + description: Optional[str] = None + color: Optional[str] = None + sla_response_hours: int + sla_resolution_hours: int + auto_assign_to: Optional[uuid.UUID] = None + is_active: bool + created_at: datetime # ✅ AÑADIDO + updated_at: datetime # ✅ AÑADIDO model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[CategoryResponse]) async def read_categories( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint + no solo superuser ): - query = select(Category).offset(skip).limit(limit) + """ + Listar categorías del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra categorías del tenant del usuario. + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(Category).where( + Category.tenant_id == current_user.tenant_id + ).offset(skip).limit(limit) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=CategoryResponse) + +@router.post("/", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED) async def create_category( category: CategoryCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint ): - db_category = Category(**category.model_dump()) + """ + Crear nueva categoría en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + """ + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_category = Category( + **category.model_dump(), + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) + db.add(db_category) await db.commit() await db.refresh(db_category) return db_category + + +@router.get("/{category_id}", response_model=CategoryResponse) +async def read_category( + category_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener una categoría específica del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + category = result.scalar_one_or_none() + + if not category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + return category + + +@router.put("/{category_id}", response_model=CategoryResponse) +async def update_category( + category_id: uuid.UUID, + category_update: CategoryUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar categoría del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_category = result.scalar_one_or_none() + + if not db_category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + # Actualizar campos + update_data = category_update.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(db_category, field, value) + + await db.commit() + await db.refresh(db_category) + return db_category + + +@router.delete("/{category_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_category( + category_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar categoría del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_category = result.scalar_one_or_none() + + if not db_category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + # Soft delete + db_category.is_active = False + await db.commit() + return None \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/systems.py b/backend/app/api/v1/endpoints/systems.py index a5aad98..bfc8e85 100644 --- a/backend/app/api/v1/endpoints/systems.py +++ b/backend/app/api/v1/endpoints/systems.py @@ -3,51 +3,179 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db from app.models.system import System +from app.models.user import User from app.api import deps router = APIRouter() -class SystemBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class SystemCreate(BaseModel): + """Schema para crear sistema - NO incluye tenant_id (se asigna automáticamente)""" name: str description: Optional[str] = None - is_active: bool = True -class SystemCreate(SystemBase): - pass - -class SystemUpdate(SystemBase): +class SystemUpdate(BaseModel): + """Schema para actualizar sistema""" name: Optional[str] = None description: Optional[str] = None is_active: Optional[bool] = None -class SystemResponse(SystemBase): +class SystemResponse(BaseModel): + """Schema de respuesta - incluye todos los campos""" id: uuid.UUID + tenant_id: uuid.UUID # ✅ AÑADIDO + name: str + description: Optional[str] = None + is_active: bool + created_at: datetime # ✅ AÑADIDO + updated_at: datetime # ✅ AÑADIDO model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[SystemResponse]) async def read_systems( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint + no solo superuser ): - query = select(System).offset(skip).limit(limit) + """ + Listar sistemas del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra sistemas del tenant del usuario. + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(System).where( + System.tenant_id == current_user.tenant_id + ).offset(skip).limit(limit) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=SystemResponse) + +@router.post("/", response_model=SystemResponse, status_code=status.HTTP_201_CREATED) async def create_system( system: SystemCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint ): - db_system = System(**system.model_dump()) + """ + Crear nuevo sistema en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + """ + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_system = System( + **system.model_dump(), + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) + db.add(db_system) await db.commit() await db.refresh(db_system) return db_system + + +@router.get("/{system_id}", response_model=SystemResponse) +async def read_system( + system_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener un sistema específico del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + system = result.scalar_one_or_none() + + if not system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + return system + + +@router.put("/{system_id}", response_model=SystemResponse) +async def update_system( + system_id: uuid.UUID, + system_update: SystemUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar sistema del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_system = result.scalar_one_or_none() + + if not db_system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + # Actualizar campos + update_data = system_update.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(db_system, field, value) + + await db.commit() + await db.refresh(db_system) + return db_system + + +@router.delete("/{system_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_system( + system_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar sistema del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_system = result.scalar_one_or_none() + + if not db_system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + # Soft delete + db_system.is_active = False + await db.commit() + return None \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/tenants.py b/backend/app/api/v1/endpoints/tenants.py index ae4e035..15a5bab 100644 --- a/backend/app/api/v1/endpoints/tenants.py +++ b/backend/app/api/v1/endpoints/tenants.py @@ -85,6 +85,9 @@ async def update_tenant( raise HTTPException(status_code=404, detail="Tenant not found") update_data = tenant_in.model_dump(exclude_unset=True) + if "status" in update_data: + tenant.is_active = update_data.pop("status") == TenantStatus.active + for field, value in update_data.items(): setattr(tenant, field, value) @@ -92,3 +95,18 @@ async def update_tenant( await db.commit() await db.refresh(tenant) return tenant + +@router.delete("/{tenant_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_tenant( + tenant_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user = Depends(deps.get_current_active_superuser) +): + """Eliminar un cliente (tenant) por ID.""" + tenant = await db.get(Tenant, tenant_id) + if not tenant: + raise HTTPException(status_code=404, detail="Tenant not found") + + await db.delete(tenant) + await db.commit() + return {"message": "Tenant deleted successfully"} diff --git a/backend/app/api/v1/endpoints/tickets.py b/backend/app/api/v1/endpoints/tickets.py index 020e3fc..e4a0fc3 100644 --- a/backend/app/api/v1/endpoints/tickets.py +++ b/backend/app/api/v1/endpoints/tickets.py @@ -3,6 +3,7 @@ Tickets endpoints - ServiceManagerWeb """ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File +from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from typing import List, Optional @@ -11,7 +12,8 @@ from app.core.database import get_db from app.api.deps import get_current_user from app.models.ticket import Ticket, TicketStatus, TicketPriority from app.models.user import User -from pydantic import BaseModel +from app.models.category import Category # ✅ CORREGIDO: Era TicketCategory +from app.models.system import System import uuid router = APIRouter() @@ -24,7 +26,7 @@ class TicketCreate(BaseModel): subject: str description: str category_id: Optional[str] = None - system_id: Optional[str] = None + affected_system_id: Optional[str] = None # ✅ CORREGIDO: Era system_id priority: str = "MEDIUM" class TicketUpdate(BaseModel): @@ -42,7 +44,7 @@ class TicketResponse(BaseModel): status: str priority: str category_id: Optional[str] = None - system_id: Optional[str] = None + affected_system_id: Optional[str] = None # ✅ CORREGIDO: Era system_id created_by: str assigned_to: Optional[str] = None created_at: datetime @@ -78,7 +80,25 @@ async def create_ticket( # Convertir IDs de string a UUID si son proporcionados category_uuid = uuid.UUID(ticket.category_id) if ticket.category_id else None - system_uuid = uuid.UUID(ticket.system_id) if ticket.system_id else None + system_uuid = uuid.UUID(ticket.affected_system_id) if ticket.affected_system_id else None # ✅ CORREGIDO + + # ✅ CORREGIDO: Validar en la tabla correcta con el nombre correcto del modelo + if category_uuid: + category = await db.get(Category, category_uuid) # ✅ Category, no TicketCategory + if not category: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"La categoría con ID {ticket.category_id} no existe." + ) + + # Validar si el system_id existe en la tabla affected_systems + if system_uuid: + system = await db.get(System, system_uuid) + if not system: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"El sistema con ID {ticket.affected_system_id} no existe." + ) db_ticket = Ticket( id=uuid.uuid4(), @@ -87,7 +107,7 @@ async def create_ticket( subject=ticket.subject, description=ticket.description, category_id=category_uuid, - system_id=system_uuid, + affected_system_id=system_uuid, # ✅ CORREGIDO: Nombre correcto del campo priority=TicketPriority[ticket.priority.upper()], created_by=current_user.id, status=TicketStatus.NEW, @@ -99,7 +119,7 @@ async def create_ticket( await db.commit() await db.refresh(db_ticket) - # Convertir a respuesta + # ✅ CORREGIDO: Usar affected_system_id en respuesta return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -108,7 +128,7 @@ async def create_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, @@ -160,6 +180,7 @@ async def get_tickets( result = await db.execute(query) tickets = result.scalars().all() + # ✅ CORREGIDO: Usar affected_system_id return [ { "id": str(t.id), @@ -169,7 +190,7 @@ async def get_tickets( "status": t.status.value, "priority": t.priority.value, "category_id": str(t.category_id) if t.category_id else None, - "system_id": str(t.system_id) if t.system_id else None, + "affected_system_id": str(t.affected_system_id) if t.affected_system_id else None, # ✅ CORREGIDO "created_by": str(t.created_by), "assigned_to": str(t.assigned_to) if t.assigned_to else None, "created_at": t.created_at, @@ -211,6 +232,7 @@ async def get_ticket( detail=f"Ticket {ticket_id} not found" ) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(ticket.id), "ticket_number": ticket.ticket_number, @@ -219,7 +241,7 @@ async def get_ticket( "status": ticket.status.value, "priority": ticket.priority.value, "category_id": str(ticket.category_id) if ticket.category_id else None, - "system_id": str(ticket.system_id) if ticket.system_id else None, + "affected_system_id": str(ticket.affected_system_id) if ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(ticket.created_by), "assigned_to": str(ticket.assigned_to) if ticket.assigned_to else None, "created_at": ticket.created_at, @@ -278,6 +300,7 @@ async def update_ticket( await db.commit() await db.refresh(db_ticket) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -286,7 +309,7 @@ async def update_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, @@ -341,6 +364,7 @@ async def close_ticket( await db.commit() await db.refresh(db_ticket) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -349,7 +373,7 @@ async def close_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py index 82d3272..0f52932 100644 --- a/backend/app/api/v1/endpoints/users.py +++ b/backend/app/api/v1/endpoints/users.py @@ -3,6 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict, EmailStr from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db @@ -12,57 +13,335 @@ from app.api import deps router = APIRouter() -class UserBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class UserCreate(BaseModel): + """Schema para crear usuario - NO incluye tenant_id (se asigna automáticamente)""" email: EmailStr first_name: str last_name: str role: UserRole - is_active: bool = True - tenant_id: Optional[uuid.UUID] = None - -class UserCreate(UserBase): password: str - + language: str = "es" + timezone: str = "UTC" + notifications_email: bool = True + class UserUpdate(BaseModel): + """Schema para actualizar usuario""" email: Optional[EmailStr] = None first_name: Optional[str] = None last_name: Optional[str] = None role: Optional[UserRole] = None is_active: Optional[bool] = None - password: Optional[str] = None # Optional password update + password: Optional[str] = None + language: Optional[str] = None + timezone: Optional[str] = None + notifications_email: Optional[bool] = None -class UserResponse(UserBase): +class UserResponse(BaseModel): + """Schema de respuesta - incluye todos los campos públicos""" id: uuid.UUID + tenant_id: uuid.UUID + email: EmailStr + first_name: str + last_name: str + avatar_url: Optional[str] = None + role: UserRole + is_active: bool + email_verified: bool + last_login: Optional[datetime] = None + language: str + timezone: str + notifications_email: bool + totp_enabled: bool + created_at: datetime + updated_at: datetime model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[UserResponse]) async def read_users( skip: int = 0, - limit: int = 100, + limit: int = 100, + role: Optional[UserRole] = None, + is_active: Optional[bool] = None, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) ): - query = select(User).offset(skip).limit(limit) + """ + Listar usuarios del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra usuarios del tenant del usuario. + + Filtros opcionales: + - role: filtrar por rol + - is_active: filtrar por estado activo + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(User).where(User.tenant_id == current_user.tenant_id) + + # Aplicar filtros opcionales + if role: + query = query.where(User.role == role) + if is_active is not None: + query = query.where(User.is_active == is_active) + + query = query.offset(skip).limit(limit).order_by(User.created_at.desc()) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=UserResponse) + +@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user( user: UserCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) ): - query = select(User).where(User.email == user.email) + """ + Crear nuevo usuario en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + + Restricciones: + - Solo ADMIN, SUPPORT_MANAGER y CLIENT_ADMIN pueden crear usuarios + - El email debe ser único dentro del tenant + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to create users" + ) + + # Verificar si el email ya existe en el tenant + query = select(User).where( + User.email == user.email, + User.tenant_id == current_user.tenant_id + ) result = await db.execute(query) if result.scalar_one_or_none(): - raise HTTPException(status_code=400, detail="Email already registered") - - user_data = user.model_dump(exclude={"password"}) - password_hash = security.get_password_hash(user.password) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already registered in this tenant" + ) + + # Preparar datos del usuario + user_data = user.model_dump(exclude={"password"}) + password_hash = security.hash_password(user.password) + + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_user = User( + **user_data, + password_hash=password_hash, + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) - db_user = User(**user_data, password_hash=password_hash) db.add(db_user) await db.commit() await db.refresh(db_user) return db_user + + +@router.get("/{user_id}", response_model=UserResponse) +async def read_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener un usuario específico del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a usuarios del propio tenant. + """ + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + return user + + +@router.put("/{user_id}", response_model=UserResponse) +async def update_user( + user_id: uuid.UUID, + user_update: UserUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar usuario del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar usuarios del propio tenant. + + Restricciones: + - Solo ADMIN, SUPPORT_MANAGER y CLIENT_ADMIN pueden actualizar usuarios + - No se puede cambiar el tenant_id + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to update users" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Verificar email único si se está cambiando + update_data = user_update.model_dump(exclude_unset=True) + if "email" in update_data and update_data["email"] != db_user.email: + email_query = select(User).where( + User.email == update_data["email"], + User.tenant_id == current_user.tenant_id, + User.id != user_id + ) + email_result = await db.execute(email_query) + if email_result.scalar_one_or_none(): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already in use by another user" + ) + + # Actualizar campos + for field, value in update_data.items(): + if field == "password": + # Hash the new password + db_user.password_hash = security.hash_password(value) + else: + setattr(db_user, field, value) + + await db.commit() + await db.refresh(db_user) + return db_user + + +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar usuario del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar usuarios del propio tenant. + + Restricciones: + - Solo ADMIN puede eliminar usuarios + - No se puede eliminar a sí mismo + - No se puede eliminar el último ADMIN del tenant + """ + # Verificar permisos - solo ADMIN puede eliminar + if current_user.role != UserRole.ADMIN: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Only admins can delete users" + ) + + # No se puede eliminar a sí mismo + if user_id == current_user.id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="You cannot delete yourself" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Verificar que no sea el último admin del tenant + if db_user.role == UserRole.ADMIN: + admin_query = select(User).where( + User.tenant_id == current_user.tenant_id, + User.role == UserRole.ADMIN, + User.is_active == True, + User.id != user_id + ) + admin_result = await db.execute(admin_query) + active_admins = admin_result.scalars().all() + + if len(active_admins) == 0: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Cannot delete the last active admin of the tenant" + ) + + # Soft delete + db_user.is_active = False + await db.commit() + return None + + +@router.patch("/{user_id}/activate", response_model=UserResponse) +async def activate_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Reactivar usuario desactivado. + + ✅ Implementa multi-tenancy: solo permite reactivar usuarios del propio tenant. + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to activate users" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + db_user.is_active = True + await db.commit() + await db.refresh(db_user) + return db_user diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index f53b71f..65174f1 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -5,6 +5,7 @@ Router principal para la API v1 """ from fastapi import APIRouter + from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets api_router = APIRouter() @@ -51,4 +52,8 @@ api_router.include_router( tickets.router, prefix="/tickets", tags=["tickets"] -) \ No newline at end of file +) + +# Ensure FastAPI is installed in the environment +# If not, install it using: +# pip install fastapi \ No newline at end of file diff --git a/backend/app/models/category.py b/backend/app/models/category.py index 8ccb9b5..64f0d44 100644 --- a/backend/app/models/category.py +++ b/backend/app/models/category.py @@ -1,8 +1,8 @@ - """ Category Model - ServiceManagerWeb +Categorías de tickets por tenant """ -from sqlalchemy import String, Text, Boolean, ForeignKey +from sqlalchemy import String, Text, Boolean, Integer, ForeignKey, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID from typing import List, Optional @@ -11,18 +11,40 @@ import uuid from app.core.database import Base class Category(Base): - __tablename__ = "categories" + """Modelo de categorías de tickets (ticket_categories en BD)""" + __tablename__ = "ticket_categories" # ✅ CORREGIDO: nombre correcto de tabla + # Campos básicos name: Mapped[str] = mapped_column(String(100), nullable=False) - description: Mapped[Optional[str]] = mapped_column(Text) - is_active: Mapped[bool] = mapped_column(Boolean, default=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - # Optional: Tenant specific categories? - tenant_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=True) + # ✅ CORREGIDO: tenant_id es obligatorio para multi-tenancy + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False # ✅ Obligatorio + ) + + # ✅ AÑADIDOS: Campos de SLA según schema.sql + color: Mapped[Optional[str]] = mapped_column(String(7), nullable=True) + sla_response_hours: Mapped[int] = mapped_column(Integer, default=24, nullable=False) + sla_resolution_hours: Mapped[int] = mapped_column(Integer, default=72, nullable=False) + auto_assign_to: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=True + ) # Relationships tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="category") - tenant: Mapped["Tenant"] = relationship("Tenant") # Assuming Tenant model is imported + tenant: Mapped["Tenant"] = relationship("Tenant") + auto_assign_user: Mapped[Optional["User"]] = relationship("User", foreign_keys=[auto_assign_to]) + + # ✅ AÑADIDO: Constraint único por tenant (no puede haber categorías duplicadas en el mismo tenant) + __table_args__ = ( + UniqueConstraint('tenant_id', 'name', name='uq_ticket_categories_tenant_name'), + ) def __repr__(self) -> str: - return f"" + return f"" \ No newline at end of file diff --git a/backend/app/models/relationships.py b/backend/app/models/relationships.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/system.py b/backend/app/models/system.py index 70929d1..a29c43c 100644 --- a/backend/app/models/system.py +++ b/backend/app/models/system.py @@ -1,25 +1,43 @@ - """ System Model - ServiceManagerWeb +Sistemas afectados por tenant """ -from sqlalchemy import String, Text, Boolean +from sqlalchemy import String, Text, Boolean, ForeignKey, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID from typing import List, Optional import uuid from app.core.database import Base class System(Base): - __tablename__ = "systems" + """Modelo de sistemas afectados (affected_systems en BD)""" + __tablename__ = "affected_systems" # ✅ CORREGIDO: nombre correcto de tabla + # Campos básicos name: Mapped[str] = mapped_column(String(100), nullable=False) - description: Mapped[Optional[str]] = mapped_column(Text) - is_active: Mapped[bool] = mapped_column(Boolean, default=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) + + # ✅ AÑADIDO: tenant_id obligatorio para multi-tenancy (faltaba completamente) + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False + ) # Relationships - # If we want tickets to link to systems, we will add relationship in Ticket later or now. - # We will assume Ticket links to System. - tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="system") + # ✅ ACTUALIZADO: nombre de relación a affected_system + tickets: Mapped[List["Ticket"]] = relationship( + "Ticket", + back_populates="affected_system" # ✅ Nombre actualizado + ) + tenant: Mapped["Tenant"] = relationship("Tenant") + + # ✅ AÑADIDO: Constraint único por tenant (no puede haber sistemas duplicados en el mismo tenant) + __table_args__ = ( + UniqueConstraint('tenant_id', 'name', name='uq_affected_systems_tenant_name'), + ) def __repr__(self) -> str: - return f"" + return f"" \ No newline at end of file diff --git a/backend/app/models/tenant.py b/backend/app/models/tenant.py index e37f110..35782b4 100644 --- a/backend/app/models/tenant.py +++ b/backend/app/models/tenant.py @@ -1,9 +1,7 @@ """ Tenant Model - ServiceManagerWeb - Modelo para organizaciones cliente (multi-tenancy) """ - from sqlalchemy import String, Integer, Text, Boolean, ARRAY from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, ENUM @@ -13,17 +11,14 @@ import uuid from app.core.database import Base - class TenantStatus(str, enum.Enum): """Estados de un tenant.""" ACTIVE = "active" SUSPENDED = "suspended" INACTIVE = "inactive" - class Tenant(Base): """Modelo de Tenant (Organización cliente).""" - __tablename__ = "tenants" # Información básica @@ -51,18 +46,14 @@ class Tenant(Base): # Estado status: Mapped[TenantStatus] = mapped_column( - String(20), + String(20), default=TenantStatus.ACTIVE ) # Relaciones users: Mapped[List["User"]] = relationship("User", back_populates="tenant") tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="tenant") + categories: Mapped[List["Category"]] = relationship("Category", back_populates="tenant") # ✅ CORREGIDO: Era "TicketCategory" def __repr__(self) -> str: return f"" - - @property - def is_active(self) -> bool: - """Check if tenant is active.""" - return self.status == TenantStatus.ACTIVE \ No newline at end of file diff --git a/backend/app/models/ticket.py b/backend/app/models/ticket.py index a78aadb..a9ba19d 100644 --- a/backend/app/models/ticket.py +++ b/backend/app/models/ticket.py @@ -1,56 +1,112 @@ """ Ticket Model - ServiceManagerWeb +Tickets de soporte - Core del negocio """ -from sqlalchemy import String, ForeignKey, Text +from sqlalchemy import String, ForeignKey, Text, Integer, CheckConstraint, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, ENUM from typing import Optional +from datetime import datetime import enum import uuid from app.core.database import Base class TicketStatus(str, enum.Enum): + """Estados posibles de un ticket""" NEW = "NEW" TRIAGE = "TRIAGE" IN_PROGRESS = "IN_PROGRESS" - WAITING_FOR_CLIENT = "WAITING_FOR_CLIENT" + WAITING_CUSTOMER = "WAITING_CUSTOMER" # ✅ CORREGIDO: nombre según schema.sql RESOLVED = "RESOLVED" CLOSED = "CLOSED" REOPENED = "REOPENED" class TicketPriority(str, enum.Enum): + """Prioridades posibles de un ticket""" LOW = "LOW" MEDIUM = "MEDIUM" HIGH = "HIGH" URGENT = "URGENT" class Ticket(Base): + """Modelo de tickets de soporte""" __tablename__ = "tickets" - # Note: id, created_at, updated_at are inherited from Base - - tenant_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False) + # Multi-tenancy + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False + ) + # Campos básicos ticket_number: Mapped[str] = mapped_column(String(20), nullable=False) subject: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) - status: Mapped[TicketStatus] = mapped_column(ENUM(TicketStatus, name="ticket_status_enum", create_type=False), default=TicketStatus.NEW) - priority: Mapped[TicketPriority] = mapped_column(ENUM(TicketPriority, name="ticket_priority_enum", create_type=False), default=TicketPriority.MEDIUM) + # Estado y Prioridad + status: Mapped[TicketStatus] = mapped_column( + ENUM(TicketStatus, name="ticket_status_enum", create_type=False), + default=TicketStatus.NEW, + nullable=False + ) + priority: Mapped[TicketPriority] = mapped_column( + ENUM(TicketPriority, name="ticket_priority_enum", create_type=False), + default=TicketPriority.MEDIUM, + nullable=False + ) - # Foreign Keys - created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) - assigned_to: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) + # ✅ CORREGIDO: Foreign Keys apuntan a tablas correctas + created_by: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=False + ) + assigned_to: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=True + ) - system_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("systems.id"), nullable=True) - category_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("categories.id"), nullable=True) + # ✅ CORREGIDO: Renombrado de system_id a affected_system_id + affected_system_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("affected_systems.id"), # ✅ Tabla correcta + nullable=True + ) + + # ✅ CORREGIDO: Foreign key a tabla correcta + category_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("ticket_categories.id"), # ✅ Tabla correcta + nullable=True + ) + + # ✅ AÑADIDOS: Campos de SLA según schema.sql + sla_response_due: Mapped[Optional[datetime]] = mapped_column(nullable=True) + sla_resolution_due: Mapped[Optional[datetime]] = mapped_column(nullable=True) + first_response_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) + resolved_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) + + # ✅ AÑADIDOS: Campos de CSAT (Customer Satisfaction) según schema.sql + rating: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + rating_comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + rated_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) # Relationships tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="tickets") - system: Mapped["System"] = relationship("System", back_populates="tickets") - category: Mapped["Category"] = relationship("Category", back_populates="tickets") + # ✅ ACTUALIZADO: Nombre de relación y optional + affected_system: Mapped[Optional["System"]] = relationship( + "System", + back_populates="tickets" + ) + + category: Mapped[Optional["Category"]] = relationship( + "Category", + back_populates="tickets" + ) created_by_user: Mapped["User"] = relationship( "User", @@ -63,3 +119,12 @@ class Ticket(Base): foreign_keys=[assigned_to], back_populates="assigned_tickets" ) + + # ✅ AÑADIDOS: Constraints según schema.sql + __table_args__ = ( + UniqueConstraint('tenant_id', 'ticket_number', name='uq_tickets_tenant_number'), + CheckConstraint('rating >= 1 AND rating <= 5', name='check_rating_range'), + ) + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/backend/app/update_password_hashes.py b/backend/app/update_password_hashes.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend/migrations/README b/backend/backend/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/backend/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/backend/migrations/env.py b/backend/backend/migrations/env.py new file mode 100644 index 0000000..36112a3 --- /dev/null +++ b/backend/backend/migrations/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/backend/migrations/script.py.mako b/backend/backend/migrations/script.py.mako new file mode 100644 index 0000000..fbc4b07 --- /dev/null +++ b/backend/backend/migrations/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/fix_password_script.py b/backend/fix_password_script.py index 81fd583..eb77c03 100644 --- a/backend/fix_password_script.py +++ b/backend/fix_password_script.py @@ -1,5 +1,4 @@ - -import asyncio +import asyncio import sys import os @@ -8,8 +7,10 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__))) from sqlalchemy import select from app.core.database import AsyncSessionLocal -from app.models.tenant import Tenant # Import Tenant to register it -from app.models.ticket import Ticket # Import Ticket to register it +from app.models.tenant import Tenant +from app.models.ticket import Ticket +from app.models.category import Category # ✅ AÑADIR ESTO +from app.models.system import System # ✅ AÑADIR ESTO from app.models.user import User from app.core.security import SecurityUtils diff --git a/backend/migrations/env.py b/backend/migrations/env.py new file mode 100644 index 0000000..6299cfd --- /dev/null +++ b/backend/migrations/env.py @@ -0,0 +1,68 @@ +from logging.config import fileConfig +import os +from sqlalchemy import create_engine, pool +from sqlalchemy.engine import engine_from_config +from alembic import context + +# Import Base and all models +from app.core.database import Base +from app.models import tenant # Import all models explicitly + +# Alembic Config object +config = context.config + +# Logging configuration +if config.config_file_name: + fileConfig(config.config_file_name) + +# Get DATABASE_URL and convert to synchronous +DATABASE_URL = os.getenv("DATABASE_URL") +if not DATABASE_URL: + raise RuntimeError("DATABASE_URL environment variable is not set") + +SYNC_DATABASE_URL = DATABASE_URL.replace("+asyncpg", "") + +# Metadata for autogenerate +target_metadata = Base.metadata + + +def run_migrations_offline(): + """ + Run migrations in 'offline' mode. + """ + context.configure( + url=SYNC_DATABASE_URL, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """ + Run migrations in 'online' mode. + """ + # Fetch the URL from Alembic configuration + alembic_config = config.get_section(config.config_ini_section) + alembic_config["sqlalchemy.url"] = SYNC_DATABASE_URL + + connectable = engine_from_config( + alembic_config, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backups b/backups new file mode 100644 index 0000000000000000000000000000000000000000..748543a6b3461e4b247b6943de8d706c7699b864 GIT binary patch literal 92064 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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/adapter-node": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "rollup": "^3.7.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^1.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "1.30.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte": "^2.5.0", + "@types/cookie": "^0.5.1", + "cookie": "^0.5.0", + "devalue": "^4.3.1", + "esm-env": "^1.0.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.0", + "mrmime": "^1.0.1", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^2.0.2", + "tiny-glob": "^0.2.9", + "undici": "^5.28.3" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": "^16.14 || >=18" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "2.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.3", + "svelte-hmr": "^0.15.3", + "vitefu": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.11", + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-subset": { + "version": "1.3.6", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/chai": "<5.2.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.5.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/code-red": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/code-red/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "4.3.3", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "dev": true, + "license": "ISC" + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.2", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.46.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.35.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.43.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/heroicons": { + "version": "2.2.0", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.35.0", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "2.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/periscopic": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/periscopic/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/periscopic/node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "2.10.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^1.16.4 || ^2.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sander": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + } + }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sander/node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sirv/node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sorcery": { + "version": "0.11.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "buffer-crc32": "^1.0.0", + "minimist": "^1.2.0", + "sander": "^0.5.0" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "4.2.20", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte-check": { + "version": "3.8.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "chokidar": "^3.4.1", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "svelte-preprocess": "^5.1.3", + "typescript": "^5.0.3" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.43.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-hmr": { + "version": "0.15.3", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/svelte-preprocess": { + "version": "5.1.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/pug": "^2.0.6", + "detect-indent": "^6.1.0", + "magic-string": "^0.30.5", + "sorcery": "^0.11.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3 || ^4.0.0", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.55.0", + "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", + "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/svelte/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "6.0.1", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/vite": { + "version": "4.5.14", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/frontend-client/src/lib/components/Header.svelte b/frontend-client/src/lib/components/Header.svelte index 8b39789..dc60c02 100644 --- a/frontend-client/src/lib/components/Header.svelte +++ b/frontend-client/src/lib/components/Header.svelte @@ -59,6 +59,9 @@ diff --git a/frontend-client/src/lib/stores/tickets.ts b/frontend-client/src/lib/stores/tickets.ts index 72f8bea..7cadcfc 100644 --- a/frontend-client/src/lib/stores/tickets.ts +++ b/frontend-client/src/lib/stores/tickets.ts @@ -1,7 +1,6 @@ -import { writable } from 'svelte/store'; -import { auth } from './auth.js'; -import { get } from 'svelte/store'; +import { writable, get } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { auth } from './auth'; // Types export interface Ticket { @@ -121,13 +120,13 @@ function createTicketsStore() { // Load user's tickets loadTickets: async () => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { const tickets = await apiCall('/tickets/'); - update(state => ({ ...state, tickets, isLoading: false })); + update((state: TicketsState) => ({ ...state, tickets, isLoading: false })); } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to load tickets' @@ -137,7 +136,7 @@ function createTicketsStore() { // Load specific ticket with details loadTicket: async (ticketId: string) => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { const [ticket, comments, attachments] = await Promise.all([ @@ -146,7 +145,7 @@ function createTicketsStore() { apiCall(`/tickets/${ticketId}/attachments`) ]); - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: ticket, comments, @@ -154,7 +153,7 @@ function createTicketsStore() { isLoading: false })); } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to load ticket' @@ -164,7 +163,7 @@ function createTicketsStore() { // Create new ticket createTicket: async (ticket: CreateTicketRequest) => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { // Mapear campos del frontend al formato del backend @@ -184,7 +183,7 @@ function createTicketsStore() { body: JSON.stringify(ticketData) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, tickets: [newTicket, ...state.tickets], isLoading: false @@ -193,7 +192,7 @@ function createTicketsStore() { return newTicket; } catch (error) { console.error('Create ticket error:', error); - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to create ticket' @@ -210,14 +209,14 @@ function createTicketsStore() { body: JSON.stringify({ content }) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, comments: [...state.comments, comment] })); return comment; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to add comment' })); @@ -247,14 +246,14 @@ function createTicketsStore() { const attachment = await response.json(); - update(state => ({ + update((state: TicketsState) => ({ ...state, attachments: [...state.attachments, attachment] })); return attachment; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to upload attachment' })); @@ -270,15 +269,15 @@ function createTicketsStore() { body: JSON.stringify({ resolution }) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: state.currentTicket?.id === ticketId ? updatedTicket : state.currentTicket, - tickets: state.tickets.map(t => t.id === ticketId ? updatedTicket : t) + tickets: state.tickets.map((t: Ticket) => t.id === ticketId ? updatedTicket : t) })); return updatedTicket; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to close ticket' })); @@ -288,12 +287,12 @@ function createTicketsStore() { // Clear error clearError: () => { - update(state => ({ ...state, error: null })); + update((state: TicketsState) => ({ ...state, error: null })); }, // Clear current ticket clearCurrentTicket: () => { - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: null, comments: [], diff --git a/frontend-internal/src/routes/tenants/+page.svelte b/frontend-internal/src/routes/tenants/+page.svelte index cf633d5..8185b30 100644 --- a/frontend-internal/src/routes/tenants/+page.svelte +++ b/frontend-internal/src/routes/tenants/+page.svelte @@ -43,11 +43,15 @@ async function handleSubmit() { try { if (editingTenant) { - await api.put(`/tenants/${editingTenant.id}`, formData); - toast.success('Cliente actualizado'); + // Asegurarse de enviar el campo "status" correctamente + const updatedData = { ...formData, status: formData.is_active ? 'active' : 'inactive' }; + await api.put(`/tenants/${editingTenant.id}`, updatedData); + toast.success(`Cliente ${formData.is_active ? 'activado' : 'desactivado'} correctamente`); } else { - await api.post('/tenants/', formData); - toast.success('Cliente creado'); + // Asegurarse de enviar el campo "status" al crear un cliente + const newData = { ...formData, status: formData.is_active ? 'active' : 'inactive' }; + await api.post('/tenants/', newData); + toast.success('Cliente creado correctamente'); } showModal = false; loadTenants(); @@ -152,6 +156,11 @@ + {#if editingTenant} + + {/if} diff --git a/frontend-internal/vite.config.js b/frontend-internal/vite.config.js index 7e41c3f..9854a1a 100644 --- a/frontend-internal/vite.config.js +++ b/frontend-internal/vite.config.js @@ -1,24 +1,24 @@ -import { sveltekit } from '@sveltejs/kit/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit()], - server: { - port: 3000, - host: '0.0.0.0', - proxy: { - '/api/v1': { - target: 'http://servicemanager-backend:8000', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') - } - } - }, - preview: { - port: 3000, - host: '0.0.0.0' - }, - build: { - target: 'esnext' - } + plugins: [sveltekit()], + server: { + port: 3000, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://servicemanager-backend:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + preview: { + port: 3000, + host: '0.0.0.0' + }, + build: { + target: 'esnext' + } });