feat(hosting): add HostWizard platform module and fix migration chain
Some checks failed
Some checks failed
- Add complete hosting module (models, routes, schemas, services, templates, migrations) - Add HostWizard platform to init_production seed (code=hosting, domain=hostwizard.lu) - Fix cms_002 migration down_revision to z_unique_subdomain_domain - Fix prospecting_001 migration to chain after cms_002 (remove branch label) - Add hosting/prospecting version_locations to alembic.ini - Fix admin_services delete endpoint to use proper response model - Add hostwizard.lu to deployment docs (DNS, Caddy, Cloudflare) - Add hosting and prospecting user journey docs to mkdocs nav Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
128
app/modules/hosting/services/client_service_service.py
Normal file
128
app/modules/hosting/services/client_service_service.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# app/modules/hosting/services/client_service_service.py
|
||||
"""
|
||||
Client service CRUD service.
|
||||
|
||||
Manages operational tracking for hosted site services (domains, email, SSL, etc.).
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.modules.hosting.exceptions import ClientServiceNotFoundException
|
||||
from app.modules.hosting.models import (
|
||||
ClientService,
|
||||
ClientServiceStatus,
|
||||
ServiceType,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientServiceService:
|
||||
"""Service for client service CRUD operations."""
|
||||
|
||||
def get_by_id(self, db: Session, service_id: int) -> ClientService:
|
||||
service = db.query(ClientService).filter(ClientService.id == service_id).first()
|
||||
if not service:
|
||||
raise ClientServiceNotFoundException(str(service_id))
|
||||
return service
|
||||
|
||||
def get_for_site(self, db: Session, hosted_site_id: int) -> list[ClientService]:
|
||||
return (
|
||||
db.query(ClientService)
|
||||
.filter(ClientService.hosted_site_id == hosted_site_id)
|
||||
.order_by(ClientService.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
def create(self, db: Session, hosted_site_id: int, data: dict) -> ClientService:
|
||||
service = ClientService(
|
||||
hosted_site_id=hosted_site_id,
|
||||
service_type=ServiceType(data["service_type"]),
|
||||
name=data["name"],
|
||||
description=data.get("description"),
|
||||
status=ClientServiceStatus.PENDING,
|
||||
billing_period=data.get("billing_period"),
|
||||
price_cents=data.get("price_cents"),
|
||||
currency=data.get("currency", "EUR"),
|
||||
addon_product_id=data.get("addon_product_id"),
|
||||
domain_name=data.get("domain_name"),
|
||||
registrar=data.get("registrar"),
|
||||
mailbox_count=data.get("mailbox_count"),
|
||||
expires_at=data.get("expires_at"),
|
||||
period_start=data.get("period_start"),
|
||||
period_end=data.get("period_end"),
|
||||
auto_renew=data.get("auto_renew", True),
|
||||
notes=data.get("notes"),
|
||||
)
|
||||
db.add(service)
|
||||
db.flush()
|
||||
logger.info("Created client service: %s (site_id=%d)", service.name, hosted_site_id)
|
||||
return service
|
||||
|
||||
def update(self, db: Session, service_id: int, data: dict) -> ClientService:
|
||||
service = self.get_by_id(db, service_id)
|
||||
|
||||
for field in [
|
||||
"name", "description", "status", "billing_period", "price_cents",
|
||||
"currency", "addon_product_id", "domain_name", "registrar",
|
||||
"mailbox_count", "expires_at", "period_start", "period_end",
|
||||
"auto_renew", "notes",
|
||||
]:
|
||||
if field in data and data[field] is not None:
|
||||
setattr(service, field, data[field])
|
||||
|
||||
db.flush()
|
||||
return service
|
||||
|
||||
def delete(self, db: Session, service_id: int) -> bool:
|
||||
service = self.get_by_id(db, service_id)
|
||||
db.delete(service)
|
||||
db.flush()
|
||||
logger.info("Deleted client service: %d", service_id)
|
||||
return True
|
||||
|
||||
def get_expiring_soon(self, db: Session, days: int = 30) -> list[ClientService]:
|
||||
"""Get services expiring within the given number of days."""
|
||||
cutoff = datetime.now(UTC) + timedelta(days=days)
|
||||
return (
|
||||
db.query(ClientService)
|
||||
.filter(
|
||||
ClientService.expires_at.isnot(None),
|
||||
ClientService.expires_at <= cutoff,
|
||||
ClientService.status == ClientServiceStatus.ACTIVE,
|
||||
)
|
||||
.order_by(ClientService.expires_at.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
def get_all(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
page: int = 1,
|
||||
per_page: int = 20,
|
||||
service_type: str | None = None,
|
||||
status: str | None = None,
|
||||
) -> tuple[list[ClientService], int]:
|
||||
"""Get all services with pagination and filters."""
|
||||
query = db.query(ClientService)
|
||||
|
||||
if service_type:
|
||||
query = query.filter(ClientService.service_type == service_type)
|
||||
if status:
|
||||
query = query.filter(ClientService.status == status)
|
||||
|
||||
total = query.count()
|
||||
services = (
|
||||
query.order_by(ClientService.created_at.desc())
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
.all()
|
||||
)
|
||||
return services, total
|
||||
|
||||
|
||||
client_service_service = ClientServiceService()
|
||||
Reference in New Issue
Block a user