/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ /** * Utility functions for usage logs feature */ import { getAllLogs, getUserLogs, getAllMidjourneyLogs, getUserMidjourneyLogs, getAllTaskLogs, getUserTaskLogs, } from '../api' import { LOG_TYPES, DISPLAYABLE_LOG_TYPES, TIMING_LOG_TYPES, } from '../constants' import type { GetLogsParams, GetLogsResponse, FetchLogsConfig, GetMidjourneyLogsParams, GetTaskLogsParams, } from '../types' // ============================================================================ // Type Checkers & Utilities // ============================================================================ /** * Check if log type is displayable (has detailed info) */ export function isDisplayableLogType(type: number): boolean { return (DISPLAYABLE_LOG_TYPES as readonly number[]).includes(type) } /** * Check if log type shows timing info */ export function isTimingLogType(type: number): boolean { return (TIMING_LOG_TYPES as readonly number[]).includes(type) } /** * Get log type configuration by type number */ export function getLogTypeConfig(type: number) { return LOG_TYPES.find((t) => t.value === type) || LOG_TYPES[0] } /** * Check if log uses per-call billing */ export function isPerCallBilling(modelPrice?: number): boolean { return (modelPrice ?? 0) > 0 } /** * Get default time range (today 00:00:00 to now + 1 hour) */ export function getDefaultTimeRange(): { start: Date; end: Date } { const now = new Date() const start = new Date(now) start.setHours(0, 0, 0, 0) const end = new Date(now.getTime() + 3600 * 1000) // +1 hour return { start, end } } /** * Convert milliseconds timestamp to seconds for API */ function timestampToSeconds(ms: number): number { return Math.floor(ms / 1000) } /** * Build query parameters from filters */ export function buildQueryParams( params: Record ): URLSearchParams { const queryParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { // Keep 0 as a valid value, only filter out undefined, null, and empty string if (value !== undefined && value !== null && value !== '') { queryParams.append(key, String(value)) } }) return queryParams } /** * Build time range parameters with default values * Shared logic for all log types */ function buildTimeRangeParams( searchParams: Record, useMilliseconds: boolean ): { start_timestamp?: number; end_timestamp?: number } { const hasTimeParams = searchParams.startTime ?? searchParams.endTime const defaultTimeRange = !hasTimeParams ? getDefaultTimeRange() : null const convertTimestamp = (timestamp: number) => useMilliseconds ? timestamp : timestampToSeconds(timestamp) const getTimestamp = (paramTime?: unknown, defaultTime?: Date) => { const time = (paramTime as number) || defaultTime?.getTime() return time ? convertTimestamp(time) : undefined } return { start_timestamp: getTimestamp( searchParams.startTime, defaultTimeRange?.start ), end_timestamp: getTimestamp(searchParams.endTime, defaultTimeRange?.end), } } /** * Build base parameters with time range (for drawing and task logs) * @param useMilliseconds - Whether to use millisecond timestamps (true for drawing logs, false for task logs) */ export function buildBaseParams(config: { page: number pageSize: number searchParams: Record useMilliseconds?: boolean }): { p: number page_size: number channel_id?: string start_timestamp?: number end_timestamp?: number } { const { page, pageSize, searchParams, useMilliseconds = false } = config return { p: page, page_size: pageSize, ...(searchParams.channel ? { channel_id: String(searchParams.channel), } : {}), ...buildTimeRangeParams(searchParams, useMilliseconds), } } /** * Build API params from search params and column filters (for common logs) */ export function buildApiParams(config: { page: number pageSize: number searchParams: Record columnFilters?: Array<{ id: string; value: unknown }> isAdmin: boolean }): GetLogsParams { const { page, pageSize, searchParams, columnFilters = [], isAdmin } = config // Helper to process type parameter (single value from array) const processType = (value: unknown): number | undefined => { const parseType = (raw: unknown): number | undefined => { const type = Number(raw) return Number.isFinite(type) ? type : undefined } if (Array.isArray(value) && value.length === 1) { return parseType(value[0]) } if (typeof value === 'string' && value !== '') { return parseType(value) } return undefined } // Build base params from search params const params: GetLogsParams = { p: page, page_size: pageSize, ...(searchParams.type ? { type: processType(searchParams.type) } : {}), ...(searchParams.model ? { model_name: String(searchParams.model) } : {}), ...(searchParams.token ? { token_name: String(searchParams.token) } : {}), ...(searchParams.group ? { group: String(searchParams.group) } : {}), ...(isAdmin && searchParams.channel ? { channel: Number(searchParams.channel) || 0 } : {}), ...(isAdmin && searchParams.username ? { username: String(searchParams.username) } : {}), ...(searchParams.requestId ? { request_id: String(searchParams.requestId) } : {}), ...(searchParams.upstreamRequestId ? { upstream_request_id: String(searchParams.upstreamRequestId) } : {}), ...buildTimeRangeParams(searchParams, false), } // Override with column filters if present if (columnFilters.length > 0) { columnFilters.forEach(({ id, value }) => { if (value === undefined || value === null || value === '') return switch (id) { case 'type': params.type = processType(value) break case 'model_name': params.model_name = String(value) break case 'token_name': params.token_name = String(value) break case 'group': params.group = String(value) break case 'channel': if (isAdmin) params.channel = Number(value) || 0 break case 'username': if (isAdmin) params.username = String(value) break } }) } return params } // ============================================================================ // Data Fetching // ============================================================================ /** * Fetch logs based on category type */ export async function fetchLogsByCategory( config: FetchLogsConfig ): Promise { const { logCategory, isAdmin, page, pageSize, searchParams, columnFilters } = config if (logCategory === 'common') { const params = buildApiParams({ page, pageSize, searchParams, columnFilters, isAdmin, }) return isAdmin ? await getAllLogs(params) : await getUserLogs(params) } // For drawing and task logs const baseParams = buildBaseParams({ page, pageSize, searchParams, useMilliseconds: logCategory === 'drawing', }) const paramsWithFilter = { ...baseParams, ...(logCategory === 'drawing' ? { mj_id: searchParams.filter as string | undefined } : {}), ...(logCategory === 'task' ? { task_id: searchParams.filter as string | undefined } : {}), } if (logCategory === 'drawing') { return isAdmin ? await getAllMidjourneyLogs(paramsWithFilter as GetMidjourneyLogsParams) : await getUserMidjourneyLogs(paramsWithFilter as GetMidjourneyLogsParams) } // task logs return isAdmin ? await getAllTaskLogs(paramsWithFilter as GetTaskLogsParams) : await getUserTaskLogs(paramsWithFilter as GetTaskLogsParams) }