Files
4ventures-client-portal/scripts/dokploy_ensure_pipeline_publish_cron.py
4Ventures 26dc411647 Harden scheduled Meta publish and auto-link Drive for pipeline creatives.
Schedule save auto-approves and publishes when due; cron recovers stale jobs every 5m. Drive sync auto-matches client folders under Clientes when not linked.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 13:33:41 +02:00

123 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Ensure / refresh Dokploy schedule for pipeline Meta publish (every 5 min)."""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT = Path("/Users/4ventures/4ventures-client-portal")
BPS = Path("/Users/4ventures/Projects/bps-facturacion")
BASE = "https://dokploy.4ventures.es"
APP_ID = "NZS797t9Rnq9Pj4mHiii3"
SCHEDULE_NAME = "pipeline-publish"
CRON_EXPR = "*/5 * * * *"
# Public URL + env expansion via bash -lc so CRON_SECRET from app env is used.
CMD = (
"bash -lc "
"'curl -sS -H \"Authorization: Bearer $CRON_SECRET\" "
"\"https://app.4ventures.es/api/cron/pipeline-publish?limit=20\"'"
)
def cred(key: str) -> str:
import os
v = os.environ.get(key, "").strip()
if v:
return v
for p in (BPS / ".env", ROOT / ".env.local"):
if not p.is_file():
continue
for line in p.read_text(encoding="utf-8").splitlines():
if line.startswith(f"{key}="):
return line.split("=", 1)[1].strip().strip('"').strip("'")
return ""
def main() -> int:
email = cred("DOKPLOY_EMAIL") or "marketing@4ventures.es"
password = cred("DOKPLOY_PASSWORD")
if not password:
print("missing DOKPLOY_PASSWORD", flush=True)
return 2
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1500, "height": 1200})
page.goto(BASE + "/", wait_until="domcontentloaded", timeout=90000)
page.wait_for_timeout(600)
if "/dashboard" not in page.url:
page.locator('input[type="email"], input[name="email"]').first.fill(email)
page.locator('input[type="password"]').first.fill(password)
page.locator('button[type="submit"]').first.click()
page.wait_for_url(re.compile(r"/dashboard"), timeout=90000)
print("login ok", flush=True)
result = page.evaluate(
"""async ({ appId, name, cron, command }) => {
const post = async (path, body) => {
const r = await fetch(path, {
method: 'POST',
credentials: 'include',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
const text = await r.text();
let json = null;
try { json = JSON.parse(text); } catch {}
return { ok: r.ok, status: r.status, text: text.slice(0, 1200), json };
};
// Delete existing schedules with same name
for (const listPath of [
'/api/schedule.all',
'/api/schedule.byApplicationId',
]) {
const listed = await post(listPath, { applicationId: appId });
const rows = Array.isArray(listed.json)
? listed.json
: listed.json?.data || listed.json?.schedules || [];
if (Array.isArray(rows)) {
for (const row of rows) {
const n = row?.name || row?.scheduleName;
const id = row?.scheduleId || row?.id;
if (n === name && id) {
await post('/api/schedule.delete', { scheduleId: id });
await post('/api/schedule.remove', { scheduleId: id });
}
}
}
}
const created = await post('/api/schedule.create', {
applicationId: appId,
name,
cron,
cronExpression: cron,
command,
shellType: 'bash',
enabled: true,
scheduleType: 'application',
});
return { created };
}""",
{
"appId": APP_ID,
"name": SCHEDULE_NAME,
"cron": CRON_EXPR,
"command": CMD,
},
)
print(json.dumps(result, indent=2)[:4000], flush=True)
ok = bool(result.get("created", {}).get("ok"))
browser.close()
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())