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>
123 lines
4.3 KiB
Python
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())
|