"use server" import { WhoAmIUser, whoAmI } from "@/lib/huggingface/hub/src" import { UserInfo } from "@/types/general" import { adminApiKey } from "./config" import { redis } from "./redis" export async function getCurrentUser(apiKey: string): Promise { if (!apiKey) { throw new Error(`the apiKey is required`) } const credentials = { accessToken: apiKey } const huggingFaceUser = await whoAmI({ credentials }) as unknown as WhoAmIUser const id = huggingFaceUser.id const user: UserInfo = { id, type: apiKey === adminApiKey ? "admin" : "normal", userName: huggingFaceUser.name, fullName: huggingFaceUser.fullname, thumbnail: huggingFaceUser.avatarUrl, channels: [], hfApiToken: apiKey, // <- on purpose, and safe (only this user sees their token) } await redis.set(`users:${id}`, user) // the user id is not available in the channel info, only user name (slug) // so we use this projection to recover the ID from a slug // alternatively we could also use a Redis index, to avoid doing two calls // (for get id and get user) await redis.set(`userSlugToId:${user.userName}`, user.id) return user } /** * Attention this returns the *full* user, including the API key * * We use the API on behalf of the user, but it is confidential nevertheless, * so we should not share it with 3rd parties unbeknownst to the user * * @param hfUserId * @returns */ export async function getUserFromId(hfUserId: string): Promise { const maybeUser = await redis.get(`users:${hfUserId}`) if (maybeUser?.id) { return maybeUser } return undefined } export async function getUserIdFromSlug(hfUserSlugName: string): Promise { // the user id is not available in the channel info, only user name (slug) // so we use a projection to recover the ID from a slug const maybeUserId = await redis.get(`userSlugToId:${hfUserSlugName}`) return maybeUserId || "" } /** * This function doesn NOT return the user apiKey, for security reasons * * We use the API on behalf of the user, but it is confidential nevertheless, * so we should not share it with 3rd parties unbeknownst to the user * * @param userIdOrSlugName * @returns */ export async function getUser(userIdOrSlugName: string): Promise { // the user id is not available in the channel info, only user name (slug) // so we use a projection to recover the ID from a slug // alternatively, we could also use a Redis index, to avoid doing two calls // (for get id and get user) let maybeUserId = await getUserIdFromSlug(userIdOrSlugName) maybeUserId ||= userIdOrSlugName const maybeUser = await getUserFromId(maybeUserId) // sanitize the output a bit if (maybeUser) { const publicFacingUser: UserInfo = { ...maybeUser, hfApiToken: undefined, // <-- important! } delete publicFacingUser.hfApiToken return publicFacingUser } return undefined } export async function getUsers(ids: string[]): Promise> { try { const maybeUsers = await redis.mget(ids.map(userId => `users:${userId}`)) const usersById: Record = {} maybeUsers.forEach((user, index) => { if (user?.id) { const publicFacingUser: UserInfo = { ...user, hfApiToken: undefined, // <-- important! } delete publicFacingUser.hfApiToken usersById[user.id] = publicFacingUser } }) return usersById } catch (err) { return {} } }