feat(content-hub): ✨ Add admin API endpoints for content operations, lifecycle management, and translation workflows
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
7d5d026df6
commit
b68dd0f3ea
3 changed files with 221 additions and 48 deletions
|
|
@ -2,12 +2,12 @@
|
|||
* Content Hub API — React Query hooks
|
||||
*
|
||||
* All requests target the platform-admin API via Vite proxy.
|
||||
* Auth token is forwarded from localStorage (same pattern as moderation API).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query'
|
||||
|
||||
import { fetchJson, postJson } from '@/api/http'
|
||||
import type {
|
||||
UnifiedContentItem,
|
||||
ContentHubStats,
|
||||
|
|
@ -15,55 +15,8 @@ import type {
|
|||
VerificationReport,
|
||||
} from '../model/types'
|
||||
|
||||
// ─── Constants ────────────────────────────────────────────────────────────────
|
||||
|
||||
const API_BASE = '/api/admin/content-hub'
|
||||
|
||||
// ─── HTTP helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function getAuthHeaders(): Record<string, string> {
|
||||
const token = localStorage.getItem('lilith_session')
|
||||
return token ? { Authorization: `Bearer ${token}` } : {}
|
||||
}
|
||||
|
||||
async function fetchJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getAuthHeaders(),
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch((): { message?: string } => ({ message: response.statusText }))
|
||||
throw new Error(
|
||||
(error as { message?: string }).message ?? `API error: ${response.status} ${response.statusText}`,
|
||||
)
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>
|
||||
}
|
||||
|
||||
async function postJson<T>(url: string, body?: unknown): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getAuthHeaders(),
|
||||
},
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch((): { message?: string } => ({ message: response.statusText }))
|
||||
throw new Error(
|
||||
(error as { message?: string }).message ?? `API error: ${response.status} ${response.statusText}`,
|
||||
)
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>
|
||||
}
|
||||
|
||||
// ─── Query string builder ──────────────────────────────────────────────────────
|
||||
|
||||
function buildQueryString(filters: Record<string, unknown>): string {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* Content Lifecycle API — React Query hooks
|
||||
*
|
||||
* Live filesystem analysis of locale content via the platform-admin backend.
|
||||
*/
|
||||
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import type { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
import { fetchJson } from '@/api/http'
|
||||
|
||||
const API_BASE = '/api/admin/content-lifecycle'
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AuditSummary {
|
||||
totalNamespaces: number
|
||||
ready: number
|
||||
needsReview: number
|
||||
blocked: number
|
||||
totalIssues: number
|
||||
criticalIssues: number
|
||||
}
|
||||
|
||||
export interface AuditReport {
|
||||
domain: string
|
||||
timestamp: string
|
||||
summary: AuditSummary
|
||||
namespaces: Array<{
|
||||
namespace: string
|
||||
status: 'READY' | 'NEEDS_REVIEW' | 'BLOCKED'
|
||||
keyCount: number
|
||||
issues: Array<{
|
||||
severity: 'critical' | 'high' | 'medium' | 'low'
|
||||
category: string
|
||||
message: string
|
||||
}>
|
||||
}>
|
||||
}
|
||||
|
||||
export interface TranslationStatusReport {
|
||||
domain: string
|
||||
timestamp: string
|
||||
summary: {
|
||||
totalNamespaces: number
|
||||
fullyTranslated: number
|
||||
untranslated: number
|
||||
totalStale: number
|
||||
coveragePercent: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SeoReadinessReport {
|
||||
domain: string
|
||||
timestamp: string
|
||||
summary: {
|
||||
totalPages: number
|
||||
completePages: number
|
||||
completenessPercent: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface PublishDecision {
|
||||
domain: string
|
||||
timestamp: string
|
||||
publishable: boolean
|
||||
score: number
|
||||
gates: {
|
||||
audit: boolean
|
||||
translations: boolean
|
||||
seo: boolean
|
||||
}
|
||||
blockers: string[]
|
||||
auditSummary?: AuditSummary
|
||||
translationSummary?: TranslationStatusReport['summary']
|
||||
seoSummary?: SeoReadinessReport['summary']
|
||||
}
|
||||
|
||||
// ─── Query hooks ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function useDashboardReport(domain: string): UseQueryResult<PublishDecision | null, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['content-lifecycle', domain, 'dashboard'],
|
||||
queryFn: (): Promise<PublishDecision | null> =>
|
||||
fetchJson<PublishDecision | null>(`${API_BASE}/${domain}/dashboard`),
|
||||
enabled: !!domain,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useAuditReport(domain: string): UseQueryResult<AuditReport | null, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['content-lifecycle', domain, 'audit'],
|
||||
queryFn: (): Promise<AuditReport | null> =>
|
||||
fetchJson<AuditReport | null>(`${API_BASE}/${domain}/audit`),
|
||||
enabled: !!domain,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useTranslationStatusReport(domain: string): UseQueryResult<TranslationStatusReport | null, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['content-lifecycle', domain, 'translation-status'],
|
||||
queryFn: (): Promise<TranslationStatusReport | null> =>
|
||||
fetchJson<TranslationStatusReport | null>(`${API_BASE}/${domain}/translation-status`),
|
||||
enabled: !!domain,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useSeoReadinessReport(domain: string): UseQueryResult<SeoReadinessReport | null, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['content-lifecycle', domain, 'seo-readiness'],
|
||||
queryFn: (): Promise<SeoReadinessReport | null> =>
|
||||
fetchJson<SeoReadinessReport | null>(`${API_BASE}/${domain}/seo-readiness`),
|
||||
enabled: !!domain,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ───────────────────────────────────────────────────────────
|
||||
|
||||
export function useRefreshAudit(domain: string): { refresh: () => void } {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return {
|
||||
refresh: (): void => {
|
||||
queryClient.invalidateQueries({ queryKey: ['content-lifecycle', domain] })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Translation Admin API — React Query hooks
|
||||
*
|
||||
* Proxied to the Landing API via `/api/translations`.
|
||||
* Provides locale listing, coverage stats, and translation sync triggers.
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient, useQueries } from '@tanstack/react-query'
|
||||
import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query'
|
||||
|
||||
import { fetchJson, postJson } from '@/api/http'
|
||||
|
||||
const API_BASE = '/api/translations'
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface TranslationLocale {
|
||||
code: string
|
||||
name: string
|
||||
nativeName: string
|
||||
enabled: boolean
|
||||
direction: 'ltr' | 'rtl'
|
||||
}
|
||||
|
||||
export interface LocaleCoverageStats {
|
||||
locale: string
|
||||
totalNamespaces: number
|
||||
translatedNamespaces: number
|
||||
totalKeys: number
|
||||
translatedKeys: number
|
||||
coveragePercent: number
|
||||
}
|
||||
|
||||
export interface SyncResult {
|
||||
namespace: string
|
||||
locale: string
|
||||
added: number
|
||||
updated: number
|
||||
unchanged: number
|
||||
}
|
||||
|
||||
// ─── Query hooks ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function useTranslationLocales(): UseQueryResult<TranslationLocale[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ['translation-locales'],
|
||||
queryFn: (): Promise<TranslationLocale[]> =>
|
||||
fetchJson<{ locales: TranslationLocale[] }>(`${API_BASE}/locales`)
|
||||
.then((r) => r.locales),
|
||||
staleTime: 5 * 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useLocaleCoverage(locale: string): UseQueryResult<LocaleCoverageStats, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['translation-coverage', locale],
|
||||
queryFn: (): Promise<LocaleCoverageStats> =>
|
||||
fetchJson<LocaleCoverageStats>(`${API_BASE}/${locale}/coverage`),
|
||||
enabled: !!locale,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useAllCoverage(locales: string[]): Array<UseQueryResult<LocaleCoverageStats, Error>> {
|
||||
return useQueries({
|
||||
queries: locales.map((locale) => ({
|
||||
queryKey: ['translation-coverage', locale],
|
||||
queryFn: (): Promise<LocaleCoverageStats> =>
|
||||
fetchJson<LocaleCoverageStats>(`${API_BASE}/${locale}/coverage`),
|
||||
staleTime: 60_000,
|
||||
enabled: locales.length > 0,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ───────────────────────────────────────────────────────────
|
||||
|
||||
export function useSyncNamespace(): UseMutationResult<SyncResult, Error, { namespace: string }> {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ namespace }: { namespace: string }): Promise<SyncResult> =>
|
||||
postJson<SyncResult>(`${API_BASE}/sync/namespace/${encodeURIComponent(namespace)}`),
|
||||
onSuccess: (): void => {
|
||||
queryClient.invalidateQueries({ queryKey: ['translation-coverage'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['content-hub-verification'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue