feat: all project setup
This commit is contained in:
43
lib/api/analytics.ts
Normal file
43
lib/api/analytics.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// ============================================================================
|
||||
// Analytics API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
AnalyticsOverview,
|
||||
CostsReport,
|
||||
AnalyticsChannelData,
|
||||
AnalyticsCreativeData,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const analyticsApi = {
|
||||
/**
|
||||
* Get overview analytics
|
||||
*/
|
||||
overview: (params?: { target_channel_id?: string }) =>
|
||||
api.get<AnalyticsOverview>("/analytics/overview", { params }),
|
||||
|
||||
/**
|
||||
* Get costs report
|
||||
*/
|
||||
costs: (params: {
|
||||
period?: "day" | "week" | "month";
|
||||
target_channel_id?: string;
|
||||
}) => api.get<CostsReport>("/analytics/costs", { params }),
|
||||
|
||||
/**
|
||||
* Get channels analytics
|
||||
*/
|
||||
channels: (params?: { target_channel_id?: string }) =>
|
||||
api.get<{ data: AnalyticsChannelData[] }>("/analytics/channels", {
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get creatives analytics
|
||||
*/
|
||||
creatives: (params?: { target_channel_id?: string }) =>
|
||||
api.get<{ data: AnalyticsCreativeData[] }>("/analytics/creatives", {
|
||||
params,
|
||||
}),
|
||||
};
|
||||
109
lib/api/auth.ts
Normal file
109
lib/api/auth.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
// ============================================================================
|
||||
// Auth API
|
||||
// ============================================================================
|
||||
|
||||
import { api, saveAuthTokens, clearAuthTokens } from "./client";
|
||||
import { STORAGE_KEYS } from "@/lib/utils/constants";
|
||||
import type {
|
||||
User,
|
||||
AuthInitResponse,
|
||||
AuthCompleteRequest,
|
||||
AuthCompleteResponse,
|
||||
AuthRefreshRequest,
|
||||
AuthRefreshResponse,
|
||||
} 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
|
||||
*/
|
||||
complete: async (token: string): Promise<AuthCompleteResponse> => {
|
||||
const data: AuthCompleteRequest = { token };
|
||||
const response = await api.post<AuthCompleteResponse>(
|
||||
"/auth/complete",
|
||||
data,
|
||||
{
|
||||
requireAuth: false,
|
||||
}
|
||||
);
|
||||
|
||||
// Save tokens to storage
|
||||
saveAuthTokens(response.access_token, response.refresh_token);
|
||||
|
||||
// Save user to storage
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(response.user));
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
me: () => api.get<User>("/auth/me"),
|
||||
|
||||
/**
|
||||
* Logout (clear tokens)
|
||||
*/
|
||||
logout: () => {
|
||||
clearAuthTokens();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user from storage (no API call)
|
||||
*/
|
||||
getUserFromStorage: (): User | null => {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
const userJson = localStorage.getItem(STORAGE_KEYS.USER);
|
||||
if (!userJson) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(userJson);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
isAuthenticated: (): boolean => {
|
||||
if (typeof window === "undefined") return false;
|
||||
return !!localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
||||
},
|
||||
};
|
||||
37
lib/api/channels.ts
Normal file
37
lib/api/channels.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// ============================================================================
|
||||
// Target Channels API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
TargetChannel,
|
||||
TargetChannelDetail,
|
||||
TargetChannelQueryParams,
|
||||
TargetChannelUpdateRequest,
|
||||
ListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const channelsApi = {
|
||||
/**
|
||||
* Get list of 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),
|
||||
|
||||
/**
|
||||
* Delete (disconnect) target channel
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/target-channels/${id}`),
|
||||
};
|
||||
347
lib/api/client.ts
Normal file
347
lib/api/client.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
// ============================================================================
|
||||
// API Client with Mock Support
|
||||
// ============================================================================
|
||||
|
||||
import { API_URL, USE_MOCKS, STORAGE_KEYS } from "@/lib/utils/constants";
|
||||
import { mockApiHandlers } from "@/lib/mocks/handlers";
|
||||
import type { ApiError } from "@/lib/types/api";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
params?: Record<string, any>;
|
||||
requireAuth?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Build URL with query parameters
|
||||
*/
|
||||
const buildUrl = (endpoint: string, params?: Record<string, any>): string => {
|
||||
if (!params || Object.keys(params).length === 0) {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `${endpoint}?${queryString}` : endpoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get auth token from storage
|
||||
*/
|
||||
const getAuthToken = (): string | null => {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear auth tokens from storage
|
||||
*/
|
||||
export const clearAuthTokens = (): void => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
|
||||
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
|
||||
localStorage.removeItem(STORAGE_KEYS.USER);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save auth tokens to storage
|
||||
*/
|
||||
export const saveAuthTokens = (
|
||||
accessToken: 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 (endpoint === "/auth/complete") {
|
||||
return mockApiHandlers.authComplete(body);
|
||||
}
|
||||
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(params);
|
||||
}
|
||||
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") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deleteTargetChannel(id);
|
||||
}
|
||||
|
||||
// External Channels
|
||||
if (endpoint === "/external-channels" && method === "GET") {
|
||||
return mockApiHandlers.getExternalChannels(params);
|
||||
}
|
||||
if (endpoint === "/external-channels" && method === "POST") {
|
||||
return mockApiHandlers.createExternalChannel(body);
|
||||
}
|
||||
if (
|
||||
endpoint.startsWith("/external-channels/") &&
|
||||
!endpoint.includes("/import") &&
|
||||
method === "GET"
|
||||
) {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getExternalChannel(id);
|
||||
}
|
||||
if (endpoint.startsWith("/external-channels/") && 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);
|
||||
}
|
||||
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") {
|
||||
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);
|
||||
}
|
||||
|
||||
// Purchases
|
||||
if (endpoint === "/purchases" && method === "GET") {
|
||||
return mockApiHandlers.getPurchases(params);
|
||||
}
|
||||
if (endpoint === "/purchases" && method === "POST") {
|
||||
return mockApiHandlers.createPurchase(body);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "GET") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.getPurchase(id);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "PATCH") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.updatePurchase(id, body);
|
||||
}
|
||||
if (endpoint.match(/^\/purchases\/[^\/]+$/) && method === "DELETE") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.deletePurchase(id);
|
||||
}
|
||||
if (endpoint.includes("/refresh-views") && method === "POST") {
|
||||
const id = endpoint.split("/")[2];
|
||||
return mockApiHandlers.refreshPurchaseViews(id);
|
||||
}
|
||||
|
||||
// Analytics
|
||||
if (endpoint === "/analytics/overview") {
|
||||
return mockApiHandlers.getAnalyticsOverview(params);
|
||||
}
|
||||
if (endpoint === "/analytics/costs") {
|
||||
return mockApiHandlers.getAnalyticsCosts(params);
|
||||
}
|
||||
if (endpoint === "/analytics/channels") {
|
||||
return mockApiHandlers.getAnalyticsChannels(params);
|
||||
}
|
||||
if (endpoint === "/analytics/creatives") {
|
||||
return mockApiHandlers.getAnalyticsCreatives(params);
|
||||
}
|
||||
|
||||
throw {
|
||||
error: {
|
||||
code: "NOT_IMPLEMENTED",
|
||||
message: `Mock handler not implemented for ${method} ${endpoint}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// API Client
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Main API request function with mock support
|
||||
*/
|
||||
export const apiRequest = async <T = any>(
|
||||
endpoint: string,
|
||||
options: RequestOptions = {}
|
||||
): 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
|
||||
const url = buildUrl(`${API_URL}${endpoint}`, params);
|
||||
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
...fetchOptions.headers,
|
||||
};
|
||||
|
||||
// Add auth token if required
|
||||
if (requireAuth) {
|
||||
const token = getAuthToken();
|
||||
if (token) {
|
||||
(headers as Record<string, string>)["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
});
|
||||
|
||||
// Parse response
|
||||
const data = await response.json();
|
||||
|
||||
// Handle errors
|
||||
if (!response.ok) {
|
||||
const error: ApiError = data;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as T;
|
||||
} catch (error: any) {
|
||||
// Network error or parsing error
|
||||
if (!error.error) {
|
||||
throw {
|
||||
error: {
|
||||
code: "NETWORK_ERROR",
|
||||
message: error.message || "Network error occurred",
|
||||
},
|
||||
} as ApiError;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Convenience Methods
|
||||
// ============================================================================
|
||||
|
||||
export const api = {
|
||||
get: <T = any>(endpoint: string, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, { ...options, method: "GET" }),
|
||||
|
||||
post: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
patch: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
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> => {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
44
lib/api/creatives.ts
Normal file
44
lib/api/creatives.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// ============================================================================
|
||||
// Creatives API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Creative,
|
||||
CreativeDetail,
|
||||
CreativeQueryParams,
|
||||
CreativeCreateRequest,
|
||||
CreativeUpdateRequest,
|
||||
ListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const creativesApi = {
|
||||
/**
|
||||
* Get list of creatives
|
||||
*/
|
||||
list: (params?: CreativeQueryParams) =>
|
||||
api.get<ListResponse<Creative>>("/creatives", { params }),
|
||||
|
||||
/**
|
||||
* Get creative details
|
||||
*/
|
||||
get: (id: string) => api.get<CreativeDetail>(`/creatives/${id}`),
|
||||
|
||||
/**
|
||||
* Create new creative
|
||||
*/
|
||||
create: (data: CreativeCreateRequest) =>
|
||||
api.post<Creative>("/creatives", data),
|
||||
|
||||
/**
|
||||
* Update creative
|
||||
*/
|
||||
update: (id: string, data: CreativeUpdateRequest) =>
|
||||
api.patch<Creative>(`/creatives/${id}`, data),
|
||||
|
||||
/**
|
||||
* Delete creative
|
||||
*/
|
||||
delete: (id: string) => api.delete<SuccessResponse>(`/creatives/${id}`),
|
||||
};
|
||||
61
lib/api/external-channels.ts
Normal file
61
lib/api/external-channels.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// ============================================================================
|
||||
// External Channels API
|
||||
// ============================================================================
|
||||
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
ExternalChannel,
|
||||
ExternalChannelDetail,
|
||||
ExternalChannelQueryParams,
|
||||
ExternalChannelCreateRequest,
|
||||
ExternalChannelUpdateRequest,
|
||||
ExternalChannelImportResponse,
|
||||
ListResponse,
|
||||
SuccessResponse,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const externalChannelsApi = {
|
||||
/**
|
||||
* Get list of external channels
|
||||
*/
|
||||
list: (params?: ExternalChannelQueryParams) =>
|
||||
api.get<ListResponse<ExternalChannel>>("/external-channels", { params }),
|
||||
|
||||
/**
|
||||
* Get external channel details
|
||||
*/
|
||||
get: (id: string) =>
|
||||
api.get<ExternalChannelDetail>(`/external-channels/${id}`),
|
||||
|
||||
/**
|
||||
* Create new external channel
|
||||
*/
|
||||
create: (data: ExternalChannelCreateRequest) =>
|
||||
api.post<ExternalChannel>("/external-channels", data),
|
||||
|
||||
/**
|
||||
* Update external channel
|
||||
*/
|
||||
update: (id: string, data: ExternalChannelUpdateRequest) =>
|
||||
api.patch<ExternalChannel>(`/external-channels/${id}`, data),
|
||||
|
||||
/**
|
||||
* Delete external channel
|
||||
*/
|
||||
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
|
||||
);
|
||||
},
|
||||
};
|
||||
11
lib/api/index.ts
Normal file
11
lib/api/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// ============================================================================
|
||||
// API Export
|
||||
// ============================================================================
|
||||
|
||||
export * from "./client";
|
||||
export * from "./auth";
|
||||
export * from "./channels";
|
||||
export * from "./external-channels";
|
||||
export * from "./creatives";
|
||||
export * from "./purchases";
|
||||
export * from "./analytics";
|
||||
52
lib/api/purchases.ts
Normal file
52
lib/api/purchases.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// ============================================================================
|
||||
// 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