@djangocfg/ext-newsletter 1.0.0

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.
Files changed (91) hide show
  1. package/README.md +140 -0
  2. package/dist/chunk-LQLPNWHR.js +2075 -0
  3. package/dist/hooks.cjs +2539 -0
  4. package/dist/hooks.d.cts +267 -0
  5. package/dist/hooks.d.ts +267 -0
  6. package/dist/hooks.js +370 -0
  7. package/dist/index.cjs +2153 -0
  8. package/dist/index.d.cts +2084 -0
  9. package/dist/index.d.ts +2084 -0
  10. package/dist/index.js +1 -0
  11. package/package.json +80 -0
  12. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter.ts +210 -0
  13. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__bulk_email.ts +93 -0
  14. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__campaigns.ts +338 -0
  15. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__logs.ts +92 -0
  16. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__newsletters.ts +150 -0
  17. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__subscriptions.ts +210 -0
  18. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__testing.ts +93 -0
  19. package/src/api/generated/ext_newsletter/_utils/fetchers/index.ts +34 -0
  20. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter.ts +81 -0
  21. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__bulk_email.ts +42 -0
  22. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__campaigns.ts +130 -0
  23. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__logs.ts +37 -0
  24. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__newsletters.ts +52 -0
  25. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__subscriptions.ts +78 -0
  26. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__testing.ts +42 -0
  27. package/src/api/generated/ext_newsletter/_utils/hooks/index.ts +34 -0
  28. package/src/api/generated/ext_newsletter/_utils/schemas/BulkEmailRequest.schema.ts +26 -0
  29. package/src/api/generated/ext_newsletter/_utils/schemas/BulkEmailResponse.schema.ts +23 -0
  30. package/src/api/generated/ext_newsletter/_utils/schemas/EmailLog.schema.ts +31 -0
  31. package/src/api/generated/ext_newsletter/_utils/schemas/ErrorResponse.schema.ts +20 -0
  32. package/src/api/generated/ext_newsletter/_utils/schemas/Newsletter.schema.ts +26 -0
  33. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterCampaign.schema.ts +33 -0
  34. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterCampaignRequest.schema.ts +26 -0
  35. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterSubscription.schema.ts +27 -0
  36. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedEmailLogList.schema.ts +24 -0
  37. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterCampaignList.schema.ts +24 -0
  38. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterList.schema.ts +24 -0
  39. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterSubscriptionList.schema.ts +24 -0
  40. package/src/api/generated/ext_newsletter/_utils/schemas/PatchedNewsletterCampaignRequest.schema.ts +26 -0
  41. package/src/api/generated/ext_newsletter/_utils/schemas/PatchedUnsubscribeRequest.schema.ts +19 -0
  42. package/src/api/generated/ext_newsletter/_utils/schemas/SendCampaignRequest.schema.ts +19 -0
  43. package/src/api/generated/ext_newsletter/_utils/schemas/SendCampaignResponse.schema.ts +22 -0
  44. package/src/api/generated/ext_newsletter/_utils/schemas/SubscribeRequest.schema.ts +20 -0
  45. package/src/api/generated/ext_newsletter/_utils/schemas/SubscribeResponse.schema.ts +21 -0
  46. package/src/api/generated/ext_newsletter/_utils/schemas/SuccessResponse.schema.ts +20 -0
  47. package/src/api/generated/ext_newsletter/_utils/schemas/TestEmailRequest.schema.ts +21 -0
  48. package/src/api/generated/ext_newsletter/_utils/schemas/Unsubscribe.schema.ts +19 -0
  49. package/src/api/generated/ext_newsletter/_utils/schemas/UnsubscribeRequest.schema.ts +19 -0
  50. package/src/api/generated/ext_newsletter/_utils/schemas/index.ts +40 -0
  51. package/src/api/generated/ext_newsletter/api-instance.ts +131 -0
  52. package/src/api/generated/ext_newsletter/client.ts +319 -0
  53. package/src/api/generated/ext_newsletter/enums.ts +24 -0
  54. package/src/api/generated/ext_newsletter/errors.ts +116 -0
  55. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/client.ts +38 -0
  56. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/index.ts +2 -0
  57. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/models.ts +71 -0
  58. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/client.ts +24 -0
  59. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/index.ts +2 -0
  60. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/models.ts +29 -0
  61. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/client.ts +85 -0
  62. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/index.ts +2 -0
  63. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/models.ts +100 -0
  64. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/client.ts +35 -0
  65. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/index.ts +2 -0
  66. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/models.ts +51 -0
  67. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/client.ts +45 -0
  68. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/index.ts +2 -0
  69. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/models.ts +42 -0
  70. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/client.ts +55 -0
  71. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/index.ts +2 -0
  72. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/models.ts +92 -0
  73. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/client.ts +24 -0
  74. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/index.ts +2 -0
  75. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/models.ts +24 -0
  76. package/src/api/generated/ext_newsletter/http.ts +103 -0
  77. package/src/api/generated/ext_newsletter/index.ts +315 -0
  78. package/src/api/generated/ext_newsletter/logger.ts +259 -0
  79. package/src/api/generated/ext_newsletter/retry.ts +175 -0
  80. package/src/api/generated/ext_newsletter/schema.json +1739 -0
  81. package/src/api/generated/ext_newsletter/storage.ts +161 -0
  82. package/src/api/generated/ext_newsletter/validation-events.ts +133 -0
  83. package/src/api/index.ts +9 -0
  84. package/src/components/Hero/index.tsx +160 -0
  85. package/src/components/Hero/types.ts +37 -0
  86. package/src/config.ts +20 -0
  87. package/src/contexts/newsletter/NewsletterContext.tsx +264 -0
  88. package/src/contexts/newsletter/types.ts +32 -0
  89. package/src/hooks/index.ts +21 -0
  90. package/src/index.ts +14 -0
  91. package/src/utils/logger.ts +9 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Simple serializer for test email.
