- Sponsors table, LNbits createInvoice, webhook handler - Sponsor routes: create, homepage, list, my-ads, click, extend, check-payment - Admin routes for sponsor management - Frontend: SponsorForm, SponsorTimeSlider, SponsorCard, SponsorsSection - Sponsors page, My Ads page, homepage sponsor block - Header login dropdown with My Ads, Create Sponsor - Transactions integration for sponsor payments - View/click tracking - OG meta fetch for sponsor images - Sponsor modal spacing, invoice polling fallback - Remove Lightning address and Category fields from sponsor form Made-with: Cursor
92 lines
4.0 KiB
TypeScript
92 lines
4.0 KiB
TypeScript
import "dotenv/config";
|
|
|
|
function env(key: string, defaultValue?: string): string {
|
|
const v = process.env[key];
|
|
if (v !== undefined) return v;
|
|
if (defaultValue !== undefined) return defaultValue;
|
|
throw new Error(`Missing required env: ${key}`);
|
|
}
|
|
|
|
function envInt(key: string, defaultValue: number): number {
|
|
const v = process.env[key];
|
|
if (v === undefined) return defaultValue;
|
|
const n = parseInt(v, 10);
|
|
if (Number.isNaN(n)) throw new Error(`Invalid integer env: ${key}`);
|
|
return n;
|
|
}
|
|
|
|
function envBool(key: string, defaultValue: boolean): boolean {
|
|
const v = process.env[key];
|
|
if (v === undefined) return defaultValue;
|
|
return v === "true" || v === "1";
|
|
}
|
|
|
|
export const config = {
|
|
port: envInt("PORT", 3001),
|
|
trustProxy: envBool("TRUST_PROXY", false),
|
|
/** Public path prefix when behind a reverse proxy that strips it (e.g. nginx /api/ -> backend /). */
|
|
publicBasePath: (process.env.PUBLIC_BASE_PATH ?? "").replace(/\/$/, ""),
|
|
/** Host of the API subdomain (e.g. faucetapi.lnpulse.app). When request Host matches, OpenAPI server URL is /. */
|
|
apiHost: (process.env.API_HOST ?? "faucetapi.lnpulse.app").split(":")[0],
|
|
allowedOrigins: (process.env.ALLOWED_ORIGINS ?? process.env.FRONTEND_URL ?? "http://localhost:5173,http://localhost:5174").split(",").map((s) => s.trim()),
|
|
|
|
// Database: omit DATABASE_URL for SQLite; set for Postgres
|
|
databaseUrl: process.env.DATABASE_URL as string | undefined,
|
|
sqlitePath: process.env.SQLITE_PATH ?? "./data/faucet.db",
|
|
|
|
// Security
|
|
hmacIpSecret: env("HMAC_IP_SECRET"),
|
|
jwtSecret: env("JWT_SECRET", process.env.HMAC_IP_SECRET ?? ""),
|
|
jwtExpiresInSeconds: envInt("JWT_EXPIRES_IN_SECONDS", 86400 * 7), // 7 days
|
|
nip98MaxSkewSeconds: envInt("NIP98_MAX_SKEW_SECONDS", 300),
|
|
nonceTtlSeconds: envInt("NONCE_TTL_SECONDS", 600),
|
|
|
|
// Faucet economics
|
|
faucetEnabled: envBool("FAUCET_ENABLED", true),
|
|
emergencyStop: envBool("EMERGENCY_STOP", false),
|
|
faucetMinSats: envInt("FAUCET_MIN_SATS", 1),
|
|
faucetMaxSats: envInt("FAUCET_MAX_SATS", 5),
|
|
dailyBudgetSats: envInt("DAILY_BUDGET_SATS", 10000),
|
|
maxClaimsPerDay: envInt("MAX_CLAIMS_PER_DAY", 100),
|
|
minWalletBalanceSats: envInt("MIN_WALLET_BALANCE_SATS", 1000),
|
|
|
|
// Eligibility
|
|
minAccountAgeDays: envInt("MIN_ACCOUNT_AGE_DAYS", 14),
|
|
minActivityScore: envInt("MIN_ACTIVITY_SCORE", 30),
|
|
minNotesCount: envInt("MIN_NOTES_COUNT", 5),
|
|
minFollowingCount: envInt("MIN_FOLLOWING_COUNT", 10),
|
|
minFollowersCount: envInt("MIN_FOLLOWERS_COUNT", 0),
|
|
activityLookbackDays: envInt("ACTIVITY_LOOKBACK_DAYS", 90),
|
|
|
|
// Cooldowns
|
|
cooldownDays: envInt("COOLDOWN_DAYS", 7),
|
|
ipCooldownDays: envInt("IP_COOLDOWN_DAYS", 7),
|
|
maxClaimsPerIpPerPeriod: envInt("MAX_CLAIMS_PER_IP_PER_PERIOD", 1),
|
|
|
|
// Nostr (defaults include relays common for remote signers / NIP-05)
|
|
nostrRelays: (process.env.NOSTR_RELAYS ?? "wss://relay.damus.io,wss://relay.nostr.band,wss://relay.getalby.com,wss://nos.lol").split(",").map((s) => s.trim()),
|
|
relayTimeoutMs: envInt("RELAY_TIMEOUT_MS", 5000),
|
|
maxEventsFetch: envInt("MAX_EVENTS_FETCH", 500),
|
|
metadataCacheHours: envInt("METADATA_CACHE_HOURS", 24),
|
|
|
|
// LNbits
|
|
lnbitsBaseUrl: env("LNBITS_BASE_URL").replace(/\/$/, ""),
|
|
lnbitsAdminKey: env("LNBITS_ADMIN_KEY"),
|
|
lnbitsWalletId: env("LNBITS_WALLET_ID"),
|
|
depositLightningAddress: process.env.DEPOSIT_LIGHTNING_ADDRESS ?? "",
|
|
depositLnurlp: process.env.DEPOSIT_LNURLP ?? "",
|
|
cashuRedeemApiUrl: (process.env.CASHU_REDEEM_API_URL ?? "https://cashu-redeem.azzamo.net").replace(/\/$/, ""),
|
|
|
|
// Sponsors
|
|
baseSponsorPricePerDay: envInt("BASE_SPONSOR_PRICE_PER_DAY", 200),
|
|
sponsorMaxActivePerUser: envInt("SPONSOR_MAX_ACTIVE_PER_USER", 5),
|
|
sponsorMaxVisible: envInt("SPONSOR_MAX_VISIBLE", 6),
|
|
adminPubkeys: (process.env.ADMIN_PUBKEYS ?? "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
/** Public API base URL for LNbits webhook (e.g. https://api.example.com). Required for sponsor payments. */
|
|
publicApiUrl: (process.env.PUBLIC_API_URL ?? process.env.FRONTEND_URL ?? "").replace(/\/$/, ""),
|
|
};
|
|
|
|
export function usePostgres(): boolean {
|
|
return Boolean(config.databaseUrl);
|
|
}
|