✨ feat: 完整应用重构 - 优化界面架构与用户体验
主要变更: - 重构应用界面:优化首页、登录、认证流程 - 新增用户档案模块:包含完整的档案管理组件 - 优化路由结构:重新组织页面布局和导航 - 改进API集成:新增活动、内容分类等API模块 - 删除冗余组件:清理不必要的文件和依赖 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
26
lib/api/activities.ts
Normal file
26
lib/api/activities.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { apiRequest } from './client';
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
desc: string;
|
||||
descEn: string;
|
||||
coverUrl: string;
|
||||
videoUrl: string;
|
||||
link: string;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface GetActivitiesParams {
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export async function getActivities(params: GetActivitiesParams = {}): Promise<Activity[]> {
|
||||
return apiRequest<{ data: Activity[] }>(`/api/activities`, {
|
||||
params
|
||||
}).then(res => res.data);
|
||||
}
|
||||
8
lib/api/categories-with-children.ts
Normal file
8
lib/api/categories-with-children.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { CategoriesWithChildrenResponse } from '../types/template';
|
||||
import { apiClient } from './client';
|
||||
|
||||
export async function categoriesWithChildren(): Promise<CategoriesWithChildrenResponse> {
|
||||
return apiClient<CategoriesWithChildrenResponse>('/api/categories-with-children', {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
@@ -1,82 +1,73 @@
|
||||
import { fetch, FetchRequestInit } from 'expo/fetch';
|
||||
import { getAuthHeaders, tokenManager } from '../auth/token-manager';
|
||||
import { storage } from '../storage';
|
||||
|
||||
const BASE_URL = 'https://api-test.mixvideo.bowong.cc';
|
||||
|
||||
export interface ApiRequestOptions extends FetchRequestInit {
|
||||
params?: Record<string, string | number | boolean | undefined>;
|
||||
export interface ApiRequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
body?: string;
|
||||
params?: Record<string, any>;
|
||||
headers?: Record<string, string>;
|
||||
requiresAuth?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准API请求函数
|
||||
*/
|
||||
async function getAuthToken(): Promise<string> {
|
||||
return (await storage.getItem(`bestaibest.better-auth.session_token`)) || '';
|
||||
}
|
||||
|
||||
export async function apiRequest<T = any>(
|
||||
endpoint: string,
|
||||
options: ApiRequestOptions = {}
|
||||
): Promise<T> {
|
||||
const { params, requiresAuth = false, ...fetchOptions } = options;
|
||||
const {
|
||||
method = 'GET',
|
||||
body,
|
||||
params,
|
||||
headers = {},
|
||||
requiresAuth = true,
|
||||
} = options;
|
||||
|
||||
let url = `${BASE_URL}${endpoint}`;
|
||||
// 构建 URL
|
||||
const url = new URL(endpoint, BASE_URL);
|
||||
|
||||
// 添加查询参数
|
||||
if (params) {
|
||||
const queryParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
queryParams.append(key, String(value));
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
}
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
// 准备请求头
|
||||
const requestHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...fetchOptions.headers,
|
||||
...headers,
|
||||
};
|
||||
|
||||
// 如果需要认证,自动添加认证头
|
||||
// 如果需要认证,添加 Bearer token
|
||||
if (requiresAuth) {
|
||||
const authHeaders = await getAuthHeaders();
|
||||
Object.assign(headers, authHeaders);
|
||||
const token = await getAuthToken();
|
||||
if (token) {
|
||||
requestHeaders['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
});
|
||||
// 发起请求
|
||||
const response = await fetch(url.toString(), {
|
||||
method,
|
||||
headers: requestHeaders,
|
||||
body: method !== 'GET' ? body : undefined,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
const errorMessage = `API Error: ${response.status} - ${errorText}`;
|
||||
|
||||
// 如果是401错误且需要认证,尝试自动处理
|
||||
if (response.status === 401 && requiresAuth) {
|
||||
console.log('检测到认证失败,清除token...');
|
||||
await tokenManager.clearToken();
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.text() as unknown as T;
|
||||
} catch (error: any) {
|
||||
console.error('API请求失败:', error);
|
||||
throw error;
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 带认证的API客户端
|
||||
*/
|
||||
export async function apiClient<T = any>(
|
||||
endpoint: string,
|
||||
options: ApiRequestOptions = {}
|
||||
|
||||
46
lib/api/template-generations.ts
Normal file
46
lib/api/template-generations.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { apiRequest } from './client';
|
||||
|
||||
export interface TemplateGeneration {
|
||||
id: string;
|
||||
userId: string;
|
||||
templateId: string;
|
||||
type: 'VIDEO' | 'IMAGE' | 'TEXT';
|
||||
resultUrl: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||||
creditsCost: number;
|
||||
creditsTransactionId: string | null;
|
||||
template: {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetTemplateGenerationsParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
type?: 'VIDEO' | 'IMAGE';
|
||||
}
|
||||
|
||||
export interface TemplateGenerationsResponseData {
|
||||
generations: TemplateGeneration[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface TemplateGenerationsResponse {
|
||||
success: boolean;
|
||||
data: TemplateGenerationsResponseData;
|
||||
}
|
||||
|
||||
export async function getTemplateGenerations(
|
||||
params: GetTemplateGenerationsParams = {}
|
||||
): Promise<TemplateGenerationsResponse> {
|
||||
return apiRequest<TemplateGenerationsResponse>('/api/template-generations', {
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { usernameClient } from "better-auth/client/plugins";
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { storage } from '../storage';
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: "https://api.mixvideo.bowong.cc/api/auth",
|
||||
baseURL: "https://api-test.mixvideo.bowong.cc/api/auth",
|
||||
fetchOptions: {
|
||||
credentials: "omit",
|
||||
auth: {
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
import { storage } from '@/lib/storage';
|
||||
import { ApiRequestOptions, apiRequest } from '@/lib/api/client';
|
||||
|
||||
const SESSION_TOKEN_KEY = 'bestaibest.better-auth.session_token';
|
||||
|
||||
/**
|
||||
* 安全API客户端
|
||||
* 自动处理认证状态和token管理
|
||||
*/
|
||||
class SecureApiClient {
|
||||
private isRefreshing = false;
|
||||
private refreshQueue: Array<() => void> = [];
|
||||
|
||||
/**
|
||||
* 获取认证token
|
||||
*/
|
||||
private async getAuthToken(): Promise<string | null> {
|
||||
return await storage.getItem(SESSION_TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除认证token
|
||||
*/
|
||||
private async clearAuthToken(): Promise<void> {
|
||||
await storage.removeItem(SESSION_TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起认证请求
|
||||
*/
|
||||
async makeAuthenticatedRequest<T = any>(
|
||||
requestFn: () => Promise<T>
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error: any) {
|
||||
// 检查是否是认证错误
|
||||
if (error?.status === 401 || error?.message?.includes('Unauthorized')) {
|
||||
console.log('检测到认证失败,尝试刷新token...');
|
||||
return await this.handleAuthFailure(requestFn);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证失败
|
||||
*/
|
||||
private async handleAuthFailure<T>(
|
||||
requestFn: () => Promise<T>
|
||||
): Promise<T> {
|
||||
try {
|
||||
// 尝试刷新token
|
||||
await this.refreshToken();
|
||||
// 重新执行请求
|
||||
return await requestFn();
|
||||
} catch (refreshError) {
|
||||
// 刷新失败,清除token并重新登录
|
||||
console.error('Token刷新失败:', refreshError);
|
||||
await this.clearAuthToken();
|
||||
|
||||
// 触发登录
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
throw new Error('认证已过期,请重新登录');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
private async refreshToken(): Promise<void> {
|
||||
if (this.isRefreshing) {
|
||||
// 如果正在刷新,等待刷新完成
|
||||
return new Promise((resolve) => {
|
||||
this.refreshQueue.push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
this.isRefreshing = true;
|
||||
|
||||
try {
|
||||
// TODO: 实现token刷新逻辑
|
||||
// 通常需要调用refresh token的API端点
|
||||
console.log('刷新token...');
|
||||
|
||||
// 示例刷新逻辑(需要根据实际的认证服务实现)
|
||||
// const response = await apiRequest('/auth/refresh', { method: 'POST' });
|
||||
// const newToken = response.token;
|
||||
// await storage.setItem(SESSION_TOKEN_KEY, newToken);
|
||||
|
||||
this.isRefreshing = false;
|
||||
|
||||
// 通知所有等待的请求
|
||||
this.refreshQueue.forEach((resolve) => resolve());
|
||||
this.refreshQueue = [];
|
||||
} catch (error) {
|
||||
this.isRefreshing = false;
|
||||
this.refreshQueue.forEach((resolve) => resolve());
|
||||
this.refreshQueue = [];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带认证的API请求
|
||||
*/
|
||||
async request<T = any>(url: string, options: ApiRequestOptions = {}): Promise<T> {
|
||||
return this.makeAuthenticatedRequest(async () => {
|
||||
const token = await this.getAuthToken();
|
||||
|
||||
const headers = {
|
||||
...options.headers,
|
||||
Authorization: token ? `Bearer ${token}` : undefined,
|
||||
};
|
||||
|
||||
return apiRequest(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
*/
|
||||
async get<T = any>(url: string, params?: Record<string, any>): Promise<T> {
|
||||
return this.request<T>(url, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
*/
|
||||
async post<T = any>(url: string, data?: any): Promise<T> {
|
||||
return this.request<T>(url, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
*/
|
||||
async put<T = any>(url: string, data?: any): Promise<T> {
|
||||
return this.request<T>(url, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
*/
|
||||
async delete<T = any>(url: string): Promise<T> {
|
||||
return this.request<T>(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例实例
|
||||
*/
|
||||
export const secureApiClient = new SecureApiClient();
|
||||
|
||||
/**
|
||||
* 带认证检查的API请求装饰器
|
||||
*/
|
||||
export function withAuthCheck<T extends any[], R>(
|
||||
fn: (...args: T) => Promise<R>
|
||||
) {
|
||||
return async (...args: T): Promise<R> => {
|
||||
return secureApiClient.makeAuthenticatedRequest(() => fn(...args));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动添加认证头部的API请求函数
|
||||
*/
|
||||
export async function authenticatedApiRequest<T = any>(
|
||||
url: string,
|
||||
options: ApiRequestOptions = {}
|
||||
): Promise<T> {
|
||||
const token = await storage.getItem(SESSION_TOKEN_KEY);
|
||||
|
||||
const headers = {
|
||||
...options.headers,
|
||||
Authorization: token ? `Bearer ${token}` : undefined,
|
||||
};
|
||||
|
||||
return apiRequest(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
import { storage } from '@/lib/storage';
|
||||
|
||||
const SESSION_TOKEN_KEY = 'bestaibest.better-auth.session_token';
|
||||
const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // 5分钟
|
||||
|
||||
interface TokenInfo {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token管理器
|
||||
* 负责处理token的存储、刷新和生命周期管理
|
||||
*/
|
||||
class TokenManager {
|
||||
private refreshTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* 设置token信息
|
||||
*/
|
||||
async setToken(token: string, expiresIn?: number, refreshToken?: string): Promise<void> {
|
||||
const now = Date.now();
|
||||
const expiresAt = expiresIn ? now + expiresIn * 1000 : now + 24 * 60 * 60 * 1000; // 默认24小时
|
||||
|
||||
const tokenInfo: TokenInfo = {
|
||||
token,
|
||||
expiresAt,
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
await storage.setItem(SESSION_TOKEN_KEY, JSON.stringify(tokenInfo));
|
||||
this.scheduleTokenRefresh(expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token信息
|
||||
*/
|
||||
async getToken(): Promise<TokenInfo | null> {
|
||||
const tokenData = await storage.getItem(SESSION_TOKEN_KEY);
|
||||
if (!tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenInfo: TokenInfo = JSON.parse(tokenData);
|
||||
|
||||
// 检查token是否即将过期
|
||||
if (this.isTokenExpiring(tokenInfo.expiresAt)) {
|
||||
console.log('Token即将过期,尝试刷新...');
|
||||
try {
|
||||
await this.refreshToken(tokenInfo.refreshToken);
|
||||
return this.getToken(); // 递归获取新的token
|
||||
} catch (error) {
|
||||
console.error('Token刷新失败:', error);
|
||||
await this.clearToken();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return tokenInfo;
|
||||
} catch (error) {
|
||||
console.error('解析token失败:', error);
|
||||
await this.clearToken();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始token字符串
|
||||
*/
|
||||
async getTokenString(): Promise<string | null> {
|
||||
const tokenInfo = await this.getToken();
|
||||
return tokenInfo?.token ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查token是否即将过期
|
||||
*/
|
||||
private isTokenExpiring(expiresAt: number): boolean {
|
||||
const now = Date.now();
|
||||
return expiresAt - now < TOKEN_REFRESH_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排token刷新
|
||||
*/
|
||||
private scheduleTokenRefresh(expiresAt: number): void {
|
||||
if (this.refreshTimer) {
|
||||
clearTimeout(this.refreshTimer);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const refreshTime = expiresAt - TOKEN_REFRESH_THRESHOLD - now;
|
||||
|
||||
if (refreshTime > 0) {
|
||||
this.refreshTimer = setTimeout(async () => {
|
||||
console.log('自动刷新token...');
|
||||
try {
|
||||
await this.refreshToken();
|
||||
} catch (error) {
|
||||
console.error('自动刷新token失败:', error);
|
||||
}
|
||||
}, refreshTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
private async refreshToken(refreshToken?: string): Promise<void> {
|
||||
// TODO: 实现实际的token刷新逻辑
|
||||
// 这里应该调用认证服务的refresh端点
|
||||
|
||||
console.log('执行token刷新...');
|
||||
|
||||
try {
|
||||
// 示例刷新逻辑
|
||||
// const response = await fetch('/api/auth/refresh', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// refreshToken: refreshToken || await storage.getItem('refresh_token'),
|
||||
// }),
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error('刷新失败');
|
||||
// }
|
||||
|
||||
// const data = await response.json();
|
||||
// await this.setToken(data.accessToken, data.expiresIn, data.refreshToken);
|
||||
|
||||
// 临时实现:清除token,强制重新登录
|
||||
throw new Error('Refresh token not implemented');
|
||||
} catch (error) {
|
||||
console.error('Token刷新失败:', error);
|
||||
await this.clearToken();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除token
|
||||
*/
|
||||
async clearToken(): Promise<void> {
|
||||
if (this.refreshTimer) {
|
||||
clearTimeout(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
|
||||
await storage.removeItem(SESSION_TOKEN_KEY);
|
||||
console.log('Token已清除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查token是否有效
|
||||
*/
|
||||
async isTokenValid(): Promise<boolean> {
|
||||
const tokenInfo = await this.getToken();
|
||||
if (!tokenInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
return tokenInfo.expiresAt > now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token剩余有效时间(毫秒)
|
||||
*/
|
||||
async getTokenRemainingTime(): Promise<number> {
|
||||
const tokenInfo = await this.getToken();
|
||||
if (!tokenInfo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
return Math.max(0, tokenInfo.expiresAt - now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例实例
|
||||
*/
|
||||
export const tokenManager = new TokenManager();
|
||||
|
||||
/**
|
||||
* 便捷函数:获取认证头
|
||||
*/
|
||||
export async function getAuthHeaders(): Promise<Record<string, string>> {
|
||||
const token = await tokenManager.getTokenString();
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷函数:检查是否已认证
|
||||
*/
|
||||
export async function isAuthenticated(): Promise<boolean> {
|
||||
return tokenManager.isTokenValid();
|
||||
}
|
||||
@@ -2,6 +2,10 @@ export interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
nameEn: string;
|
||||
description?: string;
|
||||
descriptionEn?: string;
|
||||
sortOrder?: number;
|
||||
isActive?: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -16,6 +20,96 @@ export interface Tag {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface TemplateMediaAsset {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface TemplateNodeOutput {
|
||||
images?: TemplateMediaAsset[];
|
||||
videos?: TemplateMediaAsset[];
|
||||
texts?: string[];
|
||||
coverUrl?: string;
|
||||
}
|
||||
|
||||
export interface TemplateNodeActionData {
|
||||
n?: number;
|
||||
prompt?: string;
|
||||
duration?: string;
|
||||
aspectRatio?: string;
|
||||
selectedModel?: string;
|
||||
advancedParams?: Record<string, string | number | boolean | null>;
|
||||
}
|
||||
|
||||
export interface TemplateNodeMetrics {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface TemplateNodePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface TemplateGraphNodeData {
|
||||
label?: string;
|
||||
text?: string;
|
||||
description?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
flowType?: string;
|
||||
autoPlay?: boolean;
|
||||
controls?: boolean;
|
||||
output?: TemplateNodeOutput;
|
||||
actionData?: TemplateNodeActionData;
|
||||
}
|
||||
|
||||
export interface TemplateGraphNode {
|
||||
id: string;
|
||||
data: TemplateGraphNodeData;
|
||||
type: string;
|
||||
dragging: boolean;
|
||||
measured: TemplateNodeMetrics;
|
||||
position: TemplateNodePosition;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface TemplateGraphEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
sourceHandle: string;
|
||||
targetHandle: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface TemplateGraphViewport {
|
||||
x: number;
|
||||
y: number;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
export interface TemplateGraph {
|
||||
edges: TemplateGraphEdge[];
|
||||
nodes: TemplateGraphNode[];
|
||||
viewport: TemplateGraphViewport;
|
||||
}
|
||||
|
||||
export interface TemplateFormSchema {
|
||||
startNodes: TemplateGraphNode[];
|
||||
endNodes: TemplateGraphNode[];
|
||||
}
|
||||
|
||||
export interface TemplateTagLink {
|
||||
templateId: string;
|
||||
tagId: string;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tag: Tag;
|
||||
}
|
||||
|
||||
export type TemplateAssignedTag = Pick<Tag, 'id' | 'name' | 'nameEn' | 'createdAt' | 'updatedAt'>;
|
||||
|
||||
export interface Template {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -27,13 +121,17 @@ export interface Template {
|
||||
aspectRatio: string;
|
||||
userId: string;
|
||||
categoryId: string;
|
||||
content?: unknown;
|
||||
formSchema?: unknown;
|
||||
content?: TemplateGraph | null;
|
||||
formSchema?: TemplateFormSchema | null;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
uploadSapecifications?: string | null;
|
||||
uploadSapecificationsEn?: string | null;
|
||||
sortOrder?: number;
|
||||
category: Category | null;
|
||||
tags: Tag[];
|
||||
templateTags?: TemplateTagLink[];
|
||||
}
|
||||
|
||||
export interface PaginationMeta {
|
||||
@@ -53,3 +151,73 @@ export interface TemplateResponse {
|
||||
success: boolean;
|
||||
data: Template;
|
||||
}
|
||||
|
||||
export interface CategoryTagLink {
|
||||
categoryId: string;
|
||||
tagId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tag: Tag;
|
||||
}
|
||||
|
||||
export interface CategoryTemplate extends Omit<Template, 'category' | 'tags' | 'templateTags'> {
|
||||
templateTags: TemplateTagLink[];
|
||||
tags: TemplateAssignedTag[];
|
||||
}
|
||||
|
||||
export interface CategoryWithChildren extends Category {
|
||||
description: string;
|
||||
descriptionEn: string;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
categoryTags: CategoryTagLink[];
|
||||
templates: CategoryTemplate[];
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export interface CategoriesWithChildrenResponse {
|
||||
success: boolean;
|
||||
data: CategoryWithChildren[];
|
||||
}
|
||||
|
||||
export interface TemplateGeneration {
|
||||
id: string;
|
||||
userId: string;
|
||||
templateId: string;
|
||||
type: 'VIDEO' | 'IMAGE' | 'TEXT';
|
||||
resultUrl: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||||
creditsCost: number;
|
||||
creditsTransactionId: string | null;
|
||||
template: {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEn: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetTemplateGenerationsParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
status?: 'pending' | 'processing' | 'completed' | 'failed';
|
||||
templateId?: string;
|
||||
}
|
||||
|
||||
export interface TemplateGenerationsResponseData {
|
||||
generations: TemplateGeneration[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface TemplateGenerationsResponse {
|
||||
success: boolean;
|
||||
data: TemplateGenerationsResponseData;
|
||||
}
|
||||
|
||||
export interface TemplateGenerationResponse {
|
||||
success: boolean;
|
||||
data: TemplateGeneration;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user