Source: utils/userService.js

/**
 * Shared user service for accessing cached user data across APIs
 * Handles cache misses by falling back to members API
 *
 * For containerized services, use the HTTP client version below
 */
import { getRedisClient } from '@commtool/shared-auth';

const MEMBERS_API_URL = process.env.MEMBERS_API_URL || 'http://localhost:3000';

/**
 * Get user data from cache or members API
 * @param {string} userUID - User UUID
 * @param {string} orgaUID - Organization UUID
 * @param {string} authToken - Optional auth token for API calls
 * @returns {Object} - User validation data
 */
export const getCachedUserData = async (userUID, orgaUID, authToken = null) => {
    try {
        // Try cache first
        const cacheKey = `user-org-${userUID}-${orgaUID}`;
        const cached = await getRedisClient().get(cacheKey);

        if (cached) {
            console.log(`[getCachedUserData] Cache hit for user ${userUID} in org ${orgaUID}`);
            return JSON.parse(cached);
        }

        // Cache miss - try to populate from members API
        console.log(`[getCachedUserData] Cache miss for user ${userUID} in org ${orgaUID}, calling members API`);

        if (authToken) {
            const userData = await fetchUserFromMembersAPI(userUID, orgaUID, authToken);

            if (userData) {
                // Cache the result for future use
                await getRedisClient().setEx(cacheKey, 900, JSON.stringify(userData));
                console.log(`[getCachedUserData] Cached user data for ${userUID} in org ${orgaUID}`);
                return userData;
            }
        }

        // No auth token or API call failed
        console.warn(`[getCachedUserData] Unable to fetch user data for ${userUID} in org ${orgaUID}`);
        return { valid: false, isAdmin: false };

    } catch (error) {
        console.error(`[getCachedUserData] Error: ${error.message}`);
        return { valid: false, isAdmin: false };
    }
};

/**
 * HTTP Client for other containerized APIs
 * This can be copied to other services or published as a shared library
 */
export class UserServiceClient {
    constructor(membersApiUrl = 'http://members-api:3000', redisUrl = null) {
        this.membersApiUrl = membersApiUrl;
        this.redisUrl = redisUrl; // For direct Redis access if available
    }

    /**
     * Get user data - works across containers via HTTP
     * @param {string} userUID - User UUID
     * @param {string} orgaUID - Organization UUID
     * @param {string} authToken - Auth token for API calls
     * @returns {Object} - User validation data
     */
    async getUserData(userUID, orgaUID, authToken) {
        try {
            const response = await fetch(`${this.membersApiUrl}/api/validate-user`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${authToken}`,
                    'X-User-UID': userUID,
                    'X-Orga-UID': orgaUID
                },
                body: JSON.stringify({ userUID, orgaUID })
            });

            if (response.ok) {
                const data = await response.json();
                if (data.success) {
                    return {
                        valid: data.valid,
                        isAdmin: data.isAdmin,
                        userData: data.userData,
                        userUID: data.userUID
                    };
                }
            }

            console.warn(`[UserServiceClient] API call failed: ${response.status}`);
            return { valid: false, isAdmin: false };

        } catch (error) {
            console.error(`[UserServiceClient] Error: ${error.message}`);
            return { valid: false, isAdmin: false };
        }
    }
}

/**
 * Fetch user data from members API to populate cache
 * @param {string} userUID - User UUID
 * @param {string} orgaUID - Organization UUID
 * @param {string} authToken - Auth token for API call
 * @returns {Object} - User data or null
 */
const fetchUserFromMembersAPI = async (userUID, orgaUID, authToken) => {
    try {
        const response = await fetch(`${MEMBERS_API_URL}/validate-user`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${authToken}`,
                'X-User-UID': userUID,
                'X-Orga-UID': orgaUID
            }
        });

        if (response.ok) {
            const data = await response.json();
            if (data.success && data.userData) {
                return {
                    valid: true,
                    isAdmin: data.isAdmin || false,
                    userData: data.userData,
                    userUID: userUID
                };
            }
        }

        console.warn(`[fetchUserFromMembersAPI] API call failed: ${response.status}`);
        return null;

    } catch (error) {
        console.error(`[fetchUserFromMembersAPI] Error: ${error.message}`);
        return null;
    }
};

/**
 * Pre-warm cache with commonly accessed users
 * @param {Array} userOrgPairs - Array of {userUID, orgaUID} objects
 * @param {string} authToken - Auth token for API calls
 */
export const preWarmUserCache = async (userOrgPairs, authToken) => {
    console.log(`[preWarmUserCache] Pre-warming cache for ${userOrgPairs.length} user-org pairs`);

    const promises = userOrgPairs.map(({ userUID, orgaUID }) =>
        getCachedUserData(userUID, orgaUID, authToken)
    );

    await Promise.allSettled(promises);
    console.log(`[preWarmUserCache] Cache pre-warming completed`);
};

/**
 * Check if user data exists in cache
 * @param {string} userUID - User UUID
 * @param {string} orgaUID - Organization UUID
 * @returns {boolean} - True if data exists in cache
 */
export const isUserDataCached = async (userUID, orgaUID) => {
    const cacheKey = `user-org-${userUID}-${orgaUID}`;
    const cached = await getRedisClient().get(cacheKey);
    return cached !== null;
};