@djangocfg/ui-nextjs 1.4.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/package.json +110 -0
- package/src/animations/AnimatedBackground.tsx +645 -0
- package/src/animations/index.ts +2 -0
- package/src/blocks/ArticleCard.tsx +94 -0
- package/src/blocks/ArticleList.tsx +95 -0
- package/src/blocks/CTASection.tsx +136 -0
- package/src/blocks/FeatureSection.tsx +104 -0
- package/src/blocks/Hero.tsx +102 -0
- package/src/blocks/NewsletterSection.tsx +119 -0
- package/src/blocks/StatsSection.tsx +103 -0
- package/src/blocks/SuperHero.tsx +328 -0
- package/src/blocks/TestimonialSection.tsx +122 -0
- package/src/blocks/index.ts +9 -0
- package/src/components/README.md +2018 -0
- package/src/components/breadcrumb-navigation.tsx +127 -0
- package/src/components/breadcrumb.tsx +132 -0
- package/src/components/button-download.tsx +275 -0
- package/src/components/dropdown-menu.tsx +219 -0
- package/src/components/index.ts +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +338 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/components/menubar.tsx +274 -0
- package/src/components/multi-select-pro/async.tsx +608 -0
- package/src/components/multi-select-pro/helpers.tsx +84 -0
- package/src/components/multi-select-pro/index.tsx +622 -0
- package/src/components/navigation-menu.tsx +153 -0
- package/src/components/pagination-static.tsx +348 -0
- package/src/components/pagination.tsx +138 -0
- package/src/components/phone-input.tsx +276 -0
- package/src/components/sidebar.tsx +866 -0
- package/src/components/sonner.tsx +31 -0
- package/src/components/ssr-pagination.tsx +237 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/useCfgRouter.ts +153 -0
- package/src/hooks/useLocalStorage.ts +221 -0
- package/src/hooks/useQueryParams.ts +73 -0
- package/src/hooks/useSessionStorage.ts +188 -0
- package/src/hooks/useTheme.ts +57 -0
- package/src/index.ts +24 -0
- package/src/lib/index.ts +2 -0
- package/src/styles/index.css +2 -0
- package/src/theme/ForceTheme.tsx +115 -0
- package/src/theme/ThemeProvider.tsx +82 -0
- package/src/theme/ThemeToggle.tsx +52 -0
- package/src/theme/index.ts +3 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +212 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
- package/src/tools/JsonForm/widgets/index.ts +12 -0
- package/src/tools/JsonTree/index.tsx +252 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
- package/src/tools/LottiePlayer/index.tsx +54 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +163 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
- package/src/tools/Mermaid/index.tsx +40 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
- package/src/tools/OpenapiViewer/index.tsx +36 -0
- package/src/tools/OpenapiViewer/types.ts +152 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
- package/src/tools/PrettyCode/index.tsx +43 -0
- package/src/tools/VideoPlayer/README.md +239 -0
- package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
- package/src/tools/VideoPlayer/index.ts +9 -0
- package/src/tools/VideoPlayer/types.ts +62 -0
- package/src/tools/index.ts +43 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// API Key type for playground
|
|
2
|
+
export interface ApiKey {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
created_at?: string;
|
|
6
|
+
last_used_at?: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// OpenAPI Schema types
|
|
10
|
+
export interface ApiEndpoint {
|
|
11
|
+
name: string;
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
description: string;
|
|
15
|
+
category: string;
|
|
16
|
+
parameters?: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
type: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
description?: string;
|
|
21
|
+
}>;
|
|
22
|
+
requestBody?: {
|
|
23
|
+
type: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
};
|
|
26
|
+
responses?: Array<{
|
|
27
|
+
code: string;
|
|
28
|
+
description: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OpenApiSchema {
|
|
33
|
+
openapi: string;
|
|
34
|
+
info: {
|
|
35
|
+
title: string;
|
|
36
|
+
version: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
};
|
|
39
|
+
servers?: Array<{
|
|
40
|
+
url: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
}>;
|
|
43
|
+
paths: Record<string, Record<string, any>>;
|
|
44
|
+
components?: {
|
|
45
|
+
schemas?: Record<string, any>;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Schema source configuration (URL-based only)
|
|
50
|
+
export interface SchemaSource {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
url: string; // URL to fetch OpenAPI schema from (full URL)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Playground configuration
|
|
57
|
+
export interface PlaygroundConfig {
|
|
58
|
+
schemas: SchemaSource[]; // Array of schema URLs (full URLs from API)
|
|
59
|
+
defaultSchemaId?: string; // Default schema to select
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Playground state types
|
|
63
|
+
export interface PlaygroundState {
|
|
64
|
+
// Step management
|
|
65
|
+
currentStep: PlaygroundStep;
|
|
66
|
+
steps: PlaygroundStep[];
|
|
67
|
+
|
|
68
|
+
// Endpoint selection
|
|
69
|
+
selectedEndpoint: ApiEndpoint | null;
|
|
70
|
+
selectedCategory: string;
|
|
71
|
+
searchTerm: string;
|
|
72
|
+
selectedVersion: string; // API version selection
|
|
73
|
+
|
|
74
|
+
// Request configuration
|
|
75
|
+
requestUrl: string;
|
|
76
|
+
requestMethod: string;
|
|
77
|
+
requestHeaders: string;
|
|
78
|
+
requestBody: string;
|
|
79
|
+
selectedApiKey: string | null;
|
|
80
|
+
manualApiToken: string; // Manual Bearer token input
|
|
81
|
+
parameters: Record<string, string>; // Form parameters for URL substitution
|
|
82
|
+
|
|
83
|
+
// Response
|
|
84
|
+
response: ApiResponse | null;
|
|
85
|
+
loading: boolean;
|
|
86
|
+
|
|
87
|
+
// UI state
|
|
88
|
+
sidebarOpen: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type PlaygroundStep = 'endpoints' | 'request' | 'response';
|
|
92
|
+
|
|
93
|
+
export interface ApiResponse {
|
|
94
|
+
status?: number;
|
|
95
|
+
statusText?: string;
|
|
96
|
+
headers?: any;
|
|
97
|
+
data?: any;
|
|
98
|
+
error?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Context types
|
|
102
|
+
export interface PlaygroundContextType {
|
|
103
|
+
// State
|
|
104
|
+
state: PlaygroundState;
|
|
105
|
+
config: PlaygroundConfig;
|
|
106
|
+
apiKeys: ApiKey[];
|
|
107
|
+
apiKeysLoading: boolean;
|
|
108
|
+
|
|
109
|
+
// Step management
|
|
110
|
+
setCurrentStep: (step: PlaygroundStep) => void;
|
|
111
|
+
goToNextStep: () => void;
|
|
112
|
+
goToPreviousStep: () => void;
|
|
113
|
+
|
|
114
|
+
// Endpoint management
|
|
115
|
+
setSelectedEndpoint: (endpoint: ApiEndpoint | null) => void;
|
|
116
|
+
setSelectedCategory: (category: string) => void;
|
|
117
|
+
setSearchTerm: (term: string) => void;
|
|
118
|
+
setSelectedVersion: (version: string) => void;
|
|
119
|
+
|
|
120
|
+
// Request management
|
|
121
|
+
setRequestUrl: (url: string) => void;
|
|
122
|
+
setRequestMethod: (method: string) => void;
|
|
123
|
+
setRequestHeaders: (headers: string) => void;
|
|
124
|
+
setRequestBody: (body: string) => void;
|
|
125
|
+
setSelectedApiKey: (apiKeyId: string | null) => void;
|
|
126
|
+
setManualApiToken: (token: string) => void;
|
|
127
|
+
setParameters: (parameters: Record<string, string>) => void;
|
|
128
|
+
|
|
129
|
+
// Response management
|
|
130
|
+
setResponse: (response: ApiResponse | null) => void;
|
|
131
|
+
setLoading: (loading: boolean) => void;
|
|
132
|
+
|
|
133
|
+
// UI management
|
|
134
|
+
setSidebarOpen: (open: boolean) => void;
|
|
135
|
+
|
|
136
|
+
// Actions
|
|
137
|
+
clearAll: () => void;
|
|
138
|
+
copyToClipboard: (text: string) => void;
|
|
139
|
+
sendRequest: () => Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Hook return types
|
|
143
|
+
export interface UseOpenApiSchemaReturn {
|
|
144
|
+
loading: boolean;
|
|
145
|
+
error: string | null;
|
|
146
|
+
endpoints: ApiEndpoint[];
|
|
147
|
+
categories: string[];
|
|
148
|
+
schemas: SchemaSource[];
|
|
149
|
+
currentSchema: SchemaSource | null;
|
|
150
|
+
setCurrentSchema: (schemaId: string) => void;
|
|
151
|
+
refresh: () => void;
|
|
152
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Management Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized functions for managing API keys in headers and requests.
|
|
5
|
+
* This utility can be used across different components that need to handle API keys.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ApiKey } from '../types';
|
|
9
|
+
|
|
10
|
+
export type { ApiKey };
|
|
11
|
+
|
|
12
|
+
export interface HeadersWithApiKey {
|
|
13
|
+
[key: string]: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add API key to request headers
|
|
18
|
+
* @param headers - Existing headers object
|
|
19
|
+
* @param apiKey - API key object or string
|
|
20
|
+
* @returns Headers with API key added
|
|
21
|
+
*/
|
|
22
|
+
export function addApiKeyToHeaders(headers: HeadersWithApiKey, apiKey: ApiKey | string | null): HeadersWithApiKey {
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
return headers;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const keyValue = typeof apiKey === 'string' ? apiKey : (apiKey.id || '');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
...headers,
|
|
31
|
+
'X-API-Key': keyValue,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Remove API key from headers object
|
|
37
|
+
* @param headers - Headers object
|
|
38
|
+
* @returns Headers without API key
|
|
39
|
+
*/
|
|
40
|
+
export function removeApiKeyFromHeaders(headers: HeadersWithApiKey): HeadersWithApiKey {
|
|
41
|
+
const { 'X-API-Key': removed, ...remainingHeaders } = headers;
|
|
42
|
+
return remainingHeaders;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Remove API key from headers JSON string
|
|
47
|
+
* @param headersJson - JSON string of headers
|
|
48
|
+
* @returns JSON string without API key
|
|
49
|
+
*/
|
|
50
|
+
export function removeApiKeyFromHeadersJson(headersJson: string): string {
|
|
51
|
+
try {
|
|
52
|
+
const headers = JSON.parse(headersJson);
|
|
53
|
+
const updatedHeaders = removeApiKeyFromHeaders(headers);
|
|
54
|
+
return JSON.stringify(updatedHeaders, null, 2);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error parsing headers JSON:', error);
|
|
57
|
+
return headersJson;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Update headers JSON string with API key
|
|
63
|
+
* @param headersJson - JSON string of headers
|
|
64
|
+
* @param apiKey - API key object or string
|
|
65
|
+
* @returns Updated JSON string
|
|
66
|
+
*/
|
|
67
|
+
export function updateHeadersJsonWithApiKey(headersJson: string, apiKey: ApiKey | string | null): string {
|
|
68
|
+
try {
|
|
69
|
+
const headers = JSON.parse(headersJson);
|
|
70
|
+
const updatedHeaders = addApiKeyToHeaders(headers, apiKey);
|
|
71
|
+
return JSON.stringify(updatedHeaders, null, 2);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error parsing headers JSON:', error);
|
|
74
|
+
return headersJson;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find API key by ID in a list of API keys
|
|
80
|
+
* @param apiKeys - Array of API keys
|
|
81
|
+
* @param keyId - ID of the API key to find
|
|
82
|
+
* @returns API key object or null
|
|
83
|
+
*/
|
|
84
|
+
export function findApiKeyById(apiKeys: ApiKey[], keyId: string): ApiKey | null {
|
|
85
|
+
return apiKeys.find((key) => key.id === keyId) || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate API key format
|
|
90
|
+
* @param apiKey - API key string to validate
|
|
91
|
+
* @returns Whether the API key format is valid
|
|
92
|
+
*/
|
|
93
|
+
export function validateApiKeyFormat(apiKey: string): boolean {
|
|
94
|
+
// Basic validation - adjust based on your API key format requirements
|
|
95
|
+
return typeof apiKey === 'string' && apiKey.length > 0 && apiKey.trim() !== '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create default headers with API key
|
|
100
|
+
* @param apiKey - API key object or string
|
|
101
|
+
* @returns Default headers with API key
|
|
102
|
+
*/
|
|
103
|
+
export function createDefaultHeaders(apiKey?: ApiKey | string): HeadersWithApiKey {
|
|
104
|
+
const defaultHeaders: HeadersWithApiKey = {
|
|
105
|
+
'Content-Type': 'application/json',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (apiKey) {
|
|
109
|
+
return addApiKeyToHeaders(defaultHeaders, apiKey);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return defaultHeaders;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Merge headers with API key
|
|
117
|
+
* @param baseHeaders - Base headers object
|
|
118
|
+
* @param additionalHeaders - Additional headers to merge
|
|
119
|
+
* @param apiKey - API key to add
|
|
120
|
+
* @returns Merged headers with API key
|
|
121
|
+
*/
|
|
122
|
+
export function mergeHeadersWithApiKey(baseHeaders: HeadersWithApiKey, additionalHeaders: HeadersWithApiKey, apiKey?: ApiKey | string): HeadersWithApiKey {
|
|
123
|
+
const mergedHeaders = { ...baseHeaders, ...additionalHeaders };
|
|
124
|
+
|
|
125
|
+
if (apiKey) {
|
|
126
|
+
return addApiKeyToHeaders(mergedHeaders, apiKey);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return mergedHeaders;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Log API key usage (for debugging/analytics)
|
|
134
|
+
* @param apiKey - API key that was used
|
|
135
|
+
* @param endpoint - Endpoint that was called
|
|
136
|
+
* @param success - Whether the request was successful
|
|
137
|
+
*/
|
|
138
|
+
export function logApiKeyUsage(apiKey: ApiKey | string, endpoint: string, success: boolean): void {
|
|
139
|
+
const keyValue = typeof apiKey === 'string' ? apiKey : (apiKey.id || '');
|
|
140
|
+
const keyName = typeof apiKey === 'string' ? 'Unknown' : apiKey.name;
|
|
141
|
+
|
|
142
|
+
console.log(`API Key Usage:`, {
|
|
143
|
+
keyName,
|
|
144
|
+
keyValue: keyValue.substring(0, 8) + '...',
|
|
145
|
+
endpoint,
|
|
146
|
+
success,
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ApiKey } from '../types';
|
|
2
|
+
import { HTTP_METHOD_COLORS, HTTP_STATUS_COLORS } from '../constants';
|
|
3
|
+
|
|
4
|
+
export const getMethodColor = (
|
|
5
|
+
method: string
|
|
6
|
+
): 'success' | 'primary' | 'warning' | 'error' | 'default' => {
|
|
7
|
+
return HTTP_METHOD_COLORS[method.toUpperCase() as keyof typeof HTTP_METHOD_COLORS] || 'default';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const getStatusColor = (
|
|
11
|
+
status: number
|
|
12
|
+
): 'success' | 'warning' | 'error' | 'default' => {
|
|
13
|
+
const firstDigit = Math.floor(status / 100).toString();
|
|
14
|
+
return HTTP_STATUS_COLORS[firstDigit as keyof typeof HTTP_STATUS_COLORS] || 'default';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const formatApiKeyDisplay = (apiKey: ApiKey): string => {
|
|
18
|
+
const preview = apiKey.id.substring(0, 8);
|
|
19
|
+
return `${apiKey.name} (${preview}...)`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const isValidJson = (str: string): boolean => {
|
|
23
|
+
if (!str || typeof str !== 'string') return false;
|
|
24
|
+
try {
|
|
25
|
+
JSON.parse(str);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const formatRequestHeaders = (headers: Record<string, string>): string => {
|
|
33
|
+
return JSON.stringify(headers, null, 2);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const parseRequestHeaders = (headersString: string): Record<string, string> => {
|
|
37
|
+
if (!headersString || typeof headersString !== 'string') {
|
|
38
|
+
return { 'Content-Type': 'application/json' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(headersString);
|
|
43
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : { 'Content-Type': 'application/json' };
|
|
44
|
+
} catch {
|
|
45
|
+
return { 'Content-Type': 'application/json' };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Substitute URL parameters like {id}, {userId}, etc.
|
|
50
|
+
export const substituteUrlParameters = (
|
|
51
|
+
url: string,
|
|
52
|
+
parameters: Record<string, string>
|
|
53
|
+
): string => {
|
|
54
|
+
let substitutedUrl = url;
|
|
55
|
+
|
|
56
|
+
Object.entries(parameters).forEach(([key, value]) => {
|
|
57
|
+
if (value && value.trim() !== '') {
|
|
58
|
+
// Replace both {key} and %7Bkey%7D patterns (URL encoded version)
|
|
59
|
+
const patterns = [
|
|
60
|
+
new RegExp(`\\{${key}\\}`, 'g'),
|
|
61
|
+
new RegExp(`%7B${key}%7D`, 'gi'),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
patterns.forEach((pattern) => {
|
|
65
|
+
substitutedUrl = substitutedUrl.replace(pattern, encodeURIComponent(value));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return substitutedUrl;
|
|
71
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Version Management Utilities
|
|
3
|
+
* Handles version detection, filtering, and URL transformation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ApiEndpoint } from '../types';
|
|
7
|
+
|
|
8
|
+
export interface ApiVersion {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
isDefault: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Available API versions
|
|
16
|
+
export const API_VERSIONS: ApiVersion[] = [
|
|
17
|
+
{
|
|
18
|
+
id: 'v1',
|
|
19
|
+
name: 'v1',
|
|
20
|
+
description: 'Current stable version',
|
|
21
|
+
isDefault: true,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect version from endpoint path
|
|
27
|
+
*/
|
|
28
|
+
export const detectEndpointVersion = (path: string): string => {
|
|
29
|
+
// Check for versioned paths like /api/vehicles_api/v1/...
|
|
30
|
+
const versionMatch = path.match(/\/api\/[^/]+\/(v\d+)\//);
|
|
31
|
+
if (versionMatch && versionMatch[1]) {
|
|
32
|
+
return versionMatch[1];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If no version found, default to v1
|
|
36
|
+
return 'v1';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if endpoint belongs to specific version
|
|
41
|
+
*/
|
|
42
|
+
export const isEndpointInVersion = (endpoint: ApiEndpoint, version: string): boolean => {
|
|
43
|
+
const endpointVersion = detectEndpointVersion(endpoint.path);
|
|
44
|
+
return endpointVersion === version;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Filter endpoints by version
|
|
49
|
+
*/
|
|
50
|
+
export const filterEndpointsByVersion = (endpoints: ApiEndpoint[], version: string): ApiEndpoint[] => {
|
|
51
|
+
return endpoints.filter(endpoint => isEndpointInVersion(endpoint, version));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Remove duplicate endpoints across versions
|
|
56
|
+
* Keeps only the specified version, removes duplicates from other versions
|
|
57
|
+
*/
|
|
58
|
+
export const deduplicateEndpoints = (endpoints: ApiEndpoint[], preferredVersion: string): ApiEndpoint[] => {
|
|
59
|
+
const endpointMap = new Map<string, ApiEndpoint>();
|
|
60
|
+
|
|
61
|
+
// Group endpoints by normalized path (without version)
|
|
62
|
+
const groupedEndpoints = new Map<string, ApiEndpoint[]>();
|
|
63
|
+
|
|
64
|
+
endpoints.forEach(endpoint => {
|
|
65
|
+
const normalizedPath = normalizeEndpointPath(endpoint.path);
|
|
66
|
+
if (!groupedEndpoints.has(normalizedPath)) {
|
|
67
|
+
groupedEndpoints.set(normalizedPath, []);
|
|
68
|
+
}
|
|
69
|
+
groupedEndpoints.get(normalizedPath)!.push(endpoint);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// For each group, pick the endpoint from preferred version
|
|
73
|
+
groupedEndpoints.forEach((endpointGroup, normalizedPath) => {
|
|
74
|
+
let selectedEndpoint: ApiEndpoint | null = null;
|
|
75
|
+
|
|
76
|
+
// Try to find endpoint in preferred version
|
|
77
|
+
const versionEndpoint = endpointGroup.find(ep => isEndpointInVersion(ep, preferredVersion));
|
|
78
|
+
if (versionEndpoint) {
|
|
79
|
+
selectedEndpoint = versionEndpoint;
|
|
80
|
+
} else if (endpointGroup.length > 0) {
|
|
81
|
+
// Fallback to first available endpoint
|
|
82
|
+
selectedEndpoint = endpointGroup[0] || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (selectedEndpoint) {
|
|
86
|
+
endpointMap.set(normalizedPath, selectedEndpoint);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return Array.from(endpointMap.values());
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Normalize endpoint path by removing version prefix
|
|
95
|
+
* /api/vehicles_api/v1/vehicles/ -> /api/vehicles_api/vehicles/
|
|
96
|
+
* /api/vehicles_api/vehicles/ -> /api/vehicles_api/vehicles/
|
|
97
|
+
*/
|
|
98
|
+
export const normalizeEndpointPath = (path: string): string => {
|
|
99
|
+
// Remove version prefix like /v1/, /v2/, etc.
|
|
100
|
+
return path.replace(/\/v\d+\//, '/');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert endpoint path to specific version
|
|
105
|
+
*/
|
|
106
|
+
export const convertEndpointToVersion = (endpoint: ApiEndpoint, targetVersion: string): ApiEndpoint => {
|
|
107
|
+
const currentVersion = detectEndpointVersion(endpoint.path);
|
|
108
|
+
|
|
109
|
+
// If already in target version, return as is
|
|
110
|
+
if (currentVersion === targetVersion) {
|
|
111
|
+
return endpoint;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let newPath = endpoint.path;
|
|
115
|
+
|
|
116
|
+
// Replace version: /api/vehicles_api/v1/vehicles/ -> /api/vehicles_api/v2/vehicles/
|
|
117
|
+
newPath = newPath.replace(/\/v\d+\//, `/${targetVersion}/`);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...endpoint,
|
|
121
|
+
path: newPath,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get version info by ID
|
|
127
|
+
*/
|
|
128
|
+
export const getVersionById = (versionId: string): ApiVersion | undefined => {
|
|
129
|
+
return API_VERSIONS.find(v => v.id === versionId);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get default version
|
|
134
|
+
*/
|
|
135
|
+
export const getDefaultVersion = (): ApiVersion => {
|
|
136
|
+
const defaultVersion = API_VERSIONS.find(v => v.isDefault);
|
|
137
|
+
if (defaultVersion) {
|
|
138
|
+
return defaultVersion;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fallback to first version if no default is set
|
|
142
|
+
if (API_VERSIONS.length > 0 && API_VERSIONS[0]) {
|
|
143
|
+
return API_VERSIONS[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// This should never happen, but TypeScript requires it
|
|
147
|
+
throw new Error('No API versions defined');
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get version statistics from endpoints
|
|
152
|
+
*/
|
|
153
|
+
export const getVersionStats = (endpoints: ApiEndpoint[]): Record<string, number> => {
|
|
154
|
+
const stats: Record<string, number> = {};
|
|
155
|
+
|
|
156
|
+
API_VERSIONS.forEach(version => {
|
|
157
|
+
stats[version.id] = filterEndpointsByVersion(endpoints, version.id).length;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return stats;
|
|
161
|
+
};
|