📦 EqualifyEverything / equalify-hub

📄 equalifyStats.ts · 65 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import { getGitHubCache, getStaleGitHubCache, setGitHubCache } from '#src/utils/db';

// Equalify's Hasura endpoint. With GRAPHQL_ADMIN_SECRET set (Lambda env), queries run as
// admin so tables the anonymous role can't aggregate (e.g. blockers) are available.
const GRAPHQL_URL = process.env.GRAPHQL_URL || 'https://graphql.equalifyapp.com/v1/graphql';
const GRAPHQL_ADMIN_SECRET = process.env.GRAPHQL_ADMIN_SECRET;

// Synthetic cache key — reuses the GitHub response cache (1 min fresh, 7 day stale fallback)
const CACHE_KEY = 'equalify-stats://blockers';

// Last known values, used only if the API and cache are both unavailable (as of 2026-09-03)
const FALLBACK: BlockerStats = { total: 2509460, last30d: 256841 };

export interface BlockerStats {
    total: number;   // COUNT(*) FROM blockers — matches "Total Blockers Found" in Equalify's admin stats
    last30d: number; // blockers created in the last 30 days
}

async function graphql(query: string, variables?: Record<string, unknown>): Promise<any> {
    const response = await fetch(GRAPHQL_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'User-Agent': 'Equalify-Hub',
            ...(GRAPHQL_ADMIN_SECRET ? { 'X-Hasura-Admin-Secret': GRAPHQL_ADMIN_SECRET } : {}),
        },
        body: JSON.stringify({ query, variables }),
    });
    if (!response.ok) throw new Error(`Equalify GraphQL HTTP ${response.status}`);
    const json = await response.json();
    if (json.errors?.length) throw new Error(`Equalify GraphQL: ${json.errors[0].message}`);
    return json.data;
}

async function fetchBlockerStats(): Promise<BlockerStats> {
    const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
    const data = await graphql(`query ($since: timestamptz!) {
        total: blockers_aggregate { aggregate { count } }
        recent: blockers_aggregate(where: { created_at: { _gte: $since } }) { aggregate { count } }
    }`, { since });
    const total = data.total?.aggregate?.count;
    const last30d = data.recent?.aggregate?.count;
    if (typeof total !== 'number' || typeof last30d !== 'number') throw new Error('Equalify GraphQL: unexpected shape');
    return { total, last30d };
}

function isStats(v: any): v is BlockerStats {
    return typeof v?.total === 'number' && typeof v?.last30d === 'number';
}

export async function getBlockerStats(): Promise<BlockerStats> {
    const cached = await getGitHubCache(CACHE_KEY);
    if (isStats(cached)) return cached;

    try {
        const stats = await fetchBlockerStats();
        await setGitHubCache(CACHE_KEY, stats);
        return stats;
    } catch (error) {
        console.error('Error fetching Equalify blocker stats:', error);
        const stale = await getStaleGitHubCache(CACHE_KEY);
        return isStats(stale) ? stale : FALLBACK;
    }
}