Platform bundle: marketing publish, IT ops, packaging, agents mesh.
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, File, HTTPException, UploadFile
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.db import fetch_all, fetch_one
|
||||
from app.services.social_publish import PLATFORMS, get_configured_channels, run_publish_job
|
||||
|
||||
router = APIRouter(prefix="/api/marketing", tags=["marketing-api"])
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
UPLOAD_DIR = BASE_DIR / "static" / "uploads" / "marketing"
|
||||
|
||||
|
||||
def _serialize_row(row: dict | None) -> dict | None:
|
||||
if not row:
|
||||
return None
|
||||
out = dict(row)
|
||||
for key, value in list(out.items()):
|
||||
if hasattr(value, "isoformat"):
|
||||
out[key] = value.isoformat()
|
||||
return out
|
||||
|
||||
|
||||
def _serialize_rows(rows: list[dict]) -> list[dict]:
|
||||
return [_serialize_row(row) for row in rows]
|
||||
|
||||
|
||||
class PublishRequest(BaseModel):
|
||||
text: str = Field(..., min_length=1, max_length=5000)
|
||||
image_url: str | None = None
|
||||
media_ids: list[int] = Field(default_factory=list)
|
||||
channels: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_marketing_media(file: UploadFile = File(...)) -> dict:
|
||||
data = await file.read()
|
||||
if not data:
|
||||
raise HTTPException(status_code=400, detail="empty file")
|
||||
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
ext = Path(file.filename or "upload.bin").suffix or ".bin"
|
||||
filename = f"{uuid4().hex}{ext.lower()}"
|
||||
path = UPLOAD_DIR / filename
|
||||
path.write_bytes(data)
|
||||
media_url = f"/static/uploads/marketing/{filename}"
|
||||
|
||||
row = fetch_one(
|
||||
"""
|
||||
INSERT INTO marketing_media (filename, original_name, file_path, media_url, mime_type, size_bytes, created_at)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, NOW())
|
||||
RETURNING id
|
||||
""",
|
||||
(
|
||||
filename,
|
||||
file.filename or filename,
|
||||
str(path),
|
||||
media_url,
|
||||
file.content_type or "application/octet-stream",
|
||||
len(data),
|
||||
),
|
||||
)
|
||||
return {"media_id": row["id"], "url": media_url}
|
||||
|
||||
|
||||
@router.post("/publish", status_code=202)
|
||||
def create_publish_job(body: PublishRequest, background_tasks: BackgroundTasks) -> dict:
|
||||
channels = [c.strip().lower() for c in body.channels if c.strip()]
|
||||
invalid = [c for c in channels if c not in PLATFORMS]
|
||||
if invalid:
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported channels: {', '.join(invalid)}")
|
||||
if not channels:
|
||||
channels = list(PLATFORMS)
|
||||
|
||||
row = fetch_one(
|
||||
"""
|
||||
INSERT INTO social_publish_jobs (text, image_url, media_ids, channels, status, created_at, updated_at)
|
||||
VALUES (%s, %s, %s::jsonb, %s::jsonb, %s, NOW(), NOW())
|
||||
RETURNING id
|
||||
""",
|
||||
(
|
||||
body.text,
|
||||
body.image_url,
|
||||
json.dumps(body.media_ids),
|
||||
json.dumps(channels),
|
||||
"queued",
|
||||
),
|
||||
)
|
||||
job_id = int(row["id"])
|
||||
background_tasks.add_task(run_publish_job, job_id, body.text, channels, body.image_url, body.media_ids)
|
||||
return {"job_id": job_id, "status": "queued", "channels": channels}
|
||||
|
||||
|
||||
@router.get("/publish/{job_id}")
|
||||
def get_publish_job(job_id: int) -> dict:
|
||||
row = fetch_one("SELECT * FROM social_publish_jobs WHERE id = %s", (job_id,))
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
return {"job": _serialize_row(row)}
|
||||
|
||||
|
||||
@router.get("/publish/history")
|
||||
def list_publish_history(limit: int = 50) -> dict:
|
||||
safe_limit = max(1, min(limit, 200))
|
||||
rows = fetch_all(
|
||||
"SELECT * FROM social_publish_jobs ORDER BY created_at DESC LIMIT %s",
|
||||
(safe_limit,),
|
||||
)
|
||||
return {"items": _serialize_rows(rows), "count": len(rows)}
|
||||
|
||||
|
||||
@router.get("/channels")
|
||||
def list_channels() -> dict:
|
||||
items = get_configured_channels()
|
||||
return {"items": items, "platforms": list(PLATFORMS)}
|
||||
Reference in New Issue
Block a user