Files
ComfyUI_frontend/src/services/customerEventsService.ts
Alexander Brown 874ef3ba0c Lint: Add eslint import plugin (#5955)
## Summary

Adds the linter, turns on the recommended and a few extra rules, fixes
existing violations.

Doesn't prohibit `../../...` imports yet, that'll be it's own PR.

## Changes

- **What**: Consistent and fixable imports
- **Dependencies**: The plugin and parser

## Review Focus

How do you feel about the recommended rules?
What about the extra ones?
[Any
more](https://github.com/un-ts/eslint-plugin-import-x?tab=readme-ov-file#rules)
you'd want to turn on?

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5955-Lint-Add-eslint-import-plugin-2856d73d3650819985c0fb9ca3fa94b0)
by [Unito](https://www.unito.io)
2025-10-07 20:31:00 -07:00

209 lines
5.3 KiB
TypeScript

import type { AxiosError, AxiosResponse } from 'axios'
import axios from 'axios'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
export enum EventType {
CREDIT_ADDED = 'credit_added',
ACCOUNT_CREATED = 'account_created',
API_USAGE_STARTED = 'api_usage_started',
API_USAGE_COMPLETED = 'api_usage_completed'
}
type CustomerEventsResponse =
operations['GetCustomerEvents']['responses']['200']['content']['application/json']
type CustomerEventsResponseQuery =
operations['GetCustomerEvents']['parameters']['query']
export type AuditLog = components['schemas']['AuditLog']
const customerApiClient = axios.create({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
})
export const useCustomerEventsService = () => {
const isLoading = ref(false)
const error = ref<string | null>(null)
const { d } = useI18n()
const handleRequestError = (
err: unknown,
context: string,
routeSpecificErrors?: Record<number, string>
) => {
// Don't treat cancellation as an error
if (isAbortError(err)) return
let message: string
if (!axios.isAxiosError(err)) {
message = `${context} failed: ${err instanceof Error ? err.message : String(err)}`
} else {
const axiosError = err as AxiosError<{ message: string }>
const status = axiosError.response?.status
if (status && routeSpecificErrors?.[status]) {
message = routeSpecificErrors[status]
} else {
message =
axiosError.response?.data?.message ??
`${context} failed with status ${status}`
}
}
error.value = message
}
const executeRequest = async <T>(
requestCall: () => Promise<AxiosResponse<T>>,
options: {
errorContext: string
routeSpecificErrors?: Record<number, string>
}
): Promise<T | null> => {
const { errorContext, routeSpecificErrors } = options
isLoading.value = true
error.value = null
try {
const response = await requestCall()
return response.data
} catch (err) {
handleRequestError(err, errorContext, routeSpecificErrors)
return null
} finally {
isLoading.value = false
}
}
function formatEventType(eventType: string) {
switch (eventType) {
case 'credit_added':
return 'Credits Added'
case 'account_created':
return 'Account Created'
case 'api_usage_completed':
return 'API Usage'
default:
return eventType
}
}
function formatDate(dateString: string): string {
const date = new Date(dateString)
return d(date, {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
function formatJsonKey(key: string) {
return key
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}
function formatJsonValue(value: any) {
if (typeof value === 'number') {
// Format numbers with commas and decimals if needed
return value.toLocaleString()
}
if (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}/)) {
// Format dates nicely
return new Date(value).toLocaleString()
}
return value
}
function getEventSeverity(eventType: string) {
switch (eventType) {
case 'credit_added':
return 'success'
case 'account_created':
return 'info'
case 'api_usage_completed':
return 'warning'
default:
return 'info'
}
}
function hasAdditionalInfo(event: AuditLog) {
const { amount, api_name, model, ...otherParams } = event.params || {}
return Object.keys(otherParams).length > 0
}
function getTooltipContent(event: AuditLog) {
const { ...params } = event.params || {}
return Object.entries(params)
.map(([key, value]) => {
const formattedKey = formatJsonKey(key)
const formattedValue = formatJsonValue(value)
return `<strong>${formattedKey}:</strong> ${formattedValue}`
})
.join('<br>')
}
function formatAmount(amountMicros?: number) {
if (!amountMicros) return '0.00'
return (amountMicros / 100).toFixed(2)
}
async function getMyEvents({
page = 1,
limit = 10
}: CustomerEventsResponseQuery = {}): Promise<CustomerEventsResponse | null> {
const errorContext = 'Fetching customer events'
const routeSpecificErrors = {
400: 'Invalid input, object invalid',
404: 'Not found'
}
// Get auth headers
const authHeaders = await useFirebaseAuthStore().getAuthHeader()
if (!authHeaders) {
error.value = 'Authentication header is missing'
return null
}
return executeRequest<CustomerEventsResponse>(
() =>
customerApiClient.get('/customers/events', {
params: { page, limit },
headers: authHeaders
}),
{ errorContext, routeSpecificErrors }
)
}
return {
// State
isLoading,
error,
// Methods
getMyEvents,
formatEventType,
getEventSeverity,
formatAmount,
hasAdditionalInfo,
formatDate,
formatJsonKey,
formatJsonValue,
getTooltipContent
}
}