ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/llms-txt-hub / apps/web/lib/member-server-utils.ts

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

Source identity

FieldRecorded value
Source typeGitHub source
Original recordJanady13/llms-txt-hub / apps/web/lib/member-server-utils.ts
DateNot specified in source
RepositoryJanady13/llms-txt-hub
Revisione0f4bf978d4df168369c7601d9c5f5ea89e84377
Branchmain
Path or archive memberapps/web/lib/member-server-utils.ts
Original SHA-256dd4665c7e2016e2a4c236492e8a4423d01f22a1adc8739309b7e9ee437116f44
Git blob SHA8e3423363ec990b95769b5a865eff8013bc3fd1d
Reading-copy SHA-256dd4665c7e2016e2a4c236492e8a4423d01f22a1adc8739309b7e9ee437116f44

Original source ↗

Recovered source transcript

/** * Server-side member processing utilities */ import { createClerkClient } from '@clerk/backend' import { logger } from '@thedaviddias/logging' import { revalidateTag, unstable_cache } from 'next/cache' import SafeRedis, { CACHE_KEYS } from '@/lib/redis' import { hashSensitiveData } from '@/lib/server-crypto' const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY! }) export interface Member { id: string firstName?: string | null lastName?: string | null username?: string | null imageUrl?: string | null createdAt: string publicMetadata?: { github_username?: string | null migrated_from?: string | null isProfilePrivate?: boolean } hasContributions?: boolean } /** * Check if user has shared enough information to be displayed publicly * * @param user - User object from Clerk * @returns True if user should be visible in public listings */ export function hasSharedInfo(user: any): boolean { // Exclude users who explicitly set their profile to private if (user.publicMetadata?.isProfilePrivate === true) { return false } // Exclude users with "null" string values if (user.firstName === 'null' || user.lastName === 'null') { return false } // A user should be visible if they have: // - A first name OR // - A username (Clerk or GitHub) const hasName = !!(user.firstName && user.firstName !== '') const hasUsername = !!( user.username || user.publicMetadata?.github_username || user.publicMetadata?.githubUsername ) return hasName || hasUsername } /** * Process a single user and check their contributions * * @param user - Raw user data from Clerk * @returns Processed member object with contribution status */ export async function processUser(user: any): Promise<Member> { let hasContributions = false // First check for OAuth-verified GitHub account let githubUsername: string | null = null if (user.externalAccounts) { const githubAccount = user.externalAccounts.find( (account: any) => account.provider === 'oauth_github' ) if (githubAccount?.username) { githubUsername = githubAccount.username logger.info('Found OAuth GitHub account', { data: { usernameHash: hashSensitiveData(githubUsername || '') }, tags: { type: 'members', security: 'audit' } }) } } // Fallback to metadata username (for migrated Supabase users) if (!githubUsername) { githubUsername = user.publicMetadata?.github_username || user.username if (githubUsername) { logger.info('Using metadata GitHub username (migrated user)', { data: { usernameHash: hashSensitiveData(githubUsername || '') }, tags: { type: 'members', security: 'audit' } }) } } // Skip contribution checking for performance - GitHub API rate limits // Contribution data can be fetched client-side when needed hasContributions = false return { id: user.id, firstName: user.firstName, lastName: user.lastName, username: user.username, imageUrl: user.imageUrl, createdAt: user.createdAt ? new Date(user.createdAt).toISOString() : new Date().toISOString(), publicMetadata: { github_username: (user.publicMetadata?.github_username as string) || null, migrated_from: (user.publicMetadata?.migrated_from as string) || null, isProfilePrivate: (user.publicMetadata?.isProfilePrivate as boolean) || false }, hasContributions } } const REDIS_MEMBERS_KEY = `${CACHE_KEYS.MEMBER_DATA}all-v5` const REDIS_MEMBERS_TTL = 86400 // 24 hours in Redis (persistent across restarts) /** * Invalidate both tiers of the members cache. * Call this from Clerk webhooks when users are created/updated/deleted. */ export async function invalidateMembersCache(): Promise<void> { await SafeRedis.del(REDIS_MEMBERS_KEY) revalidateTag('members', { expire: 0 }) logger.info('Members cache invalidated (Redis + unstable_cache)', { tags: { type: 'page' } }) } /** * Fetch all members from Clerk API with batched pagination. * Returns the processed member array or throws on failure. */ async function fetchMembersFromClerk(): Promise<Member[]> { if (!process.env.CLERK_SECRET_KEY) { throw new Error('CLERK_SECRET_KEY not configured') } const allUsers: Member[] = [] let offset = 0 const limit = 500 let fetchError = false while (true) { try { const response = await clerk.users.getUserList({ limit, offset, orderBy: '-created_at' }) if (!response.data || response.data.length === 0) break const validUsers = await Promise.all(response.data.filter(hasSharedInfo).map(processUser)) allUsers.push(...validUsers) if (response.data.length < limit) break offset += limit } catch (error) { const errorInfo: Record<string, unknown> = { offset } if (error instanceof Error) { errorInfo.message = error.message || '(empty message)' errorInfo.name = error.name } if (error && typeof error === 'object') { if ('status' in error) { errorInfo.status = (error as { status: unknown }).status } if ('errors' in error) { const errWithErrors = error as { errors: unknown } if (Array.isArray(errWithErrors.errors)) { errorInfo.clerkErrors = errWithErrors.errors.map( (e: { code?: string; message?: string; longMessage?: string }) => ({ code: e.code, message: e.message, longMessage: e.longMessage }) ) } } } logger.error('Error fetching batch from Clerk', { data: errorInfo, tags: { type: 'page', security: 'error' } }) fetchError = true break } } if (allUsers.length === 0 && fetchError) { throw new Error('Clerk API failed before fetching any members') } return allUsers } /** * Two-tier cached member fetching: * Tier 1: unstable_cache (in-process, 5 min) — avoids Redis round-trip * Tier 2: Upstash Redis (persistent, 1 hour) — avoids Clerk API calls * Source: Clerk API (paginated) — only called on full cache miss * * On Clerk failure, falls back to stale Redis data if available. */ export const getCachedMembers = unstable_cache( async (): Promise<Member[]> => { // Tier 2: Check Redis for cached members const cached = await SafeRedis.get<Member[]>(REDIS_MEMBERS_KEY) if (cached && Array.isArray(cached) && cached.length > 0) { logger.info('Members loaded from Redis cache', { data: { count: cached.length }, tags: { type: 'page' } }) return cached } // Source: Fetch from Clerk API try { const members = await fetchMembersFromClerk() logger.info('Fetched all members from Clerk', { data: { totalCount: members.length }, tags: { type: 'page', security: 'audit' } }) // Write-through to Redis for next time await SafeRedis.set(REDIS_MEMBERS_KEY, members, REDIS_MEMBERS_TTL) return members } catch (error) { // Clerk failed — try stale Redis data one more time (key may have been // evicted between the check above and now, but worth a retry) const stale = await SafeRedis.get<Member[]>(REDIS_MEMBERS_KEY) if (stale && Array.isArray(stale) && stale.length > 0) { logger.warn('Clerk API failed, serving stale Redis data', { data: { count: stale.length }, tags: { type: 'page', security: 'error' } }) return stale } const errorInfo: Record<string, unknown> = error instanceof Error ? { message: error.message, name: error.name } : { message: 'Unknown error occurred' } if (error && typeof error === 'object') { if ('status' in error && error.status) { errorInfo.status = error.status } if ('code' in error && error.code) { errorInfo.code = error.code } } logger.error('Error fetching members — no cache available', { data: errorInfo, tags: { type: 'page', security: 'error' } }) throw error } }, ['all-members-v5'], { revalidate: 300, // In-process cache: 5 minutes (Redis handles the longer TTL) tags: ['members'] } )

Search equations, builds, manuscript text, and recovered sources.