/opt/canhelp/apps/api/src/lib
NameSizeModeActions
activity.ts13240644editdlrm
apple-client-secret.ts40730644editdlrm
balance.ts35060644editdlrm
daily-limits.ts42820644editdlrm
email.ts527080644editdlrm
fomo.ts113660644editdlrm
google-oauth.ts105260644editdlrm
helper-capability.ts42070644editdlrm
messaging-rules.ts31230644editdlrm
nameUtils.ts7090644editdlrm
notif.ts189290644editdlrm
plan-history.ts10200644editdlrm
push.ts63860644editdlrm
referral.ts61100644editdlrm
telegram.ts62070644editdlrm
turnstile.ts7410644editdlrm
Edit: /opt/canhelp/apps/api/src/lib/helper-capability.ts (4207B)
import { and, eq } from 'drizzle-orm' import { specialistCards, users } from '@canhelp/db' import { db } from '../db.js' type HelperCapabilityCacheEntry = { value: boolean expiresAt: number } const helperCapabilityCache = new Map() const helperCapabilityCacheTtlMs = Number(process.env.HELPER_CAPABILITY_CACHE_TTL_MS ?? 30000) export type LegacyMarketRole = 'customer' | 'specialist' export type PlanAudience = 'requester' | 'helper' export function isAdminRole(role: string | null | undefined): boolean { return role === 'admin' } export function toNormalizedRole(role: string | null | undefined): 'user' | 'admin' { return role === 'admin' ? 'admin' : 'user' } export function toLegacyMarketRole(isHelperReady: boolean): LegacyMarketRole { return isHelperReady ? 'specialist' : 'customer' } /** * The role exposed to API clients (web/mobile). Unlike the raw DB role * (`user` | `admin`), this collapses helper capability into a market role so the * frontend can branch on `'specialist' | 'customer' | 'admin'` consistently. */ export function toClientRole( rawRole: string | null | undefined, isHelperReady: boolean, ): 'admin' | 'specialist' | 'customer' { if (isAdminRole(rawRole)) return 'admin' return isHelperReady ? 'specialist' : 'customer' } export function toLegacyMarketRoleFromInput(role: string | null | undefined): LegacyMarketRole { return role === 'specialist' || role === 'helper' ? 'specialist' : 'customer' } export function mapAudienceToLegacyPlanRole(audience: PlanAudience): LegacyMarketRole { return audience === 'helper' ? 'specialist' : 'customer' } function getCachedHelperCapability(userId: string): boolean | null { const cached = helperCapabilityCache.get(userId) if (!cached) return null if (cached.expiresAt <= Date.now()) { helperCapabilityCache.delete(userId) return null } return cached.value } function setCachedHelperCapability(userId: string, value: boolean) { const ttl = Number.isFinite(helperCapabilityCacheTtlMs) ? helperCapabilityCacheTtlMs : 30000 helperCapabilityCache.set(userId, { value, expiresAt: Date.now() + Math.max(1000, ttl), }) } export function clearHelperCapabilityCache(userId?: string) { if (userId) { helperCapabilityCache.delete(userId) return } helperCapabilityCache.clear() } export async function getHelperCapability( userId: string, options?: { bypassCache?: boolean }, ): Promise { if (!options?.bypassCache) { const cached = getCachedHelperCapability(userId) if (cached !== null) return cached } const [userRows, cards] = await Promise.all([ db .select({ id: users.id, bio: users.bio, specialistCategories: users.specialistCategories, specialistLocations: users.specialistLocations, }) .from(users) .where(eq(users.id, userId)) .limit(1), db .select({ categories: specialistCards.categories, locations: specialistCards.locations, }) .from(specialistCards) .where(and(eq(specialistCards.specialistId, userId), eq(specialistCards.isActive, true))), ]) const [user] = userRows if (!user) { setCachedHelperCapability(userId, false) return false } const hasBio = !!user.bio?.trim() const cardCategories = cards.flatMap((c) => c.categories ?? []) const cardLocations = cards.flatMap((c) => c.locations ?? []) const allCategories = new Set([...(user.specialistCategories ?? []), ...cardCategories]) const allLocations = new Set([...(user.specialistLocations ?? []), ...cardLocations]) const hasActiveCards = cards.length > 0 const isHelperReady = hasBio && hasActiveCards && allCategories.size > 0 && allLocations.size > 0 setCachedHelperCapability(userId, isHelperReady) return isHelperReady } export async function enrichUserWithHelperCapability(user: T) { const isHelperReady = await getHelperCapability(user.id) const legacyMarketRole = toLegacyMarketRole(isHelperReady) return { ...user, role: toClientRole(user.role, isHelperReady), isHelperReady, legacyMarketRole, } }