Prefer the latest year in the archive, normalize Instagram casing so stories show under IG/FB, sync pipeline dates on move, and stop stale plan re-renders from undoing a successful drag. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.7 KiB
Python
135 lines
4.7 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 * * * *"
|
|
# Runs inside the app container so $CRON_SECRET from Dokploy env is available.
|
|
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 };
|
|
};
|
|
const get = async (path) => {
|
|
const r = await fetch(path, { credentials: 'include' });
|
|
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, 2000), json };
|
|
};
|
|
|
|
// List application schedules (Dokploy v0.30)
|
|
const listed = await get(
|
|
'/api/trpc/schedule.list?batch=1&input=' +
|
|
encodeURIComponent(JSON.stringify({
|
|
'0': { json: { id: appId, scheduleType: 'application' } }
|
|
}))
|
|
);
|
|
const rows =
|
|
listed.json?.[0]?.result?.data?.json ||
|
|
listed.json?.[0]?.result?.data ||
|
|
[];
|
|
const existing = Array.isArray(rows)
|
|
? rows.filter((r) => (r?.name || '') === name)
|
|
: [];
|
|
|
|
for (const row of existing) {
|
|
const sid = row.scheduleId || row.id;
|
|
if (!sid) continue;
|
|
await post('/api/trpc/schedule.delete?batch=1', {
|
|
'0': { json: { scheduleId: sid } },
|
|
});
|
|
}
|
|
|
|
const created = await post('/api/trpc/schedule.create?batch=1', {
|
|
'0': {
|
|
json: {
|
|
name,
|
|
cronExpression: cron,
|
|
shellType: 'bash',
|
|
command,
|
|
applicationId: appId,
|
|
scheduleType: 'application',
|
|
enabled: true,
|
|
},
|
|
},
|
|
});
|
|
return { deleted: existing.length, 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())
|