3
+ *
4
+ * Request model (no read-only fields).
5
+ */
6
+ export interface TestEmailRequest {
7
+ email: string;
8
+ subject?: string;
9
+ message?: string;
10
+ }
11
+
12
+ /**
13
+ * Response for bulk email sending.
14
+ *
15
+ * Response model (includes read-only fields).
16
+ */
17
+ export interface BulkEmailResponse {
18
+ success: boolean;
19
+ sent_count: number;
20
+ failed_count: number;
21
+ total_recipients: number;
22
+ error?: string;
23
+ }
24
+
@@ -0,0 +1,103 @@
1
+ /**
2
+ * HTTP Client Adapter Pattern
3
+ *
4
+ * Allows switching between fetch/axios/httpx without changing generated code.
5
+ * Provides unified interface for making HTTP requests.
6
+ */
7
+
8
+ export interface HttpRequest {
9
+ method: string;
10
+ url: string;
11
+ headers?: Record<string, string>;
12
+ body?: any;
13
+ params?: Record<string, any>;
14
+ /** FormData for file uploads (multipart/form-data) */
15
+ formData?: FormData;
16
+ }
17
+
18
+ export interface HttpResponse<T = any> {
19
+ data: T;
20
+ status: number;
21
+ statusText: string;
22
+ headers: Record<string, string>;
23
+ }
24
+
25
+ /**
26
+ * HTTP Client Adapter Interface.
27
+ * Implement this to use custom HTTP clients (axios, httpx, etc.)
28
+ */
29
+ export interface HttpClientAdapter {
30
+ request<T = any>(request: HttpRequest): Promise<HttpResponse<T>>;
31
+ }
32
+
33
+ /**
34
+ * Default Fetch API adapter.
35
+ * Uses native browser fetch() with proper error handling.
36
+ */
37
+ export class FetchAdapter implements HttpClientAdapter {
38
+ async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
39
+ const { method, url, headers, body, params, formData } = request;
40
+
41
+ // Build URL with query params
42
+ let finalUrl = url;
43
+ if (params) {
44
+ const searchParams = new URLSearchParams();
45
+ Object.entries(params).forEach(([key, value]) => {
46
+ if (value !== null && value !== undefined) {
47
+ searchParams.append(key, String(value));
48
+ }
49
+ });
50
+ const queryString = searchParams.toString();
51
+ if (queryString) {
52
+ finalUrl = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`;
53
+ }
54
+ }
55
+
56
+ // Build headers
57
+ const finalHeaders: Record<string, string> = { ...headers };
58
+
59
+ // Determine body and content-type
60
+ let requestBody: string | FormData | undefined;
61
+
62
+ if (formData) {
63
+ // For multipart/form-data, let browser set Content-Type with boundary
64
+ requestBody = formData;
65
+ // Don't set Content-Type - browser will set it with boundary
66
+ } else if (body) {
67
+ // JSON request
68
+ finalHeaders['Content-Type'] = 'application/json';
69
+ requestBody = JSON.stringify(body);
70
+ }
71
+
72
+ // Make request
73
+ const response = await fetch(finalUrl, {
74
+ method,
75
+ headers: finalHeaders,
76
+ body: requestBody,
77
+ credentials: 'include', // Include Django session cookies
78
+ });
79
+
80
+ // Parse response
81
+ let data: any = null;
82
+ const contentType = response.headers.get('content-type');
83
+
84
+ if (response.status !== 204 && contentType?.includes('application/json')) {
85
+ data = await response.json();
86
+ } else if (response.status !== 204) {
87
+ data = await response.text();
88
+ }
89
+
90
+ // Convert Headers to plain object
91
+ const responseHeaders: Record<string, string> = {};
92
+ response.headers.forEach((value, key) => {
93
+ responseHeaders[key] = value;
94
+ });
95
+
96
+ return {
97
+ data,
98
+ status: response.status,
99
+ statusText: response.statusText,
100
+ headers: responseHeaders,
101
+ };
102
+ }
103
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Django CFG API - API Client with JWT Management
3
+ *
4
+ * Usage:
5
+ * ```typescript
6
+ * import { API } from './api';
7
+ *
8
+ * const api = new API('https://api.example.com');
9
+ *
10
+ * // Set JWT token
11
+ * api.setToken('your-jwt-token', 'refresh-token');
12
+ *
13
+ * // Use API
14
+ * const posts = await api.posts.list();
15
+ * const user = await api.users.retrieve(1);
16
+ *
17
+ * // Check authentication
18
+ * if (api.isAuthenticated()) {
19
+ * // ...
20
+ * }
21
+ *
22
+ * // Custom storage with logging (for Electron/Node.js)
23
+ * import { MemoryStorageAdapter, APILogger } from './storage';
24
+ * const logger = new APILogger({ enabled: true, logLevel: 'debug' });
25
+ * const api = new API('https://api.example.com', {
26
+ * storage: new MemoryStorageAdapter(logger),
27
+ * loggerConfig: { enabled: true, logLevel: 'debug' }
28
+ * });
29
+ *
30
+ * // Get OpenAPI schema
31
+ * const schema = api.getSchema();
32
+ * ```
33
+ */
34
+
35
+ import { APIClient } from "./client";
36
+ import {
37
+ StorageAdapter,
38
+ LocalStorageAdapter,
39
+ CookieStorageAdapter,
40
+ MemoryStorageAdapter
41
+ } from "./storage";
42
+ import type { RetryConfig } from "./retry";
43
+ import type { LoggerConfig } from "./logger";
44
+ import { APILogger } from "./logger";
45
+ import { ExtNewsletterBulkEmail } from "./ext_newsletter__newsletter__bulk_email/client";
46
+ import { ExtNewsletterCampaigns } from "./ext_newsletter__newsletter__campaigns/client";
47
+ import { ExtNewsletterLogs } from "./ext_newsletter__newsletter__logs/client";
48
+ import { ExtNewsletterNewsletters } from "./ext_newsletter__newsletter__newsletters/client";
49
+ import { ExtNewsletterSubscriptions } from "./ext_newsletter__newsletter__subscriptions/client";
50
+ import { ExtNewsletterTesting } from "./ext_newsletter__newsletter__testing/client";
51
+ import { ExtNewsletterNewsletter } from "./ext_newsletter__newsletter/client";
52
+ export * as ExtNewsletterBulkEmailTypes from "./ext_newsletter__newsletter__bulk_email/models";
53
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
54
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
55
+ export * as ExtNewsletterCampaignsTypes from "./ext_newsletter__newsletter__campaigns/models";
56
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
57
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
58
+ export * as ExtNewsletterLogsTypes from "./ext_newsletter__newsletter__logs/models";
59
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
60
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
61
+ export * as ExtNewsletterNewslettersTypes from "./ext_newsletter__newsletter__newsletters/models";
62
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
63
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
64
+ export * as ExtNewsletterSubscriptionsTypes from "./ext_newsletter__newsletter__subscriptions/models";
65
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
66
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
67
+ export * as ExtNewsletterTestingTypes from "./ext_newsletter__newsletter__testing/models";
68
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
69
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
70
+ export * as ExtNewsletterNewsletterTypes from "./ext_newsletter__newsletter/models";
71
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
72
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
73
+ export * as Enums from "./enums";
74
+
75
+ // Re-export Zod schemas for runtime validation
76
+ export * as Schemas from "./_utils/schemas";
77
+ // Also export all schemas directly for convenience
78
+ export * from "./_utils/schemas";
79
+
80
+ // Re-export Zod validation events for browser integration
81
+ export type { ValidationErrorDetail, ValidationErrorEvent } from "./validation-events";
82
+ export { dispatchValidationError, onValidationError, formatZodError } from "./validation-events";
83
+
84
+ // Re-export typed fetchers for universal usage
85
+ export * as Fetchers from "./_utils/fetchers";
86
+ export * from "./_utils/fetchers";
87
+
88
+ // Re-export API instance configuration functions
89
+ export {
90
+ configureAPI,
91
+ getAPIInstance,
92
+ reconfigureAPI,
93
+ clearAPITokens,
94
+ resetAPI,
95
+ isAPIConfigured
96
+ } from "./api-instance";
97
+ // NOTE: SWR hooks are generated in ./_utils/hooks/ but NOT exported here to keep
98
+ // the main bundle server-safe. Import hooks directly from the hooks directory:
99
+ // import { useUsers } from './_utils/hooks';
100
+ // Or use a separate entry point like '@djangocfg/api/hooks' for client components.
101
+
102
+ // Re-export core client
103
+ export { APIClient };
104
+
105
+ // Re-export storage adapters for convenience
106
+ export type { StorageAdapter };
107
+ export { LocalStorageAdapter, CookieStorageAdapter, MemoryStorageAdapter };
108
+
109
+ // Re-export error classes for convenience
110
+ export { APIError, NetworkError } from "./errors";
111
+
112
+ // Re-export HTTP adapters for custom implementations
113
+ export type { HttpClientAdapter, HttpRequest, HttpResponse } from "./http";
114
+ export { FetchAdapter } from "./http";
115
+
116
+ // Re-export logger types and classes
117
+ export type { LoggerConfig, RequestLog, ResponseLog, ErrorLog } from "./logger";
118
+ export { APILogger } from "./logger";
119
+
120
+ // Re-export retry configuration and utilities
121
+ export type { RetryConfig, FailedAttemptInfo } from "./retry";
122
+ export { withRetry, shouldRetry, DEFAULT_RETRY_CONFIG } from "./retry";
123
+
124
+ export const TOKEN_KEY = "auth_token";
125
+ export const REFRESH_TOKEN_KEY = "refresh_token";
126
+
127
+ export interface APIOptions {
128
+ /** Custom storage adapter (defaults to LocalStorageAdapter) */
129
+ storage?: StorageAdapter;
130
+ /** Retry configuration for failed requests */
131
+ retryConfig?: RetryConfig;
132
+ /** Logger configuration */
133
+ loggerConfig?: Partial<LoggerConfig>;
134
+ }
135
+
136
+ export class API {
137
+ private baseUrl: string;
138
+ private _client: APIClient;
139
+ private _token: string | null = null;
140
+ private _refreshToken: string | null = null;
141
+ private storage: StorageAdapter;
142
+ private options?: APIOptions;
143
+
144
+ // Sub-clients
145
+ public ext_newsletter_bulk_email!: ExtNewsletterBulkEmail;
146
+ public ext_newsletter_campaigns!: ExtNewsletterCampaigns;
147
+ public ext_newsletter_logs!: ExtNewsletterLogs;
148
+ public ext_newsletter_newsletters!: ExtNewsletterNewsletters;
149
+ public ext_newsletter_subscriptions!: ExtNewsletterSubscriptions;
150
+ public ext_newsletter_testing!: ExtNewsletterTesting;
151
+ public ext_newsletter_newsletter!: ExtNewsletterNewsletter;
152
+
153
+ constructor(baseUrl: string, options?: APIOptions) {
154
+ this.baseUrl = baseUrl;
155
+ this.options = options;
156
+
157
+ // Create logger if config provided
158
+ const logger = options?.loggerConfig ? new APILogger(options.loggerConfig) : undefined;
159
+
160
+ // Initialize storage with logger
161
+ this.storage = options?.storage || new LocalStorageAdapter(logger);
162
+
163
+ this._loadTokensFromStorage();
164
+
165
+ // Initialize APIClient
166
+ this._client = new APIClient(this.baseUrl, {
167
+ retryConfig: this.options?.retryConfig,
168
+ loggerConfig: this.options?.loggerConfig,
169
+ });
170
+
171
+ // Always inject auth header wrapper (reads token dynamically from storage)
172
+ this._injectAuthHeader();
173
+
174
+ // Initialize sub-clients from APIClient
175
+ this.ext_newsletter_bulk_email = this._client.ext_newsletter_bulk_email;
176
+ this.ext_newsletter_campaigns = this._client.ext_newsletter_campaigns;
177
+ this.ext_newsletter_logs = this._client.ext_newsletter_logs;
178
+ this.ext_newsletter_newsletters = this._client.ext_newsletter_newsletters;
179
+ this.ext_newsletter_subscriptions = this._client.ext_newsletter_subscriptions;
180
+ this.ext_newsletter_testing = this._client.ext_newsletter_testing;
181
+ this.ext_newsletter_newsletter = this._client.ext_newsletter_newsletter;
182
+ }
183
+
184
+ private _loadTokensFromStorage(): void {
185
+ this._token = this.storage.getItem(TOKEN_KEY);
186
+ this._refreshToken = this.storage.getItem(REFRESH_TOKEN_KEY);
187
+ }
188
+
189
+ private _reinitClients(): void {
190
+ this._client = new APIClient(this.baseUrl, {
191
+ retryConfig: this.options?.retryConfig,
192
+ loggerConfig: this.options?.loggerConfig,
193
+ });
194
+
195
+ // Always inject auth header wrapper (reads token dynamically from storage)
196
+ this._injectAuthHeader();
197
+
198
+ // Reinitialize sub-clients
199
+ this.ext_newsletter_bulk_email = this._client.ext_newsletter_bulk_email;
200
+ this.ext_newsletter_campaigns = this._client.ext_newsletter_campaigns;
201
+ this.ext_newsletter_logs = this._client.ext_newsletter_logs;
202
+ this.ext_newsletter_newsletters = this._client.ext_newsletter_newsletters;
203
+ this.ext_newsletter_subscriptions = this._client.ext_newsletter_subscriptions;
204
+ this.ext_newsletter_testing = this._client.ext_newsletter_testing;
205
+ this.ext_newsletter_newsletter = this._client.ext_newsletter_newsletter;
206
+ }
207
+
208
+ private _injectAuthHeader(): void {
209
+ // Override request method to inject auth header
210
+ const originalRequest = this._client.request.bind(this._client);
211
+ this._client.request = async <T>(
212
+ method: string,
213
+ path: string,
214
+ options?: { params?: Record<string, any>; body?: any; formData?: FormData; headers?: Record<string, string> }
215
+ ): Promise<T> => {
216
+ // Read token from storage dynamically (supports JWT injection after instantiation)
217
+ const token = this.getToken();
218
+ const mergedOptions = {
219
+ ...options,
220
+ headers: {
221
+ ...(options?.headers || {}),
222
+ ...(token ? { 'Authorization': `Bearer ${token}` } : {}),
223
+ },
224
+ };
225
+
226
+ return originalRequest(method, path, mergedOptions);
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Get current JWT token
232
+ */
233
+ getToken(): string | null {
234
+ return this.storage.getItem(TOKEN_KEY);
235
+ }
236
+
237
+ /**
238
+ * Get current refresh token
239
+ */
240
+ getRefreshToken(): string | null {
241
+ return this.storage.getItem(REFRESH_TOKEN_KEY);
242
+ }
243
+
244
+ /**
245
+ * Set JWT token and refresh token
246
+ * @param token - JWT access token
247
+ * @param refreshToken - JWT refresh token (optional)
248
+ */
249
+ setToken(token: string, refreshToken?: string): void {
250
+ this._token = token;
251
+ this.storage.setItem(TOKEN_KEY, token);
252
+
253
+ if (refreshToken) {
254
+ this._refreshToken = refreshToken;
255
+ this.storage.setItem(REFRESH_TOKEN_KEY, refreshToken);
256
+ }
257
+
258
+ // Reinitialize clients with new token
259
+ this._reinitClients();
260
+ }
261
+
262
+ /**
263
+ * Clear all tokens
264
+ */
265
+ clearTokens(): void {
266
+ this._token = null;
267
+ this._refreshToken = null;
268
+ this.storage.removeItem(TOKEN_KEY);
269
+ this.storage.removeItem(REFRESH_TOKEN_KEY);
270
+
271
+ // Reinitialize clients without token
272
+ this._reinitClients();
273
+ }
274
+
275
+ /**
276
+ * Check if user is authenticated
277
+ */
278
+ isAuthenticated(): boolean {
279
+ return !!this.getToken();
280
+ }
281
+
282
+ /**
283
+ * Update base URL and reinitialize clients
284
+ * @param url - New base URL
285
+ */
286
+ setBaseUrl(url: string): void {
287
+ this.baseUrl = url;
288
+ this._reinitClients();
289
+ }
290
+
291
+ /**
292
+ * Get current base URL
293
+ */
294
+ getBaseUrl(): string {
295
+ return this.baseUrl;
296
+ }
297
+
298
+ /**
299
+ * Get OpenAPI schema path
300
+ * @returns Path to the OpenAPI schema JSON file
301
+ *
302
+ * Note: The OpenAPI schema is available in the schema.json file.
303
+ * You can load it dynamically using:
304
+ * ```typescript
305
+ * const schema = await fetch('./schema.json').then(r => r.json());
306
+ * // or using fs in Node.js:
307
+ * // const schema = JSON.parse(fs.readFileSync('./schema.json', 'utf-8'));
308
+ * ```
309
+ */
310
+ getSchemaPath(): string {
311
+ return './schema.json';
312
+ }
313
+ }
314
+
315
+ export default API;
@@ -0,0 +1,259 @@
1
+ /**
2
+ * API Logger with Consola
3
+ * Beautiful console logging for API requests and responses
4
+ *
5
+ * Installation:
6
+ * npm install consola
7
+ */
8
+
9
+ import { type ConsolaInstance, createConsola } from 'consola';
10
+
11
+ /**
12
+ * Request log data
13
+ */
14
+ export interface RequestLog {
15
+ method: string;
16
+ url: string;
17
+ headers?: Record<string, string>;
18
+ body?: any;
19
+ timestamp: number;
20
+ }
21
+
22
+ /**
23
+ * Response log data
24
+ */
25
+ export interface ResponseLog {
26
+ status: number;
27
+ statusText: string;
28
+ data?: any;
29
+ duration: number;
30
+ timestamp: number;
31
+ }
32
+
33
+ /**
34
+ * Error log data
35
+ */
36
+ export interface ErrorLog {
37
+ message: string;
38
+ statusCode?: number;
39
+ fieldErrors?: Record<string, string[]>;
40
+ duration: number;
41
+ timestamp: number;
42
+ }
43
+
44
+ /**
45
+ * Logger configuration
46
+ */
47
+ export interface LoggerConfig {
48
+ /** Enable logging */
49
+ enabled: boolean;
50
+ /** Log requests */
51
+ logRequests: boolean;
52
+ /** Log responses */
53
+ logResponses: boolean;
54
+ /** Log errors */
55
+ logErrors: boolean;
56
+ /** Log request/response bodies */
57
+ logBodies: boolean;
58
+ /** Log headers (excluding sensitive ones) */
59
+ logHeaders: boolean;
60
+ /** Custom consola instance */
61
+ consola?: ConsolaInstance;
62
+ }
63
+
64
+ /**
65
+ * Default logger configuration
66
+ */
67
+ const DEFAULT_CONFIG: LoggerConfig = {
68
+ enabled: process.env.NODE_ENV !== 'production',
69
+ logRequests: true,
70
+ logResponses: true,
71
+ logErrors: true,
72
+ logBodies: true,
73
+ logHeaders: false,
74
+ };
75
+
76
+ /**
77
+ * Sensitive header names to filter out
78
+ */
79
+ const SENSITIVE_HEADERS = [
80
+ 'authorization',
81
+ 'cookie',
82
+ 'set-cookie',
83
+ 'x-api-key',
84
+ 'x-csrf-token',
85
+ ];
86
+
87
+ /**
88
+ * API Logger class
89
+ */
90
+ export class APILogger {
91
+ private config: LoggerConfig;
92
+ private consola: ConsolaInstance;
93
+
94
+ constructor(config: Partial<LoggerConfig> = {}) {
95
+ this.config = { ...DEFAULT_CONFIG, ...config };
96
+ this.consola = config.consola || createConsola({
97
+ level: this.config.enabled ? 4 : 0,
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Enable logging
103
+ */
104
+ enable(): void {
105
+ this.config.enabled = true;
106
+ }
107
+
108
+ /**
109
+ * Disable logging
110
+ */
111
+ disable(): void {
112
+ this.config.enabled = false;
113
+ }
114
+
115
+ /**
116
+ * Update configuration
117
+ */
118
+ setConfig(config: Partial<LoggerConfig>): void {
119
+ this.config = { ...this.config, ...config };
120
+ }
121
+
122
+ /**
123
+ * Filter sensitive headers
124
+ */
125
+ private filterHeaders(headers?: Record<string, string>): Record<string, string> {
126
+ if (!headers) return {};
127
+
128
+ const filtered: Record<string, string> = {};
129
+ Object.keys(headers).forEach((key) => {
130
+ const lowerKey = key.toLowerCase();
131
+ if (SENSITIVE_HEADERS.includes(lowerKey)) {
132
+ filtered[key] = '***';
133
+ } else {
134
+ filtered[key] = headers[key] || '';
135
+ }
136
+ });
137
+
138
+ return filtered;
139
+ }
140
+
141
+ /**
142
+ * Log request
143
+ */
144
+ logRequest(request: RequestLog): void {
145
+ if (!this.config.enabled || !this.config.logRequests) return;
146
+
147
+ const { method, url, headers, body } = request;
148
+
149
+ this.consola.start(`${method} ${url}`);
150
+
151
+ if (this.config.logHeaders && headers) {
152
+ this.consola.debug('Headers:', this.filterHeaders(headers));
153
+ }
154
+
155
+ if (this.config.logBodies && body) {
156
+ this.consola.debug('Body:', body);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Log response
162
+ */
163
+ logResponse(request: RequestLog, response: ResponseLog): void {
164
+ if (!this.config.enabled || !this.config.logResponses) return;
165
+
166
+ const { method, url } = request;
167
+ const { status, statusText, data, duration } = response;
168
+
169
+ const statusColor = status >= 500 ? 'red'
170
+ : status >= 400 ? 'yellow'
171
+ : status >= 300 ? 'cyan'
172
+ : 'green';
173
+
174
+ this.consola.success(
175
+ `${method} ${url} ${status} ${statusText} (${duration}ms)`
176
+ );
177
+
178
+ if (this.config.logBodies && data) {
179
+ this.consola.debug('Response:', data);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Log error
185
+ */
186
+ logError(request: RequestLog, error: ErrorLog): void {
187
+ if (!this.config.enabled || !this.config.logErrors) return;
188
+
189
+ const { method, url } = request;
190
+ const { message, statusCode, fieldErrors, duration } = error;
191
+
192
+ this.consola.error(
193
+ `${method} ${url} ${statusCode || 'Network'} Error (${duration}ms)`
194
+ );
195
+
196
+ this.consola.error('Message:', message);
197
+
198
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
199
+ this.consola.error('Field Errors:');
200
+ Object.entries(fieldErrors).forEach(([field, errors]) => {
201
+ errors.forEach((err) => {
202
+ this.consola.error(` • ${field}: ${err}`);
203
+ });
204
+ });
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Log general info
210
+ */
211
+ info(message: string, ...args: any[]): void {
212
+ if (!this.config.enabled) return;
213
+ this.consola.info(message, ...args);
214
+ }
215
+
216
+ /**
217
+ * Log warning
218
+ */
219
+ warn(message: string, ...args: any[]): void {
220
+ if (!this.config.enabled) return;
221
+ this.consola.warn(message, ...args);
222
+ }
223
+
224
+ /**
225
+ * Log error
226
+ */
227
+ error(message: string, ...args: any[]): void {
228
+ if (!this.config.enabled) return;
229
+ this.consola.error(message, ...args);
230
+ }
231
+
232
+ /**
233
+ * Log debug
234
+ */
235
+ debug(message: string, ...args: any[]): void {
236
+ if (!this.config.enabled) return;
237
+ this.consola.debug(message, ...args);
238
+ }
239
+
240
+ /**
241
+ * Log success
242
+ */
243
+ success(message: string, ...args: any[]): void {
244
+ if (!this.config.enabled) return;
245
+ this.consola.success(message, ...args);
246
+ }
247
+
248
+ /**
249
+ * Create a sub-logger with prefix
250
+ */
251
+ withTag(tag: string): ConsolaInstance {
252
+ return this.consola.withTag(tag);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Default logger instance
258
+ */
259
+ export const defaultLogger = new APILogger();