import { json } from './_session.js'; import { changeFetchFirst, getChangeConfig, normalizeChangeList } from './_change.js'; import { getGivethProjects, withGivethPageMeta } from './_giveth.js'; const DEFAULT_SEARCH_TERMS = [ 'GiveDirectly', 'Partners In Health', 'water', 'global health', 'poverty' ]; function splitTerms(value) { return String(value || '') .split(',') .map((term) => term.trim()) .filter(Boolean); } function getNonprofitId(nonprofit) { return String( nonprofit.id || nonprofit.nonprofit_id || nonprofit.nonprofitId || nonprofit.ein || '' ); } function getLogo(nonprofit) { return ( nonprofit.logo_url || nonprofit.logoUrl || nonprofit.image_url || nonprofit.imageUrl || nonprofit.cover_image_url || nonprofit.coverImageUrl || '' ); } function getDescription(nonprofit) { return ( nonprofit.description || nonprofit.mission || nonprofit.tagline || nonprofit.category || 'Verified nonprofit available through the Change charity provider.' ); } function normalizeNonprofit(nonprofit) { const id = getNonprofitId(nonprofit); const name = nonprofit.name || nonprofit.display_name || nonprofit.displayName || nonprofit.legal_name || id; return { provider: 'change', id, name, description: getDescription(nonprofit), ein: nonprofit.ein || '', website: nonprofit.website || nonprofit.website_url || nonprofit.websiteUrl || '', logoUrl: getLogo(nonprofit), supporterCount: Number(nonprofit.donation_count || nonprofit.donationCount || nonprofit.contribution_count || 0), category: nonprofit.category || nonprofit.ntee_major_group || nonprofit.nteeMajorGroup || '' }; } async function searchChangeNonprofits(env, searchTerm) { const paths = [ `/nonprofits/search?term=${encodeURIComponent(searchTerm)}`, `/nonprofits/search?query=${encodeURIComponent(searchTerm)}`, `/nonprofits?search=${encodeURIComponent(searchTerm)}`, `/nonprofits?query=${encodeURIComponent(searchTerm)}` ]; const payload = await changeFetchFirst(env, paths); return normalizeChangeList(payload); } function sortCharities(charities) { return charities.sort((a, b) => { const aHasLogo = a.logoUrl ? 1 : 0; const bHasLogo = b.logoUrl ? 1 : 0; if (aHasLogo !== bHasLogo) return bHasLogo - aHasLogo; return String(a.name).localeCompare(String(b.name)); }); } export async function onRequestGet({ env }) { const provider = String(env.SOULWALL_CHARITY_PROVIDER || 'giveth').trim().toLowerCase(); if (provider === 'giveth') { const projects = getGivethProjects(env); const shouldFetchPageMeta = env.GIVETH_FETCH_PAGE_META === '1'; const charities = shouldFetchPageMeta ? await Promise.all(projects.map(withGivethPageMeta)) : projects; return json({ provider: 'giveth', charities, message: 'Giveth project feed loaded. Donations happen on official Giveth project pages; SoulWall verifies the submitted transaction hash before unlocking.' }); } if (!getChangeConfig(env)) { return json({ provider: 'change', charities: [], message: 'Change API key missing. Set CHANGE_SECRET_KEY, or CHANGE_PUBLIC_KEY plus CHANGE_SECRET_KEY.' }, 503); } const searchTerms = splitTerms(env.CHANGE_SEARCH_TERMS); const terms = searchTerms.length ? searchTerms : DEFAULT_SEARCH_TERMS; const byId = new Map(); const warnings = []; for (const term of terms) { try { const nonprofits = await searchChangeNonprofits(env, term); for (const nonprofit of nonprofits) { const normalized = normalizeNonprofit(nonprofit); if (!normalized.id || !normalized.name) continue; if (!byId.has(normalized.id)) byId.set(normalized.id, normalized); } } catch (error) { warnings.push({ term, message: error.message }); } } const charities = sortCharities(Array.from(byId.values())).slice(0, 8); return json({ provider: 'change', charities, warnings, message: charities.length ? 'Live Change nonprofit feed loaded.' : 'Change nonprofit feed returned no charities for the configured search terms.' }, charities.length ? 200 : 503); }