left4me/l4d2web/services/global_overlays.py
mwiegand ffc4cdbd7d
refactor(l4d2-web): remove legacy external overlay type
The workshop + managed-global overlay surface fully covers the
admin-SFTP flow that 'external' was a placeholder for. Drop the type
from the model defaults, builder registry, routes, template, and
tests, and add migration 0004 that deletes any leftover external
rows along with their blueprint and job references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:31:04 +02:00

112 lines
3.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import os
from sqlalchemy import select
from sqlalchemy.orm import Session
from l4d2host.paths import get_left4me_root
from l4d2web.models import GlobalOverlaySource, Job, Overlay
from l4d2web.services.overlay_creation import generate_overlay_path
@dataclass(frozen=True)
class ManagedGlobalOverlay:
name: str
overlay_type: str
source_type: str
source_url: str
GLOBAL_OVERLAYS = (
ManagedGlobalOverlay(
name="l4d2center-maps",
overlay_type="l4d2center_maps",
source_type="l4d2center_csv",
source_url="https://l4d2center.com/maps/servers/index.csv",
),
ManagedGlobalOverlay(
name="cedapug-maps",
overlay_type="cedapug_maps",
source_type="cedapug_custom_page",
source_url="https://cedapug.com/custom",
),
)
MANAGED_GLOBAL_OVERLAY_TYPES = {overlay.overlay_type for overlay in GLOBAL_OVERLAYS}
USER_CREATABLE_TYPES = {"workshop"}
ADMIN_CREATABLE_TYPES = {"workshop"}
def is_creatable_overlay_type(overlay_type: str, *, admin: bool) -> bool:
allowed = ADMIN_CREATABLE_TYPES if admin else USER_CREATABLE_TYPES
return overlay_type in allowed
def ensure_global_overlays(session: Session) -> set[str]:
created_sources: set[str] = set()
for managed in GLOBAL_OVERLAYS:
overlay = session.scalar(
select(Overlay).where(Overlay.name == managed.name, Overlay.user_id.is_(None))
)
overlay_created = overlay is None
if overlay is None:
overlay = Overlay(name=managed.name, path="", type=managed.overlay_type, user_id=None)
session.add(overlay)
session.flush()
overlay.path = generate_overlay_path(overlay.id)
else:
overlay.type = managed.overlay_type
overlay.user_id = None
if not overlay.path:
overlay.path = generate_overlay_path(overlay.id)
target = get_left4me_root() / "overlays" / overlay.path
os.makedirs(target, exist_ok=not overlay_created)
source = session.scalar(
select(GlobalOverlaySource).where(GlobalOverlaySource.source_key == managed.name)
)
if source is None:
source = GlobalOverlaySource(
overlay_id=overlay.id,
source_key=managed.name,
source_type=managed.source_type,
source_url=managed.source_url,
)
session.add(source)
created_sources.add(managed.name)
else:
source.overlay_id = overlay.id
source.source_type = managed.source_type
source.source_url = managed.source_url
session.flush()
return created_sources
def enqueue_refresh_global_overlays(session: Session, *, user_id: int | None) -> Job:
existing = session.scalar(
select(Job)
.where(
Job.operation == "refresh_global_overlays",
Job.state.in_({"queued", "running", "cancelling"}),
)
.order_by(Job.created_at, Job.id)
)
if existing is not None:
return existing
job = Job(
user_id=user_id,
server_id=None,
overlay_id=None,
operation="refresh_global_overlays",
state="queued",
)
session.add(job)
session.flush()
return job