free-apis-collection commited on
Commit
d77da37
·
verified ·
1 Parent(s): b46a931

Deploy 7de0c2ab99518aefca3cce1496d6b74cae82705f

Browse files
Files changed (3) hide show
  1. app/main.py +10 -0
  2. app/services/cron_scheduler.py +52 -9
  3. start.sh +5 -0
app/main.py CHANGED
@@ -86,7 +86,17 @@ async def lifespan(app: FastAPI):
86
  asyncio.create_task(_self_ping())
87
  else:
88
  logger.info("self-ping disabled (SELF_PING_ENABLED=false) — not scheduling")
 
 
 
 
 
 
 
 
89
  cron_scheduler = start_cron_scheduler()
 
 
90
  yield
91
  if cron_scheduler is not None:
92
  cron_scheduler.shutdown(wait=False)
 
86
  asyncio.create_task(_self_ping())
87
  else:
88
  logger.info("self-ping disabled (SELF_PING_ENABLED=false) — not scheduling")
89
+ if settings.cron_enabled:
90
+ logger.info(
91
+ "scheduling frontend credit crons -> %s/api/cron + %s/api/v1/cron (daily 00:00)",
92
+ (settings.frontend_base_url or "").rstrip("/") or "(FRONTEND_BASE_URL missing)",
93
+ (settings.frontend_base_url or "").rstrip("/") or "(FRONTEND_BASE_URL missing)",
94
+ )
95
+ else:
96
+ logger.info("frontend credit crons disabled (CRON_ENABLED=false) — not scheduling /api/cron nor /api/v1/cron")
97
  cron_scheduler = start_cron_scheduler()
98
+ if cron_scheduler is None:
99
+ logger.warning("frontend credit cron scheduler NOT running — /api/cron and /api/v1/cron will not fire (see reason above)")
100
  yield
101
  if cron_scheduler is not None:
102
  cron_scheduler.shutdown(wait=False)
app/services/cron_scheduler.py CHANGED
@@ -46,14 +46,28 @@ def resolve_timezone() -> Any:
46
  def _targets() -> list[tuple[str, str]]:
47
  base = (settings.frontend_base_url or "").rstrip("/")
48
  if not base:
 
 
 
 
49
  return []
50
  out = []
51
  for path, secret_field in ROUTES:
52
  token = (getattr(settings, secret_field, "") or "").strip()
53
  if not token:
54
- logger.warning("cron route skipped (secret missing)", extra={"route": path})
 
 
 
55
  continue
56
- out.append((f"{base}{path}", token))
 
 
 
 
 
 
 
57
  return out
58
 
59
 
@@ -64,10 +78,13 @@ async def _fire_one(url: str, token: str) -> dict[str, Any]:
64
  async with httpx.AsyncClient(timeout=120.0) as client:
65
  resp = await client.post(url, headers={"Authorization": f"Bearer {token}"})
66
  ok = resp.status_code == 200
67
- logger.info("cron fired", extra={"url": url, "status": resp.status_code})
 
 
 
68
  return {"url": url, "status": resp.status_code, "ok": ok}
69
  except Exception as exc: # noqa: BLE001
70
- logger.warning("cron fire failed", extra={"url": url, "error": str(exc)})
71
  return {"url": url, "status": None, "ok": False, "error": str(exc)}
72
 
73
 
@@ -77,21 +94,44 @@ async def fire_frontend_crons() -> list[dict[str, Any]]:
77
 
78
  targets = _targets()
79
  if not targets:
80
- logger.warning("cron tick: nothing configured, skipping")
81
  return []
 
82
  results = await asyncio.gather(*(_fire_one(url, token) for url, token in targets))
 
 
83
  return list(results)
84
 
85
 
86
  def start_cron_scheduler() -> AsyncIOScheduler | None:
87
  """Start the daily 00:00 scheduler. Returns None when disabled/unconfigured."""
88
  if not settings.cron_enabled:
89
- logger.info("cron scheduler disabled (CRON_ENABLED=false)")
90
  return None
91
- if not (settings.frontend_base_url or "").strip():
92
- logger.warning("cron scheduler autoboot skipped (FRONTEND_BASE_URL not set)")
 
 
 
 
93
  return None
94
  tz = resolve_timezone()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  scheduler = AsyncIOScheduler(timezone=tz)
96
  scheduler.add_job(
97
  fire_frontend_crons,
@@ -103,5 +143,8 @@ def start_cron_scheduler() -> AsyncIOScheduler | None:
103
  )
104
  scheduler.start()
105
  nxt = scheduler.get_job("midnight-frontend-crons").next_run_time
106
- logger.info("cron scheduler started: daily 00:00", extra={"timezone": str(tz), "next_run": str(nxt)})
 
 
 
107
  return scheduler
 
46
  def _targets() -> list[tuple[str, str]]:
47
  base = (settings.frontend_base_url or "").rstrip("/")
48
  if not base:
49
+ logger.warning(
50
+ "frontend credit cron disabled: FRONTEND_BASE_URL not set — "
51
+ "skipping both /api/cron and /api/v1/cron"
52
+ )
53
  return []
54
  out = []
55
  for path, secret_field in ROUTES:
56
  token = (getattr(settings, secret_field, "") or "").strip()
57
  if not token:
58
+ logger.warning(
59
+ "frontend credit cron disabled for route (secret missing, skipping)",
60
+ extra={"route": path, "secret": secret_field},
61
+ )
62
  continue
