@djangocfg/api 2.1.87 → 2.1.89

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 (59) hide show
  1. package/dist/auth-server.cjs +1963 -4
  2. package/dist/auth-server.cjs.map +1 -1
  3. package/dist/auth-server.d.cts +35 -1
  4. package/dist/auth-server.d.ts +35 -1
  5. package/dist/auth-server.mjs +1953 -4
  6. package/dist/auth-server.mjs.map +1 -1
  7. package/dist/auth.cjs +692 -497
  8. package/dist/auth.cjs.map +1 -1
  9. package/dist/auth.d.cts +18 -2
  10. package/dist/auth.d.ts +18 -2
  11. package/dist/auth.mjs +655 -460
  12. package/dist/auth.mjs.map +1 -1
  13. package/dist/clients.cjs +460 -383
  14. package/dist/clients.cjs.map +1 -1
  15. package/dist/clients.d.cts +26 -6
  16. package/dist/clients.d.ts +26 -6
  17. package/dist/clients.mjs +460 -383
  18. package/dist/clients.mjs.map +1 -1
  19. package/dist/hooks.cjs +130 -105
  20. package/dist/hooks.cjs.map +1 -1
  21. package/dist/hooks.d.cts +24 -4
  22. package/dist/hooks.d.ts +24 -4
  23. package/dist/hooks.mjs +130 -105
  24. package/dist/hooks.mjs.map +1 -1
  25. package/dist/index.cjs +373 -311
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +54 -8
  28. package/dist/index.d.ts +54 -8
  29. package/dist/index.mjs +373 -311
  30. package/dist/index.mjs.map +1 -1
  31. package/package.json +3 -3
  32. package/src/auth/context/AccountsContext.tsx +3 -3
  33. package/src/auth/context/AuthContext.tsx +56 -10
  34. package/src/auth/hooks/index.ts +3 -0
  35. package/src/auth/hooks/useProfileCache.ts +1 -1
  36. package/src/auth/hooks/useTokenRefresh.ts +161 -0
  37. package/src/auth/middlewares/index.ts +8 -1
  38. package/src/auth/middlewares/tokenRefresh.ts +158 -0
  39. package/src/generated/cfg_accounts/CLAUDE.md +2 -9
  40. package/src/generated/cfg_accounts/_utils/fetchers/accounts__user_profile.ts +2 -1
  41. package/src/generated/cfg_accounts/_utils/hooks/accounts__user_profile.ts +2 -1
  42. package/src/generated/cfg_accounts/_utils/schemas/CfgAccountsProfileAvatarCreateRequest.schema.ts +15 -0
  43. package/src/generated/cfg_accounts/_utils/schemas/index.ts +1 -0
  44. package/src/generated/cfg_accounts/accounts/models.ts +0 -1
  45. package/src/generated/cfg_accounts/accounts__user_profile/client.ts +4 -2
  46. package/src/generated/cfg_accounts/accounts__user_profile/models.ts +9 -1
  47. package/src/generated/cfg_accounts/client.ts +18 -0
  48. package/src/generated/cfg_accounts/index.ts +3 -1
  49. package/src/generated/cfg_accounts/schema.json +2 -2
  50. package/src/generated/cfg_centrifugo/CLAUDE.md +1 -8
  51. package/src/generated/cfg_centrifugo/client.ts +18 -0
  52. package/src/generated/cfg_centrifugo/index.ts +3 -1
  53. package/src/generated/cfg_totp/CLAUDE.md +1 -8
  54. package/src/generated/cfg_totp/client.ts +18 -0
  55. package/src/generated/cfg_totp/index.ts +3 -1
  56. package/src/generated/cfg_totp/totp/models.ts +2 -0
  57. package/src/generated/cfg_webpush/CLAUDE.md +1 -8
  58. package/src/generated/cfg_webpush/client.ts +18 -0
  59. package/src/generated/cfg_webpush/index.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/api",
3
- "version": "2.1.87",
3
+ "version": "2.1.89",
4
4
  "description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
