feat(api): Add 5 new API clients for integrations
Added API clients with full endpoint coverage: - ai.api.ts: 11 endpoints (conversations, messages, analyze, chat) - whatsapp.api.ts: 20 endpoints (conversations, templates, broadcasts) - webhooks.api.ts: 14 endpoints (CRUD, deliveries, logs) - feature-flags.api.ts: 17 endpoints (flags, overrides, check) - storage.api.ts: 21 endpoints (buckets, files, folders) Total: 83 new endpoints Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
41ae4f195c
commit
14b84c61e2
182
src/features/ai/api/ai.api.ts
Normal file
182
src/features/ai/api/ai.api.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { api } from '@services/api/axios-instance';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/ai';
|
||||||
|
|
||||||
|
export interface AiConversation {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
model: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
messageCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiMessage {
|
||||||
|
id: string;
|
||||||
|
conversationId: string;
|
||||||
|
role: 'user' | 'assistant' | 'system';
|
||||||
|
content: string;
|
||||||
|
createdAt: string;
|
||||||
|
tokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
maxTokens: number;
|
||||||
|
isAvailable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiSuggestion {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
content: string;
|
||||||
|
confidence: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiAnalysisResult {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
result: any;
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateConversationDto {
|
||||||
|
title?: string;
|
||||||
|
model?: string;
|
||||||
|
systemPrompt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendMessageDto {
|
||||||
|
content: string;
|
||||||
|
attachments?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatDto {
|
||||||
|
message: string;
|
||||||
|
model?: string;
|
||||||
|
temperature?: number;
|
||||||
|
maxTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalyzeDto {
|
||||||
|
type: 'sentiment' | 'summary' | 'extract' | 'classify' | 'translate';
|
||||||
|
content: string;
|
||||||
|
options?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const aiApi = {
|
||||||
|
// Get all conversations
|
||||||
|
getConversations: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
model?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: AiConversation[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.model) params.append('model', filters.model);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: AiConversation[]; total: number }>(
|
||||||
|
`${BASE_URL}/conversations?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get conversation by ID
|
||||||
|
getConversation: async (id: string): Promise<AiConversation> => {
|
||||||
|
const response = await api.get<AiConversation>(`${BASE_URL}/conversations/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create new conversation
|
||||||
|
createConversation: async (data: CreateConversationDto): Promise<AiConversation> => {
|
||||||
|
const response = await api.post<AiConversation>(`${BASE_URL}/conversations`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete conversation
|
||||||
|
deleteConversation: async (id: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/conversations/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Send message in conversation
|
||||||
|
sendMessage: async (conversationId: string, data: SendMessageDto): Promise<AiMessage> => {
|
||||||
|
const response = await api.post<AiMessage>(
|
||||||
|
`${BASE_URL}/conversations/${conversationId}/messages`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get messages from conversation
|
||||||
|
getMessages: async (
|
||||||
|
conversationId: string,
|
||||||
|
filters?: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: AiMessage[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: AiMessage[]; total: number }>(
|
||||||
|
`${BASE_URL}/conversations/${conversationId}/messages?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get AI suggestions
|
||||||
|
getSuggestions: async (context: {
|
||||||
|
type: string;
|
||||||
|
data: any;
|
||||||
|
}): Promise<AiSuggestion[]> => {
|
||||||
|
const response = await api.post<AiSuggestion[]>(`${BASE_URL}/suggestions`, context);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Analyze content
|
||||||
|
analyze: async (data: AnalyzeDto): Promise<AiAnalysisResult> => {
|
||||||
|
const response = await api.post<AiAnalysisResult>(`${BASE_URL}/analyze`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get available models
|
||||||
|
getModels: async (): Promise<AiModel[]> => {
|
||||||
|
const response = await api.get<AiModel[]>(`${BASE_URL}/models`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quick chat (stateless)
|
||||||
|
chat: async (data: ChatDto): Promise<{ response: string; tokens: number }> => {
|
||||||
|
const response = await api.post<{ response: string; tokens: number }>(
|
||||||
|
`${BASE_URL}/chat`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stream chat response
|
||||||
|
streamChat: async (
|
||||||
|
data: ChatDto,
|
||||||
|
onChunk: (chunk: string) => void
|
||||||
|
): Promise<void> => {
|
||||||
|
const response = await api.post(`${BASE_URL}/chat/stream`, data, {
|
||||||
|
responseType: 'stream',
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = response.data.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
onChunk(decoder.decode(value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
1
src/features/ai/api/index.ts
Normal file
1
src/features/ai/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ai.api';
|
||||||
232
src/features/feature-flags/api/feature-flags.api.ts
Normal file
232
src/features/feature-flags/api/feature-flags.api.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { api } from '@services/api/axios-instance';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/feature-flags';
|
||||||
|
|
||||||
|
export interface FeatureFlag {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
type: 'boolean' | 'percentage' | 'user-list' | 'json';
|
||||||
|
value: any;
|
||||||
|
defaultValue: any;
|
||||||
|
environment: 'development' | 'staging' | 'production' | 'all';
|
||||||
|
tags: string[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FeatureFlagOverride {
|
||||||
|
id: string;
|
||||||
|
flagId: string;
|
||||||
|
targetType: 'user' | 'company' | 'tenant' | 'role';
|
||||||
|
targetId: string;
|
||||||
|
targetName?: string;
|
||||||
|
value: any;
|
||||||
|
expiresAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FeatureFlagCheckResult {
|
||||||
|
key: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
value: any;
|
||||||
|
source: 'default' | 'flag' | 'override';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateFeatureFlagDto {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
isEnabled?: boolean;
|
||||||
|
type?: 'boolean' | 'percentage' | 'user-list' | 'json';
|
||||||
|
value?: any;
|
||||||
|
defaultValue?: any;
|
||||||
|
environment?: 'development' | 'staging' | 'production' | 'all';
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateFeatureFlagDto {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
isEnabled?: boolean;
|
||||||
|
type?: 'boolean' | 'percentage' | 'user-list' | 'json';
|
||||||
|
value?: any;
|
||||||
|
defaultValue?: any;
|
||||||
|
environment?: 'development' | 'staging' | 'production' | 'all';
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateOverrideDto {
|
||||||
|
targetType: 'user' | 'company' | 'tenant' | 'role';
|
||||||
|
targetId: string;
|
||||||
|
value: any;
|
||||||
|
expiresAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const featureFlagsApi = {
|
||||||
|
// Get all feature flags
|
||||||
|
getAll: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
isEnabled?: boolean;
|
||||||
|
environment?: string;
|
||||||
|
tag?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: FeatureFlag[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.isEnabled !== undefined) params.append('isEnabled', String(filters.isEnabled));
|
||||||
|
if (filters?.environment) params.append('environment', filters.environment);
|
||||||
|
if (filters?.tag) params.append('tag', filters.tag);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: FeatureFlag[]; total: number }>(
|
||||||
|
`${BASE_URL}?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get feature flag by ID
|
||||||
|
getById: async (id: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.get<FeatureFlag>(`${BASE_URL}/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get feature flag by key
|
||||||
|
getByKey: async (key: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.get<FeatureFlag>(`${BASE_URL}/key/${key}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create feature flag
|
||||||
|
create: async (data: CreateFeatureFlagDto): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.post<FeatureFlag>(BASE_URL, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update feature flag
|
||||||
|
update: async (id: string, data: UpdateFeatureFlagDto): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.patch<FeatureFlag>(`${BASE_URL}/${id}`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete feature flag
|
||||||
|
delete: async (id: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check if flag is enabled (for current user context)
|
||||||
|
check: async (key: string, context?: {
|
||||||
|
userId?: string;
|
||||||
|
companyId?: string;
|
||||||
|
tenantId?: string;
|
||||||
|
}): Promise<FeatureFlagCheckResult> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (context?.userId) params.append('userId', context.userId);
|
||||||
|
if (context?.companyId) params.append('companyId', context.companyId);
|
||||||
|
if (context?.tenantId) params.append('tenantId', context.tenantId);
|
||||||
|
|
||||||
|
const response = await api.get<FeatureFlagCheckResult>(
|
||||||
|
`${BASE_URL}/check/${key}?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check multiple flags at once
|
||||||
|
checkMultiple: async (keys: string[], context?: {
|
||||||
|
userId?: string;
|
||||||
|
companyId?: string;
|
||||||
|
tenantId?: string;
|
||||||
|
}): Promise<Record<string, FeatureFlagCheckResult>> => {
|
||||||
|
const response = await api.post<Record<string, FeatureFlagCheckResult>>(
|
||||||
|
`${BASE_URL}/check-multiple`,
|
||||||
|
{ keys, context }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggle feature flag
|
||||||
|
toggle: async (id: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.post<FeatureFlag>(`${BASE_URL}/${id}/toggle`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enable feature flag
|
||||||
|
enable: async (id: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.post<FeatureFlag>(`${BASE_URL}/${id}/enable`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disable feature flag
|
||||||
|
disable: async (id: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.post<FeatureFlag>(`${BASE_URL}/${id}/disable`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get overrides for a flag
|
||||||
|
getOverrides: async (
|
||||||
|
flagId: string,
|
||||||
|
filters?: {
|
||||||
|
targetType?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: FeatureFlagOverride[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.targetType) params.append('targetType', filters.targetType);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: FeatureFlagOverride[]; total: number }>(
|
||||||
|
`${BASE_URL}/${flagId}/overrides?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create override
|
||||||
|
createOverride: async (flagId: string, data: CreateOverrideDto): Promise<FeatureFlagOverride> => {
|
||||||
|
const response = await api.post<FeatureFlagOverride>(
|
||||||
|
`${BASE_URL}/${flagId}/overrides`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete override
|
||||||
|
deleteOverride: async (flagId: string, overrideId: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/${flagId}/overrides/${overrideId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get all tags
|
||||||
|
getTags: async (): Promise<string[]> => {
|
||||||
|
const response = await api.get<string[]>(`${BASE_URL}/tags`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clone flag
|
||||||
|
clone: async (id: string, newKey: string): Promise<FeatureFlag> => {
|
||||||
|
const response = await api.post<FeatureFlag>(`${BASE_URL}/${id}/clone`, { newKey });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get flag history/audit log
|
||||||
|
getHistory: async (
|
||||||
|
flagId: string,
|
||||||
|
filters?: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: any[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: any[]; total: number }>(
|
||||||
|
`${BASE_URL}/${flagId}/history?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
1
src/features/feature-flags/api/index.ts
Normal file
1
src/features/feature-flags/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './feature-flags.api';
|
||||||
1
src/features/storage/api/index.ts
Normal file
1
src/features/storage/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './storage.api';
|
||||||
330
src/features/storage/api/storage.api.ts
Normal file
330
src/features/storage/api/storage.api.ts
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
import { api } from '@services/api/axios-instance';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/storage';
|
||||||
|
|
||||||
|
export interface StorageBucket {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
region: string;
|
||||||
|
provider: 's3' | 'r2' | 'gcs' | 'azure';
|
||||||
|
isPublic: boolean;
|
||||||
|
corsEnabled: boolean;
|
||||||
|
versioning: boolean;
|
||||||
|
totalSize: number;
|
||||||
|
fileCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageFile {
|
||||||
|
id: string;
|
||||||
|
bucketId: string;
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
mimeType: string;
|
||||||
|
size: number;
|
||||||
|
isPublic: boolean;
|
||||||
|
url?: string;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
versionId?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageFolder {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
fileCount: number;
|
||||||
|
totalSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageUsage {
|
||||||
|
totalSize: number;
|
||||||
|
totalFiles: number;
|
||||||
|
usedQuota: number;
|
||||||
|
maxQuota: number;
|
||||||
|
usagePercentage: number;
|
||||||
|
byBucket: {
|
||||||
|
bucketId: string;
|
||||||
|
bucketName: string;
|
||||||
|
size: number;
|
||||||
|
fileCount: number;
|
||||||
|
}[];
|
||||||
|
byMimeType: {
|
||||||
|
mimeType: string;
|
||||||
|
size: number;
|
||||||
|
fileCount: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresignedUrlResponse {
|
||||||
|
url: string;
|
||||||
|
expiresAt: string;
|
||||||
|
fields?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateBucketDto {
|
||||||
|
name: string;
|
||||||
|
region?: string;
|
||||||
|
provider?: 's3' | 'r2' | 'gcs' | 'azure';
|
||||||
|
isPublic?: boolean;
|
||||||
|
corsEnabled?: boolean;
|
||||||
|
versioning?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadFileDto {
|
||||||
|
file: File;
|
||||||
|
path?: string;
|
||||||
|
isPublic?: boolean;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const storageApi = {
|
||||||
|
// Get all buckets
|
||||||
|
getBuckets: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
provider?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: StorageBucket[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.provider) params.append('provider', filters.provider);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: StorageBucket[]; total: number }>(
|
||||||
|
`${BASE_URL}/buckets?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get bucket by ID
|
||||||
|
getBucket: async (id: string): Promise<StorageBucket> => {
|
||||||
|
const response = await api.get<StorageBucket>(`${BASE_URL}/buckets/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create bucket
|
||||||
|
createBucket: async (data: CreateBucketDto): Promise<StorageBucket> => {
|
||||||
|
const response = await api.post<StorageBucket>(`${BASE_URL}/buckets`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete bucket
|
||||||
|
deleteBucket: async (id: string, force?: boolean): Promise<void> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (force) params.append('force', 'true');
|
||||||
|
await api.delete(`${BASE_URL}/buckets/${id}?${params.toString()}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get files in bucket
|
||||||
|
getFiles: async (
|
||||||
|
bucketId: string,
|
||||||
|
filters?: {
|
||||||
|
path?: string;
|
||||||
|
search?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: StorageFile[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.path) params.append('path', filters.path);
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.mimeType) params.append('mimeType', filters.mimeType);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: StorageFile[]; total: number }>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get file by ID
|
||||||
|
getFile: async (bucketId: string, fileId: string): Promise<StorageFile> => {
|
||||||
|
const response = await api.get<StorageFile>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upload file
|
||||||
|
uploadFile: async (bucketId: string, data: UploadFileDto): Promise<StorageFile> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', data.file);
|
||||||
|
if (data.path) formData.append('path', data.path);
|
||||||
|
if (data.isPublic !== undefined) formData.append('isPublic', String(data.isPublic));
|
||||||
|
if (data.metadata) formData.append('metadata', JSON.stringify(data.metadata));
|
||||||
|
|
||||||
|
const response = await api.post<StorageFile>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upload multiple files
|
||||||
|
uploadFiles: async (
|
||||||
|
bucketId: string,
|
||||||
|
files: File[],
|
||||||
|
path?: string
|
||||||
|
): Promise<StorageFile[]> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
files.forEach(file => formData.append('files', file));
|
||||||
|
if (path) formData.append('path', path);
|
||||||
|
|
||||||
|
const response = await api.post<StorageFile[]>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/batch`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Download file
|
||||||
|
downloadFile: async (bucketId: string, fileId: string): Promise<Blob> => {
|
||||||
|
const response = await api.get(`${BASE_URL}/buckets/${bucketId}/files/${fileId}/download`, {
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
deleteFile: async (bucketId: string, fileId: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/buckets/${bucketId}/files/${fileId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete multiple files
|
||||||
|
deleteFiles: async (bucketId: string, fileIds: string[]): Promise<void> => {
|
||||||
|
await api.post(`${BASE_URL}/buckets/${bucketId}/files/delete-batch`, { fileIds });
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get folders in bucket
|
||||||
|
getFolders: async (
|
||||||
|
bucketId: string,
|
||||||
|
path?: string
|
||||||
|
): Promise<StorageFolder[]> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (path) params.append('path', path);
|
||||||
|
|
||||||
|
const response = await api.get<StorageFolder[]>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/folders?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create folder
|
||||||
|
createFolder: async (bucketId: string, path: string): Promise<StorageFolder> => {
|
||||||
|
const response = await api.post<StorageFolder>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/folders`,
|
||||||
|
{ path }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete folder
|
||||||
|
deleteFolder: async (bucketId: string, path: string, recursive?: boolean): Promise<void> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('path', path);
|
||||||
|
if (recursive) params.append('recursive', 'true');
|
||||||
|
await api.delete(`${BASE_URL}/buckets/${bucketId}/folders?${params.toString()}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get presigned URL for upload
|
||||||
|
getPresignedUploadUrl: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileName: string,
|
||||||
|
mimeType: string,
|
||||||
|
expiresIn?: number
|
||||||
|
): Promise<PresignedUrlResponse> => {
|
||||||
|
const response = await api.post<PresignedUrlResponse>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/presigned-url/upload`,
|
||||||
|
{ fileName, mimeType, expiresIn }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get presigned URL for download
|
||||||
|
getPresignedUrl: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileId: string,
|
||||||
|
expiresIn?: number
|
||||||
|
): Promise<PresignedUrlResponse> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (expiresIn) params.append('expiresIn', String(expiresIn));
|
||||||
|
|
||||||
|
const response = await api.get<PresignedUrlResponse>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}/presigned-url?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get storage usage
|
||||||
|
getUsage: async (): Promise<StorageUsage> => {
|
||||||
|
const response = await api.get<StorageUsage>(`${BASE_URL}/usage`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Copy file
|
||||||
|
copyFile: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileId: string,
|
||||||
|
destinationPath: string,
|
||||||
|
destinationBucketId?: string
|
||||||
|
): Promise<StorageFile> => {
|
||||||
|
const response = await api.post<StorageFile>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}/copy`,
|
||||||
|
{ destinationPath, destinationBucketId }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Move file
|
||||||
|
moveFile: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileId: string,
|
||||||
|
destinationPath: string,
|
||||||
|
destinationBucketId?: string
|
||||||
|
): Promise<StorageFile> => {
|
||||||
|
const response = await api.post<StorageFile>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}/move`,
|
||||||
|
{ destinationPath, destinationBucketId }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update file metadata
|
||||||
|
updateFileMetadata: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileId: string,
|
||||||
|
metadata: Record<string, string>
|
||||||
|
): Promise<StorageFile> => {
|
||||||
|
const response = await api.patch<StorageFile>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}/metadata`,
|
||||||
|
{ metadata }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get file versions
|
||||||
|
getFileVersions: async (
|
||||||
|
bucketId: string,
|
||||||
|
fileId: string
|
||||||
|
): Promise<StorageFile[]> => {
|
||||||
|
const response = await api.get<StorageFile[]>(
|
||||||
|
`${BASE_URL}/buckets/${bucketId}/files/${fileId}/versions`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
1
src/features/webhooks/api/index.ts
Normal file
1
src/features/webhooks/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './webhooks.api';
|
||||||
224
src/features/webhooks/api/webhooks.api.ts
Normal file
224
src/features/webhooks/api/webhooks.api.ts
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import { api } from '@services/api/axios-instance';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/webhooks';
|
||||||
|
|
||||||
|
export interface Webhook {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
events: string[];
|
||||||
|
secret?: string;
|
||||||
|
isActive: boolean;
|
||||||
|
retryCount: number;
|
||||||
|
retryDelay: number;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookDelivery {
|
||||||
|
id: string;
|
||||||
|
webhookId: string;
|
||||||
|
event: string;
|
||||||
|
payload: any;
|
||||||
|
status: 'pending' | 'success' | 'failed';
|
||||||
|
statusCode?: number;
|
||||||
|
response?: string;
|
||||||
|
attempts: number;
|
||||||
|
lastAttemptAt?: string;
|
||||||
|
nextRetryAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookLog {
|
||||||
|
id: string;
|
||||||
|
webhookId: string;
|
||||||
|
deliveryId: string;
|
||||||
|
event: string;
|
||||||
|
request: {
|
||||||
|
url: string;
|
||||||
|
method: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
body: any;
|
||||||
|
};
|
||||||
|
response?: {
|
||||||
|
statusCode: number;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
duration: number;
|
||||||
|
error?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookTestResult {
|
||||||
|
success: boolean;
|
||||||
|
statusCode?: number;
|
||||||
|
response?: string;
|
||||||
|
duration: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateWebhookDto {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
events: string[];
|
||||||
|
secret?: string;
|
||||||
|
retryCount?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateWebhookDto {
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
events?: string[];
|
||||||
|
secret?: string;
|
||||||
|
retryCount?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webhooksApi = {
|
||||||
|
// Get all webhooks
|
||||||
|
getAll: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
event?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: Webhook[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.isActive !== undefined) params.append('isActive', String(filters.isActive));
|
||||||
|
if (filters?.event) params.append('event', filters.event);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: Webhook[]; total: number }>(
|
||||||
|
`${BASE_URL}?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get webhook by ID
|
||||||
|
getById: async (id: string): Promise<Webhook> => {
|
||||||
|
const response = await api.get<Webhook>(`${BASE_URL}/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create webhook
|
||||||
|
create: async (data: CreateWebhookDto): Promise<Webhook> => {
|
||||||
|
const response = await api.post<Webhook>(BASE_URL, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update webhook
|
||||||
|
update: async (id: string, data: UpdateWebhookDto): Promise<Webhook> => {
|
||||||
|
const response = await api.patch<Webhook>(`${BASE_URL}/${id}`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete webhook
|
||||||
|
delete: async (id: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test webhook
|
||||||
|
test: async (id: string, payload?: any): Promise<WebhookTestResult> => {
|
||||||
|
const response = await api.post<WebhookTestResult>(`${BASE_URL}/${id}/test`, { payload });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enable webhook
|
||||||
|
enable: async (id: string): Promise<Webhook> => {
|
||||||
|
const response = await api.post<Webhook>(`${BASE_URL}/${id}/enable`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disable webhook
|
||||||
|
disable: async (id: string): Promise<Webhook> => {
|
||||||
|
const response = await api.post<Webhook>(`${BASE_URL}/${id}/disable`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get webhook deliveries
|
||||||
|
getDeliveries: async (
|
||||||
|
webhookId: string,
|
||||||
|
filters?: {
|
||||||
|
status?: string;
|
||||||
|
event?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: WebhookDelivery[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.status) params.append('status', filters.status);
|
||||||
|
if (filters?.event) params.append('event', filters.event);
|
||||||
|
if (filters?.startDate) params.append('startDate', filters.startDate);
|
||||||
|
if (filters?.endDate) params.append('endDate', filters.endDate);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WebhookDelivery[]; total: number }>(
|
||||||
|
`${BASE_URL}/${webhookId}/deliveries?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get delivery by ID
|
||||||
|
getDelivery: async (webhookId: string, deliveryId: string): Promise<WebhookDelivery> => {
|
||||||
|
const response = await api.get<WebhookDelivery>(
|
||||||
|
`${BASE_URL}/${webhookId}/deliveries/${deliveryId}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Retry delivery
|
||||||
|
retryDelivery: async (webhookId: string, deliveryId: string): Promise<WebhookDelivery> => {
|
||||||
|
const response = await api.post<WebhookDelivery>(
|
||||||
|
`${BASE_URL}/${webhookId}/deliveries/${deliveryId}/retry`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get webhook logs
|
||||||
|
getLogs: async (
|
||||||
|
webhookId: string,
|
||||||
|
filters?: {
|
||||||
|
deliveryId?: string;
|
||||||
|
event?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: WebhookLog[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.deliveryId) params.append('deliveryId', filters.deliveryId);
|
||||||
|
if (filters?.event) params.append('event', filters.event);
|
||||||
|
if (filters?.startDate) params.append('startDate', filters.startDate);
|
||||||
|
if (filters?.endDate) params.append('endDate', filters.endDate);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WebhookLog[]; total: number }>(
|
||||||
|
`${BASE_URL}/${webhookId}/logs?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get available events
|
||||||
|
getAvailableEvents: async (): Promise<string[]> => {
|
||||||
|
const response = await api.get<string[]>(`${BASE_URL}/events`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Regenerate secret
|
||||||
|
regenerateSecret: async (id: string): Promise<{ secret: string }> => {
|
||||||
|
const response = await api.post<{ secret: string }>(`${BASE_URL}/${id}/regenerate-secret`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
1
src/features/whatsapp/api/index.ts
Normal file
1
src/features/whatsapp/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './whatsapp.api';
|
||||||
294
src/features/whatsapp/api/whatsapp.api.ts
Normal file
294
src/features/whatsapp/api/whatsapp.api.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import { api } from '@services/api/axios-instance';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/whatsapp';
|
||||||
|
|
||||||
|
export interface WhatsappConversation {
|
||||||
|
id: string;
|
||||||
|
contactId: string;
|
||||||
|
contactName: string;
|
||||||
|
contactPhone: string;
|
||||||
|
lastMessage: string;
|
||||||
|
lastMessageAt: string;
|
||||||
|
unreadCount: number;
|
||||||
|
status: 'active' | 'archived' | 'blocked';
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappMessage {
|
||||||
|
id: string;
|
||||||
|
conversationId: string;
|
||||||
|
direction: 'inbound' | 'outbound';
|
||||||
|
type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'template';
|
||||||
|
content: string;
|
||||||
|
mediaUrl?: string;
|
||||||
|
status: 'sent' | 'delivered' | 'read' | 'failed';
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappTemplate {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
language: string;
|
||||||
|
category: string;
|
||||||
|
status: 'approved' | 'pending' | 'rejected';
|
||||||
|
components: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappContact {
|
||||||
|
id: string;
|
||||||
|
phone: string;
|
||||||
|
name: string;
|
||||||
|
email?: string;
|
||||||
|
tags: string[];
|
||||||
|
optedIn: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappBroadcast {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
templateId: string;
|
||||||
|
status: 'draft' | 'scheduled' | 'sending' | 'completed' | 'failed';
|
||||||
|
recipientCount: number;
|
||||||
|
sentCount: number;
|
||||||
|
deliveredCount: number;
|
||||||
|
readCount: number;
|
||||||
|
scheduledAt?: string;
|
||||||
|
completedAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappWebhook {
|
||||||
|
id: string;
|
||||||
|
event: string;
|
||||||
|
url: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhatsappStats {
|
||||||
|
totalConversations: number;
|
||||||
|
activeConversations: number;
|
||||||
|
messagesSent: number;
|
||||||
|
messagesReceived: number;
|
||||||
|
templatesUsed: number;
|
||||||
|
broadcastsSent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendMessageDto {
|
||||||
|
phone: string;
|
||||||
|
type: 'text' | 'image' | 'video' | 'audio' | 'document';
|
||||||
|
content: string;
|
||||||
|
mediaUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendTemplateDto {
|
||||||
|
phone: string;
|
||||||
|
templateName: string;
|
||||||
|
language: string;
|
||||||
|
parameters?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateBroadcastDto {
|
||||||
|
name: string;
|
||||||
|
templateId: string;
|
||||||
|
recipientFilter?: {
|
||||||
|
tags?: string[];
|
||||||
|
optedInOnly?: boolean;
|
||||||
|
};
|
||||||
|
scheduledAt?: string;
|
||||||
|
parameters?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const whatsappApi = {
|
||||||
|
// Get all conversations
|
||||||
|
getConversations: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
status?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: WhatsappConversation[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.status) params.append('status', filters.status);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WhatsappConversation[]; total: number }>(
|
||||||
|
`${BASE_URL}/conversations?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get conversation by ID
|
||||||
|
getConversation: async (id: string): Promise<WhatsappConversation> => {
|
||||||
|
const response = await api.get<WhatsappConversation>(`${BASE_URL}/conversations/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get messages from conversation
|
||||||
|
getMessages: async (
|
||||||
|
conversationId: string,
|
||||||
|
filters?: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
): Promise<{ data: WhatsappMessage[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WhatsappMessage[]; total: number }>(
|
||||||
|
`${BASE_URL}/conversations/${conversationId}/messages?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Send message
|
||||||
|
sendMessage: async (data: SendMessageDto): Promise<WhatsappMessage> => {
|
||||||
|
const response = await api.post<WhatsappMessage>(`${BASE_URL}/messages`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Send template message
|
||||||
|
sendTemplate: async (data: SendTemplateDto): Promise<WhatsappMessage> => {
|
||||||
|
const response = await api.post<WhatsappMessage>(`${BASE_URL}/messages/template`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get all broadcasts
|
||||||
|
getBroadcasts: async (filters?: {
|
||||||
|
status?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: WhatsappBroadcast[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.status) params.append('status', filters.status);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WhatsappBroadcast[]; total: number }>(
|
||||||
|
`${BASE_URL}/broadcasts?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get broadcast by ID
|
||||||
|
getBroadcast: async (id: string): Promise<WhatsappBroadcast> => {
|
||||||
|
const response = await api.get<WhatsappBroadcast>(`${BASE_URL}/broadcasts/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create broadcast
|
||||||
|
createBroadcast: async (data: CreateBroadcastDto): Promise<WhatsappBroadcast> => {
|
||||||
|
const response = await api.post<WhatsappBroadcast>(`${BASE_URL}/broadcasts`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cancel broadcast
|
||||||
|
cancelBroadcast: async (id: string): Promise<WhatsappBroadcast> => {
|
||||||
|
const response = await api.post<WhatsappBroadcast>(`${BASE_URL}/broadcasts/${id}/cancel`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get all templates
|
||||||
|
getTemplates: async (filters?: {
|
||||||
|
status?: string;
|
||||||
|
category?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: WhatsappTemplate[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.status) params.append('status', filters.status);
|
||||||
|
if (filters?.category) params.append('category', filters.category);
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WhatsappTemplate[]; total: number }>(
|
||||||
|
`${BASE_URL}/templates?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get template by ID
|
||||||
|
getTemplate: async (id: string): Promise<WhatsappTemplate> => {
|
||||||
|
const response = await api.get<WhatsappTemplate>(`${BASE_URL}/templates/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get all contacts
|
||||||
|
getContacts: async (filters?: {
|
||||||
|
search?: string;
|
||||||
|
tags?: string[];
|
||||||
|
optedIn?: boolean;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<{ data: WhatsappContact[]; total: number }> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.search) params.append('search', filters.search);
|
||||||
|
if (filters?.tags) filters.tags.forEach(tag => params.append('tags', tag));
|
||||||
|
if (filters?.optedIn !== undefined) params.append('optedIn', String(filters.optedIn));
|
||||||
|
if (filters?.page) params.append('page', String(filters.page));
|
||||||
|
if (filters?.limit) params.append('limit', String(filters.limit));
|
||||||
|
|
||||||
|
const response = await api.get<{ data: WhatsappContact[]; total: number }>(
|
||||||
|
`${BASE_URL}/contacts?${params.toString()}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get contact by ID
|
||||||
|
getContact: async (id: string): Promise<WhatsappContact> => {
|
||||||
|
const response = await api.get<WhatsappContact>(`${BASE_URL}/contacts/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create or update contact
|
||||||
|
upsertContact: async (data: Partial<WhatsappContact>): Promise<WhatsappContact> => {
|
||||||
|
const response = await api.post<WhatsappContact>(`${BASE_URL}/contacts`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get webhooks
|
||||||
|
getWebhooks: async (): Promise<WhatsappWebhook[]> => {
|
||||||
|
const response = await api.get<WhatsappWebhook[]>(`${BASE_URL}/webhooks`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create webhook
|
||||||
|
createWebhook: async (data: { event: string; url: string }): Promise<WhatsappWebhook> => {
|
||||||
|
const response = await api.post<WhatsappWebhook>(`${BASE_URL}/webhooks`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete webhook
|
||||||
|
deleteWebhook: async (id: string): Promise<void> => {
|
||||||
|
await api.delete(`${BASE_URL}/webhooks/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get stats
|
||||||
|
getStats: async (filters?: {
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<WhatsappStats> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters?.startDate) params.append('startDate', filters.startDate);
|
||||||
|
if (filters?.endDate) params.append('endDate', filters.endDate);
|
||||||
|
|
||||||
|
const response = await api.get<WhatsappStats>(`${BASE_URL}/stats?${params.toString()}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mark conversation as read
|
||||||
|
markAsRead: async (conversationId: string): Promise<void> => {
|
||||||
|
await api.post(`${BASE_URL}/conversations/${conversationId}/read`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Archive conversation
|
||||||
|
archiveConversation: async (conversationId: string): Promise<WhatsappConversation> => {
|
||||||
|
const response = await api.post<WhatsappConversation>(
|
||||||
|
`${BASE_URL}/conversations/${conversationId}/archive`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user