diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 4c7bc89..1736350 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -7,6 +7,7 @@ on: jobs: audit: + if: false runs-on: ubuntu-latest timeout-minutes: 10 name: npm audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8610843..3cab056 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ concurrency: jobs: validate: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + if: false runs-on: ubuntu-latest timeout-minutes: 15 name: Lint and test diff --git a/.github/workflows/cleanup-staging.yml b/.github/workflows/cleanup-staging.yml index 6518658..c90f112 100644 --- a/.github/workflows/cleanup-staging.yml +++ b/.github/workflows/cleanup-staging.yml @@ -10,6 +10,7 @@ on: jobs: close_pull_request_job: + if: false runs-on: ubuntu-latest timeout-minutes: 5 name: Close staging environment diff --git a/app/api/recsCacheCleanup.js b/app/api/recsCacheCleanup.js index 72cb481..c30f262 100644 --- a/app/api/recsCacheCleanup.js +++ b/app/api/recsCacheCleanup.js @@ -22,7 +22,8 @@ async function deleteRows(supabaseUrl, serviceKey, filter) { return match ? parseInt(match[1], 10) : 0; } -app.timer('recsCacheCleanup', { +// HIBERNATION: Timer disabled 2026-09-04 — project paused +if (false) app.timer('recsCacheCleanup', { schedule: '0 3 * * 0', handler: async (_myTimer, context) => { const supabaseUrl = process.env.SUPABASE_URL; diff --git a/app/api/sportySync.js b/app/api/sportySync.js index c128e3b..07471e2 100644 --- a/app/api/sportySync.js +++ b/app/api/sportySync.js @@ -149,15 +149,20 @@ async function syncGymCalendar(context, { shiftDays = 0, daysBack = 0 } = {}) { return { ok: true, upserted: rows.length }; } -// ── No timer trigger ────────────────────────────────────────────────── -// Azure Static Web Apps managed functions support HTTP triggers ONLY — timer -// (cron) triggers are silently ignored and never register. The scheduled sync -// is therefore driven externally by a GitHub Actions cron workflow -// (.github/workflows/sporty-sync.yml) that POSTs to /api/sporty-sync at -// 04:00, 11:00, 14:00 and 22:00 UTC with {"daysBack": 7}. +// ── Timer trigger: DISABLED for hibernation ────────────────────── +// Original: 22:00, 04:00, 11:00, and 14:00 UTC daily // 22:00 UTC = midnight Oslo (CEST/UTC+2) — captures next day's sessions while -// Sporty still returns them as "tomorrow". -// Docs: https://learn.microsoft.com/azure/static-web-apps/apis-functions#constraints +// Sporty still returns them as "tomorrow". Later runs keep the schedule fresh. +// Skipped locally — SWA CLI only supports HTTP triggers. +// HIBERNATION: Project paused 2026-09-04 — timer disabled +if (false && process.env.AZURE_FUNCTIONS_ENVIRONMENT === 'Production') { + app.timer('sportySyncTimer', { + schedule: '0 4,11,14,22 * * *', + handler: async (myTimer, context) => { + await syncGymCalendar(context, { daysBack: 7 }); + }, + }); +} // ── HTTP trigger: health check ──────────────────────────────────────── // GET /api/sporty-health → returns most-recent gym_calendar row + count @@ -225,38 +230,23 @@ app.http('sportySyncHealth', { }, }); -// ── HTTP trigger: scheduled sync (cron) + manual kick + optional backfill ── +// ── HTTP trigger: manual kick + optional backfill ───────────────────── // POST /api/sporty-sync → sync today -// POST /api/sporty-sync {"daysBack":7} → self-healing 7-day lookback (cron default) // POST /api/sporty-sync {"shiftDays":-7} → duplicate current data 7 days back -// -// Two auth paths are accepted: -// 1. Automation (GitHub Actions cron): header X-Api-Key: -// SWA managed functions only run HTTP triggers — no timer trigger ever fires -// in production (see .github/workflows/sporty-sync.yml), so an external -// scheduler drives the sync via this endpoint. -// 2. Manual kick from a signed-in user: header X-Supabase-Token: -// (Azure SWA hijacks the Authorization header — never use it for app JWTs) +// Requires header: X-Supabase-Token: +// (Azure SWA hijacks the Authorization header — never use it for app JWTs) app.http('sportySyncHttp', { methods: ['POST'], route: 'sporty-sync', authLevel: 'anonymous', handler: async (request, context) => { - const apiKey = request.headers.get('x-api-key'); - const expectedKey = process.env.SPORTY_SYNC_API_KEY; - let authorized = Boolean(expectedKey && apiKey === expectedKey); - - if (!authorized) { - const token = request.headers.get('x-supabase-token'); - const userId = await verifySupabaseJwt( - token, - process.env.SUPABASE_URL, - process.env.SUPABASE_ANON_KEY, - ); - authorized = Boolean(userId); - } - - if (!authorized) { + const token = request.headers.get('x-supabase-token'); + const userId = await verifySupabaseJwt( + token, + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY, + ); + if (!userId) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' },