feat(outbox): UI fuer Event-Outbox — Modul 9/16 des UI-Backlogs
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Outbox API client — transactional outbox monitoring & management.
|
||||
*
|
||||
* Backend: /api/v1/outbox (stats, failed, replay, replay-all,
|
||||
* consumer-registry, recover-stuck, cleanup-published).
|
||||
* All endpoints require the admin role (require_admin, enforced server-side).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiGet, apiPost } from '@/api/client';
|
||||
|
||||
export interface OutboxStats {
|
||||
/** Count per status: pending, processing, published, failed, no_handlers. */
|
||||
counts: Record<string, number>;
|
||||
total: number;
|
||||
oldest_pending_age_seconds: number | null;
|
||||
}
|
||||
|
||||
export interface FailedOutboxEvent {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
event_name: string;
|
||||
error_message: string | null;
|
||||
failed_at: string | null;
|
||||
attempts: number;
|
||||
created_at: string | null;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface FailedEventsResponse {
|
||||
events: FailedOutboxEvent[];
|
||||
limit: number;
|
||||
offset: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface ConsumerRegistryResponse {
|
||||
registry: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface ReplaySingleResponse {
|
||||
replayed: boolean;
|
||||
event_id: string;
|
||||
}
|
||||
|
||||
export interface ReplayAllResponse {
|
||||
replayed_count: number;
|
||||
tenant_id: string;
|
||||
}
|
||||
|
||||
export interface RecoverStuckResponse {
|
||||
recovered_count: number;
|
||||
timeout_seconds: number;
|
||||
}
|
||||
|
||||
export interface CleanupPublishedResponse {
|
||||
deleted_count: number;
|
||||
retention_days: number;
|
||||
}
|
||||
|
||||
// ─── Query hooks ─────────────────────────────────────────────
|
||||
|
||||
export function useOutboxStats(enabled = true) {
|
||||
return useQuery<OutboxStats>({
|
||||
queryKey: ['outbox', 'stats'],
|
||||
queryFn: () => apiGet<OutboxStats>('/outbox/stats'),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOutboxFailedEvents(limit: number, offset: number, enabled = true) {
|
||||
return useQuery<FailedEventsResponse>({
|
||||
queryKey: ['outbox', 'failed', limit, offset],
|
||||
queryFn: () =>
|
||||
apiGet<FailedEventsResponse>(`/outbox/failed?limit=${limit}&offset=${offset}`),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOutboxConsumerRegistry(enabled = true) {
|
||||
return useQuery<ConsumerRegistryResponse>({
|
||||
queryKey: ['outbox', 'consumer-registry'],
|
||||
queryFn: () => apiGet<ConsumerRegistryResponse>('/outbox/consumer-registry'),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ──────────────────────────────────────────
|
||||
|
||||
export function useReplayFailedEvent() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<ReplaySingleResponse, Error, string>({
|
||||
mutationFn: (eventId) => apiPost<ReplaySingleResponse>(`/outbox/replay/${eventId}`),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['outbox'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReplayAllFailedEvents() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<ReplayAllResponse, Error, void>({
|
||||
mutationFn: () => apiPost<ReplayAllResponse>('/outbox/replay-all'),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['outbox'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRecoverStuckEvents() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<RecoverStuckResponse, Error, number>({
|
||||
mutationFn: (timeoutSeconds) =>
|
||||
apiPost<RecoverStuckResponse>(`/outbox/recover-stuck?timeout_seconds=${timeoutSeconds}`),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['outbox'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCleanupPublishedEvents() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<CleanupPublishedResponse, Error, number>({
|
||||
mutationFn: (retentionDays) =>
|
||||
apiPost<CleanupPublishedResponse>(`/outbox/cleanup-published?retention_days=${retentionDays}`),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['outbox'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user