63
+ url = f"{base}{path}"
64
+ logger.info(
65
+ "frontend credit cron enabled: %s -> %s (daily 00:00 auth=%s)",
66
+ path,
67
+ url,
68
+ "Bearer ***",
69
+ )
70
+ out.append((url, token))
71
  return out
72
 
73
 
 
78
  async with httpx.AsyncClient(timeout=120.0) as client:
79
  resp = await client.post(url, headers={"Authorization": f"Bearer {token}"})
80
  ok = resp.status_code == 200
81
+ if ok:
82
+ logger.info("frontend credit cron fired ok", extra={"url": url, "status": resp.status_code})
83
+ else:
84
+ logger.warning("frontend credit cron fired non-200", extra={"url": url, "status": resp.status_code})
85
  return {"url": url, "status": resp.status_code, "ok": ok}
86
  except Exception as exc: # noqa: BLE001
87
+ logger.warning("frontend credit cron fire failed", extra={"url": url, "error": str(exc)})
88
  return {"url": url, "status": None, "ok": False, "error": str(exc)}
89
 
90
 
 
94
 
95
  targets = _targets()
96
  if not targets:
97
+ logger.warning("frontend credit cron tick: nothing configured, skipping both /api/cron and /api/v1/cron")
98
  return []
99
+ logger.info("frontend credit cron tick: firing %d route(s) (/api/cron, /api/v1/cron)", len(targets))
100
  results = await asyncio.gather(*(_fire_one(url, token) for url, token in targets))
101
+ ok_count = sum(1 for r in results if r.get("ok"))
102
+ logger.info("frontend credit cron tick done: %d/%d ok", ok_count, len(results))
103
  return list(results)
104
 
105
 
106
  def start_cron_scheduler() -> AsyncIOScheduler | None:
107
  """Start the daily 00:00 scheduler. Returns None when disabled/unconfigured."""
108
  if not settings.cron_enabled:
109
+ logger.info("frontend credit cron disabled (CRON_ENABLED=false) — not scheduling /api/cron nor /api/v1/cron")
110
  return None
111
+ base = (settings.frontend_base_url or "").strip()
112
+ if not base:
113
+ logger.warning(
114
+ "frontend credit cron disabled: FRONTEND_BASE_URL not set — "
115
+ "not scheduling /api/cron nor /api/v1/cron"
116
+ )
117
  return None
118
  tz = resolve_timezone()
119
+ # Per-route boot log (mirrors self-ping style): on/off for each cron.
120
+ for path, secret_field in ROUTES:
121
+ token = (getattr(settings, secret_field, "") or "").strip()
122
+ url = f"{base.rstrip('/')}{path}"
123
+ if token:
124
+ logger.info(
125
+ "scheduling frontend credit cron -> %s (daily 00:00 auth=%s)",
126
+ url,
127
+ "Bearer ***",
128
+ )
129
+ else:
130
+ logger.warning(
131
+ "frontend credit cron NOT scheduled -> %s (%s not set)",
132
+ url,
133
+ secret_field.upper(),
134
+ )
135
  scheduler = AsyncIOScheduler(timezone=tz)
136
  scheduler.add_job(
137
  fire_frontend_crons,
 
143
  )
144
  scheduler.start()
145
  nxt = scheduler.get_job("midnight-frontend-crons").next_run_time
146
+ logger.info(
147
+ "frontend credit cron scheduler started: daily 00:00 -> /api/cron + /api/v1/cron",
148
+ extra={"timezone": str(tz), "next_run": str(nxt)},
149
+ )
150
  return scheduler
start.sh CHANGED
@@ -91,5 +91,10 @@ python -c "import fastapi, uvicorn, httpx, pydantic" 2>/dev/null \
91
  HOST="${HOST:-0.0.0.0}"
92
  PORT="${PORT:-7860}"
93
  log "Self-ping: SELF_PING_ENABLED=${SELF_PING_ENABLED:-true} SELF_PING_URL=${SELF_PING_URL:-'(derived from SPACE_ID)'} interval=${SELF_PING_INTERVAL:-600}s"
 
 
 
 
 
94
  log "Starting uvicorn: host=$HOST port=$PORT workers=1 app=app.main:app"
95
  exec python -m uvicorn app.main:app --host "$HOST" --port "$PORT" --workers 1
 
91
  HOST="${HOST:-0.0.0.0}"
92
  PORT="${PORT:-7860}"
93
  log "Self-ping: SELF_PING_ENABLED=${SELF_PING_ENABLED:-true} SELF_PING_URL=${SELF_PING_URL:-'(derived from SPACE_ID)'} interval=${SELF_PING_INTERVAL:-600}s"
94
+ _cron_base="${FRONTEND_BASE_URL:-'(missing)'}"
95
+ _cron_secret_state="missing"; [ -n "${CRON_SECRET:-}" ] && _cron_secret_state="set"
96
+ _cron_v1_state="missing"; [ -n "${EXTERNAL_API_CRON_TOKEN:-}" ] && _cron_v1_state="set"
97
+ log "Frontend credit crons: CRON_ENABLED=${CRON_ENABLED:-true} CRON_TIMEZONE=${CRON_TIMEZONE:-'(system)'} FRONTEND_BASE_URL=${_cron_base} /api/cron(${_cron_secret_state}) /api/v1/cron(${_cron_v1_state}) daily=00:00"
98
+ unset _cron_base _cron_secret_state _cron_v1_state
99
  log "Starting uvicorn: host=$HOST port=$PORT workers=1 app=app.main:app"
100
  exec python -m uvicorn app.main:app --host "$HOST" --port "$PORT" --workers 1