"use server"; import { revalidateTag } from "next/cache"; import { readdirSync } from "fs"; import { join } from "path"; export async function refetchPlatform( platform: string ): Promise<{ count: number | null }> { // Validate platform against actual files to prevent path traversal const dir = join(process.cwd(), "lib", "followers"); const validPlatforms = readdirSync(dir) .filter((f) => f.endsWith(".ts") && f !== "index.ts") .map((f) => f.replace(".ts", "")); if (!validPlatforms.includes(platform)) { throw new Error("Invalid platform"); } let count: number | null = null; let tiktokProfile = null; try { const mod = await import(`@/lib/followers/${platform}`); const fns = Object.entries(mod).filter( ([, v]) => typeof v === "function" ) as [string, Function][]; // Prefer function with "Count" or "Member" in name const countFn = fns.find(([name]) => /count|member/i.test(name)); const fn = countFn || fns[0]; if (fn) { const result = await fn[1](); if (typeof result === "number") { count = result; } else if (result === null) { count = null; } else if ( result && typeof result === "object" && "followerCount" in result ) { count = result.followerCount; tiktokProfile = result; } } } catch (err) { console.error(`Failed to fetch ${platform}:`, err); throw new Error(`Failed to fetch ${platform}`); } // Update Redis cache try { const { getRedisClient } = await import("@/lib/redis"); const redis = await getRedisClient(); const cached = await redis.get("erika:follower_counts"); const counts: Record = cached ? JSON.parse(cached) : {}; counts[platform] = count; await redis.set("erika:follower_counts", JSON.stringify(counts), { ex: 3600 }); if (tiktokProfile) { await redis.set( "erika:tiktok_profile", JSON.stringify(tiktokProfile), { ex: 3600 } ); } } catch { // Redis unavailable } // @ts-ignore revalidateTag("follower-counts-api"); return { count }; }