Capa de Servicios ================== NoxPanel sigue un patrón de **capa de servicios** (Service Layer) que encapsula la lógica de negocio separándola de las vistas y los modelos. Cada servicio se encarga de un dominio funcional específico y coordina operaciones entre modelos Django e integraciones externas. HostingService --------------- **Ubicación**: ``hosting/services.py`` Servicio principal para la gestión de hosting multi-tenant. Abstrae las operaciones del motor de hosting con lógica de cuotas, validación y aislamiento de tenant. Constructor ~~~~~~~~~~~~ .. code-block:: python HostingService(tenant: Optional[HostingTenant] = None) Recibe un tenant opcional. Cuando se proporciona, todas las operaciones se limitan a ese tenant. Si es ``None``, se permite el modo admin. Métodos de Gestión de Clientes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``create_tenant_client(tenant, user) → ISPConfigClient`` Crea un cliente en el motor de hosting para un par tenant/usuario. Genera credenciales únicas con formato ``{tenant.slug}_{user.username}``, invoca ``ISPConfigAPIClient.create_client()`` con las cuotas del tenant, y persiste el mapeo en la base de datos Django. Métodos de Sitios Web ~~~~~~~~~~~~~~~~~~~~~~ ``create_site(tenant, domain, ip_address="*") → HostingSite`` Crea un sitio web verificando la cuota del tenant. Obtiene o crea el cliente del motor de hosting necesario, llama a la API del motor de hosting usando credenciales admin (``create_site_for_client``), y registra el sitio en Django con el ID del recurso retornado. ``list_sites(tenant) → List[HostingSite]`` Lista todos los sitios activos del tenant. ``get_site(tenant, site_id) → Optional[HostingSite]`` Obtiene un sitio específico con validación de pertenencia al tenant. ``delete_site(tenant, site_id) → bool`` Elimina un sitio del motor de hosting mediante la API admin y lo marca como ``is_active=False`` en Django (soft-delete). ``request_ssl_certificate(site) → Dict`` Solicita un certificado Let's Encrypt para un sitio. Obtiene los datos actuales del sitio desde el motor de hosting, activa ``ssl_letsencrypt=y`` y ejecuta un update completo (el motor de hosting requiere enviar todos los campos). Métodos de Bases de Datos ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``create_database(tenant, name, db_user=None, site=None) → HostingDatabase`` Crea una base de datos MySQL verificando cuotas. Genera usuario y contraseña, llama a ``ISPConfigAPIClient.create_database()`` que primero crea el usuario de DB y luego la base de datos. Registra en Django. ``list_databases(tenant) → List[HostingDatabase]`` Lista bases de datos activas del tenant. Workflow All-in-One: provision_full_site ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``provision_full_site(tenant, domain, ...) → Dict`` Workflow completo tipo cPanel que crea todos los recursos de un dominio en un solo paso: 1. **Site**: Crea el sitio web en el motor de hosting. 2. **DNS Zone**: Crea la zona DNS con registros por defecto (A, CNAME, MX, TXT/SPF, A para mail). 3. **Mail Domain**: Configura el dominio de email en el motor de hosting. 4. **FTP Account**: Crea cuenta FTP con usuario ``{slug}_{dominio}`` y permisos correctos (uid/gid del motor de hosting). 5. **Database**: Crea base de datos MySQL con nombre sanitizado. 6. **Traefik Config**: Genera configuración de Traefik para SSL automático. Cada paso es independiente y tolerante a fallos: si uno falla, los demás continúan. El resultado incluye un campo ``errors`` con los errores ocurridos. **Parámetros opcionales**: ``create_dns``, ``create_mail``, ``create_ftp``, ``create_db``, ``create_traefik`` (todos ``True`` por defecto). Sincronización ~~~~~~~~~~~~~~~ ``sync_tenant_from_hosting_engine(tenant) → Dict`` Sincroniza recursos de un tenant desde el motor de hosting hacia Django. Consulta la API admin para obtener sitios y bases de datos del cliente del motor de hosting asociado al tenant y realiza ``update_or_create`` en los modelos Django. Actualiza el campo ``last_sync`` del ``ISPConfigClient``. Métodos Helper Internos ~~~~~~~~~~~~~~~~~~~~~~~~~ - ``_get_admin_client()``: Obtiene instancia del cliente del motor de hosting con credenciales admin. - ``_get_tenant_client(ispconfig_client)``: Obtiene cliente del motor de hosting con credenciales del tenant. - ``_generate_password(length=20)``: Genera contraseña segura usando ``secrets.choice()``. - ``_get_ispconfig_client_for_tenant(tenant)``: Busca el ``ISPConfigClient`` activo del tenant. - ``_create_dns_zone_for_site()``: Crea zona DNS local y en el motor de hosting. - ``_create_ispconfig_dns_records()``: Crea registros A, MX, TXT en el motor de hosting. - ``_create_default_dns_records()``: Crea registros DNS locales en Django. - ``_create_mail_domain_for_site()``: Crea dominio de mail en el motor de hosting. - ``_create_default_ftp_account()``: Crea cuenta FTP con uid/gid del motor de hosting. - ``_create_default_database()``: Crea base de datos con nombre sanitizado. Cliente del Motor de Hosting (API) ----------------------------------- **Ubicación**: ``integrations/ispconfig_client.py`` Cliente Python para la API JSON del motor de hosting ubicada en ``/remote/json.php``. Todas las operaciones usan autenticación por sesión. Autenticación y Conexión ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``__init__(base_url=None, username=None, password=None)`` Inicializa el cliente. Si no se proporcionan credenciales, las lee de ``settings.py`` o del archivo compartido de contraseñas del motor de hosting. ``ping() → Dict`` Verifica conectividad: realiza login y retorna ``{"ok": True}``. ``_login() → str`` Autentica con el motor de hosting y obtiene un ``session_id`` reutilizable. Lanza ``ISPConfigNotConfigured`` si las credenciales faltan o son inválidas. ``logout() → Dict`` Cierra la sesión activa del motor de hosting. ``_call(method, **params) → Dict`` Método base para todas las llamadas API. Realiza ``POST`` a ``/remote/json.php?{method}`` con cuerpo JSON. Retorna ``{"ok": True, "response": ...}`` en éxito o ``{"ok": False, "error": ...}`` en fallo. Gestión de Clientes ~~~~~~~~~~~~~~~~~~~~ ``list_clients() → Dict`` Lista todos los clientes del motor de hosting con sus datos completos. ``get_client(client_id) → Dict`` Obtiene información detallada de un cliente específico. ``create_client(username, password, company_name, contact_name, email, **kwargs) → Dict`` Crea un cliente con todas las cuotas configurables (web, DB, mail, FTP, DNS, shell). Si el username ya existe, busca el cliente existente. Gestión de Sitios Web ~~~~~~~~~~~~~~~~~~~~~~ ``list_sites() / list_sites_by_client(client_id) → Dict`` Listar sitios web (todos o por cliente). ``create_site(domain, ip_address, **kwargs) → Dict`` Crea sitio básico (requiere ``client_id`` en kwargs). ``create_site_for_client(client_id, domain, ip_address, **kwargs) → Dict`` Versión completa con todos los campos requeridos por el motor de hosting (PHP-FPM, subdomain www, CGI, SSI, suexec, etc.). ``delete_site(site_id) → Dict`` Elimina un sitio web. Gestión de FTP, Mail, DNS, Databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cada recurso tiene métodos CRUD consistentes: - **FTP**: ``list_ftp_users``, ``create_ftp_user``, ``update_ftp_user``, ``delete_ftp_user``. - **Mail Domains**: ``list_mail_domains``, ``create_mail_domain``, ``delete_mail_domain``. - **Mailboxes**: ``list_mailboxes``, ``create_mailbox``, ``update_mailbox``, ``delete_mailbox``. - **Mail Aliases**: ``list_mail_aliases``, ``create_mail_alias``, ``delete_mail_alias``. - **Databases**: ``create_database`` (crea usuario + DB), ``delete_database``. - **DNS Zones**: ``list_dns_zones``, ``create_dns_zone``, ``delete_dns_zone``. - **DNS Records**: ``list_dns_records``, ``create_dns_record``, ``update_dns_record``, ``delete_dns_record``. Usa métodos específicos por tipo (``dns_a_add``, ``dns_mx_add``, etc.). - **Shell Users**: ``list_shell_users``, ``create_shell_user``, ``delete_shell_user``. - **SSL**: ``request_letsencrypt_certificate(site_id, client_id)``. InvoiceService --------------- **Ubicación**: ``billing/services.py`` Servicio para generación y gestión de facturas. ``generate_invoice_for_tenant(tenant, period_start, period_end, ...) → Invoice`` Genera una factura completa para un tenant: 1. Crea la factura con número secuencial (``INV-{año}-{seq:04d}``). 2. Snapshot de datos fiscales del ``BillingProfile`` (inmutabilidad). 3. Agrega líneas por cada suscripción activa con su precio efectivo. 4. Agrega líneas por registros de uso no facturados del periodo. 5. Marca los ``UsageRecord`` como ``invoiced=True``. 6. Recalcula subtotal, IVA (21% default) y total. Ejecuta todo en una transacción atómica (``@transaction.atomic``). ``mark_invoice_paid(invoice, gateway, gateway_payment_id, amount) → Payment`` Marca una factura como pagada: crea un registro ``Payment`` con estado ``completed`` y actualiza el estado de la factura a ``paid``. ``_next_invoice_number() → str`` Genera el siguiente número de factura secuencial por año. ``_format_address(profile) → str`` Formatea la dirección fiscal del perfil de facturación. UsageTracker ------------- **Ubicación**: ``billing/services.py`` Servicio para registrar el consumo de recursos facturables. ``record_vm_usage(subscription, vm_name, vm_id, hours=1) → UsageRecord`` Registra uso horario de una máquina virtual. Calcula el total como ``hours × effective_price`` de la suscripción. ``record_hosting_usage(subscription, site_name, site_id, days=1) → UsageRecord`` Registra uso diario de hosting. Calcula el precio diario como ``effective_price / 30`` y el total como ``days × daily_price``. Estos métodos son invocados por las tareas periódicas de Celery: - ``billing.track_vm_usage_hourly``: Cada hora. - ``billing.track_hosting_usage_daily``: Cada 24 horas. TicketService -------------- **Ubicación**: ``ticketing/services.py`` Servicio principal del sistema de helpdesk. ``create_ticket(subject, body, requester, ...) → Ticket`` Crea un ticket nuevo con los siguientes pasos: 1. Instancia el ``Ticket`` con todos los datos proporcionados. 2. Busca y aplica la política SLA correspondiente a la prioridad. 3. Calcula las fechas límite: ``sla_first_response_due`` y ``sla_resolution_due``. 4. Guarda el ticket y crea el primer ``TicketMessage``. ``add_reply(ticket, body, author, is_agent, message_type) → TicketMessage`` Añade una respuesta al ticket: - Si es de agente y no hay primera respuesta registrada, marca ``first_responded_at``. - Si es de agente, cambia el estado a ``pending`` (esperando cliente). - Si es del cliente y el estado era ``pending``, cambia a ``open``. ``assign_ticket(ticket, agent)`` Asigna un ticket a un agente. Si el ticket estaba en estado ``new``, lo cambia a ``open``. ``resolve_ticket(ticket)`` Marca el ticket como ``resolved`` y registra ``resolved_at``. ``close_ticket(ticket)`` Cierra el ticket definitivamente y registra ``closed_at``. EmailSender ------------ **Ubicación**: ``ticketing/services.py`` Servicio para enviar respuestas de tickets por email. ``send_reply(ticket, message, mail_account=None)`` Envía una respuesta de ticket al email del solicitante: 1. Obtiene la cuenta de email activa (``MailAccount``). 2. Construye el email con headers ``In-Reply-To`` y ``References`` para mantener el threading de email. 3. Según el protocolo de la cuenta: - **IMAP/POP3**: Usa ``smtplib`` directamente con STARTTLS y credenciales descifradas con ``decrypt_value()``. - **OAuth2 Microsoft**: Delega a ``MicrosoftOAuth.send_email()``. - **OAuth2 Google**: Delega a ``GoogleOAuth.send_email()``. Soporta cuerpo en texto plano y HTML (``MIMEMultipart('alternative')``). Tareas Periódicas Celery -------------------------- Las siguientes tareas se ejecutan automáticamente según el schedule definido en ``settings.py`` (``CELERY_BEAT_SCHEDULE``): .. list-table:: :header-rows: 1 :widths: 35 15 50 * - Tarea - Frecuencia - Descripción * - ``billing.track_vm_usage_hourly`` - Cada hora - Registra uso horario de VMs activas usando ``UsageTracker``. * - ``billing.track_hosting_usage_daily`` - Cada 24h - Registra uso diario de sitios de hosting activos. * - ``billing.generate_monthly_invoices`` - Diaria - Verifica si es fin de mes y genera facturas para todos los tenants con suscripciones activas. * - ``billing.check_overdue_invoices`` - Cada hora - Marca facturas vencidas como ``overdue`` según su ``due_date``. * - ``ticketing.fetch_emails`` - Cada 5 min - Consulta las cuentas de email activas vía IMAP/OAuth2 y crea tickets automáticamente con los emails recibidos. * - ``ticketing.check_sla_breaches`` - Cada 15 min - Verifica tickets abiertos contra sus plazos SLA y genera alertas. * - ``ticketing.auto_close_resolved`` - Diaria - Cierra automáticamente tickets en estado ``resolved`` que lleven un periodo configurable sin actividad. Mixins Reutilizables --------------------- **Ubicación**: ``hosting/mixins.py`` Mixins que eliminan duplicación de código en ViewSets y vistas: ``is_admin_user(user) → bool`` Centraliza la verificación de permisos admin (superuser o rol admin). ``get_tenant_or_403(request) → HostingTenant`` Obtiene el tenant activo de la petición o lanza ``PermissionDenied``. ``TenantQuerySetMixin`` Mixin para ViewSets que filtra querysets por tenant automáticamente. Los admins ven todos los registros; los usuarios regulares solo los de su tenant. ``TenantCreateMixin`` Auto-asigna el tenant del usuario en operaciones ``perform_create()``. ``SoftDeleteMixin`` Implementa borrado suave (``is_active=False``) con validación de pertenencia al tenant y logging.