Skip to main content
Glama
incident-service.ts7.71 kB
/** * Incident Service * Business logic for F5 Cloud incidents and maintenance */ import { DataAccessLayer } from '../data-access/index.js'; import { CacheService } from '../cache/index.js'; import { config } from '../utils/config.js'; import { logger } from '../utils/logger.js'; import { NotFoundError } from '../utils/errors.js'; import type { Incident, IncidentStatus, IncidentImpact, Maintenance } from '../types/domain.js'; import type { RawIncidentsResponse, RawIncident, RawMaintenance, RawScheduledMaintenancesResponse, } from '../types/api.js'; /** * Incident Service class */ export class IncidentService { private dataAccess: DataAccessLayer; private cache: CacheService; constructor(dataAccess: DataAccessLayer, cache: CacheService) { this.dataAccess = dataAccess; this.cache = cache; } /** * Get all incidents */ async getAllIncidents(): Promise<Incident[]> { logger.debug('Getting all incidents'); return this.cache.get('all-incidents', config.cache.ttlIncidents, async () => { const rawIncidents = await this.dataAccess.getIncidents(); return this.transformIncidents(rawIncidents); }); } /** * Get unresolved incidents */ async getUnresolvedIncidents(): Promise<Incident[]> { logger.debug('Getting unresolved incidents'); return this.cache.get('unresolved-incidents', config.cache.ttlIncidents, async () => { const rawIncidents = await this.dataAccess.getUnresolvedIncidents(); return rawIncidents.incidents.map((raw) => this.transformIncident(raw)); }); } /** * Get incident by ID */ async getIncidentById(id: string): Promise<Incident> { const incidents = await this.getAllIncidents(); const incident = incidents.find((i) => i.id === id); if (!incident) { throw new NotFoundError(`Incident not found: ${id}`); } return incident; } /** * Get incidents by status */ async getIncidentsByStatus(status: IncidentStatus): Promise<Incident[]> { const incidents = await this.getAllIncidents(); return incidents.filter((i) => i.status === status); } /** * Get incidents by impact */ async getIncidentsByImpact(impact: IncidentImpact): Promise<Incident[]> { const incidents = await this.getAllIncidents(); return incidents.filter((i) => i.impact === impact); } /** * Get recent incidents (last N days) */ async getRecentIncidents(days: number = 7): Promise<Incident[]> { const incidents = await this.getAllIncidents(); const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - days); return incidents.filter((i) => i.createdAt >= cutoff); } /** * Check if there are active incidents */ async hasActiveIncidents(): Promise<boolean> { const unresolved = await this.getUnresolvedIncidents(); return unresolved.length > 0; } /** * Get all scheduled maintenances */ async getAllMaintenances(): Promise<Maintenance[]> { logger.debug('Getting all maintenances'); return this.cache.get('all-maintenances', config.cache.ttlMaintenance, async () => { const rawMaintenances = await this.dataAccess.getScheduledMaintenances(); return this.transformMaintenances(rawMaintenances); }); } /** * Get active maintenances */ async getActiveMaintenances(): Promise<Maintenance[]> { logger.debug('Getting active maintenances'); return this.cache.get('active-maintenances', config.cache.ttlMaintenance, async () => { const rawMaintenances = await this.dataAccess.getActiveMaintenances(); return rawMaintenances.scheduled_maintenances.map((raw) => this.transformMaintenance(raw)); }); } /** * Get upcoming maintenances */ async getUpcomingMaintenances(): Promise<Maintenance[]> { logger.debug('Getting upcoming maintenances'); return this.cache.get('upcoming-maintenances', config.cache.ttlMaintenance, async () => { const rawMaintenances = await this.dataAccess.getUpcomingMaintenances(); return rawMaintenances.scheduled_maintenances.map((raw) => this.transformMaintenance(raw)); }); } /** * Get maintenance by ID */ async getMaintenanceById(id: string): Promise<Maintenance> { const maintenances = await this.getAllMaintenances(); const maintenance = maintenances.find((m) => m.id === id); if (!maintenance) { throw new NotFoundError(`Maintenance not found: ${id}`); } return maintenance; } /** * Check if there are active maintenances */ async hasActiveMaintenances(): Promise<boolean> { const active = await this.getActiveMaintenances(); return active.length > 0; } /** * Transform raw incidents to domain model */ private transformIncidents(rawIncidents: RawIncidentsResponse | Incident[]): Incident[] { // If already transformed (from scraper), return as-is if (Array.isArray(rawIncidents)) { return rawIncidents; } // Transform API response return rawIncidents.incidents.map((raw) => this.transformIncident(raw)); } /** * Transform single raw incident */ private transformIncident(raw: RawIncident): Incident { return { id: raw.id, name: raw.name, status: raw.status, impact: raw.impact, createdAt: new Date(raw.created_at), updatedAt: new Date(raw.updated_at), resolvedAt: raw.resolved_at ? new Date(raw.resolved_at) : undefined, shortlink: raw.shortlink, updates: raw.incident_updates.map((update) => ({ id: update.id, status: update.status, body: update.body, createdAt: new Date(update.created_at), displayAt: new Date(update.display_at), affectedComponents: update.affected_components?.map((c) => c.code) || [], })), affectedComponents: raw.components.map((c) => c.id), }; } /** * Transform raw maintenances to domain model */ private transformMaintenances(rawMaintenances: RawScheduledMaintenancesResponse): Maintenance[] { return rawMaintenances.scheduled_maintenances.map((raw) => this.transformMaintenance(raw)); } /** * Transform single raw maintenance */ private transformMaintenance(raw: RawMaintenance): Maintenance { return { id: raw.id, name: raw.name, status: raw.status, impact: raw.impact, scheduledFor: new Date(raw.scheduled_for), scheduledUntil: new Date(raw.scheduled_until), createdAt: new Date(raw.created_at), updatedAt: new Date(raw.updated_at), shortlink: raw.shortlink, updates: raw.incident_updates.map((update) => ({ id: update.id, status: update.status, body: update.body, createdAt: new Date(update.created_at), displayAt: new Date(update.display_at), affectedComponents: update.affected_components?.map((c) => c.code) || [], })), affectedComponents: raw.components.map((c) => c.id), }; } /** * Invalidate incidents cache */ invalidateIncidentsCache(): void { this.cache.invalidatePattern(/incidents/); logger.info('Incidents cache invalidated'); } /** * Invalidate maintenances cache */ invalidateMaintenancesCache(): void { this.cache.invalidatePattern(/maintenances/); logger.info('Maintenances cache invalidated'); } /** * Invalidate all caches */ invalidateAllCaches(): void { this.invalidateIncidentsCache(); this.invalidateMaintenancesCache(); } } /** * Create incident service instance */ export function createIncidentService( dataAccess: DataAccessLayer, cache: CacheService ): IncidentService { return new IncidentService(dataAccess, cache); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/robinmordasiewicz/f5cloudstatus-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server