Spaces:
Running
Running
| import { db } from '@/backend/services/firebase'; | |
| export const CreditService = { | |
| async checkEligibility(sessionId, guestId, toolKey, isOwnKey, creditCostOverride = null) { | |
| if (!sessionId) throw new Error("Authentication failed."); | |
| const now = new Date(); | |
| const today = now.toLocaleDateString('en-CA'); | |
| const usageKey = isOwnKey ? `own_${toolKey}` : `app_${toolKey}`; | |
| // 1. Fetch System Tier Settings | |
| const sysSnapshot = await db.ref('system/settings/tiers').once('value'); | |
| const tiers = sysSnapshot.val() || {}; | |
| const toolMapper = { | |
| 'count_transcript': 'transcript', | |
| 'count_translate': 'translate', | |
| 'count_srt_translate': 'srt_translator', | |
| 'count_tts': 'tts', | |
| 'count_subtitle': 'subtitle_gen', | |
| 'count_creator_text': 'content_creator', | |
| 'count_creator_image': 'ai_image', | |
| 'downloader': 'downloader', | |
| 'book_recap': 'book_recap' | |
| }; | |
| const tierToolKey = toolMapper[toolKey] || toolKey; | |
| let userKey, userData, userClass, storagePath; | |
| if (sessionId === 'free_access') { | |
| userClass = 'FREE'; | |
| if (!guestId || guestId === 'anonymous') { | |
| // Return generic guest data for free mode | |
| userData = { Usage: {}, Credits: 0, Class: 'FREE', Last_Usage_Date: today }; | |
| storagePath = `guests/temp_guest`; | |
| } else { | |
| storagePath = `guests/${guestId}/${today}`; | |
| const usageSnapshot = await db.ref(storagePath).once('value'); | |
| const currentUsage = usageSnapshot.val() || {}; | |
| userData = { Usage: currentUsage, Credits: 0, Class: 'FREE', Last_Usage_Date: today }; | |
| } | |
| } else { | |
| // Find user by temporary session ID | |
| const snapshot = await db.ref('users').orderByChild('Active_Session_ID').equalTo(sessionId).once('value'); | |
| if (!snapshot.exists()) throw new Error("Invalid Session. Please login again."); | |
| userKey = Object.keys(snapshot.val())[0]; | |
| userData = snapshot.val()[userKey]; | |
| if (userData.Status !== 'ACTIVE') throw new Error("Account suspended."); | |
| userClass = userData.Class === 'MEMBER+' ? 'MEMBER_PLUS' : (userData.Class === 'BASIC' ? 'BASIC' : 'MEMBER'); | |
| storagePath = `users/${userKey}`; | |
| } | |
| const tierConfig = tiers[userClass] || { apiAccess: { app: true, own: true } }; | |
| // Check if user is trying to use their own API but it's disabled for their class | |
| if (isOwnKey && tierConfig.apiAccess?.own === false) throw new Error("OWN_API_RESTRICTED"); | |
| if (!isOwnKey && tierConfig.apiAccess?.app === false) throw new Error("APP_API_RESTRICTED"); | |
| // Check Specific Tool Toggle (Mode Specific) | |
| const toolToggleMap = isOwnKey ? (tierConfig.ownTools || {}) : (tierConfig.tools || {}); | |
| if (toolToggleMap[tierToolKey] === false) throw new Error("TOOL_DISABLED"); | |
| const usage = (userData.Last_Usage_Date === today) ? (userData.Usage || {}) : {}; | |
| // 1. Declare the limit variable based on the tier settings | |
| let limit = isOwnKey | |
| ? (tierConfig.ownLimits?.[tierToolKey] ?? -1) | |
| : (tierConfig.limits?.[tierToolKey] ?? -1); | |
| // 2. Override: If using Own API Key, force the limit to -1 (Unlimited) | |
| if (isOwnKey) { | |
| limit = -1; | |
| } | |
| // 3. Perform the check only if a limit exists | |
| if (limit !== -1) { | |
| const currentCount = parseInt(usage[usageKey] || 0); | |
| if (currentCount >= limit) { | |
| throw new Error("DAILY_LIMIT_REACHED"); | |
| } | |
| } | |
| if (isOwnKey) { | |
| limit = -1; // -1 means "No Limit" or "Unlimited" in your logic (LOL kyaw Gyi) | |
| } | |
| // Check Daily Frequency Limit (Mode Specific) | |
| // let limit = isOwnKey ? (tierConfig.ownLimits?.[tierToolKey] ?? -1) : (tierConfig.limits?.[tierToolKey] ?? -1); | |
| if (limit !== -1) { | |
| const currentCount = parseInt(usage[usageKey] || 0); | |
| if (currentCount >= limit) throw new Error("DAILY_LIMIT_REACHED"); | |
| } | |
| let cost = 0; | |
| if (userClass !== 'FREE' && !isOwnKey) { | |
| const costMap = { | |
| 'count_transcript': 4, | |
| 'count_translate': 4, | |
| 'count_srt_translate': 7, | |
| 'count_tts': 3, | |
| 'count_subtitle': 2, | |
| 'count_creator_text': 3, | |
| 'count_creator_image': 5, | |
| 'downloader': 2, | |
| 'book_recap': 8 | |
| }; | |
| cost = (creditCostOverride !== null) ? creditCostOverride : (costMap[toolKey] || 1); | |
| if (cost > 0 && (userData.Credits || 0) < cost) { | |
| throw new Error(`INSUFFICIENT_BALANCE: Needs ${cost} credits.`); | |
| } | |
| } | |
| return { userKey, storagePath, userData, usageKey, cost, today, isGuest: (userClass === 'FREE'), isOwnKey }; | |
| }, | |
| async commitDeduction(eligibilityData) { | |
| if (!eligibilityData) return; | |
| const { storagePath, userData, usageKey, cost, today, isGuest, isOwnKey } = eligibilityData; | |
| // Own API mode records usage but never deducts credits | |
| const finalCost = isOwnKey ? 0 : cost; | |
| const updates = {}; | |
| if (isGuest) { | |
| if (storagePath === 'guests/temp_guest') return; | |
| const currentUsage = (userData.Usage?.[usageKey]) || 0; | |
| updates[`${storagePath}/${usageKey}`] = currentUsage + 1; | |
| await db.ref().update(updates); | |
| return; | |
| } | |
| if (finalCost > 0) { | |
| const newBalance = (userData.Credits || 0) - finalCost; | |
| updates[`${storagePath}/Credits`] = Math.max(0, newBalance); | |
| } | |
| if (userData.Last_Usage_Date !== today) { | |
| updates[`${storagePath}/Last_Usage_Date`] = today; | |
| updates[`${storagePath}/Usage`] = { [usageKey]: 1 }; | |
| } else { | |
| const currentUsage = (userData.Usage?.[usageKey]) || 0; | |
| updates[`${storagePath}/Usage/${usageKey}`] = currentUsage + 1; | |
| } | |
| await db.ref().update(updates); | |
| } | |
| }; | |