5
5
  "keywords": [
6
6
  "django",
@@ -74,7 +74,7 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/ui-nextjs": "^2.1.87",
77
+ "@djangocfg/ui-nextjs": "^2.1.89",
78
78
  "consola": "^3.4.2",
79
79
  "next": "^14 || ^15",
80
80
  "p-retry": "^7.0.0",
@@ -85,7 +85,7 @@
85
85
  "devDependencies": {
86
86
  "@types/node": "^24.7.2",
87
87
  "@types/react": "^19.0.0",
88
- "@djangocfg/typescript-config": "^2.1.87",
88
+ "@djangocfg/typescript-config": "^2.1.89",
89
89
  "next": "^15.0.0",
90
90
  "react": "^19.0.0",
91
91
  "tsup": "^8.5.0",
@@ -59,7 +59,7 @@ export interface AccountsContextValue {
59
59
  // Profile operations
60
60
  updateProfile: (data: UserProfileUpdateRequest) => Promise<User>;
61
61
  partialUpdateProfile: (data: PatchedUserProfileUpdateRequest) => Promise<User>;
62
- uploadAvatar: (formData: FormData) => Promise<User>;
62
+ uploadAvatar: (avatar: File | Blob) => Promise<User>;
63
63
  refreshProfile: (callerId?: string) => Promise<User | undefined>;
64
64
 
65
65
  // Authentication
@@ -165,8 +165,8 @@ export function AccountsProvider({ children }: AccountsProviderProps) {
165
165
  };
166
166
 
167
167
  // Upload avatar
168
- const uploadAvatar = async (formData: FormData): Promise<User> => {
169
- const result = await avatarMutation(formData, apiAccounts);
168
+ const uploadAvatar = async (avatar: File | Blob): Promise<User> => {
169
+ const result = await avatarMutation({ avatar }, apiAccounts);
170
170
  await refreshProfile('AccountsContext.uploadAvatar');
171
171
  return result as User;
172
172
  };
@@ -11,6 +11,7 @@ import { useCfgRouter, useLocalStorage, useQueryParams } from '@djangocfg/ui-nex
11
11
  import { api as apiAccounts, Enums } from '../../';
12
12
  import { clearProfileCache, getCachedProfile } from '../hooks/useProfileCache';
13
13
  import { useAuthRedirectManager } from '../hooks/useAuthRedirect';
14
+ import { useTokenRefresh } from '../hooks/useTokenRefresh';
14
15
  import { Analytics, AnalyticsCategory, AnalyticsEvent } from '../utils/analytics';
15
16
  import { authLogger } from '../utils/logger';
16
17
  import { AccountsProvider, useAccountsContext } from './AccountsContext';
@@ -65,6 +66,18 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
65
66
  const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage<string | null>(EMAIL_STORAGE_KEY, null);
66
67
  const [storedPhone, setStoredPhone, clearStoredPhone] = useLocalStorage<string | null>(PHONE_STORAGE_KEY, null);
67
68
 
69
+ // Automatic token refresh - refreshes token before expiry, on focus, and on network reconnect
70
+ useTokenRefresh({
71
+ enabled: true,
72
+ onRefresh: (newToken) => {
73
+ authLogger.info('Token auto-refreshed successfully');
74
+ },
75
+ onRefreshError: (error) => {
76
+ authLogger.warn('Token auto-refresh failed:', error.message);
77
+ // Don't logout on refresh error - user might still have valid session
78
+ },
79
+ });
80
+
68
81
  // Map AccountsContext profile to UserProfile
69
82
  const user = accounts.profile as UserProfile | null;
70
83
 
@@ -95,14 +108,26 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
95
108
  }, []);
96
109
 
97
110
  // Global error handler for auth-related errors
111
+ // Only clears auth on actual authentication errors (401), not on any API error
98
112
  const handleGlobalAuthError = useCallback((error: any, context: string = 'API Request') => {
99
- // Simple error check - if response has error flag, it's an error
100
- if (error?.success === false) {
101
- authLogger.warn(`Error detected in ${context}, clearing tokens`);
113
+ // Only clear auth on actual authentication errors (401)
114
+ // Don't logout on validation errors, server errors, network issues, etc.
115
+ const isAuthError = error?.status === 401 ||
116
+ error?.statusCode === 401 ||
117
+ error?.code === 'token_not_valid' ||
118
+ error?.code === 'authentication_failed';
119
+
120
+ if (isAuthError) {
121
+ authLogger.warn(`Authentication error in ${context}, clearing tokens`);
102
122
  clearAuthState(`globalAuthError:${context}`);
103
123
  return true;
104
124
  }
105
125
 
126
+ // Log but don't logout for other errors
127
+ if (error?.success === false) {
128
+ authLogger.warn(`Non-auth error in ${context} (not clearing session):`, error?.message || error);
129
+ }
130
+
106
131
  return false;
107
132
  }, [clearAuthState]);
108
133
 
@@ -150,11 +175,22 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
150
175
  // Always mark as initialized if we have valid tokens
151
176
  // Don't clear tokens just because profile fetch failed
152
177
  setInitialized(true);
153
- } catch (error) {
178
+ } catch (error: any) {
154
179
  authLogger.error('Failed to load profile:', error);
155
- // Use global error handler first, fallback to clearing state
156
- if (!handleGlobalAuthError(error, 'loadCurrentProfile')) {
157
- clearAuthState('loadCurrentProfile:error');
180
+ // Only clear auth state on actual authentication errors (401)
181
+ // Don't logout on network errors, server errors, etc.
182
+ const isAuthError = error?.status === 401 ||
183
+ error?.statusCode === 401 ||
184
+ error?.code === 'token_not_valid' ||
185
+ error?.code === 'authentication_failed';
186
+
187
+ if (isAuthError) {
188
+ authLogger.warn('Authentication error, clearing session');
189
+ clearAuthState('loadCurrentProfile:authError');
190
+ } else {
191
+ // Keep tokens, mark as initialized - user can retry
192
+ authLogger.warn('Profile load failed but keeping session (non-auth error)');
193
+ setInitialized(true);
158
194
  }
159
195
  } finally {
160
196
  isLoadingProfileRef.current = false;
@@ -214,10 +250,20 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
214
250
  try {
215
251
  authLogger.info('No cached profile found, loading from API...');
216
252
  await loadCurrentProfile('AuthContext.initializeAuth');
217
- } catch (error) {
253
+ } catch (error: any) {
218
254
  authLogger.error('Failed to load profile during initialization:', error);
219
- // If profile loading fails, clear auth state
220
- clearAuthState('initializeAuth:loadProfileFailed');
255
+ // Only clear on 401 auth error, otherwise keep session
256
+ const isAuthError = error?.status === 401 ||
257
+ error?.statusCode === 401 ||
258
+ error?.code === 'token_not_valid' ||
259
+ error?.code === 'authentication_failed';
260
+
261
+ if (isAuthError) {
262
+ clearAuthState('initializeAuth:authError');
263
+ } else {
264
+ authLogger.warn('Init profile load failed but keeping session');
265
+ setInitialized(true);
266
+ }
221
267
  }
222
268
  setIsLoading(false);
223
269
  } else {
@@ -45,3 +45,6 @@ export {
45
45
  getCacheMetadata,
46
46
  type ProfileCacheOptions,
47
47
  } from './useProfileCache';
48
+
49
+ // Token refresh
50
+ export { useTokenRefresh } from './useTokenRefresh';
@@ -16,7 +16,7 @@ import { decodeBase64, encodeBase64 } from './useBase64';
16
16
  // Cache configuration
17
17
  const CACHE_KEY = 'user_profile_cache';
18
18
  const CACHE_VERSION = 1;
19
- const DEFAULT_TTL = 3600000; // 1 hour in milliseconds
19
+ const DEFAULT_TTL = 14400000; // 4 hours in milliseconds (reduced API calls, more resilient)
20
20
 
21
21
  export interface ProfileCacheOptions {
22
22
  /** Time to live in milliseconds (default: 1 hour) */
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Token Refresh Hook
5
+ *
6
+ * Provides automatic token refresh functionality:
7
+ * - Proactively refreshes token before expiry
8
+ * - Refreshes on window focus
9
+ * - Refreshes on network reconnect
10
+ */
11
+
12
+ import { useCallback, useEffect, useRef } from 'react';
13
+ import { api as apiAccounts } from '../../';
14
+ import { authLogger } from '../utils/logger';
15
+
16
+ // Configuration
17
+ const TOKEN_REFRESH_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes before expiry
18
+ const CHECK_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes
19
+
20
+ interface UseTokenRefreshOptions {
21
+ /** Enable automatic token refresh (default: true) */
22
+ enabled?: boolean;
23
+ /** Callback when token is refreshed */
24
+ onRefresh?: (newToken: string) => void;
25
+ /** Callback when refresh fails */
26
+ onRefreshError?: (error: Error) => void;
27
+ }
28
+
29
+ /**
30
+ * Decode JWT and get expiry time
31
+ */
32
+ function getTokenExpiry(token: string): number | null {
33
+ try {
34
+ const payload = JSON.parse(atob(token.split('.')[1]));
35
+ return payload.exp * 1000; // Convert to milliseconds
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Check if token is expiring soon
43
+ */
44
+ function isTokenExpiringSoon(token: string, thresholdMs: number): boolean {
45
+ const expiry = getTokenExpiry(token);
46
+ if (!expiry) return false;
47
+
48
+ const timeUntilExpiry = expiry - Date.now();
49
+ return timeUntilExpiry < thresholdMs;
50
+ }
51
+
52
+ /**
53
+ * Hook for automatic token refresh
54
+ */
55
+ export function useTokenRefresh(options: UseTokenRefreshOptions = {}) {
56
+ const { enabled = true, onRefresh, onRefreshError } = options;
57
+ const isRefreshingRef = useRef(false);
58
+
59
+ /**
60
+ * Refresh the token
61
+ */
62
+ const refreshToken = useCallback(async (): Promise<boolean> => {
63
+ if (isRefreshingRef.current) {
64
+ authLogger.debug('Token refresh already in progress');
65
+ return false;
66
+ }
67
+
68
+ const refreshTokenValue = apiAccounts.getRefreshToken();
69
+ if (!refreshTokenValue) {
70
+ authLogger.warn('No refresh token available');
71
+ return false;
72
+ }
73
+
74
+ isRefreshingRef.current = true;
75
+ authLogger.info('Refreshing token...');
76
+
77
+ try {
78
+ // Use generated API client with correct URL (/cfg/accounts/token/refresh/)
79
+ const result = await apiAccounts.auth.accountsTokenRefreshCreate({
80
+ refresh: refreshTokenValue,
81
+ });
82
+
83
+ const newAccessToken = result.access;
84
+
85
+ if (!newAccessToken) {
86
+ throw new Error('No access token in refresh response');
87
+ }
88
+
89
+ apiAccounts.setToken(newAccessToken, refreshTokenValue);
90
+ authLogger.info('Token refreshed successfully');
91
+
92
+ onRefresh?.(newAccessToken);
93
+ return true;
94
+ } catch (error) {
95
+ authLogger.error('Token refresh error:', error);
96
+ onRefreshError?.(error instanceof Error ? error : new Error(String(error)));
97
+ return false;
98
+ } finally {
99
+ isRefreshingRef.current = false;
100
+ }
101
+ }, [onRefresh, onRefreshError]);
102
+
103
+ /**
104
+ * Check and refresh if needed
105
+ */
106
+ const checkAndRefresh = useCallback(async () => {
107
+ const token = apiAccounts.getToken();
108
+ if (!token) return;
109
+
110
+ if (isTokenExpiringSoon(token, TOKEN_REFRESH_THRESHOLD_MS)) {
111
+ authLogger.info('Token expiring soon, refreshing proactively');
112
+ await refreshToken();
113
+ }
114
+ }, [refreshToken]);
115
+
116
+ // Periodic check
117
+ useEffect(() => {
118
+ if (!enabled) return;
119
+
120
+ // Check immediately
121
+ checkAndRefresh();
122
+
123
+ // Set up interval
124
+ const intervalId = setInterval(checkAndRefresh, CHECK_INTERVAL_MS);
125
+
126
+ return () => clearInterval(intervalId);
127
+ }, [enabled, checkAndRefresh]);
128
+
129
+ // Refresh on window focus
130
+ useEffect(() => {
131
+ if (!enabled) return;
132
+
133
+ const handleFocus = () => {
134
+ authLogger.debug('Window focused, checking token...');
135
+ checkAndRefresh();
136
+ };
137
+
138
+ window.addEventListener('focus', handleFocus);
139
+ return () => window.removeEventListener('focus', handleFocus);
140
+ }, [enabled, checkAndRefresh]);
141
+
142
+ // Refresh on network reconnect
143
+ useEffect(() => {
144
+ if (!enabled) return;
145
+
146
+ const handleOnline = () => {
147
+ authLogger.info('Network reconnected, checking token...');
148
+ checkAndRefresh();
149
+ };
150
+
151
+ window.addEventListener('online', handleOnline);
152
+ return () => window.removeEventListener('online', handleOnline);
153
+ }, [enabled, checkAndRefresh]);
154
+
155
+ return {
156
+ refreshToken,
157
+ checkAndRefresh,
158
+ };
159
+ }
160
+
161
+ export default useTokenRefresh;
@@ -1 +1,8 @@
1
- export { proxyMiddleware, proxyMiddlewareConfig } from './proxy';
1
+ export { proxyMiddleware, proxyMiddlewareConfig } from './proxy';
2
+ export {
3
+ refreshAccessToken,
4
+ createAutoRefreshFetch,
5
+ isTokenExpiringSoon,
6
+ refreshIfExpiringSoon,
7
+ isAuthenticationError,
8
+ } from './tokenRefresh';
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Token Auto-Refresh Middleware
3
+ *
4
+ * Automatically refreshes access token when receiving 401 responses.
5
+ * Implements request queuing to prevent multiple simultaneous refresh attempts.
6
+ */
7
+
8
+ import { api as apiAccounts } from '../../';
9
+ import { authLogger } from '../utils/logger';
10
+
11
+ // Refresh state management
12
+ let isRefreshing = false;
13
+ let refreshSubscribers: Array<(token: string | null) => void> = [];
14
+
15
+ /**
16
+ * Subscribe to token refresh completion
17
+ */
18
+ function subscribeTokenRefresh(callback: (token: string | null) => void): void {
19
+ refreshSubscribers.push(callback);
20
+ }
21
+
22
+ /**
23
+ * Notify all subscribers when token is refreshed
24
+ */
25
+ function onTokenRefreshed(token: string | null): void {
26
+ refreshSubscribers.forEach(callback => callback(token));
27
+ refreshSubscribers = [];
28
+ }
29
+
30
+ /**
31
+ * Attempt to refresh the access token using the refresh token
32
+ * @returns New access token or null if refresh failed
33
+ */
34
+ export async function refreshAccessToken(): Promise<string | null> {
35
+ // If already refreshing, wait for completion
36
+ if (isRefreshing) {
37
+ return new Promise(resolve => {
38
+ subscribeTokenRefresh(token => resolve(token));
39
+ });
40
+ }
41
+
42
+ isRefreshing = true;
43
+ authLogger.info('Starting token refresh...');
44
+
45
+ try {
46
+ const refreshToken = apiAccounts.getRefreshToken();
47
+ if (!refreshToken) {
48
+ authLogger.warn('No refresh token available for refresh');
49
+ onTokenRefreshed(null);
50
+ return null;
51
+ }
52
+
53
+ // Use generated API client with correct URL (/cfg/accounts/token/refresh/)
54
+ const result = await apiAccounts.auth.accountsTokenRefreshCreate({
55
+ refresh: refreshToken,
56
+ });
57
+
58
+ const newAccessToken = result.access;
59
+
60
+ if (!newAccessToken) {
61
+ authLogger.error('Token refresh response missing access token');
62
+ onTokenRefreshed(null);
63
+ return null;
64
+ }
65
+
66
+ // Update tokens in storage
67
+ apiAccounts.setToken(newAccessToken, refreshToken);
68
+ authLogger.info('Token refreshed successfully');
69
+
70
+ onTokenRefreshed(newAccessToken);
71
+ return newAccessToken;
72
+ } catch (error) {
73
+ authLogger.error('Token refresh error:', error);
74
+ onTokenRefreshed(null);
75
+ return null;
76
+ } finally {
77
+ isRefreshing = false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Check if a response indicates an authentication error
83
+ */
84
+ export function isAuthenticationError(response: Response): boolean {
85
+ return response.status === 401;
86
+ }
87
+
88
+ /**
89
+ * Create a fetch wrapper that automatically refreshes tokens on 401
90
+ *
91
+ * @param originalFetch - The original fetch function to wrap
92
+ * @returns Wrapped fetch function with auto-refresh capability
93
+ */
94
+ export function createAutoRefreshFetch(originalFetch: typeof fetch): typeof fetch {
95
+ return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
96
+ // Make the initial request
97
+ let response = await originalFetch(input, init);
98
+
99
+ // If 401 and we have a refresh token, try to refresh
100
+ if (isAuthenticationError(response) && apiAccounts.getRefreshToken()) {
101
+ authLogger.info('Received 401, attempting token refresh...');
102
+
103
+ const newToken = await refreshAccessToken();
104
+
105
+ if (newToken) {
106
+ // Retry the request with new token
107
+ const newInit: RequestInit = {
108
+ ...init,
109
+ headers: {
110
+ ...init?.headers,
111
+ Authorization: `Bearer ${newToken}`,
112
+ },
113
+ };
114
+
115
+ authLogger.info('Retrying request with new token...');
116
+ response = await originalFetch(input, newInit);
117
+ } else {
118
+ authLogger.warn('Token refresh failed, returning original 401 response');
119
+ }
120
+ }
121
+
122
+ return response;
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Check if token is about to expire (within threshold)
128
+ * @param thresholdMs - Time in ms before expiry to consider "expiring soon"
129
+ * @returns true if token expires within threshold
130
+ */
131
+ export function isTokenExpiringSoon(thresholdMs: number = 5 * 60 * 1000): boolean {
132
+ const token = apiAccounts.getToken();
133
+ if (!token) return false;
134
+
135
+ try {
136
+ // Decode JWT payload (base64)
137
+ const payload = JSON.parse(atob(token.split('.')[1]));
138
+ const expiresAt = payload.exp * 1000; // Convert to milliseconds
139
+ const now = Date.now();
140
+ const timeUntilExpiry = expiresAt - now;
141
+
142
+ return timeUntilExpiry < thresholdMs;
143
+ } catch (error) {
144
+ authLogger.error('Error checking token expiry:', error);
145
+ return false;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Proactively refresh token if it's expiring soon
151
+ * Call this periodically or before important operations
152
+ */
153
+ export async function refreshIfExpiringSoon(thresholdMs: number = 5 * 60 * 1000): Promise<void> {
154
+ if (isTokenExpiringSoon(thresholdMs)) {
155
+ authLogger.info('Token expiring soon, proactively refreshing...');
156
+ await refreshAccessToken();
157
+ }
158
+ }
@@ -12,7 +12,7 @@ python manage.py generate_client --groups cfg_accounts --typescript
12
12
  |---|---|
13
13
  | Version | 3.0.3 |
14
14
  | Operations | 14 |
15
- | Schemas | 19 |
15
+ | Schemas | 20 |
16
16
 
17
17
  ## Resources
18
18
 
@@ -81,12 +81,5 @@ openapi_client = OpenAPIClientConfig(
81
81
  )
82
82
  ```
83
83
 
84
- **Copy to Next.js** (if `nextjs_admin` configured):
85
- ```python
86
- nextjs_admin = NextJsAdminConfig(
87
- project_path="../frontend/apps/...",
88
- api_output_path="app/_lib/api/generated",
89
- )
90
- ```
91
-
92
84
  @see https://djangocfg.com/docs/features/api-generation
85
+
@@ -31,6 +31,7 @@
31
31
  * ```
32
32
  */
33
33
  import { consola } from 'consola'
34
+ import { CfgAccountsProfileAvatarCreateRequestSchema, type CfgAccountsProfileAvatarCreateRequest } from '../schemas/CfgAccountsProfileAvatarCreateRequest.schema'
34
35
  import { PatchedUserProfileUpdateRequestSchema, type PatchedUserProfileUpdateRequest } from '../schemas/PatchedUserProfileUpdateRequest.schema'
35
36
  import { UserSchema, type User } from '../schemas/User.schema'
36
37
  import { UserProfileUpdateRequestSchema, type UserProfileUpdateRequest } from '../schemas/UserProfileUpdateRequest.schema'
@@ -99,7 +100,7 @@ export async function getAccountsProfileRetrieve( client?: any
99
100
  * @method POST
100
101
  * @path /cfg/accounts/profile/avatar/
101
102
  */
102
- export async function createAccountsProfileAvatarCreate( data: any, client?: any
103
+ export async function createAccountsProfileAvatarCreate( data: CfgAccountsProfileAvatarCreateRequest, client?: any
103
104
  ): Promise<User> {
104
105
  const api = client || getAPIInstance()
105
106
  const response = await api.user_profile.accountsProfileAvatarCreate(data)
@@ -21,6 +21,7 @@ import useSWR from 'swr'
21
21
  import { useSWRConfig } from 'swr'
22
22
  import * as Fetchers from '../fetchers/accounts__user_profile'
23
23
  import type { API } from '../../index'
24
+ import type { CfgAccountsProfileAvatarCreateRequest } from '../schemas/CfgAccountsProfileAvatarCreateRequest.schema'
24
25
  import type { PatchedUserProfileUpdateRequest } from '../schemas/PatchedUserProfileUpdateRequest.schema'
25
26
  import type { User } from '../schemas/User.schema'
26
27
  import type { UserProfileUpdateRequest } from '../schemas/UserProfileUpdateRequest.schema'
@@ -48,7 +49,7 @@ export function useAccountsProfileRetrieve(client?: API): ReturnType<typeof useS
48
49
  export function useCreateAccountsProfileAvatarCreate() {
49
50
  const { mutate } = useSWRConfig()
50
51
 
51
- return async (data: any, client?: API): Promise<User> => {
52
+ return async (data: CfgAccountsProfileAvatarCreateRequest, client?: API): Promise<User> => {
52
53
  const result = await Fetchers.createAccountsProfileAvatarCreate(data, client)
53
54
  // Revalidate related queries
54
55
  mutate('cfg-accounts-profile-avatar')
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Zod schema for CfgAccountsProfileAvatarCreateRequest
3
+ *
4
+ * This schema provides runtime validation and type inference.
5
+ * */
6
+ import { z } from 'zod'
7
+
8
+ export const CfgAccountsProfileAvatarCreateRequestSchema = z.object({
9
+ avatar: z.union([z.instanceof(File), z.instanceof(Blob)]),
10
+ })
11
+
12
+ /**
13
+ * Infer TypeScript type from Zod schema
14
+ */
15
+ export type CfgAccountsProfileAvatarCreateRequest = z.infer<typeof CfgAccountsProfileAvatarCreateRequestSchema>
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  export * from './CentrifugoToken.schema'
21
+ export * from './CfgAccountsProfileAvatarCreateRequest.schema'
21
22
  export * from './OAuthAuthorizeRequestRequest.schema'
22
23
  export * from './OAuthAuthorizeResponse.schema'
23
24
  export * from './OAuthCallbackRequestRequest.schema'
@@ -104,7 +104,6 @@ export interface User {
104
104
  is_superuser: boolean;
105
105
  date_joined: string;
106
106
  last_login?: string | null;
107
- /** Get count of unanswered messages for the user. */
108
107
  unanswered_messages_count: number;
109
108
  centrifugo: CentrifugoToken | null;
110
109
  }
@@ -27,8 +27,10 @@ export class UserProfile {
27
27
  * Upload avatar image for the current authenticated user. Accepts
28
28
  * multipart/form-data with 'avatar' field.
29
29
  */
30
- async accountsProfileAvatarCreate(data: FormData): Promise<Models.User> {
31
- const response = await this.client.request('POST', "/cfg/accounts/profile/avatar/", { formData: data });
30
+ async accountsProfileAvatarCreate(data: Models.CfgAccountsProfileAvatarCreateRequest): Promise<Models.User> {
31
+ const formData = new FormData();
32
+ formData.append('avatar', data.avatar);
33
+ const response = await this.client.request('POST', "/cfg/accounts/profile/avatar/", { formData });
32
34
  return response;
33
35
  }
34
36
 
@@ -25,11 +25,19 @@ export interface User {
25
25
  is_superuser: boolean;
26
26
  date_joined: string;
27
27
  last_login?: string | null;
28
- /** Get count of unanswered messages for the user. */
29
28
  unanswered_messages_count: number;
30
29
  centrifugo: CentrifugoToken | null;
31
30
  }
32
31
 
32
+ /**
33
+ *
34
+ * Request model (no read-only fields).
35
+ */
36
+ export interface CfgAccountsProfileAvatarCreateRequest {
37
+ /** Avatar image file (JPEG, PNG, GIF, WebP, max 5MB) */
38
+ avatar: File | Blob;
39
+ }
40
+
33
41
  /**
34
42
  * Serializer for updating user profile.
35
43
  *
@@ -28,6 +28,7 @@ export class APIClient {
28
28
  private httpClient: HttpClientAdapter;
29
29
  private logger: APILogger | null = null;
30
30
  private retryConfig: RetryConfig | null = null;
31
+ private tokenGetter: (() => string | null) | null = null;
31
32
 
32
33
  // Sub-clients
33
34
  public auth: Auth;
@@ -41,10 +42,12 @@ export class APIClient {
41
42
  httpClient?: HttpClientAdapter;
42
43
  loggerConfig?: Partial<LoggerConfig>;
43
44
  retryConfig?: RetryConfig;
45
+ tokenGetter?: () => string | null;
44
46
  }
45
47
  ) {
46
48
  this.baseUrl = baseUrl.replace(/\/$/, '');
47
49
  this.httpClient = options?.httpClient || new FetchAdapter();
50
+ this.tokenGetter = options?.tokenGetter || null;
48
51
 
49
52
  // Initialize logger if config provided
50
53
  if (options?.loggerConfig !== undefined) {
@@ -78,6 +81,21 @@ export class APIClient {
78
81
  return null;
79
82
  }
80
83
 
84
+ /**
85
+ * Get the base URL for building streaming/download URLs.
86
+ */
87
+ getBaseUrl(): string {
88
+ return this.baseUrl;
89
+ }
90
+
91
+ /**
92
+ * Get JWT token for URL authentication (used in streaming endpoints).
93
+ * Returns null if no token getter is configured or no token is available.
94
+ */
95
+ getToken(): string | null {
96
+ return this.tokenGetter ? this.tokenGetter() : null;
97
+ }
98
+
81
99
  /**
82
100
  * Make HTTP request with Django CSRF and session handling.
83
101
  * Automatically retries on network errors and 5xx server errors.