kintex/src/frontend/src/screens/cms/cmsApi.ts

175 lines
5.9 KiB
TypeScript

/*
* M17 CMS 실배선 API 계약(SCR-35·36·37). 공용 클라이언트(../../api/client)만 사용 — client/endpoints/types 무수정.
* 정본: 백엔드 컨트롤러
* - GET /api/cms/contents → PageResponse<CmsContent>
* - POST /api/cms/contents → CmsContent(신규 초안)
* - PATCH /api/cms/contents/{id}/status?value= → CmsContent(전이·역전이 400)
* - GET/PUT /api/cms/contents/{id}/translations → CmsTranslation[]
* - GET/PUT /api/exhibitors/{exhibitorId}/microsite → Microsite
* - GET /api/public/microsites/{exhibitorId} → Microsite(공개·무인증)
*/
import { api } from '../../api/client';
import type { PageResponse } from '../../api/types';
export type FlowStatus = 'draft' | 'review' | 'approved' | 'published';
export type TransStatus = 'none' | 'ai' | 'reviewed';
export type Lang = 'ko' | 'en' | 'zh' | 'ja';
export interface CmsContent {
id: string;
eventId: string | null;
contentType: string | null;
title: string;
body: string | null;
status: FlowStatus;
lang: string | null;
scheduledAt: string | null;
signage: boolean;
mailing: boolean;
authorName: string | null;
publishedAt: string | null;
createdAt: string | null;
updatedAt: string | null;
}
export interface CmsTranslation {
contentId: string;
lang: Lang;
title: string | null;
body: string | null;
transStatus: TransStatus;
updatedAt: string | null;
}
export interface CmsContentVersion {
id: string;
contentId: string;
versionNo: number;
title: string | null;
body: string | null;
status: FlowStatus;
authorName: string | null;
note: string | null;
createdAt: string | null;
}
export interface CmsMedia {
id: string;
eventId: string | null;
fileName: string;
url: string;
contentType: string | null;
sizeBytes: number | null;
altText: string | null;
uploaderName: string | null;
createdAt: string | null;
}
export interface ContentUpdateRequest {
title: string;
body?: string | null;
scheduledAt?: string | null;
signage?: boolean | null;
mailing?: boolean | null;
}
export interface MicrositeSection {
id: string;
type: string;
visible: boolean;
order?: number;
payload?: Record<string, unknown>;
}
export interface Microsite {
exhibitorId: string;
eventId: string | null;
slug: string | null;
exhibitorName: string | null;
theme: string;
sections: MicrositeSection[];
seoTitle: string | null;
seoMeta: string | null;
langs: string | null;
status: 'draft' | 'published';
publishedAt: string | null;
updatedAt: string | null;
}
export interface ContentCreateRequest {
eventId?: string | null;
contentType?: string;
title: string;
body?: string;
lang?: string;
}
export interface TranslationSaveRequest {
lang: Lang;
title?: string | null;
body?: string | null;
transStatus?: TransStatus;
}
export interface MicrositeSaveRequest {
eventId?: string | null;
slug?: string | null;
exhibitorName?: string | null;
theme?: string;
sections?: MicrositeSection[];
seoTitle?: string | null;
seoMeta?: string | null;
langs?: string | null;
status?: 'draft' | 'published';
}
export const cmsApi = {
listContents: (p: { status?: string; type?: string; keyword?: string; page?: number; size?: number } = {}) => {
const qs = new URLSearchParams();
if (p.status) qs.set('status', p.status);
if (p.type) qs.set('type', p.type);
if (p.keyword) qs.set('keyword', p.keyword);
qs.set('page', String(p.page ?? 0));
qs.set('size', String(p.size ?? 50));
return api.get<PageResponse<CmsContent>>(`/api/cms/contents?${qs.toString()}`);
},
createContent: (body: ContentCreateRequest) => api.post<CmsContent>('/api/cms/contents', body),
getContent: (id: string) => api.get<CmsContent>(`/api/cms/contents/${encodeURIComponent(id)}`),
updateContent: (id: string, body: ContentUpdateRequest) =>
api.put<CmsContent>(`/api/cms/contents/${encodeURIComponent(id)}`, body),
transition: (id: string, value: FlowStatus) =>
api.patch<CmsContent>(`/api/cms/contents/${encodeURIComponent(id)}/status?value=${value}`),
getVersions: (id: string) =>
api.get<CmsContentVersion[]>(`/api/cms/contents/${encodeURIComponent(id)}/versions`),
rollback: (id: string, versionNo: number) =>
api.post<CmsContent>(`/api/cms/contents/${encodeURIComponent(id)}/rollback?versionNo=${versionNo}`),
getTranslations: (id: string) =>
api.get<CmsTranslation[]>(`/api/cms/contents/${encodeURIComponent(id)}/translations`),
saveTranslation: (id: string, body: TranslationSaveRequest) =>
api.put<CmsTranslation[]>(`/api/cms/contents/${encodeURIComponent(id)}/translations`, body),
/** AI 자동 번역(초벌) — 백엔드 미배선 시 501. 프론트는 degraded("준비 중")로 처리. */
aiTranslate: (id: string, lang: Lang) =>
api.post<CmsTranslation>(
`/api/cms/contents/${encodeURIComponent(id)}/translations/ai?lang=${lang}`,
),
// ── 미디어 라이브러리 ──
listMedia: (limit = 60) => api.get<CmsMedia[]>(`/api/cms/media?limit=${limit}`),
uploadMedia: (file: File, altText?: string) => {
const form = new FormData();
form.append('file', file);
if (altText) form.append('altText', altText);
return api.postForm<CmsMedia>('/api/cms/media', form);
},
// ── 공개 조회(무인증·게시 상태만) ──
publicByType: (type: string, limit = 50) =>
api.get<CmsContent[]>(`/api/public/cms/${encodeURIComponent(type)}?limit=${limit}`, {
anonymous: true,
}),
getMicrosite: (exhibitorId: string) =>
api.get<Microsite>(`/api/exhibitors/${encodeURIComponent(exhibitorId)}/microsite`),
saveMicrosite: (exhibitorId: string, body: MicrositeSaveRequest) =>
api.put<Microsite>(`/api/exhibitors/${encodeURIComponent(exhibitorId)}/microsite`, body),
getPublicMicrosite: (exhibitorId: string) =>
api.get<Microsite>(`/api/public/microsites/${encodeURIComponent(exhibitorId)}`, { anonymous: true }),
};