improvement for the backend api
This commit is contained in:
@@ -22,22 +22,18 @@ export const authApi = {
|
||||
/**
|
||||
* Complete auth with one-time token from bot
|
||||
*/
|
||||
complete: async (token: string): Promise<AuthCompleteResponse> => {
|
||||
const data: AuthCompleteRequest = { token };
|
||||
const response = await api.post<AuthCompleteResponse>(
|
||||
"/auth/complete",
|
||||
data,
|
||||
complete: async (token: string): Promise<{ access_token: string }> => {
|
||||
// GET request with token as query parameter
|
||||
const response = await api.get<{ access_token: string }>(
|
||||
`/auth/complete?token=${token}`,
|
||||
{
|
||||
requireAuth: false,
|
||||
}
|
||||
);
|
||||
|
||||
// Save tokens to storage
|
||||
saveAuthTokens(response.access_token, response.refresh_token);
|
||||
|
||||
// Save user to storage
|
||||
// Save access token to storage
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(response.user));
|
||||
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.access_token);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -5,33 +5,20 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
TargetChannel,
|
||||
TargetChannelDetail,
|
||||
TargetChannelQueryParams,
|
||||
TargetChannelUpdateRequest,
|
||||
ListResponse,
|
||||
TargetChannelsListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const channelsApi = {
|
||||
/**
|
||||
* Get list of target channels
|
||||
* GET /api/v1/target_channels
|
||||
*/
|
||||
list: (params?: TargetChannelQueryParams) =>
|
||||
api.get<ListResponse<TargetChannel>>("/target-channels", { params }),
|
||||
|
||||
/**
|
||||
* Get target channel details
|
||||
*/
|
||||
get: (id: string) => api.get<TargetChannelDetail>(`/target-channels/${id}`),
|
||||
|
||||
/**
|
||||
* Update target channel (is_active)
|
||||
*/
|
||||
update: (id: string, data: TargetChannelUpdateRequest) =>
|
||||
api.patch<TargetChannel>(`/target-channels/${id}`, data),
|
||||
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}`),
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/target_channels/${id}`),
|
||||
};
|
||||
|
||||
@@ -89,8 +89,14 @@ const routeMockRequest = async (
|
||||
if (endpoint === "/auth/init") {
|
||||
return mockApiHandlers.authInit();
|
||||
}
|
||||
if (endpoint === "/auth/complete") {
|
||||
return mockApiHandlers.authComplete(body);
|
||||
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);
|
||||
@@ -100,53 +106,51 @@ const routeMockRequest = async (
|
||||
}
|
||||
|
||||
// Target Channels
|
||||
if (endpoint === "/target-channels" && method === "GET") {
|
||||
return mockApiHandlers.getTargetChannels(params);
|
||||
if (endpoint === "/target_channels" && method === "GET") {
|
||||
return mockApiHandlers.getTargetChannels();
|
||||
}
|
||||
if (endpoint.startsWith("/target-channels/") && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getTargetChannel(id);
|
||||
}
|
||||
if (endpoint.startsWith("/target-channels/") && method === "PATCH") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updateTargetChannel(id, body);
|
||||
}
|
||||
if (endpoint.startsWith("/target-channels/") && method === "DELETE") {
|
||||
if (endpoint.startsWith("/target_channels/") && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteTargetChannel(id);
|
||||
}
|
||||
|
||||
// External Channels
|
||||
if (endpoint === "/external-channels" && method === "GET") {
|
||||
return mockApiHandlers.getExternalChannels(params);
|
||||
if (endpoint.startsWith("/external_channels/target/") && method === "GET") {
|
||||
const targetChannelId = endpoint.split("/")[3];
|
||||
return mockApiHandlers.getExternalChannels(targetChannelId);
|
||||
}
|
||||
if (endpoint === "/external-channels" && method === "POST") {
|
||||
if (endpoint === "/external_channels" && method === "POST") {
|
||||
return mockApiHandlers.createExternalChannel(body);
|
||||
}
|
||||
if (
|
||||
endpoint.startsWith("/external-channels/") &&
|
||||
!endpoint.includes("/import") &&
|
||||
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/") && method === "PATCH") {
|
||||
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") {
|
||||
if (endpoint.startsWith("/external_channels/") && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteExternalChannel(id);
|
||||
}
|
||||
if (endpoint === "/external-channels/import" && method === "POST") {
|
||||
// FormData для импорта
|
||||
const formData = body as any;
|
||||
return mockApiHandlers.importExternalChannels(
|
||||
formData.file,
|
||||
formData.target_channel_id
|
||||
);
|
||||
}
|
||||
|
||||
// Creatives
|
||||
if (endpoint === "/creatives" && method === "GET") {
|
||||
@@ -168,28 +172,36 @@ const routeMockRequest = async (
|
||||
return mockApiHandlers.deleteCreative(id);
|
||||
}
|
||||
|
||||
// Purchases
|
||||
if (endpoint === "/purchases" && method === "GET") {
|
||||
return mockApiHandlers.getPurchases(params);
|
||||
// Placements
|
||||
if (endpoint === "/placements" && method === "GET") {
|
||||
return mockApiHandlers.getPlacements(params);
|
||||
}
|
||||
if (endpoint === "/purchases" && method === "POST") {
|
||||
return mockApiHandlers.createPurchase(body);
|
||||
if (endpoint === "/placements" && method === "POST") {
|
||||
return mockApiHandlers.createPlacement(body);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "GET") {
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getPurchase(id);
|
||||
return mockApiHandlers.getPlacement(id);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "PATCH") {
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "PATCH") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updatePurchase(id, body);
|
||||
return mockApiHandlers.updatePlacement(id, body);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "DELETE") {
|
||||
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deletePurchase(id);
|
||||
return mockApiHandlers.deletePlacement(id);
|
||||
}
|
||||
if (endpoint.includes("/refresh-views") && method === "POST") {
|
||||
if (endpoint.includes("/views/fetch") && method === "POST") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.refreshPurchaseViews(id);
|
||||
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
|
||||
@@ -264,6 +276,27 @@ export const apiRequest = async <T = any>(
|
||||
|
||||
// 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;
|
||||
throw error;
|
||||
}
|
||||
@@ -314,13 +347,7 @@ export const api = {
|
||||
formData: FormData,
|
||||
options?: RequestOptions
|
||||
): Promise<T> => {
|
||||
if (USE_MOCKS) {
|
||||
// Handle mock file upload
|
||||
const file = formData.get("file") as File;
|
||||
const targetChannelId = formData.get("target_channel_id") as string;
|
||||
return mockApiHandlers.importExternalChannels(file, targetChannelId) as T;
|
||||
}
|
||||
|
||||
// File import is now done on client side, no need for mock handling
|
||||
const url = `${API_URL}${endpoint}`;
|
||||
const token = getAuthToken();
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Creative,
|
||||
CreativeDetail,
|
||||
CreativeQueryParams,
|
||||
CreativeCreateRequest,
|
||||
CreativeUpdateRequest,
|
||||
ListResponse,
|
||||
CreativesListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
@@ -18,12 +17,12 @@ export const creativesApi = {
|
||||
* Get list of creatives
|
||||
*/
|
||||
list: (params?: CreativeQueryParams) =>
|
||||
api.get<ListResponse<Creative>>("/creatives", { params }),
|
||||
api.get<CreativesListResponse>("/creatives", { params }),
|
||||
|
||||
/**
|
||||
* Get creative details
|
||||
*/
|
||||
get: (id: string) => api.get<CreativeDetail>(`/creatives/${id}`),
|
||||
get: (id: string) => api.get<Creative>(`/creatives/${id}`),
|
||||
|
||||
/**
|
||||
* Create new creative
|
||||
|
||||
@@ -5,57 +5,48 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
ExternalChannel,
|
||||
ExternalChannelDetail,
|
||||
ExternalChannelQueryParams,
|
||||
ExternalChannelsListResponse,
|
||||
ExternalChannelCreateRequest,
|
||||
ExternalChannelUpdateRequest,
|
||||
ExternalChannelImportResponse,
|
||||
ListResponse,
|
||||
ExternalChannelLinksUpdateRequest,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const externalChannelsApi = {
|
||||
/**
|
||||
* Get list of external channels
|
||||
* Get list of external channels for target channel
|
||||
* GET /api/v1/external_channels/target/{target_channel_id}
|
||||
*/
|
||||
list: (params?: ExternalChannelQueryParams) =>
|
||||
api.get<ListResponse<ExternalChannel>>("/external-channels", { params }),
|
||||
|
||||
/**
|
||||
* Get external channel details
|
||||
*/
|
||||
get: (id: string) =>
|
||||
api.get<ExternalChannelDetail>(`/external-channels/${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),
|
||||
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),
|
||||
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}`),
|
||||
|
||||
/**
|
||||
* Import external channels from Excel file
|
||||
*/
|
||||
import: (file: File, targetChannelId: string) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("target_channel_id", targetChannelId);
|
||||
|
||||
return api.upload<ExternalChannelImportResponse>(
|
||||
"/external-channels/import",
|
||||
formData
|
||||
);
|
||||
},
|
||||
api.delete<SuccessResponse>(`/external_channels/${id}`),
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@ export * from "./auth";
|
||||
export * from "./channels";
|
||||
export * from "./external-channels";
|
||||
export * from "./creatives";
|
||||
export * from "./purchases";
|
||||
export * from "./placements";
|
||||
export * from "./analytics";
|
||||
|
||||
73
lib/api/placements.ts
Normal file
73
lib/api/placements.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// ============================================================================
|
||||
// Placements API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Placement,
|
||||
PlacementQueryParams,
|
||||
PlacementCreateRequest,
|
||||
PlacementUpdateRequest,
|
||||
PlacementsListResponse,
|
||||
ViewsFetchResponse,
|
||||
ViewsHistoryResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const placementsApi = {
|
||||
/**
|
||||
* Get list of placements
|
||||
*/
|
||||
list: (params?: PlacementQueryParams) =>
|
||||
api.get<PlacementsListResponse>("/placements", { params }),
|
||||
|
||||
/**
|
||||
* Get placement details
|
||||
*/
|
||||
get: (id: string) => api.get<Placement>(`/placements/${id}`),
|
||||
|
||||
/**
|
||||
* Create new placement
|
||||
*/
|
||||
create: (data: PlacementCreateRequest) =>
|
||||
api.post<Placement>("/placements", data),
|
||||
|
||||
/**
|
||||
* Update placement
|
||||
*/
|
||||
update: (id: string, data: PlacementUpdateRequest) =>
|
||||
api.patch<Placement>(`/placements/${id}`, data),
|
||||
|
||||
/**
|
||||
* Delete placement
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/placements/${id}`),
|
||||
|
||||
/**
|
||||
* Fetch views for placement
|
||||
*/
|
||||
fetchViews: (id: string) =>
|
||||
api.post<ViewsFetchResponse>(`/placements/${id}/views/fetch`),
|
||||
|
||||
/**
|
||||
* Get views history for placement
|
||||
*/
|
||||
viewsHistory: (
|
||||
id: string,
|
||||
params?: { from_date?: string; to_date?: string }
|
||||
) =>
|
||||
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 },
|
||||
}),
|
||||
};
|
||||
|
||||
// Keep purchasesApi as an alias for backwards compatibility
|
||||
export const purchasesApi = placementsApi;
|
||||
@@ -1,52 +0,0 @@
|
||||
// ============================================================================
|
||||
// Purchases API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Purchase,
|
||||
PurchaseDetail,
|
||||
PurchaseQueryParams,
|
||||
PurchaseCreateRequest,
|
||||
PurchaseCreateResponse,
|
||||
PurchaseUpdateRequest,
|
||||
PurchaseRefreshViewsResponse,
|
||||
ListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const purchasesApi = {
|
||||
/**
|
||||
* Get list of purchases
|
||||
*/
|
||||
list: (params?: PurchaseQueryParams) =>
|
||||
api.get<ListResponse<Purchase>>("/purchases", { params }),
|
||||
|
||||
/**
|
||||
* Get purchase details
|
||||
*/
|
||||
get: (id: string) => api.get<PurchaseDetail>(`/purchases/${id}`),
|
||||
|
||||
/**
|
||||
* Create new purchase
|
||||
*/
|
||||
create: (data: PurchaseCreateRequest) =>
|
||||
api.post<PurchaseCreateResponse>("/purchases", data),
|
||||
|
||||
/**
|
||||
* Update purchase
|
||||
*/
|
||||
update: (id: string, data: PurchaseUpdateRequest) =>
|
||||
api.patch<Purchase>(`/purchases/${id}`, data),
|
||||
|
||||
/**
|
||||
* Delete purchase
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/purchases/${id}`),
|
||||
|
||||
/**
|
||||
* Refresh views count for purchase
|
||||
*/
|
||||
refreshViews: (id: string) =>
|
||||
api.post<PurchaseRefreshViewsResponse>(`/purchases/${id}/refresh-views`),
|
||||
};
|
||||
Reference in New Issue
Block a user