Files
ContentGen_FE/src/lib/api/api-service.ts
T
Harun CAN 1f8f24fcf5
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
main
2026-05-06 10:47:57 +02:00

666 lines
19 KiB
TypeScript

import { createApiClient } from './create-api-client';
const API_URL = typeof window === 'undefined'
? (process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api')
: (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api');
export const apiClient = createApiClient(API_URL);
// ── Type Definitions ─────────────────────────────────────────────────
export interface Project {
id: string;
title: string;
description?: string;
prompt: string;
status: ProjectStatus;
progress: number;
language: string;
aspectRatio: string;
videoStyle: string;
cinematicReference?: string;
targetDuration: number;
creditsUsed: number;
thumbnailUrl?: string;
finalVideoUrl?: string;
errorMessage?: string;
scriptJson?: ScriptJson;
scriptVersion: number;
scenes?: Scene[];
renderJobs?: RenderJob[];
sourceType?: 'MANUAL' | 'X_TWEET' | 'YOUTUBE';
sourceTweetData?: Record<string, unknown>;
// SEO Power Engine
seoTitle?: string;
seoDescription?: string;
seoKeywords?: string[];
seoTitleAlts?: string[];
seoScore?: number;
socialContent?: {
youtubeTitle?: string;
youtubeDescription?: string;
tiktokCaption?: string;
instagramCaption?: string;
twitterText?: string;
};
createdAt: string;
updatedAt: string;
completedAt?: string;
}
export type ProjectStatus =
| 'DRAFT'
| 'GENERATING_SCRIPT'
| 'PENDING'
| 'GENERATING_MEDIA'
| 'RENDERING'
| 'COMPLETED'
| 'FAILED';
export interface Scene {
id: string;
order: number;
title?: string;
narrationText: string;
visualPrompt: string;
subtitleText?: string;
duration: number;
transitionType: string;
mediaAssets?: MediaAsset[];
}
export interface MediaAsset {
id: string;
type: string;
url?: string;
fileName?: string;
mimeType?: string;
sizeBytes?: number;
durationMs?: number;
aiProvider?: string;
}
export interface RenderJob {
id: string;
status: string;
currentStage?: string;
progress?: number;
attemptNumber: number;
processingTimeMs?: number;
errorMessage?: string;
finalVideoUrl?: string;
createdAt: string;
startedAt?: string;
completedAt?: string;
logs?: RenderLog[];
}
export interface RenderLog {
id: string;
stage: string;
level: string;
message: string;
durationMs?: number;
createdAt: string;
}
export interface ScriptJson {
metadata: {
title: string;
description: string;
totalDurationSeconds: number;
language: string;
hashtags: string[];
};
seo: {
title: string;
description: string;
keywords: string[];
hashtags: string[];
trendingHashtags?: string[];
estimatedSearchVolume?: string;
schemaMarkup: Record<string, unknown>;
};
seoTitleAlternatives?: string[];
seoScore?: number;
scenes: Array<{
order: number;
title?: string;
narrationText: string;
visualPrompt: string;
subtitleText: string;
durationSeconds: number;
transitionType: string;
}>;
musicPrompt: string;
voiceStyle: string;
socialContent?: {
youtubeTitle: string;
youtubeDescription: string;
tiktokCaption: string;
instagramCaption: string;
twitterText: string;
};
}
export interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
export interface CreateProjectPayload {
title: string;
description?: string;
prompt: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
seoKeywords?: string[];
referenceUrl?: string;
}
export interface UpdateProjectPayload {
title?: string;
description?: string;
prompt?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
seoKeywords?: string[];
}
export interface CreditBalance {
balance: number;
remaining: number;
total: number;
plan: string;
monthlyUsed: number;
monthlyLimit: number;
}
export interface Template {
id: string;
title: string;
description?: string;
thumbnailUrl?: string;
previewVideoUrl?: string;
category: string;
tags: string[];
language: string;
usageCount: number;
rating: number;
isFeatured: boolean;
}
export interface DashboardStats {
totalProjects: number;
completedVideos: number;
totalCreditsUsed: number;
creditsRemaining: number;
activeRenderJobs: number;
recentProjects: Project[];
}
// Tweet Types
export interface TweetAuthor {
id: string;
name: string;
username: string;
avatarUrl: string;
followersCount: number;
verified: boolean;
}
export interface TweetMetrics {
replies: number;
retweets: number;
likes: number;
views: number;
engagementRate: number;
}
export interface TweetMedia {
type: 'photo' | 'video' | 'gif';
url: string;
thumbnailUrl?: string;
width: number;
height: number;
}
export interface ParsedTweet {
id: string;
url: string;
text: string;
createdAt: string;
author: TweetAuthor;
metrics: TweetMetrics;
media: TweetMedia[];
quotedTweet?: ParsedTweet;
isThread: boolean;
threadTweets?: ParsedTweet[];
}
export interface TweetPreview {
tweet: ParsedTweet;
suggestedTitle: string;
suggestedPrompt: string;
viralScore: number;
contentType: 'tweet' | 'thread' | 'quote_tweet';
estimatedDuration: number;
}
export interface CreateFromTweetPayload {
tweetUrl: string;
title?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
}
export interface CreateFromYoutubePayload {
youtubeUrl: string;
title?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
}
export interface CreateFromDocumentPayload {
file: File;
title?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
}
export interface CreateFromTextPayload {
text: string;
title?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
}
export interface ExtractDocumentTopicsPayload {
file: File;
}
export interface ExtractDocumentTopicsResponse {
text: string;
topics: string[];
originalFilename: string;
}
export interface CreateFromExtractedTextPayload {
text: string;
topic: string;
originalFilename?: string;
language?: string;
aspectRatio?: string;
videoStyle?: string;
cinematicReference?: string;
targetDuration?: number;
}
export interface Notification {
id: string;
userId: string;
type: string;
title: string;
message?: string | null;
metadata?: Record<string, unknown> | null;
isRead: boolean;
createdAt: string;
updatedAt: string;
}
export interface NotificationListResponse {
data: Notification[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
unreadCount: number;
};
}
// ── API Functions ────────────────────────────────────────────────────
export const authApi = {
login: (data: any) =>
apiClient.post('/auth/login', data).then((r) => r.data),
register: (data: any) =>
apiClient.post('/auth/register', data).then((r) => r.data),
};
export const projectsApi = {
list: (params?: { page?: number; limit?: number; status?: string }) =>
apiClient.get<PaginatedResponse<Project>>('/projects', { params }).then((r) => r.data),
get: (id: string) =>
apiClient.get<Project>(`/projects/${id}`).then((r) => r.data),
create: (data: CreateProjectPayload) =>
apiClient.post<Project>('/projects', data).then((r) => r.data),
update: (id: string, data: Partial<CreateProjectPayload>) =>
apiClient.patch<Project>(`/projects/${id}`, data).then((r) => r.data),
delete: (id: string) =>
apiClient.delete(`/projects/${id}`).then((r) => r.data),
generateScript: (id: string) =>
apiClient.post<Project>(`/projects/${id}/generate-script`).then((r) => r.data),
approveAndQueue: (id: string, data?: { ttsProvider?: string; visualEffect?: string }) =>
apiClient.post<{ projectId: string; renderJobId: string; bullJobId: string }>(
`/projects/${id}/approve`,
data || {}
).then((r) => r.data),
cancelRender: (id: string) =>
apiClient.post<{ message: string; projectId: string; renderJobId: string; status: string }>(
`/projects/${id}/cancel-render`,
).then((r) => r.data),
generateSeoTitles: (id: string) =>
apiClient.post<{ titles: string[]; seoScore: number; currentTitle: string }>(
`/projects/${id}/generate-seo-titles`,
).then((r) => r.data),
generateSocialContent: (id: string) =>
apiClient.post<any>(`/projects/${id}/generate-social-content`).then((r) => r.data),
selectSeoTitle: (id: string, title: string) =>
apiClient.patch<Project>(
`/projects/${id}/select-title`,
{ title },
).then((r) => r.data),
getRenderQueue: () =>
apiClient.get<any>('/projects/render-queue').then((r) => r.data),
createFromTweet: (data: CreateFromTweetPayload) =>
apiClient.post<Project>('/projects/from-tweet', data).then((r) => r.data),
createFromYoutube: (data: CreateFromYoutubePayload) =>
apiClient.post<Project>('/projects/from-youtube', data).then((r) => r.data),
createFromDocument: (data: CreateFromDocumentPayload) => {
const formData = new FormData();
formData.append('file', data.file);
if (data.title) formData.append('title', data.title);
if (data.language) formData.append('language', data.language);
if (data.aspectRatio) formData.append('aspectRatio', data.aspectRatio);
if (data.videoStyle) formData.append('videoStyle', data.videoStyle);
if (data.targetDuration) formData.append('targetDuration', data.targetDuration.toString());
return apiClient.post<Project>('/projects/from-document', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}).then((r) => r.data);
},
createFromText: (data: CreateFromTextPayload) =>
apiClient.post<Project>('/projects/from-text', data).then((r) => r.data),
extractDocumentTopics: (data: ExtractDocumentTopicsPayload) => {
const formData = new FormData();
formData.append('file', data.file);
return apiClient.post<ExtractDocumentTopicsResponse>('/projects/extract-document-topics', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}).then((r) => r.data);
},
createFromExtractedText: (data: CreateFromExtractedTextPayload) =>
apiClient.post<Project>('/projects/document-from-topic', data).then((r) => r.data),
updateScene: (projectId: string, sceneId: string, data: Partial<Scene>) =>
apiClient.patch<Scene>(`/projects/${projectId}/scenes/${sceneId}`, data).then((r) => r.data),
regenerateScene: (projectId: string, sceneId: string) =>
apiClient.post<Scene>(`/projects/${projectId}/scenes/${sceneId}/regenerate`).then((r) => r.data),
generateSceneImage: (projectId: string, sceneId: string, customPrompt?: string) =>
apiClient.post<Scene>(`/projects/${projectId}/scenes/${sceneId}/generate-image`, { customPrompt }).then((r) => r.data),
upscaleSceneImage: (projectId: string, sceneId: string) =>
apiClient.post<Scene>(`/projects/${projectId}/scenes/${sceneId}/upscale-image`).then((r) => r.data),
};
export const toolsApi = {
analyzeYoutubeVideo: (url: string) =>
apiClient.post<any>('/youtube-tools/analyze', { url }).then((r) => r.data),
getYoutubeAnalysisHistory: () =>
apiClient.get<any[]>('/youtube-tools/history').then((r) => r.data),
getYoutubeAnalysisById: (id: string) =>
apiClient.get<any>(`/youtube-tools/analyze/${id}`).then((r) => r.data),
// SEO
analyzeYoutubeSEO: (url: string) =>
apiClient.post<any>('/youtube-tools/seo/analyze', { url }).then((r) => r.data),
getYoutubeSeoHistory: () =>
apiClient.get<any[]>('/youtube-tools/seo/history').then((r) => r.data),
getYoutubeSeoAnalysisById: (id: string) =>
apiClient.get<any>(`/youtube-tools/seo/analyze/${id}`).then((r) => r.data),
generateYoutubeSeoImage: (prompt: string) =>
apiClient.post<{ url: string }>('/youtube-tools/seo/generate-image', { prompt }).then((r) => r.data),
};
// Backend path: /billing/credits/balance (billing controller prefix)
export const creditsApi = {
getBalance: () =>
apiClient.get<CreditBalance>('/billing/credits/balance').then((r) => r.data),
getHistory: (params?: { page?: number; limit?: number }) =>
apiClient.get('/billing/credits/history', { params }).then((r) => r.data),
};
export const billingApi = {
createCheckout: (planName: string, billingCycle: 'monthly' | 'yearly') =>
apiClient.post<{ sessionId: string; url: string }>('/billing/checkout', { planName, billingCycle }).then((r) => r.data),
getSubscription: () =>
apiClient.get('/billing/subscription').then((r) => r.data),
getPlans: () =>
apiClient.get('/billing/plans').then((r) => r.data),
};
export const usersApi = {
getMe: () =>
apiClient.get('/users/me').then((r) => r.data),
updateProfile: (data: { firstName?: string; lastName?: string }) =>
apiClient.patch('/users/me', data).then((r) => r.data),
changePassword: (data: { currentPassword: string; newPassword: string }) =>
apiClient.patch('/users/me/password', data).then((r) => r.data),
};
export const templatesApi = {
list: (params?: { category?: string; language?: string; page?: number; limit?: number }) =>
apiClient.get<PaginatedResponse<Template>>('/templates', { params }).then((r) => r.data),
get: (id: string) =>
apiClient.get<Template>(`/templates/${id}`).then((r) => r.data),
clone: (id: string) =>
apiClient.post<Project>(`/templates/${id}/clone`).then((r) => r.data),
};
export const dashboardApi = {
getStats: () =>
apiClient.get<DashboardStats>('/dashboard/stats').then((r) => r.data),
};
export const xTwitterApi = {
preview: (tweetUrl: string) =>
apiClient.post<TweetPreview>('/x-twitter/preview', { tweetUrl }).then((r) => r.data),
fetch: (tweetUrl: string) =>
apiClient.post<ParsedTweet>('/x-twitter/fetch', { tweetUrl }).then((r) => r.data),
};
export const notificationsApi = {
list: (params?: { page?: number; limit?: number; unreadOnly?: boolean }) =>
apiClient.get<NotificationListResponse>('/notifications', { params }).then((r) => r.data),
getUnreadCount: () =>
apiClient.get<{ count: number }>('/notifications/unread-count').then((r) => r.data),
markAsRead: (id: string) =>
apiClient.patch<Notification>(`/notifications/${id}/read`).then((r) => r.data),
markAllAsRead: () =>
apiClient.patch<{ count: number }>('/notifications/read-all').then((r) => r.data),
delete: (id: string) =>
apiClient.delete(`/notifications/${id}`).then((r) => r.data),
};
// ── Admin API Types ────────────────────────────────────────────────────────
export interface AdminUser {
id: string;
email: string;
firstName?: string;
lastName?: string;
isActive: boolean;
createdAt: string;
roles?: Array<{ role: { id: string; name: string } }>;
}
export interface AdminProject {
id: string;
title: string;
status: string;
progress: number;
creditsUsed: number;
language: string;
sourceType: string;
createdAt: string;
user: { id: string; email: string; firstName?: string; lastName?: string };
_count: { scenes: number; renderJobs: number };
}
export interface AdminRenderJob {
id: string;
status: string;
currentStage?: string;
attemptNumber: number;
processingTimeMs?: number;
errorMessage?: string;
workerHostname?: string;
createdAt: string;
startedAt?: string;
completedAt?: string;
project: {
id: string;
title: string;
user: { id: string; email: string; firstName?: string; lastName?: string };
};
}
export interface AdminPlan {
id: string;
name: string;
displayName: string;
description?: string;
monthlyPrice: number;
yearlyPrice?: number;
currency: string;
monthlyCredits: number;
maxDuration: number;
maxResolution: string;
maxProjects: number;
isActive: boolean;
sortOrder: number;
features?: Record<string, unknown>;
_count: { subscriptions: number };
}
export interface AdminStats {
users: { total: number; active: number; inactive: number };
projects: { total: number; byStatus: Record<string, number> };
renderJobs: { byStatus: Record<string, number>; active: number };
credits: { totalGranted: number; totalUsed: number };
plans: { total: number };
templates: { total: number };
storage?: unknown;
recentUsers: AdminUser[];
}
// ── Admin API Functions ─────────────────────────────────────────────────────
export const adminApi = {
getStats: () =>
apiClient.get<AdminStats>('/admin/stats').then((r) => r.data),
getUsers: (params?: { page?: number; limit?: number }) =>
apiClient.get<PaginatedResponse<AdminUser>>('/admin/users', { params }).then((r) => r.data),
getUserDetail: (id: string) =>
apiClient.get(`/admin/users/${id}/detail`).then((r) => r.data),
toggleUserActive: (id: string) =>
apiClient.put(`/admin/users/${id}/toggle-active`).then((r) => r.data),
banUser: (id: string) =>
apiClient.put(`/admin/users/${id}/ban`).then((r) => r.data),
activateUser: (id: string) =>
apiClient.put(`/admin/users/${id}/activate`).then((r) => r.data),
grantCredits: (userId: string, data: { amount: number; description: string }) =>
apiClient.post(`/admin/users/${userId}/credits`, data).then((r) => r.data),
assignRole: (userId: string, roleId: string) =>
apiClient.post(`/admin/users/${userId}/roles/${roleId}`).then((r) => r.data),
removeRole: (userId: string, roleId: string) =>
apiClient.delete(`/admin/users/${userId}/roles/${roleId}`).then((r) => r.data),
getProjects: (params?: { page?: number; limit?: number; status?: string; userId?: string }) =>
apiClient.get<PaginatedResponse<AdminProject>>('/admin/projects', { params }).then((r) => r.data),
deleteProject: (id: string) =>
apiClient.delete(`/admin/projects/${id}`).then((r) => r.data),
getRenderJobs: (params?: { page?: number; limit?: number; status?: string }) =>
apiClient.get<PaginatedResponse<AdminRenderJob>>('/admin/render-jobs', { params }).then((r) => r.data),
getPlans: () =>
apiClient.get<AdminPlan[]>('/admin/plans').then((r) => r.data),
updatePlan: (id: string, data: Partial<AdminPlan>) =>
apiClient.put(`/admin/plans/${id}`, data).then((r) => r.data),
getRoles: () =>
apiClient.get('/admin/roles').then((r) => r.data),
createRole: (data: { name: string; description?: string }) =>
apiClient.post('/admin/roles', data).then((r) => r.data),
};