feat: global platform v2 update
This commit is contained in:
@@ -4,41 +4,49 @@
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
PlacementAnalyticsItem,
|
||||
CreativeAnalyticsItem,
|
||||
ChannelAnalyticsItem,
|
||||
SpendingAnalytics,
|
||||
PlacementsAnalytics,
|
||||
CreativesAnalytics,
|
||||
ExternalChannelsAnalytics,
|
||||
DateGrouping,
|
||||
AnalyticsQueryParams,
|
||||
SpendingAnalyticsQueryParams,
|
||||
PaginatedResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const analyticsApi = {
|
||||
/**
|
||||
* Get spending analytics
|
||||
*/
|
||||
spending: (params?: {
|
||||
target_channel_id?: string;
|
||||
date_from?: string;
|
||||
date_to?: string;
|
||||
grouping?: DateGrouping;
|
||||
}) => api.get<SpendingAnalytics>("/analytics/spending", { params }),
|
||||
|
||||
/**
|
||||
* Get placements analytics
|
||||
*/
|
||||
placements: (params?: { target_channel_id?: string }) =>
|
||||
api.get<PlacementsAnalytics>("/analytics/placements", { params }),
|
||||
placements: (workspaceId: string, params?: AnalyticsQueryParams) =>
|
||||
api.get<PaginatedResponse<PlacementAnalyticsItem>>(
|
||||
`/workspaces/${workspaceId}/analytics/placements`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get creatives analytics
|
||||
*/
|
||||
creatives: (params?: { target_channel_id?: string }) =>
|
||||
api.get<CreativesAnalytics>("/analytics/creatives", { params }),
|
||||
creatives: (workspaceId: string, params?: AnalyticsQueryParams) =>
|
||||
api.get<PaginatedResponse<CreativeAnalyticsItem>>(
|
||||
`/workspaces/${workspaceId}/analytics/creatives`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get external channels analytics
|
||||
* Get channels analytics
|
||||
*/
|
||||
externalChannels: (params?: { target_channel_id?: string }) =>
|
||||
api.get<ExternalChannelsAnalytics>("/analytics/external-channels", {
|
||||
params,
|
||||
}),
|
||||
channels: (workspaceId: string, params?: AnalyticsQueryParams) =>
|
||||
api.get<PaginatedResponse<ChannelAnalyticsItem>>(
|
||||
`/workspaces/${workspaceId}/analytics/channels`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get spending analytics (time-series data)
|
||||
*/
|
||||
spending: (workspaceId: string, params?: SpendingAnalyticsQueryParams) =>
|
||||
api.get<SpendingAnalytics>(
|
||||
`/workspaces/${workspaceId}/analytics/spending`,
|
||||
{ params }
|
||||
),
|
||||
};
|
||||
|
||||
@@ -2,33 +2,19 @@
|
||||
// Auth API
|
||||
// ============================================================================
|
||||
|
||||
import { api, saveAuthTokens, clearAuthTokens } from "./client";
|
||||
import { api, clearAuthTokens } from "./client";
|
||||
import { STORAGE_KEYS } from "@/lib/utils/constants";
|
||||
import type {
|
||||
User,
|
||||
AuthInitResponse,
|
||||
AuthCompleteRequest,
|
||||
AuthCompleteResponse,
|
||||
AuthRefreshRequest,
|
||||
AuthRefreshResponse,
|
||||
} from "@/lib/types/api";
|
||||
import type { User, AuthCompleteResponse } from "@/lib/types/api";
|
||||
|
||||
export const authApi = {
|
||||
/**
|
||||
* Initialize Telegram auth flow
|
||||
*/
|
||||
init: () => api.get<AuthInitResponse>("/auth/init", { requireAuth: false }),
|
||||
|
||||
/**
|
||||
* Complete auth with one-time token from bot
|
||||
* GET /auth/complete?token=...
|
||||
*/
|
||||
complete: async (token: string): Promise<{ access_token: string }> => {
|
||||
// GET request with token as query parameter
|
||||
const response = await api.get<{ access_token: string }>(
|
||||
complete: async (token: string): Promise<AuthCompleteResponse> => {
|
||||
const response = await api.get<AuthCompleteResponse>(
|
||||
`/auth/complete?token=${token}`,
|
||||
{
|
||||
requireAuth: false,
|
||||
}
|
||||
{ requireAuth: false }
|
||||
);
|
||||
|
||||
// Save access token to storage
|
||||
@@ -39,36 +25,9 @@ export const authApi = {
|
||||
return response;
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh access token
|
||||
*/
|
||||
refresh: async (): Promise<string> => {
|
||||
if (typeof window === "undefined") {
|
||||
throw new Error("Cannot refresh token on server");
|
||||
}
|
||||
|
||||
const refreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
|
||||
if (!refreshToken) {
|
||||
throw new Error("No refresh token available");
|
||||
}
|
||||
|
||||
const data: AuthRefreshRequest = { refresh_token: refreshToken };
|
||||
const response = await api.post<AuthRefreshResponse>(
|
||||
"/auth/refresh",
|
||||
data,
|
||||
{
|
||||
requireAuth: false,
|
||||
}
|
||||
);
|
||||
|
||||
// Update access token
|
||||
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.access_token);
|
||||
|
||||
return response.access_token;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current user info
|
||||
* GET /auth/me
|
||||
*/
|
||||
me: () => api.get<User>("/auth/me"),
|
||||
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
// ============================================================================
|
||||
// Target Channels API
|
||||
// Channels API (Catalog for Placements)
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
TargetChannel,
|
||||
TargetChannelsListResponse,
|
||||
SuccessResponse,
|
||||
Channel,
|
||||
ChannelsQueryParams,
|
||||
PaginatedResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const channelsApi = {
|
||||
/**
|
||||
* Get list of target channels
|
||||
* GET /api/v1/target_channels
|
||||
* Get catalog of channels for placements
|
||||
*/
|
||||
list: () => api.get<TargetChannelsListResponse>("/target_channels"),
|
||||
|
||||
/**
|
||||
* Delete (disconnect) target channel
|
||||
* DELETE /api/v1/target_channels/{channel_id}
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/target_channels/${id}`),
|
||||
list: (params?: ChannelsQueryParams) =>
|
||||
api.get<PaginatedResponse<Channel>>("/channels", { params }),
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// ============================================================================
|
||||
// API Client with Mock Support
|
||||
// API Client
|
||||
// ============================================================================
|
||||
|
||||
import { API_URL, USE_MOCKS, STORAGE_KEYS } from "@/lib/utils/constants";
|
||||
import { mockApiHandlers } from "@/lib/mocks/handlers";
|
||||
import { API_URL, STORAGE_KEYS } from "@/lib/utils/constants";
|
||||
import type { ApiError } from "@/lib/types/api";
|
||||
|
||||
// ============================================================================
|
||||
@@ -61,169 +60,13 @@ export const clearAuthTokens = (): void => {
|
||||
*/
|
||||
export const saveAuthTokens = (
|
||||
accessToken: string,
|
||||
refreshToken: string
|
||||
refreshToken?: string
|
||||
): void => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
|
||||
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Mock Router
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Route request to appropriate mock handler
|
||||
*/
|
||||
const routeMockRequest = async (
|
||||
endpoint: string,
|
||||
options: RequestOptions
|
||||
): Promise<any> => {
|
||||
const method = options.method || "GET";
|
||||
const params = options.params || {};
|
||||
const body = options.body ? JSON.parse(options.body as string) : undefined;
|
||||
|
||||
console.log(`[MOCK API] ${method} ${endpoint}`, { params, body });
|
||||
|
||||
// Auth endpoints
|
||||
if (endpoint === "/auth/init") {
|
||||
return mockApiHandlers.authInit();
|
||||
if (refreshToken) {
|
||||
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
|
||||
}
|
||||
if (endpoint.startsWith("/auth/complete")) {
|
||||
// Extract token from query params
|
||||
const token =
|
||||
params.token ||
|
||||
(endpoint.includes("?")
|
||||
? new URLSearchParams(endpoint.split("?")[1]).get("token")
|
||||
: null);
|
||||
return mockApiHandlers.authComplete({ token });
|
||||
}
|
||||
if (endpoint === "/auth/refresh") {
|
||||
return mockApiHandlers.authRefresh(body);
|
||||
}
|
||||
if (endpoint === "/auth/me") {
|
||||
return mockApiHandlers.authMe();
|
||||
}
|
||||
|
||||
// Target Channels
|
||||
if (endpoint === "/target_channels" && method === "GET") {
|
||||
return mockApiHandlers.getTargetChannels();
|
||||
}
|
||||
if (endpoint.startsWith("/target_channels/") && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteTargetChannel(id);
|
||||
}
|
||||
|
||||
// External Channels
|
||||
if (endpoint.startsWith("/external_channels/target/") && method === "GET") {
|
||||
const targetChannelId = endpoint.split("/")[3];
|
||||
return mockApiHandlers.getExternalChannels(targetChannelId);
|
||||
}
|
||||
if (endpoint === "/external_channels" && method === "POST") {
|
||||
return mockApiHandlers.createExternalChannel(body);
|
||||
}
|
||||
if (
|
||||
endpoint.startsWith("/external_channels/") &&
|
||||
!endpoint.includes("/target/") &&
|
||||
!endpoint.includes("/links") &&
|
||||
method === "GET"
|
||||
) {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getExternalChannel(id);
|
||||
}
|
||||
if (
|
||||
endpoint.startsWith("/external_channels/") &&
|
||||
endpoint.includes("/links") &&
|
||||
method === "PATCH"
|
||||
) {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updateExternalChannelLinks(id, body);
|
||||
}
|
||||
if (
|
||||
endpoint.startsWith("/external_channels/") &&
|
||||
!endpoint.includes("/links") &&
|
||||
method === "PATCH"
|
||||
) {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updateExternalChannel(id, body);
|
||||
}
|
||||
if (endpoint.startsWith("/external_channels/") && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteExternalChannel(id);
|
||||
}
|
||||
|
||||
// Creatives
|
||||
if (endpoint === "/creatives" && method === "GET") {
|
||||
return mockApiHandlers.getCreatives(params);
|
||||
}
|
||||
if (endpoint === "/creatives" && method === "POST") {
|
||||
return mockApiHandlers.createCreative(body);
|
||||
}
|
||||
if (endpoint.startsWith("/creatives/") && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getCreative(id);
|
||||
}
|
||||
if (endpoint.startsWith("/creatives/") && method === "PATCH") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updateCreative(id, body);
|
||||
}
|
||||
if (endpoint.startsWith("/creatives/") && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteCreative(id);
|
||||
}
|
||||
|
||||
// Placements
|
||||
if (endpoint === "/placements" && method === "GET") {
|
||||
return mockApiHandlers.getPlacements(params);
|
||||
}
|
||||
if (endpoint === "/placements" && method === "POST") {
|
||||
return mockApiHandlers.createPlacement(body);
|
||||
}
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getPlacement(id);
|
||||
}
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "PATCH") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updatePlacement(id, body);
|
||||
}
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deletePlacement(id);
|
||||
}
|
||||
if (endpoint.includes("/views/fetch") && method === "POST") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.fetchPlacementViews(id);
|
||||
}
|
||||
if (endpoint.includes("/views/history") && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getPlacementViewsHistory(id, params);
|
||||
}
|
||||
if (endpoint.includes("/views/manual") && method === "POST") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.setPlacementViewsManually(id, params?.views_count);
|
||||
}
|
||||
|
||||
// Analytics
|
||||
if (endpoint === "/analytics/spending") {
|
||||
return mockApiHandlers.getSpendingAnalytics(params);
|
||||
}
|
||||
if (endpoint === "/analytics/placements") {
|
||||
return mockApiHandlers.getPlacementsAnalytics(params);
|
||||
}
|
||||
if (endpoint === "/analytics/creatives") {
|
||||
return mockApiHandlers.getCreativesAnalytics(params);
|
||||
}
|
||||
if (endpoint === "/analytics/external-channels") {
|
||||
return mockApiHandlers.getExternalChannelsAnalytics(params);
|
||||
}
|
||||
|
||||
throw {
|
||||
error: {
|
||||
code: "NOT_IMPLEMENTED",
|
||||
message: `Mock handler not implemented for ${method} ${endpoint}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -231,7 +74,7 @@ const routeMockRequest = async (
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Main API request function with mock support
|
||||
* Main API request function
|
||||
*/
|
||||
export const apiRequest = async <T = any>(
|
||||
endpoint: string,
|
||||
@@ -239,17 +82,7 @@ export const apiRequest = async <T = any>(
|
||||
): Promise<T> => {
|
||||
const { params, requireAuth = true, ...fetchOptions } = options;
|
||||
|
||||
// Use mocks if enabled
|
||||
if (USE_MOCKS) {
|
||||
try {
|
||||
const result = await routeMockRequest(endpoint, options);
|
||||
return result as T;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Real API request
|
||||
// Build URL
|
||||
const url = buildUrl(`${API_URL}${endpoint}`, params);
|
||||
|
||||
const headers: HeadersInit = {
|
||||
@@ -271,45 +104,29 @@ export const apiRequest = async <T = any>(
|
||||
headers,
|
||||
});
|
||||
|
||||
// Handle 204 No Content
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
// Parse response
|
||||
const data = await response.json();
|
||||
|
||||
// Handle errors
|
||||
if (!response.ok) {
|
||||
// FastAPI returns errors in format { "detail": "..." }
|
||||
// Transform to our ApiError format
|
||||
if (data.detail) {
|
||||
throw {
|
||||
error: {
|
||||
code:
|
||||
response.status === 401
|
||||
? "UNAUTHORIZED"
|
||||
: response.status === 403
|
||||
? "FORBIDDEN"
|
||||
: response.status === 404
|
||||
? "NOT_FOUND"
|
||||
: response.status === 422
|
||||
? "VALIDATION_ERROR"
|
||||
: "API_ERROR",
|
||||
message: data.detail,
|
||||
},
|
||||
detail: data.detail,
|
||||
};
|
||||
}
|
||||
// If it's already in our format, throw as is
|
||||
const error: ApiError = data;
|
||||
const error: ApiError = {
|
||||
detail: data.detail || "An error occurred",
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as T;
|
||||
} catch (error: any) {
|
||||
// Network error or parsing error
|
||||
if (!error.error) {
|
||||
if (!error.detail) {
|
||||
throw {
|
||||
error: {
|
||||
code: "NETWORK_ERROR",
|
||||
message: error.message || "Network error occurred",
|
||||
},
|
||||
detail: error.message || "Network error occurred",
|
||||
} as ApiError;
|
||||
}
|
||||
throw error;
|
||||
@@ -328,47 +145,23 @@ export const api = {
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
}),
|
||||
|
||||
put: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "PUT",
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
}),
|
||||
|
||||
patch: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(body),
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
}),
|
||||
|
||||
delete: <T = any>(endpoint: string, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, { ...options, method: "DELETE" }),
|
||||
|
||||
// Special method for file upload
|
||||
upload: async <T = any>(
|
||||
endpoint: string,
|
||||
formData: FormData,
|
||||
options?: RequestOptions
|
||||
): Promise<T> => {
|
||||
// File import is now done on client side, no need for mock handling
|
||||
const url = `${API_URL}${endpoint}`;
|
||||
const token = getAuthToken();
|
||||
|
||||
const headers: HeadersInit = {};
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
...options,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw data as ApiError;
|
||||
}
|
||||
|
||||
return data as T;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,39 +5,57 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Creative,
|
||||
CreativeQueryParams,
|
||||
CreativeCreateRequest,
|
||||
CreativeUpdateRequest,
|
||||
CreativesListResponse,
|
||||
SuccessResponse,
|
||||
CreativesQueryParams,
|
||||
PaginatedResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const creativesApi = {
|
||||
/**
|
||||
* Get list of creatives
|
||||
* Get list of creatives for workspace
|
||||
*/
|
||||
list: (params?: CreativeQueryParams) =>
|
||||
api.get<CreativesListResponse>("/creatives", { params }),
|
||||
list: (workspaceId: string, params?: CreativesQueryParams) =>
|
||||
api.get<PaginatedResponse<Creative>>(
|
||||
`/workspaces/${workspaceId}/creatives`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get creative details
|
||||
* Get single creative
|
||||
*/
|
||||
get: (id: string) => api.get<Creative>(`/creatives/${id}`),
|
||||
get: (workspaceId: string, creativeId: string) =>
|
||||
api.get<Creative>(`/workspaces/${workspaceId}/creatives/${creativeId}`),
|
||||
|
||||
/**
|
||||
* Create new creative
|
||||
* @param projectId - Required query param for the project
|
||||
*/
|
||||
create: (data: CreativeCreateRequest) =>
|
||||
api.post<Creative>("/creatives", data),
|
||||
create: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
data: CreativeCreateRequest
|
||||
) =>
|
||||
api.post<Creative>(`/workspaces/${workspaceId}/creatives`, data, {
|
||||
params: { project_id: projectId },
|
||||
}),
|
||||
|
||||
/**
|
||||
* Update creative
|
||||
*/
|
||||
update: (id: string, data: CreativeUpdateRequest) =>
|
||||
api.patch<Creative>(`/creatives/${id}`, data),
|
||||
update: (
|
||||
workspaceId: string,
|
||||
creativeId: string,
|
||||
data: CreativeUpdateRequest
|
||||
) =>
|
||||
api.patch<Creative>(
|
||||
`/workspaces/${workspaceId}/creatives/${creativeId}`,
|
||||
data
|
||||
),
|
||||
|
||||
/**
|
||||
* Delete creative
|
||||
* Delete (archive) creative
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/creatives/${id}`),
|
||||
delete: (workspaceId: string, creativeId: string) =>
|
||||
api.delete<void>(`/workspaces/${workspaceId}/creatives/${creativeId}`),
|
||||
};
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// ============================================================================
|
||||
// External Channels API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
ExternalChannel,
|
||||
ExternalChannelsListResponse,
|
||||
ExternalChannelCreateRequest,
|
||||
ExternalChannelUpdateRequest,
|
||||
ExternalChannelLinksUpdateRequest,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const externalChannelsApi = {
|
||||
/**
|
||||
* Get list of external channels for target channel
|
||||
* GET /api/v1/external_channels/target/{target_channel_id}
|
||||
*/
|
||||
list: (targetChannelId: string) =>
|
||||
api.get<ExternalChannelsListResponse>(
|
||||
`/external_channels/target/${targetChannelId}`
|
||||
),
|
||||
|
||||
/**
|
||||
* Create new external channel
|
||||
* POST /api/v1/external_channels
|
||||
*/
|
||||
create: (data: ExternalChannelCreateRequest) =>
|
||||
api.post<ExternalChannel>("/external_channels", data),
|
||||
|
||||
/**
|
||||
* Update external channel
|
||||
* PATCH /api/v1/external_channels/{channel_id}
|
||||
*/
|
||||
update: (id: string, data: ExternalChannelUpdateRequest) =>
|
||||
api.patch<ExternalChannel>(`/external_channels/${id}`, data),
|
||||
|
||||
/**
|
||||
* Update external channel links
|
||||
* PATCH /api/v1/external_channels/{channel_id}/links
|
||||
*/
|
||||
updateLinks: (id: string, data: ExternalChannelLinksUpdateRequest) =>
|
||||
api.patch<ExternalChannel>(`/external_channels/${id}/links`, data),
|
||||
|
||||
/**
|
||||
* Delete external channel
|
||||
* DELETE /api/v1/external_channels/{channel_id}
|
||||
*/
|
||||
delete: (id: string) =>
|
||||
api.delete<SuccessResponse>(`/external_channels/${id}`),
|
||||
};
|
||||
@@ -1,11 +1,15 @@
|
||||
// ============================================================================
|
||||
// API Export
|
||||
// API Exports
|
||||
// ============================================================================
|
||||
|
||||
export * from "./client";
|
||||
export * from "./auth";
|
||||
export * from "./channels";
|
||||
export * from "./external-channels";
|
||||
export * from "./creatives";
|
||||
export * from "./placements";
|
||||
export * from "./analytics";
|
||||
export { api, apiRequest, clearAuthTokens, saveAuthTokens } from "./client";
|
||||
export { authApi } from "./auth";
|
||||
export { workspacesApi } from "./workspaces";
|
||||
export { membersApi } from "./members";
|
||||
export { invitesApi } from "./invites";
|
||||
export { projectsApi } from "./projects";
|
||||
export { channelsApi } from "./channels";
|
||||
export { purchasePlanApi } from "./purchase-plan";
|
||||
export { creativesApi } from "./creatives";
|
||||
export { placementsApi } from "./placements";
|
||||
export { analyticsApi } from "./analytics";
|
||||
|
||||
29
lib/api/invites.ts
Normal file
29
lib/api/invites.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// ============================================================================
|
||||
// Workspace Invites API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
WorkspaceInvite,
|
||||
WorkspaceInviteCreateRequest,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const invitesApi = {
|
||||
/**
|
||||
* Get list of workspace invites
|
||||
*/
|
||||
list: (workspaceId: string, params?: PaginationParams) =>
|
||||
api.get<PaginatedResponse<WorkspaceInvite>>(
|
||||
`/workspaces/${workspaceId}/invites`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Create new invite
|
||||
*/
|
||||
create: (workspaceId: string, data: WorkspaceInviteCreateRequest) =>
|
||||
api.post<WorkspaceInvite>(`/workspaces/${workspaceId}/invites`, data),
|
||||
};
|
||||
|
||||
36
lib/api/members.ts
Normal file
36
lib/api/members.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// ============================================================================
|
||||
// Workspace Members API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
WorkspaceMember,
|
||||
UpdatePermissionsRequest,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const membersApi = {
|
||||
/**
|
||||
* Get list of workspace members
|
||||
*/
|
||||
list: (workspaceId: string, params?: PaginationParams) =>
|
||||
api.get<PaginatedResponse<WorkspaceMember>>(
|
||||
`/workspaces/${workspaceId}/members`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Update member permissions
|
||||
*/
|
||||
updatePermissions: (
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
data: UpdatePermissionsRequest
|
||||
) =>
|
||||
api.put<WorkspaceMember>(
|
||||
`/workspaces/${workspaceId}/members/${userId}/permissions`,
|
||||
data
|
||||
),
|
||||
};
|
||||
|
||||
@@ -5,69 +5,65 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Placement,
|
||||
PlacementQueryParams,
|
||||
PlacementCreateRequest,
|
||||
PlacementUpdateRequest,
|
||||
PlacementsListResponse,
|
||||
ViewsFetchResponse,
|
||||
ViewsHistoryResponse,
|
||||
SuccessResponse,
|
||||
PlacementsQueryParams,
|
||||
ViewsHistoryItem,
|
||||
ViewsHistoryQueryParams,
|
||||
PaginatedResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const placementsApi = {
|
||||
/**
|
||||
* Get list of placements
|
||||
* Get list of placements for workspace
|
||||
*/
|
||||
list: (params?: PlacementQueryParams) =>
|
||||
api.get<PlacementsListResponse>("/placements", { params }),
|
||||
list: (workspaceId: string, params?: PlacementsQueryParams) =>
|
||||
api.get<PaginatedResponse<Placement>>(
|
||||
`/workspaces/${workspaceId}/placements`,
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get placement details
|
||||
* Get single placement
|
||||
*/
|
||||
get: (id: string) => api.get<Placement>(`/placements/${id}`),
|
||||
get: (workspaceId: string, placementId: string) =>
|
||||
api.get<Placement>(`/workspaces/${workspaceId}/placements/${placementId}`),
|
||||
|
||||
/**
|
||||
* Create new placement
|
||||
*/
|
||||
create: (data: PlacementCreateRequest) =>
|
||||
api.post<Placement>("/placements", data),
|
||||
create: (workspaceId: string, data: PlacementCreateRequest) =>
|
||||
api.post<Placement>(`/workspaces/${workspaceId}/placements`, data),
|
||||
|
||||
/**
|
||||
* Update placement
|
||||
*/
|
||||
update: (id: string, data: PlacementUpdateRequest) =>
|
||||
api.patch<Placement>(`/placements/${id}`, data),
|
||||
update: (
|
||||
workspaceId: string,
|
||||
placementId: string,
|
||||
data: PlacementUpdateRequest
|
||||
) =>
|
||||
api.patch<Placement>(
|
||||
`/workspaces/${workspaceId}/placements/${placementId}`,
|
||||
data
|
||||
),
|
||||
|
||||
/**
|
||||
* Delete placement
|
||||
* Delete (archive) placement
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/placements/${id}`),
|
||||
|
||||
/**
|
||||
* Fetch views for placement
|
||||
*/
|
||||
fetchViews: (id: string) =>
|
||||
api.post<ViewsFetchResponse>(`/placements/${id}/views/fetch`),
|
||||
delete: (workspaceId: string, placementId: string) =>
|
||||
api.delete<void>(`/workspaces/${workspaceId}/placements/${placementId}`),
|
||||
|
||||
/**
|
||||
* Get views history for placement
|
||||
*/
|
||||
viewsHistory: (
|
||||
id: string,
|
||||
params?: { from_date?: string; to_date?: string }
|
||||
getViewsHistory: (
|
||||
workspaceId: string,
|
||||
placementId: string,
|
||||
params?: ViewsHistoryQueryParams
|
||||
) =>
|
||||
api.get<ViewsHistoryResponse>(`/placements/${id}/views/history`, {
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Set views manually
|
||||
*/
|
||||
setViewsManually: (id: string, views_count: number) =>
|
||||
api.post<Placement>(`/placements/${id}/views/manual`, null, {
|
||||
params: { views_count },
|
||||
}),
|
||||
api.get<PaginatedResponse<ViewsHistoryItem>>(
|
||||
`/workspaces/${workspaceId}/placements/${placementId}/views/history`,
|
||||
{ params }
|
||||
),
|
||||
};
|
||||
|
||||
// Keep purchasesApi as an alias for backwards compatibility
|
||||
export const purchasesApi = placementsApi;
|
||||
|
||||
19
lib/api/projects.ts
Normal file
19
lib/api/projects.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// ============================================================================
|
||||
// Projects API (Target Channels)
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type { Project, PaginatedResponse, PaginationParams } from "@/lib/types/api";
|
||||
|
||||
export const projectsApi = {
|
||||
/**
|
||||
* Get list of projects for workspace
|
||||
* Projects are created via Telegram bot (add/remove channel)
|
||||
*/
|
||||
list: (workspaceId: string, params?: PaginationParams) =>
|
||||
api.get<PaginatedResponse<Project>>(
|
||||
`/workspaces/${workspaceId}/projects`,
|
||||
{ params }
|
||||
),
|
||||
};
|
||||
|
||||
61
lib/api/purchase-plan.ts
Normal file
61
lib/api/purchase-plan.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// ============================================================================
|
||||
// Purchase Plan API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
PurchasePlanChannel,
|
||||
PurchasePlanChannelCreateRequest,
|
||||
PurchasePlanChannelUpdateRequest,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
const buildPath = (workspaceId: string, projectId: string) =>
|
||||
`/workspaces/${workspaceId}/projects/${projectId}/purchase-plan/channels`;
|
||||
|
||||
export const purchasePlanApi = {
|
||||
/**
|
||||
* Get list of channels in purchase plan
|
||||
*/
|
||||
list: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
params?: PaginationParams
|
||||
) =>
|
||||
api.get<PaginatedResponse<PurchasePlanChannel>>(
|
||||
buildPath(workspaceId, projectId),
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Add channel to purchase plan
|
||||
*/
|
||||
create: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
data: PurchasePlanChannelCreateRequest
|
||||
) =>
|
||||
api.post<PurchasePlanChannel>(buildPath(workspaceId, projectId), data),
|
||||
|
||||
/**
|
||||
* Update channel in purchase plan
|
||||
*/
|
||||
update: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
channelId: string,
|
||||
data: PurchasePlanChannelUpdateRequest
|
||||
) =>
|
||||
api.patch<PurchasePlanChannel>(
|
||||
`${buildPath(workspaceId, projectId)}/${channelId}`,
|
||||
data
|
||||
),
|
||||
|
||||
/**
|
||||
* Remove channel from purchase plan
|
||||
*/
|
||||
delete: (workspaceId: string, projectId: string, channelId: string) =>
|
||||
api.delete<void>(`${buildPath(workspaceId, projectId)}/${channelId}`),
|
||||
};
|
||||
|
||||
41
lib/api/workspaces.ts
Normal file
41
lib/api/workspaces.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// ============================================================================
|
||||
// Workspaces API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Workspace,
|
||||
WorkspaceCreateRequest,
|
||||
WorkspaceUpdateRequest,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
const BASE_PATH = "/workspaces";
|
||||
|
||||
export const workspacesApi = {
|
||||
/**
|
||||
* Get list of workspaces for current user
|
||||
*/
|
||||
list: (params?: PaginationParams) =>
|
||||
api.get<PaginatedResponse<Workspace>>(BASE_PATH, { params }),
|
||||
|
||||
/**
|
||||
* Create new workspace
|
||||
*/
|
||||
create: (data: WorkspaceCreateRequest) =>
|
||||
api.post<Workspace>(BASE_PATH, data),
|
||||
|
||||
/**
|
||||
* Update workspace
|
||||
*/
|
||||
update: (workspaceId: string, data: WorkspaceUpdateRequest) =>
|
||||
api.patch<Workspace>(`${BASE_PATH}/${workspaceId}`, data),
|
||||
|
||||
/**
|
||||
* Delete workspace
|
||||
*/
|
||||
delete: (workspaceId: string) =>
|
||||
api.delete<void>(`${BASE_PATH}/${workspaceId}`),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user