diff --git a/formulus-formplayer/src/mocks/webview-mock.ts b/formulus-formplayer/src/mocks/webview-mock.ts index 2e5211ecf..6f0d67fb9 100644 --- a/formulus-formplayer/src/mocks/webview-mock.ts +++ b/formulus-formplayer/src/mocks/webview-mock.ts @@ -328,6 +328,12 @@ class WebViewMock { this.showAudioSimulationPopup(fieldId); }); }, + getAttachmentUri: (_fileName: string): Promise => { + console.log( + '[WebView Mock] getAttachmentUri (browser dev: no local files)', + ); + return Promise.resolve(null); + }, launchIntent: ( fieldId: string, intentData: Record, diff --git a/formulus-formplayer/src/renderers/PhotoQuestionRenderer.tsx b/formulus-formplayer/src/renderers/PhotoQuestionRenderer.tsx index 49aa77415..0d586d5b7 100644 --- a/formulus-formplayer/src/renderers/PhotoQuestionRenderer.tsx +++ b/formulus-formplayer/src/renderers/PhotoQuestionRenderer.tsx @@ -77,18 +77,42 @@ const PhotoQuestionRenderer: React.FC = ({ // Get the current photo data from the form data (now JSON format) const currentPhotoData = data || null; - // Set photo URL from stored data if available + // Prefer uri; else resolve basename via bridge (draft → committed → pending upload) useEffect(() => { - console.log('Photo data changed:', currentPhotoData); - if (currentPhotoData?.uri) { - // For WebView, we need to handle file:// URLs differently - // In development/mock mode, file URLs might work, but in production we need a different approach - console.log('Setting photo URL from stored data:', currentPhotoData.uri); - setPhotoUrl(currentPhotoData.uri); - } else { - console.log('No photo URI found, clearing photoUrl state'); - setPhotoUrl(null); - } + let cancelled = false; + const run = async () => { + console.log('Photo data changed:', currentPhotoData); + if (currentPhotoData?.uri) { + const u = currentPhotoData.uri; + const display = + u.startsWith('file://') || u.startsWith('http') ? u : `file://${u}`; + console.log('Setting photo URL from stored data:', display); + if (!cancelled) setPhotoUrl(display); + return; + } + if (currentPhotoData?.filename) { + const resolved = await formulusClient.current.getAttachmentUri( + currentPhotoData.filename, + ); + if (!cancelled) { + setPhotoUrl(resolved); + console.log( + 'Resolved photo from filename:', + currentPhotoData.filename, + resolved, + ); + } + return; + } + if (!cancelled) { + console.log('No photo URI or filename, clearing photoUrl state'); + setPhotoUrl(null); + } + }; + void run(); + return () => { + cancelled = true; + }; }, [currentPhotoData]); // Handle camera request with new Promise-based approach diff --git a/formulus-formplayer/src/services/FormulusInterface.ts b/formulus-formplayer/src/services/FormulusInterface.ts index fef3131c5..1b41d82b2 100644 --- a/formulus-formplayer/src/services/FormulusInterface.ts +++ b/formulus-formplayer/src/services/FormulusInterface.ts @@ -119,6 +119,18 @@ class FormulusClient { } as CameraResult); } + /** + * Resolve an attachment basename to a WebView-loadable URL (draft, committed, or pending upload). + */ + public async getAttachmentUri(fileName: string): Promise { + await this.tryEnsureFormulus(); + if (this.formulus) { + return this.formulus.getAttachmentUri(fileName); + } + console.warn('Formulus interface not available for getAttachmentUri'); + return null; + } + /** * Request location from the Formulus RN app. * The shared interface no longer returns a typed LocationResult; this diff --git a/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts b/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts index 644cc0e39..8bc82be01 100644 --- a/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts +++ b/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts @@ -451,8 +451,9 @@ export interface FormulusInterface { /** * Resolve a synced or camera-saved attachment to a WebView-loadable `file://` URL. - * Checks `{DocumentDirectory}/attachments/` and `pending_upload/`. Pass the basename only - * (e.g. `photo.filename` from observation data); path segments and ".." are rejected. + * Checks `{DocumentDirectory}/attachments/draft/` (unsaved capture), then `attachments/`, + * then `pending_upload/`. Pass the basename only (e.g. `photo.filename` from observation + * data); path segments and ".." are rejected. * @param fileName - Attachment file basename * @returns `file://` URL if the file exists, otherwise `null` */ @@ -494,7 +495,7 @@ export interface FormulusCallbacks { /** * Current version of the interface */ -export const FORMULUS_INTERFACE_VERSION = '1.2.0'; +export const FORMULUS_INTERFACE_VERSION = '1.2.1'; /** Parses major.minor.patch from the start of a version string (ignores prerelease after `-`). */ function semverSegments(version: string): [number, number, number] { diff --git a/formulus/android/app/src/main/assets/webview/FormulusInjectionScript.js b/formulus/android/app/src/main/assets/webview/FormulusInjectionScript.js index 6e0905aa6..f008c2b73 100644 --- a/formulus/android/app/src/main/assets/webview/FormulusInjectionScript.js +++ b/formulus/android/app/src/main/assets/webview/FormulusInjectionScript.js @@ -1,6 +1,6 @@ // Auto-generated from FormulusInterfaceDefinition.ts // Do not edit directly - this file will be overwritten -// Last generated: 2026-01-23T01:14:57.364Z +// Last generated: 2026-04-09T07:22:39.750Z (function () { // Enhanced API availability detection and recovery @@ -108,60 +108,6 @@ document.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage); - // Helper to filter observations using a limited subset of the SQL-like whereClause - // produced by queryHelpers.buildWhereClause (json_extract(data, '$.field') = 'value' AND ...) - function filterObservationsByWhereClause(observations, whereClause) { - if (!whereClause || whereClause === '1=1') { - return observations; - } - - try { - const conditionStrings = whereClause.split(/\s+AND\s+/i); - const conditions = []; - - const regex = - /json_extract\(data,\s*'\$\.(.+?)'\)\s*=\s*'(.*)'/; - - for (const cond of conditionStrings) { - const match = cond.match(regex); - if (match) { - const path = match[1]; - const rawValue = match[2]; - const value = rawValue.replace(/''/g, "'"); - conditions.push({ path, value }); - } - } - - if (conditions.length === 0) { - return observations; - } - - const getNested = (obj, path) => { - const parts = path.split('.'); - let cur = obj; - for (const p of parts) { - if (!cur || typeof cur !== 'object') return undefined; - cur = cur[p]; - } - return cur; - }; - - return observations.filter(obs => - conditions.every(({ path, value }) => { - const actual = getNested(obs.data || {}, path); - return actual !== undefined && String(actual) === String(value); - }), - ); - } catch (e) { - console.warn( - 'filterObservationsByWhereClause: Failed to apply whereClause filter, returning unfiltered observations.', - whereClause, - e, - ); - return observations; - } - } - // Initialize the formulus interface globalThis.formulus = { // getVersion: => Promise @@ -284,7 +230,7 @@ }); }, - // openFormplayer: formType: string, params: Record, savedData: Record => Promise + // openFormplayer: formType: string, params: Record, savedData: Record => Promise openFormplayer: function (formType, params, savedData) { return new Promise((resolve, reject) => { const messageId = @@ -410,36 +356,68 @@ }); }, - // getObservationsByQuery: options: { formType: string; whereClause?: string; isDraft?: boolean; includeDeleted?: boolean } => Promise - // NOTE: This is implemented entirely in the WebView layer by calling getObservations - // and then applying a lightweight filter based on the generated whereClause string. - // This avoids additional native bridge work while still supporting dynamic choice lists. + // getObservationsByQuery: options: { formType: string; isDraft?: boolean; includeDeleted?: boolean; whereClause?: string; } => Promise getObservationsByQuery: function (options) { - try { - const formType = options?.formType; - const whereClause = options?.whereClause || '1=1'; - const isDraft = - typeof options?.isDraft === 'boolean' ? options.isDraft : false; - const includeDeleted = - typeof options?.includeDeleted === 'boolean' - ? options.includeDeleted - : false; - - return globalThis.formulus - .getObservations(formType, isDraft, includeDeleted) - .then(observations => - filterObservationsByWhereClause(observations, whereClause), - ); - } catch (e) { - console.error( - 'getObservationsByQuery: Failed to execute query, returning empty list.', - e, + return new Promise((resolve, reject) => { + const messageId = + 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = event => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object + } else { + // console.warn('getObservationsByQuery callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject( + new Error( + 'getObservationsByQuery callback: Received response with unexpected data type. Raw: ' + + String(event.data), + ), + ); + return; + } + if ( + data.type === 'getObservationsByQuery_response' && + data.messageId === messageId + ) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error( + "'getObservationsByQuery' callback: Error processing response:", + e, + 'Raw event.data:', + event.data, + ); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage( + JSON.stringify({ + type: 'getObservationsByQuery', + messageId, + options: options, + }), ); - return Promise.resolve([]); - } + }); }, - // submitObservation: formType: string, finalData: Record => Promise + // submitObservation: formType: string, finalData: Record => Promise submitObservation: function (formType, finalData) { return new Promise((resolve, reject) => { const messageId = @@ -501,7 +479,7 @@ }); }, - // updateObservation: observationId: string, formType: string, finalData: Record => Promise + // updateObservation: observationId: string, formType: string, finalData: Record => Promise updateObservation: function (observationId, formType, finalData) { return new Promise((resolve, reject) => { const messageId = @@ -747,7 +725,7 @@ }); }, - // launchIntent: fieldId: string, intentSpec: Record => Promise + // launchIntent: fieldId: string, intentSpec: Record => Promise launchIntent: function (fieldId, intentSpec) { return new Promise((resolve, reject) => { const messageId = @@ -809,7 +787,7 @@ }); }, - // callSubform: fieldId: string, formType: string, options: Record => Promise + // callSubform: fieldId: string, formType: string, options: Record => Promise callSubform: function (fieldId, formType, options) { return new Promise((resolve, reject) => { const messageId = @@ -933,8 +911,8 @@ }); }, - // requestSignature: fieldId: string => Promise - requestSignature: function (fieldId) { + // requestQrcode: fieldId: string => Promise + requestQrcode: function (fieldId) { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -949,18 +927,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('requestSignature callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('requestQrcode callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'requestSignature callback: Received response with unexpected data type. Raw: ' + + 'requestQrcode callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'requestSignature_response' && + data.type === 'requestQrcode_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -972,7 +950,7 @@ } } catch (e) { console.error( - "'requestSignature' callback: Error processing response:", + "'requestQrcode' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -986,7 +964,7 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'requestSignature', + type: 'requestQrcode', messageId, fieldId: fieldId, }), @@ -994,8 +972,8 @@ }); }, - // requestQrcode: fieldId: string => Promise - requestQrcode: function (fieldId) { + // requestBiometric: fieldId: string => Promise + requestBiometric: function (fieldId) { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1010,18 +988,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('requestQrcode callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('requestBiometric callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'requestQrcode callback: Received response with unexpected data type. Raw: ' + + 'requestBiometric callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'requestQrcode_response' && + data.type === 'requestBiometric_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1033,7 +1011,7 @@ } } catch (e) { console.error( - "'requestQrcode' callback: Error processing response:", + "'requestBiometric' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1047,7 +1025,7 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'requestQrcode', + type: 'requestBiometric', messageId, fieldId: fieldId, }), @@ -1055,8 +1033,8 @@ }); }, - // requestBiometric: fieldId: string => Promise - requestBiometric: function (fieldId) { + // requestConnectivityStatus: => Promise + requestConnectivityStatus: function () { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1071,18 +1049,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('requestBiometric callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('requestConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'requestBiometric callback: Received response with unexpected data type. Raw: ' + + 'requestConnectivityStatus callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'requestBiometric_response' && + data.type === 'requestConnectivityStatus_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1094,7 +1072,7 @@ } } catch (e) { console.error( - "'requestBiometric' callback: Error processing response:", + "'requestConnectivityStatus' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1108,16 +1086,138 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'requestBiometric', + type: 'requestConnectivityStatus', + messageId, + }), + ); + }); + }, + + // requestSyncStatus: => Promise + requestSyncStatus: function () { + return new Promise((resolve, reject) => { + const messageId = + 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = event => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object + } else { + // console.warn('requestSyncStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject( + new Error( + 'requestSyncStatus callback: Received response with unexpected data type. Raw: ' + + String(event.data), + ), + ); + return; + } + if ( + data.type === 'requestSyncStatus_response' && + data.messageId === messageId + ) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error( + "'requestSyncStatus' callback: Error processing response:", + e, + 'Raw event.data:', + event.data, + ); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage( + JSON.stringify({ + type: 'requestSyncStatus', + messageId, + }), + ); + }); + }, + + // runLocalModel: fieldId: string, modelId: string, input: Record => Promise + runLocalModel: function (fieldId, modelId, input) { + return new Promise((resolve, reject) => { + const messageId = + 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = event => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object + } else { + // console.warn('runLocalModel callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject( + new Error( + 'runLocalModel callback: Received response with unexpected data type. Raw: ' + + String(event.data), + ), + ); + return; + } + if ( + data.type === 'runLocalModel_response' && + data.messageId === messageId + ) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error( + "'runLocalModel' callback: Error processing response:", + e, + 'Raw event.data:', + event.data, + ); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage( + JSON.stringify({ + type: 'runLocalModel', messageId, fieldId: fieldId, + modelId: modelId, + input: input, }), ); }); }, - // requestConnectivityStatus: => Promise - requestConnectivityStatus: function () { + // getCurrentUser: => Promise<{ username: string; displayName?: string; role?: "read-only" | "read-write" | "admin"; }> + getCurrentUser: function () { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1132,18 +1232,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('requestConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('getCurrentUser callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'requestConnectivityStatus callback: Received response with unexpected data type. Raw: ' + + 'getCurrentUser callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'requestConnectivityStatus_response' && + data.type === 'getCurrentUser_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1155,7 +1255,7 @@ } } catch (e) { console.error( - "'requestConnectivityStatus' callback: Error processing response:", + "'getCurrentUser' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1169,15 +1269,15 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'requestConnectivityStatus', + type: 'getCurrentUser', messageId, }), ); }); }, - // requestSyncStatus: => Promise - requestSyncStatus: function () { + // getThemeMode: => Promise<"light" | "dark" | "system"> + getThemeMode: function () { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1192,18 +1292,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('requestSyncStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('getThemeMode callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'requestSyncStatus callback: Received response with unexpected data type. Raw: ' + + 'getThemeMode callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'requestSyncStatus_response' && + data.type === 'getThemeMode_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1215,7 +1315,7 @@ } } catch (e) { console.error( - "'requestSyncStatus' callback: Error processing response:", + "'getThemeMode' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1229,15 +1329,15 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'requestSyncStatus', + type: 'getThemeMode', messageId, }), ); }); }, - // runLocalModel: fieldId: string, modelId: string, input: Record => Promise - runLocalModel: function (fieldId, modelId, input) { + // getAttachmentUri: fileName: string => Promise + getAttachmentUri: function (fileName) { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1252,18 +1352,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('runLocalModel callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('getAttachmentUri callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'runLocalModel callback: Received response with unexpected data type. Raw: ' + + 'getAttachmentUri callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'runLocalModel_response' && + data.type === 'getAttachmentUri_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1275,7 +1375,7 @@ } } catch (e) { console.error( - "'runLocalModel' callback: Error processing response:", + "'getAttachmentUri' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1289,18 +1389,16 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'runLocalModel', + type: 'getAttachmentUri', messageId, - fieldId: fieldId, - modelId: modelId, - input: input, + fileName: fileName, }), ); }); }, - // getCurrentUser: => Promise<{ username: string; displayName?: string; }> - getCurrentUser: function () { + // getAttachmentsUri: => Promise + getAttachmentsUri: function () { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); @@ -1315,18 +1413,18 @@ } else if (typeof event.data === 'object' && event.data !== null) { data = event.data; // Already an object } else { - // console.warn('getCurrentUser callback: Received response with unexpected data type:', typeof event.data, event.data); + // console.warn('getAttachmentsUri callback: Received response with unexpected data type:', typeof event.data, event.data); window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'getCurrentUser callback: Received response with unexpected data type. Raw: ' + + 'getAttachmentsUri callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'getCurrentUser_response' && + data.type === 'getAttachmentsUri_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1338,7 +1436,7 @@ } } catch (e) { console.error( - "'getCurrentUser' callback: Error processing response:", + "'getAttachmentsUri' callback: Error processing response:", e, 'Raw event.data:', event.data, @@ -1352,38 +1450,101 @@ // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'getCurrentUser', + type: 'getAttachmentsUri', messageId, }), ); }); }, - // getThemeMode: () => Promise<'light' | 'dark' | 'system'> - getThemeMode: function () { + // getCustomAppUri: => Promise + getCustomAppUri: function () { return new Promise((resolve, reject) => { const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + // Add response handler for methods that return values + const callback = event => { try { let data; if (typeof event.data === 'string') { data = JSON.parse(event.data); } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; + data = event.data; // Already an object } else { + // console.warn('getCustomAppUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject( + new Error( + 'getCustomAppUri callback: Received response with unexpected data type. Raw: ' + + String(event.data), + ), + ); + return; + } + if ( + data.type === 'getCustomAppUri_response' && + data.messageId === messageId + ) { window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error( + "'getCustomAppUri' callback: Error processing response:", + e, + 'Raw event.data:', + event.data, + ); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage( + JSON.stringify({ + type: 'getCustomAppUri', + messageId, + }), + ); + }); + }, + + // getFormSpecsUri: => Promise + getFormSpecsUri: function () { + return new Promise((resolve, reject) => { + const messageId = + 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = event => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object + } else { + // console.warn('getFormSpecsUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener reject( new Error( - 'getThemeMode callback: Received response with unexpected data type. Raw: ' + + 'getFormSpecsUri callback: Received response with unexpected data type. Raw: ' + String(event.data), ), ); return; } if ( - data.type === 'getThemeMode_response' && + data.type === 'getFormSpecsUri_response' && data.messageId === messageId ) { window.removeEventListener('message', callback); @@ -1395,20 +1556,21 @@ } } catch (e) { console.error( - "'getThemeMode' callback: Error processing response:", + "'getFormSpecsUri' callback: Error processing response:", e, 'Raw event.data:', event.data, ); - window.removeEventListener('message', callback); + window.removeEventListener('message', callback); // Ensure listener is removed on error too reject(e); } }; window.addEventListener('message', callback); + // Send the message to React Native globalThis.ReactNativeWebView.postMessage( JSON.stringify({ - type: 'getThemeMode', + type: 'getFormSpecsUri', messageId, }), ); diff --git a/formulus/android/app/src/main/assets/webview/formulus-api.js b/formulus/android/app/src/main/assets/webview/formulus-api.js index a94129a9f..df5e796f7 100644 --- a/formulus/android/app/src/main/assets/webview/formulus-api.js +++ b/formulus/android/app/src/main/assets/webview/formulus-api.js @@ -5,7 +5,7 @@ * that's available in the WebView context as `globalThis.formulus`. * * This file is auto-generated from FormulusInterfaceDefinition.ts - * Last generated: 2026-01-23T01:15:01.676Z + * Last generated: 2026-04-09T07:22:42.291Z * * @example * // In your JavaScript file: @@ -60,6 +60,15 @@ const FormulusAPI = { */ getObservations: function (formType, isDraft, includeDeleted) {}, + /** + * Get observations with optional WHERE clause filtering (for dynamic choice lists). + * Supports format: data.field = 'value' AND data.other = 'value' + * Age filtering via age_from_dob(data.dob) is handled client-side in formplayer. + * / + * @returns {Promise} Array of filtered observations + */ + getObservationsByQuery: function (options) {}, + /** * Submit a completed form * / @@ -130,14 +139,6 @@ const FormulusAPI = { */ requestAudio: function (fieldId) {}, - /** - * Request signature for a field - * / - * @param {string} fieldId - The ID of the field - * @returns {Promise} Promise that resolves with signature result or rejects on error/cancellation - */ - requestSignature: function (fieldId) {}, - /** * Request QR code scanning for a field * / @@ -179,17 +180,50 @@ const FormulusAPI = { runLocalModel: function (fieldId, modelId, input) {}, /** - * Get information about the currently authenticated user + * Get information about the currently authenticated user. + * When no one is logged in, resolves with `{ username: '' }` (does not reject). * / - * @returns {Promise<{username: string, displayName?: string} + * @returns {Promise<{username: string, displayName?: string, role?: 'read-only' | 'read-write' | 'admin'} */ getCurrentUser: function () {}, /** * Get the current theme mode (System / Light / Dark) so custom apps can match the host app. + * / * @returns {Promise<'light' | 'dark' | 'system'>} Current theme mode; 'system' means follow device preference. */ getThemeMode: function () {}, + + /** + * Resolve a synced or camera-saved attachment to a WebView-loadable `file://` URL. + * Checks `{DocumentDirectory}/attachments/` and `pending_upload/`. Pass the basename only + * (e.g. `photo.filename` from observation data); path segments and ".." are rejected. + * / + * @returns {Promise} `file://` URL if the file exists, otherwise `null` + */ + getAttachmentUri: function (fileName) {}, + + /** + * Base `file://` URL for the attachments directory (trailing slash). + * / + * @returns {Promise} e.g. `file:///.../attachments/` + */ + getAttachmentsUri: function () {}, + + /** + * Base `file://` URL for the custom app bundle root (`DocumentDirectory/app/`, trailing slash). + * / + * @returns {Promise} App directory URL for extensions, question_types, etc. + */ + getCustomAppUri: function () {}, + + /** + * Primary `file://` URL for downloaded form specs (`DocumentDirectory/forms/`, trailing slash). + * Some bundles also use files under the custom app `forms/` subdirectory. + * / + * @returns {Promise} Forms directory URL + */ + getFormSpecsUri: function () {}, }; // Make the API available globally in browser environments diff --git a/formulus/formplayer_question_types.md b/formulus/formplayer_question_types.md index 7a02aa5af..228beddae 100644 --- a/formulus/formplayer_question_types.md +++ b/formulus/formplayer_question_types.md @@ -104,7 +104,6 @@ Allows users to scan QR codes or enter QR code data manually. - **QR Code** - Quick Response codes - **Data Structure:** The QR code field stores a simple string value: diff --git a/formulus/src/api/synkronus/generated/.openapi-generator/FILES b/formulus/src/api/synkronus/generated/.openapi-generator/FILES index 695cae5a3..03184907e 100644 --- a/formulus/src/api/synkronus/generated/.openapi-generator/FILES +++ b/formulus/src/api/synkronus/generated/.openapi-generator/FILES @@ -1,60 +1,62 @@ -.gitignore -.npmignore -api.ts -base.ts -common.ts -configuration.ts -docs/AppBundleChangeLog.md -docs/AppBundleFile.md -docs/AppBundleManifest.md -docs/AppBundlePushResponse.md -docs/AppBundleVersions.md -docs/AttachmentManifestRequest.md -docs/AttachmentManifestResponse.md -docs/AttachmentManifestResponseOperationCount.md -docs/AttachmentOperation.md -docs/AttachmentsApi.md -docs/AuthResponse.md -docs/BuildInfo.md -docs/ChangeLog.md -docs/ChangePassword200Response.md -docs/ChangePasswordRequest.md -docs/CreateUserRequest.md -docs/DataExportApi.md -docs/DatabaseInfo.md -docs/DefaultApi.md -docs/DeleteUser200Response.md -docs/ErrorResponse.md -docs/FieldChange.md -docs/FormDiff.md -docs/FormModification.md -docs/GetHealth200Response.md -docs/GetHealth503Response.md -docs/HealthApi.md -docs/LoginRequest.md -docs/Observation.md -docs/ObservationGeolocation.md -docs/ProblemDetail.md -docs/ProblemDetailErrorsInner.md -docs/RefreshTokenRequest.md -docs/RepositoryResetRequest.md -docs/RepositoryResetResponse.md -docs/ResetUserPassword200Response.md -docs/ResetUserPasswordRequest.md -docs/ServerInfo.md -docs/SwitchAppBundleVersion200Response.md -docs/SyncPullRequest.md -docs/SyncPullRequestSince.md -docs/SyncPullResponse.md -docs/SyncPushRequest.md -docs/SyncPushResponse.md -docs/SyncPushResponseWarningsInner.md -docs/SystemInfo.md -docs/SystemVersionInfo.md -docs/UploadAttachment200Response.md -docs/UserListItem.md -docs/UserPresenceClient.md -docs/UserPresenceSummary.md -docs/UserResponse.md -git_push.sh -index.ts +.gitignore +.npmignore +api.ts +base.ts +common.ts +configuration.ts +docs/APIVersionInfo.md +docs/APIVersionsResponse.md +docs/AppBundleChangeLog.md +docs/AppBundleFile.md +docs/AppBundleManifest.md +docs/AppBundlePushResponse.md +docs/AppBundleVersions.md +docs/AttachmentManifestRequest.md +docs/AttachmentManifestResponse.md +docs/AttachmentManifestResponseOperationCount.md +docs/AttachmentOperation.md +docs/AttachmentsApi.md +docs/AuthResponse.md +docs/BuildInfo.md +docs/ChangeLog.md +docs/ChangePassword200Response.md +docs/ChangePasswordRequest.md +docs/CreateUserRequest.md +docs/DataExportApi.md +docs/DatabaseInfo.md +docs/DefaultApi.md +docs/DeleteUser200Response.md +docs/ErrorResponse.md +docs/FieldChange.md +docs/FormDiff.md +docs/FormModification.md +docs/GetHealth200Response.md +docs/GetHealth503Response.md +docs/HealthApi.md +docs/LoginRequest.md +docs/Observation.md +docs/ObservationGeolocation.md +docs/ProblemDetail.md +docs/ProblemDetailErrorsInner.md +docs/RefreshTokenRequest.md +docs/RepositoryResetRequest.md +docs/RepositoryResetResponse.md +docs/ResetUserPassword200Response.md +docs/ResetUserPasswordRequest.md +docs/ServerInfo.md +docs/SwitchAppBundleVersion200Response.md +docs/SyncPullRequest.md +docs/SyncPullRequestSince.md +docs/SyncPullResponse.md +docs/SyncPushRequest.md +docs/SyncPushResponse.md +docs/SyncPushResponseWarningsInner.md +docs/SystemInfo.md +docs/SystemVersionInfo.md +docs/UploadAttachment200Response.md +docs/UserListItem.md +docs/UserPresenceClient.md +docs/UserPresenceSummary.md +docs/UserResponse.md +git_push.sh +index.ts diff --git a/formulus/src/api/synkronus/generated/api.ts b/formulus/src/api/synkronus/generated/api.ts index 6b950976d..fb84d0d88 100644 --- a/formulus/src/api/synkronus/generated/api.ts +++ b/formulus/src/api/synkronus/generated/api.ts @@ -39,6 +39,50 @@ import { operationServerMap, } from './base'; +/** + * + * @export + * @interface APIVersionInfo + */ +export interface APIVersionInfo { + /** + * + * @type {string} + * @memberof APIVersionInfo + */ + version: string; + /** + * + * @type {string} + * @memberof APIVersionInfo + */ + releaseDate: string; + /** + * + * @type {boolean} + * @memberof APIVersionInfo + */ + deprecated: boolean; +} +/** + * + * @export + * @interface APIVersionsResponse + */ +export interface APIVersionsResponse { + /** + * + * @type {Array} + * @memberof APIVersionsResponse + */ + versions: Array; + /** + * Identifier of the current API contract version + * @type {string} + * @memberof APIVersionsResponse + */ + current: string; +} /** * * @export @@ -1018,7 +1062,7 @@ export interface SyncPullRequest { */ client_id: string; /** - * Optional body copy of epoch; header x-repository-generation wins when both are sent. + * Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. * @type {number} * @memberof SyncPullRequest */ @@ -1421,12 +1465,16 @@ export const AttachmentsApiAxiosParamCreator = function ( /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getAttachmentsExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getAttachmentsExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/export-zip`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -1447,6 +1495,9 @@ export const AttachmentsApiAxiosParamCreator = function ( // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration); + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1475,16 +1526,21 @@ export const AttachmentsApiFp = function (configuration?: Configuration) { /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getAttachmentsExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getAttachmentsExportZip(options); + await localVarAxiosParamCreator.getAttachmentsExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['AttachmentsApi.getAttachmentsExportZip']?.[ @@ -1515,19 +1571,35 @@ export const AttachmentsApiFactory = function ( /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {AttachmentsApiGetAttachmentsExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ getAttachmentsExportZip( + requestParameters: AttachmentsApiGetAttachmentsExportZipRequest, options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .getAttachmentsExportZip(options) + .getAttachmentsExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, }; }; +/** + * Request parameters for getAttachmentsExportZip operation in AttachmentsApi. + * @export + * @interface AttachmentsApiGetAttachmentsExportZipRequest + */ +export interface AttachmentsApiGetAttachmentsExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof AttachmentsApiGetAttachmentsExportZip + */ + readonly xOdeVersion: string; +} + /** * AttachmentsApi - object-oriented interface * @export @@ -1538,13 +1610,17 @@ export class AttachmentsApi extends BaseAPI { /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {AttachmentsApiGetAttachmentsExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AttachmentsApi */ - public getAttachmentsExportZip(options?: RawAxiosRequestConfig) { + public getAttachmentsExportZip( + requestParameters: AttachmentsApiGetAttachmentsExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return AttachmentsApiFp(this.configuration) - .getAttachmentsExportZip(options) + .getAttachmentsExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } } @@ -1560,12 +1636,16 @@ export const DataExportApiAxiosParamCreator = function ( /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getParquetExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getParquetExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/dataexport/parquet`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -1586,6 +1666,9 @@ export const DataExportApiAxiosParamCreator = function ( // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration); + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1603,12 +1686,16 @@ export const DataExportApiAxiosParamCreator = function ( /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getRawJsonExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getRawJsonExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/dataexport/raw-json`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -1629,6 +1716,9 @@ export const DataExportApiAxiosParamCreator = function ( // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration); + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1657,16 +1747,21 @@ export const DataExportApiFp = function (configuration?: Configuration) { /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getParquetExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getParquetExportZip(options); + await localVarAxiosParamCreator.getParquetExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DataExportApi.getParquetExportZip']?.[ @@ -1683,16 +1778,21 @@ export const DataExportApiFp = function (configuration?: Configuration) { /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getRawJsonExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getRawJsonExportZip(options); + await localVarAxiosParamCreator.getRawJsonExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DataExportApi.getRawJsonExportZip']?.[ @@ -1723,28 +1823,64 @@ export const DataExportApiFactory = function ( /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {DataExportApiGetParquetExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getParquetExportZip(options?: RawAxiosRequestConfig): AxiosPromise { + getParquetExportZip( + requestParameters: DataExportApiGetParquetExportZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { return localVarFp - .getParquetExportZip(options) + .getParquetExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {DataExportApiGetRawJsonExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getRawJsonExportZip(options?: RawAxiosRequestConfig): AxiosPromise { + getRawJsonExportZip( + requestParameters: DataExportApiGetRawJsonExportZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { return localVarFp - .getRawJsonExportZip(options) + .getRawJsonExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, }; }; +/** + * Request parameters for getParquetExportZip operation in DataExportApi. + * @export + * @interface DataExportApiGetParquetExportZipRequest + */ +export interface DataExportApiGetParquetExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DataExportApiGetParquetExportZip + */ + readonly xOdeVersion: string; +} + +/** + * Request parameters for getRawJsonExportZip operation in DataExportApi. + * @export + * @interface DataExportApiGetRawJsonExportZipRequest + */ +export interface DataExportApiGetRawJsonExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DataExportApiGetRawJsonExportZip + */ + readonly xOdeVersion: string; +} + /** * DataExportApi - object-oriented interface * @export @@ -1755,26 +1891,34 @@ export class DataExportApi extends BaseAPI { /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {DataExportApiGetParquetExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DataExportApi */ - public getParquetExportZip(options?: RawAxiosRequestConfig) { + public getParquetExportZip( + requestParameters: DataExportApiGetParquetExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return DataExportApiFp(this.configuration) - .getParquetExportZip(options) + .getParquetExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {DataExportApiGetRawJsonExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DataExportApi */ - public getRawJsonExportZip(options?: RawAxiosRequestConfig) { + public getRawJsonExportZip( + requestParameters: DataExportApiGetRawJsonExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return DataExportApiFp(this.configuration) - .getRawJsonExportZip(options) + .getRawJsonExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } } @@ -1790,14 +1934,18 @@ export const DefaultApiAxiosParamCreator = function ( /** * Destructive operation: deletes all observations and attachment manifest rows, resets the observation stream cursor, increments repository_generation, and clears attachment files on disk. App bundles are not removed. Requires body `{ \"confirm\": \"RESET_REPOSITORY\" }`. * @summary Irreversibly wipe server observation and attachment sync data (admin only) + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RepositoryResetRequest} repositoryResetRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ adminRepositoryReset: async ( + xOdeVersion: string, repositoryResetRequest: RepositoryResetRequest, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('adminRepositoryReset', 'xOdeVersion', xOdeVersion); // verify required parameter 'repositoryResetRequest' is not null or undefined assertParamExists( 'adminRepositoryReset', @@ -1826,6 +1974,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarHeaderParameter['Content-Type'] = 'application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1848,7 +1999,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Change password for the currently authenticated user * @summary Change user password (authenticated user)\'s password - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ChangePasswordRequest} changePasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1914,17 +2065,21 @@ export const DefaultApiAxiosParamCreator = function ( * Checks whether the attachment is available for download. If `original=true` (or `1` / `yes`), existence is checked against the original file first, with fallback to the processed file. * @summary Check if an attachment exists * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ checkAttachmentExists: async ( attachmentId: string, + xOdeVersion: string, original?: string, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('checkAttachmentExists', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('checkAttachmentExists', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/{attachment_id}`.replace( `{${'attachment_id'}}`, encodeURIComponent(String(attachmentId)), @@ -1952,6 +2107,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarQueryParameter['original'] = original; } + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1969,7 +2127,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Create a new user with specified username, password, and role * @summary Create a new user (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {CreateUserRequest} createUserRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2031,7 +2189,7 @@ export const DefaultApiAxiosParamCreator = function ( * Delete a user by username * @summary Delete a user (admin only) * @param {string} username Username of the user to delete - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2088,7 +2246,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Download a specific file from the app bundle * @param {string} path - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {boolean} [preview] If true, returns the file from the latest version including unreleased changes * @param {string} [ifNoneMatch] * @param {*} [options] Override http request option. @@ -2152,21 +2310,75 @@ export const DefaultApiAxiosParamCreator = function ( options: localVarRequestOptions, }; }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + downloadAppBundleZip: async ( + xOdeVersion: string, + options: RawAxiosRequestConfig = {}, + ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('downloadAppBundleZip', 'xOdeVersion', xOdeVersion); + const localVarPath = `/api/app-bundle/download-zip`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: 'GET', + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ downloadAttachment: async ( attachmentId: string, + xOdeVersion: string, original?: string, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('downloadAttachment', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('downloadAttachment', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/{attachment_id}`.replace( `{${'attachment_id'}}`, encodeURIComponent(String(attachmentId)), @@ -2194,6 +2406,59 @@ export const DefaultApiAxiosParamCreator = function ( localVarQueryParameter['original'] = original; } + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAPIVersions: async ( + xOdeVersion: string, + options: RawAxiosRequestConfig = {}, + ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getAPIVersions', 'xOdeVersion', xOdeVersion); + const localVarPath = `/api/versions`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: 'GET', + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -2211,7 +2476,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [current] The current version (defaults to latest) * @param {string} [target] The target version to compare against (defaults to previous version) * @param {*} [options] Override http request option. @@ -2273,7 +2538,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Get the current custom app bundle manifest - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id for correlating app bundle checks with presence. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2328,7 +2593,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Get a list of available app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2378,7 +2643,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Returns a manifest of attachment changes (new, updated, deleted) since a specified data version * @summary Get attachment manifest for incremental sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {AttachmentManifestRequest} attachmentManifestRequest * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -2451,12 +2716,16 @@ export const DefaultApiAxiosParamCreator = function ( /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getVersion: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getVersion', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/version`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -2473,6 +2742,13 @@ export const DefaultApiAxiosParamCreator = function ( const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -2490,7 +2766,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Retrieve a list of all users in the system. Admin access required. Each item may include optional `presence` (last-seen per client, bundle/Ode hints) when the server has recorded activity. * @summary List all users (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2545,7 +2821,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Obtain a JWT token by providing username and password * @summary Authenticate user and return JWT tokens - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {LoginRequest} loginRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2602,7 +2878,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Upload a new app bundle (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} [bundle] ZIP file containing the new app bundle * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2665,7 +2941,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Obtain a new JWT token using a refresh token * @summary Refresh JWT token - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RefreshTokenRequest} refreshTokenRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2726,7 +3002,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Reset password for a specified user * @summary Reset user password (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ResetUserPasswordRequest} resetUserPasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2792,7 +3068,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Switch to a specific app bundle version (admin only) * @param {string} version Version identifier to switch to - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2848,12 +3124,12 @@ export const DefaultApiAxiosParamCreator = function ( /** * Retrieves records that have changed since a specified version. **Pagination Pattern:** 1. Send initial request with `since.version` (or omit for all records) 2. Process returned records 3. If `has_more` is true, make next request using `change_cutoff` as the new `since.version` 4. Repeat until `has_more` is false Example pagination flow: - Request 1: `since: {version: 100}` → Response: `change_cutoff: 150, has_more: true` - Request 2: `since: {version: 150}` → Response: `change_cutoff: 200, has_more: false` * @summary Pull updated records since last sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPullRequest} syncPullRequest * @param {string} [schemaType] Filter by schemaType * @param {number} [limit] Maximum number of records to return * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. - * @param {number} [xRepositoryGeneration] Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2934,7 +3210,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Push new or updated records to the server - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPushRequest} syncPushRequest * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. @@ -3009,6 +3285,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Upload a new attachment with specified ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} file The binary file to upload * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -3016,12 +3293,15 @@ export const DefaultApiAxiosParamCreator = function ( */ uploadAttachment: async ( attachmentId: string, + xOdeVersion: string, file: File, xRepositoryGeneration?: number, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('uploadAttachment', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('uploadAttachment', 'xOdeVersion', xOdeVersion); // verify required parameter 'file' is not null or undefined assertParamExists('uploadAttachment', 'file', file); const localVarPath = `/api/attachments/{attachment_id}`.replace( @@ -3057,6 +3337,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } if (xRepositoryGeneration != null) { localVarHeaderParameter['x-repository-generation'] = typeof xRepositoryGeneration === 'string' @@ -3091,11 +3374,13 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Destructive operation: deletes all observations and attachment manifest rows, resets the observation stream cursor, increments repository_generation, and clears attachment files on disk. App bundles are not removed. Requires body `{ \"confirm\": \"RESET_REPOSITORY\" }`. * @summary Irreversibly wipe server observation and attachment sync data (admin only) + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RepositoryResetRequest} repositoryResetRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ async adminRepositoryReset( + xOdeVersion: string, repositoryResetRequest: RepositoryResetRequest, options?: RawAxiosRequestConfig, ): Promise< @@ -3106,6 +3391,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { > { const localVarAxiosArgs = await localVarAxiosParamCreator.adminRepositoryReset( + xOdeVersion, repositoryResetRequest, options, ); @@ -3125,7 +3411,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Change password for the currently authenticated user * @summary Change user password (authenticated user)\'s password - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ChangePasswordRequest} changePasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3162,12 +3448,14 @@ export const DefaultApiFp = function (configuration?: Configuration) { * Checks whether the attachment is available for download. If `original=true` (or `1` / `yes`), existence is checked against the original file first, with fallback to the processed file. * @summary Check if an attachment exists * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ async checkAttachmentExists( attachmentId: string, + xOdeVersion: string, original?: string, options?: RawAxiosRequestConfig, ): Promise< @@ -3176,6 +3464,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.checkAttachmentExists( attachmentId, + xOdeVersion, original, options, ); @@ -3195,7 +3484,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Create a new user with specified username, password, and role * @summary Create a new user (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {CreateUserRequest} createUserRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3229,7 +3518,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * Delete a user by username * @summary Delete a user (admin only) * @param {string} username Username of the user to delete - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -3265,7 +3554,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Download a specific file from the app bundle * @param {string} path - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {boolean} [preview] If true, returns the file from the latest version including unreleased changes * @param {string} [ifNoneMatch] * @param {*} [options] Override http request option. @@ -3301,16 +3590,49 @@ export const DefaultApiFp = function (configuration?: Configuration) { configuration, )(axios, localVarOperationServerBasePath || basePath); }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async downloadAppBundleZip( + xOdeVersion: string, + options?: RawAxiosRequestConfig, + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = + await localVarAxiosParamCreator.downloadAppBundleZip( + xOdeVersion, + options, + ); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = + operationServerMap['DefaultApi.downloadAppBundleZip']?.[ + localVarOperationServerIndex + ]?.url; + return (axios, basePath) => + createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration, + )(axios, localVarOperationServerBasePath || basePath); + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ async downloadAttachment( attachmentId: string, + xOdeVersion: string, original?: string, options?: RawAxiosRequestConfig, ): Promise< @@ -3319,6 +3641,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.downloadAttachment( attachmentId, + xOdeVersion, original, options, ); @@ -3335,10 +3658,43 @@ export const DefaultApiFp = function (configuration?: Configuration) { configuration, )(axios, localVarOperationServerBasePath || basePath); }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAPIVersions( + xOdeVersion: string, + options?: RawAxiosRequestConfig, + ): Promise< + ( + axios?: AxiosInstance, + basePath?: string, + ) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAPIVersions( + xOdeVersion, + options, + ); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = + operationServerMap['DefaultApi.getAPIVersions']?.[ + localVarOperationServerIndex + ]?.url; + return (axios, basePath) => + createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration, + )(axios, localVarOperationServerBasePath || basePath); + }, /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [current] The current version (defaults to latest) * @param {string} [target] The target version to compare against (defaults to previous version) * @param {*} [options] Override http request option. @@ -3375,7 +3731,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Get the current custom app bundle manifest - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id for correlating app bundle checks with presence. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3412,7 +3768,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Get a list of available app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -3446,7 +3802,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Returns a manifest of attachment changes (new, updated, deleted) since a specified data version * @summary Get attachment manifest for incremental sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {AttachmentManifestRequest} attachmentManifestRequest * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -3486,10 +3842,12 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getVersion( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< ( @@ -3497,8 +3855,10 @@ export const DefaultApiFp = function (configuration?: Configuration) { basePath?: string, ) => AxiosPromise > { - const localVarAxiosArgs = - await localVarAxiosParamCreator.getVersion(options); + const localVarAxiosArgs = await localVarAxiosParamCreator.getVersion( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DefaultApi.getVersion']?.[ @@ -3515,7 +3875,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Retrieve a list of all users in the system. Admin access required. Each item may include optional `presence` (last-seen per client, bundle/Ode hints) when the server has recorded activity. * @summary List all users (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3551,7 +3911,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Obtain a JWT token by providing username and password * @summary Authenticate user and return JWT tokens - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {LoginRequest} loginRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3583,7 +3943,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Upload a new app bundle (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} [bundle] ZIP file containing the new app bundle * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3619,7 +3979,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Obtain a new JWT token using a refresh token * @summary Refresh JWT token - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RefreshTokenRequest} refreshTokenRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3652,7 +4012,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Reset password for a specified user * @summary Reset user password (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ResetUserPasswordRequest} resetUserPasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -3690,7 +4050,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Switch to a specific app bundle version (admin only) * @param {string} version Version identifier to switch to - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -3726,12 +4086,12 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Retrieves records that have changed since a specified version. **Pagination Pattern:** 1. Send initial request with `since.version` (or omit for all records) 2. Process returned records 3. If `has_more` is true, make next request using `change_cutoff` as the new `since.version` 4. Repeat until `has_more` is false Example pagination flow: - Request 1: `since: {version: 100}` → Response: `change_cutoff: 150, has_more: true` - Request 2: `since: {version: 150}` → Response: `change_cutoff: 200, has_more: false` * @summary Pull updated records since last sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPullRequest} syncPullRequest * @param {string} [schemaType] Filter by schemaType * @param {number} [limit] Maximum number of records to return * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. - * @param {number} [xRepositoryGeneration] Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -3774,7 +4134,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Push new or updated records to the server - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPushRequest} syncPushRequest * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. @@ -3817,6 +4177,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Upload a new attachment with specified ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} file The binary file to upload * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -3824,6 +4185,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { */ async uploadAttachment( attachmentId: string, + xOdeVersion: string, file: File, xRepositoryGeneration?: number, options?: RawAxiosRequestConfig, @@ -3836,6 +4198,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.uploadAttachment( attachmentId, + xOdeVersion, file, xRepositoryGeneration, options, @@ -3879,7 +4242,11 @@ export const DefaultApiFactory = function ( options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .adminRepositoryReset(requestParameters.repositoryResetRequest, options) + .adminRepositoryReset( + requestParameters.xOdeVersion, + requestParameters.repositoryResetRequest, + options, + ) .then(request => request(axios, basePath)); }, /** @@ -3915,6 +4282,7 @@ export const DefaultApiFactory = function ( return localVarFp .checkAttachmentExists( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) @@ -3979,6 +4347,21 @@ export const DefaultApiFactory = function ( ) .then(request => request(axios, basePath)); }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {DefaultApiDownloadAppBundleZipRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + downloadAppBundleZip( + requestParameters: DefaultApiDownloadAppBundleZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { + return localVarFp + .downloadAppBundleZip(requestParameters.xOdeVersion, options) + .then(request => request(axios, basePath)); + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID @@ -3993,11 +4376,27 @@ export const DefaultApiFactory = function ( return localVarFp .downloadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) .then(request => request(axios, basePath)); }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {DefaultApiGetAPIVersionsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAPIVersions( + requestParameters: DefaultApiGetAPIVersionsRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { + return localVarFp + .getAPIVersions(requestParameters.xOdeVersion, options) + .then(request => request(axios, basePath)); + }, /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions @@ -4075,14 +4474,16 @@ export const DefaultApiFactory = function ( /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {DefaultApiGetVersionRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ getVersion( + requestParameters: DefaultApiGetVersionRequest, options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .getVersion(options) + .getVersion(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, /** @@ -4257,6 +4658,7 @@ export const DefaultApiFactory = function ( return localVarFp .uploadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.file, requestParameters.xRepositoryGeneration, options, @@ -4272,6 +4674,13 @@ export const DefaultApiFactory = function ( * @interface DefaultApiAdminRepositoryResetRequest */ export interface DefaultApiAdminRepositoryResetRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiAdminRepositoryReset + */ + readonly xOdeVersion: string; + /** * * @type {RepositoryResetRequest} @@ -4287,7 +4696,7 @@ export interface DefaultApiAdminRepositoryResetRequest { */ export interface DefaultApiChangePasswordRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiChangePassword */ @@ -4314,6 +4723,13 @@ export interface DefaultApiCheckAttachmentExistsRequest { */ readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiCheckAttachmentExists + */ + readonly xOdeVersion: string; + /** * Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @type {string} @@ -4329,7 +4745,7 @@ export interface DefaultApiCheckAttachmentExistsRequest { */ export interface DefaultApiCreateUserRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiCreateUser */ @@ -4357,7 +4773,7 @@ export interface DefaultApiDeleteUserRequest { readonly username: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiDeleteUser */ @@ -4378,7 +4794,7 @@ export interface DefaultApiDownloadAppBundleFileRequest { readonly path: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiDownloadAppBundleFile */ @@ -4399,6 +4815,20 @@ export interface DefaultApiDownloadAppBundleFileRequest { readonly ifNoneMatch?: string; } +/** + * Request parameters for downloadAppBundleZip operation in DefaultApi. + * @export + * @interface DefaultApiDownloadAppBundleZipRequest + */ +export interface DefaultApiDownloadAppBundleZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiDownloadAppBundleZip + */ + readonly xOdeVersion: string; +} + /** * Request parameters for downloadAttachment operation in DefaultApi. * @export @@ -4412,6 +4842,13 @@ export interface DefaultApiDownloadAttachmentRequest { */ readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiDownloadAttachment + */ + readonly xOdeVersion: string; + /** * Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @type {string} @@ -4420,6 +4857,20 @@ export interface DefaultApiDownloadAttachmentRequest { readonly original?: string; } +/** + * Request parameters for getAPIVersions operation in DefaultApi. + * @export + * @interface DefaultApiGetAPIVersionsRequest + */ +export interface DefaultApiGetAPIVersionsRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiGetAPIVersions + */ + readonly xOdeVersion: string; +} + /** * Request parameters for getAppBundleChanges operation in DefaultApi. * @export @@ -4427,7 +4878,7 @@ export interface DefaultApiDownloadAttachmentRequest { */ export interface DefaultApiGetAppBundleChangesRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiGetAppBundleChanges */ @@ -4455,7 +4906,7 @@ export interface DefaultApiGetAppBundleChangesRequest { */ export interface DefaultApiGetAppBundleManifestRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiGetAppBundleManifest */ @@ -4476,7 +4927,7 @@ export interface DefaultApiGetAppBundleManifestRequest { */ export interface DefaultApiGetAppBundleVersionsRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiGetAppBundleVersions */ @@ -4490,7 +4941,7 @@ export interface DefaultApiGetAppBundleVersionsRequest { */ export interface DefaultApiGetAttachmentManifestRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiGetAttachmentManifest */ @@ -4511,6 +4962,20 @@ export interface DefaultApiGetAttachmentManifestRequest { readonly xRepositoryGeneration?: number; } +/** + * Request parameters for getVersion operation in DefaultApi. + * @export + * @interface DefaultApiGetVersionRequest + */ +export interface DefaultApiGetVersionRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiGetVersion + */ + readonly xOdeVersion: string; +} + /** * Request parameters for listUsers operation in DefaultApi. * @export @@ -4518,7 +4983,7 @@ export interface DefaultApiGetAttachmentManifestRequest { */ export interface DefaultApiListUsersRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiListUsers */ @@ -4539,7 +5004,7 @@ export interface DefaultApiListUsersRequest { */ export interface DefaultApiLoginRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiLogin */ @@ -4560,7 +5025,7 @@ export interface DefaultApiLoginRequest { */ export interface DefaultApiPushAppBundleRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiPushAppBundle */ @@ -4581,7 +5046,7 @@ export interface DefaultApiPushAppBundleRequest { */ export interface DefaultApiRefreshTokenRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiRefreshToken */ @@ -4602,7 +5067,7 @@ export interface DefaultApiRefreshTokenRequest { */ export interface DefaultApiResetUserPasswordRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiResetUserPassword */ @@ -4630,7 +5095,7 @@ export interface DefaultApiSwitchAppBundleVersionRequest { readonly version: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiSwitchAppBundleVersion */ @@ -4644,7 +5109,7 @@ export interface DefaultApiSwitchAppBundleVersionRequest { */ export interface DefaultApiSyncPullRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiSyncPull */ @@ -4679,7 +5144,7 @@ export interface DefaultApiSyncPullRequest { readonly xOdeClientId?: string; /** - * Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. * @type {number} * @memberof DefaultApiSyncPull */ @@ -4693,7 +5158,7 @@ export interface DefaultApiSyncPullRequest { */ export interface DefaultApiSyncPushRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @type {string} * @memberof DefaultApiSyncPush */ @@ -4734,6 +5199,13 @@ export interface DefaultApiUploadAttachmentRequest { */ readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @type {string} + * @memberof DefaultApiUploadAttachment + */ + readonly xOdeVersion: string; + /** * The binary file to upload * @type {File} @@ -4769,7 +5241,11 @@ export class DefaultApi extends BaseAPI { options?: RawAxiosRequestConfig, ) { return DefaultApiFp(this.configuration) - .adminRepositoryReset(requestParameters.repositoryResetRequest, options) + .adminRepositoryReset( + requestParameters.xOdeVersion, + requestParameters.repositoryResetRequest, + options, + ) .then(request => request(this.axios, this.basePath)); } @@ -4809,6 +5285,7 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .checkAttachmentExists( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) @@ -4880,6 +5357,23 @@ export class DefaultApi extends BaseAPI { .then(request => request(this.axios, this.basePath)); } + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {DefaultApiDownloadAppBundleZipRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public downloadAppBundleZip( + requestParameters: DefaultApiDownloadAppBundleZipRequest, + options?: RawAxiosRequestConfig, + ) { + return DefaultApiFp(this.configuration) + .downloadAppBundleZip(requestParameters.xOdeVersion, options) + .then(request => request(this.axios, this.basePath)); + } + /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID @@ -4895,12 +5389,30 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .downloadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) .then(request => request(this.axios, this.basePath)); } + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {DefaultApiGetAPIVersionsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getAPIVersions( + requestParameters: DefaultApiGetAPIVersionsRequest, + options?: RawAxiosRequestConfig, + ) { + return DefaultApiFp(this.configuration) + .getAPIVersions(requestParameters.xOdeVersion, options) + .then(request => request(this.axios, this.basePath)); + } + /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions @@ -4986,13 +5498,17 @@ export class DefaultApi extends BaseAPI { /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {DefaultApiGetVersionRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi */ - public getVersion(options?: RawAxiosRequestConfig) { + public getVersion( + requestParameters: DefaultApiGetVersionRequest, + options?: RawAxiosRequestConfig, + ) { return DefaultApiFp(this.configuration) - .getVersion(options) + .getVersion(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } @@ -5185,6 +5701,7 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .uploadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.file, requestParameters.xRepositoryGeneration, options, diff --git a/formulus/src/api/synkronus/generated/docs/APIVersionInfo.md b/formulus/src/api/synkronus/generated/docs/APIVersionInfo.md new file mode 100644 index 000000000..5c7a11d38 --- /dev/null +++ b/formulus/src/api/synkronus/generated/docs/APIVersionInfo.md @@ -0,0 +1,23 @@ +# APIVersionInfo + +## Properties + +| Name | Type | Description | Notes | +| --------------- | ----------- | ----------- | ---------------------- | +| **version** | **string** | | [default to undefined] | +| **releaseDate** | **string** | | [default to undefined] | +| **deprecated** | **boolean** | | [default to undefined] | + +## Example + +```typescript +import { APIVersionInfo } from './api'; + +const instance: APIVersionInfo = { + version, + releaseDate, + deprecated, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/formulus/src/api/synkronus/generated/docs/APIVersionsResponse.md b/formulus/src/api/synkronus/generated/docs/APIVersionsResponse.md new file mode 100644 index 000000000..5bd2f309e --- /dev/null +++ b/formulus/src/api/synkronus/generated/docs/APIVersionsResponse.md @@ -0,0 +1,21 @@ +# APIVersionsResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------ | ---------------------------------------------------- | ---------------------------------------------- | ---------------------- | +| **versions** | [**Array<APIVersionInfo>**](APIVersionInfo.md) | | [default to undefined] | +| **current** | **string** | Identifier of the current API contract version | [default to undefined] | + +## Example + +```typescript +import { APIVersionsResponse } from './api'; + +const instance: APIVersionsResponse = { + versions, + current, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/formulus/src/api/synkronus/generated/docs/AttachmentsApi.md b/formulus/src/api/synkronus/generated/docs/AttachmentsApi.md index d5301719c..9b969ecb9 100644 --- a/formulus/src/api/synkronus/generated/docs/AttachmentsApi.md +++ b/formulus/src/api/synkronus/generated/docs/AttachmentsApi.md @@ -20,12 +20,16 @@ import { AttachmentsApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new AttachmentsApi(configuration); -const { status, data } = await apiInstance.getAttachmentsExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getAttachmentsExportZip(xOdeVersion); ``` ### Parameters -This endpoint does not have any parameters. +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type diff --git a/formulus/src/api/synkronus/generated/docs/DataExportApi.md b/formulus/src/api/synkronus/generated/docs/DataExportApi.md index dbc0dc6de..b1d1909a3 100644 --- a/formulus/src/api/synkronus/generated/docs/DataExportApi.md +++ b/formulus/src/api/synkronus/generated/docs/DataExportApi.md @@ -21,12 +21,16 @@ import { DataExportApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DataExportApi(configuration); -const { status, data } = await apiInstance.getParquetExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getParquetExportZip(xOdeVersion); ``` ### Parameters -This endpoint does not have any parameters. +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -66,12 +70,16 @@ import { DataExportApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DataExportApi(configuration); -const { status, data } = await apiInstance.getRawJsonExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getRawJsonExportZip(xOdeVersion); ``` ### Parameters -This endpoint does not have any parameters. +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type diff --git a/formulus/src/api/synkronus/generated/docs/DefaultApi.md b/formulus/src/api/synkronus/generated/docs/DefaultApi.md index beb4d72ac..20147efaa 100644 --- a/formulus/src/api/synkronus/generated/docs/DefaultApi.md +++ b/formulus/src/api/synkronus/generated/docs/DefaultApi.md @@ -10,7 +10,9 @@ All URIs are relative to _http://localhost_ | [**createUser**](#createuser) | **POST** /api/users/create | Create a new user (admin only) | | [**deleteUser**](#deleteuser) | **DELETE** /api/users/{username} | Delete a user (admin only) | | [**downloadAppBundleFile**](#downloadappbundlefile) | **GET** /api/app-bundle/download/{path} | Download a specific file from the app bundle | +| [**downloadAppBundleZip**](#downloadappbundlezip) | **GET** /api/app-bundle/download-zip | Download the active app bundle as a single ZIP | | [**downloadAttachment**](#downloadattachment) | **GET** /api/attachments/{attachment_id} | Download an attachment by ID | +| [**getAPIVersions**](#getapiversions) | **GET** /api/versions | List supported API contract versions | | [**getAppBundleChanges**](#getappbundlechanges) | **GET** /api/app-bundle/changes | Get changes between two app bundle versions | | [**getAppBundleManifest**](#getappbundlemanifest) | **GET** /api/app-bundle/manifest | Get the current custom app bundle manifest | | [**getAppBundleVersions**](#getappbundleversions) | **GET** /api/app-bundle/versions | Get a list of available app bundle versions | @@ -40,18 +42,21 @@ import { DefaultApi, Configuration, RepositoryResetRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let repositoryResetRequest: RepositoryResetRequest; // const { status, data } = await apiInstance.adminRepositoryReset( + xOdeVersion, repositoryResetRequest, ); ``` ### Parameters -| Name | Type | Description | Notes | -| -------------------------- | -------------------------- | ----------- | ----- | -| **repositoryResetRequest** | **RepositoryResetRequest** | | | +| Name | Type | Description | Notes | +| -------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **repositoryResetRequest** | **RepositoryResetRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -68,13 +73,13 @@ const { status, data } = await apiInstance.adminRepositoryReset( ### HTTP response details -| Status code | Description | Response headers | -| ----------- | ------------------------- | ---------------- | -| **200** | Reset completed | - | -| **400** | Invalid confirmation body | - | -| **401** | Unauthorized | - | -| **403** | Forbidden (non-admin) | - | -| **500** | Internal server error | - | +| Status code | Description | Response headers | +| ----------- | ------------------------- | --------------------------------- | +| **200** | Reset completed | \* x-repository-generation -
| +| **400** | Invalid confirmation body | - | +| **401** | Unauthorized | - | +| **403** | Forbidden (non-admin) | - | +| **500** | Internal server error | - | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -92,7 +97,7 @@ import { DefaultApi, Configuration, ChangePasswordRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let changePasswordRequest: ChangePasswordRequest; // const { status, data } = await apiInstance.changePassword( @@ -103,10 +108,10 @@ const { status, data } = await apiInstance.changePassword( ### Parameters -| Name | Type | Description | Notes | -| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **changePasswordRequest** | **ChangePasswordRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| ------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **changePasswordRequest** | **ChangePasswordRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -146,10 +151,12 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let original: string; //Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. (optional) (default to undefined) const { status, data } = await apiInstance.checkAttachmentExists( attachmentId, + xOdeVersion, original, ); ``` @@ -159,6 +166,7 @@ const { status, data } = await apiInstance.checkAttachmentExists( | Name | Type | Description | Notes | | ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | | **attachmentId** | [**string**] | | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | | **original** | [**string**] | Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. | (optional) defaults to undefined | ### Return type @@ -198,7 +206,7 @@ import { DefaultApi, Configuration, CreateUserRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let createUserRequest: CreateUserRequest; // const { status, data } = await apiInstance.createUser( @@ -209,10 +217,10 @@ const { status, data } = await apiInstance.createUser( ### Parameters -| Name | Type | Description | Notes | -| --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **createUserRequest** | **CreateUserRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| --------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **createUserRequest** | **CreateUserRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -254,17 +262,17 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let username: string; //Username of the user to delete (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.deleteUser(username, xOdeVersion); ``` ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **username** | [**string**] | Username of the user to delete | defaults to undefined | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **username** | [**string**] | Username of the user to delete | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -304,7 +312,7 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let path: string; // (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let preview: boolean; //If true, returns the file from the latest version including unreleased changes (optional) (default to false) let ifNoneMatch: string; // (optional) (default to undefined) @@ -318,12 +326,12 @@ const { status, data } = await apiInstance.downloadAppBundleFile( ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **path** | [**string**] | | defaults to undefined | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **preview** | [**boolean**] | If true, returns the file from the latest version including unreleased changes | (optional) defaults to false | -| **ifNoneMatch** | [**string**] | | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **path** | [**string**] | | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **preview** | [**boolean**] | If true, returns the file from the latest version including unreleased changes | (optional) defaults to false | +| **ifNoneMatch** | [**string**] | | (optional) defaults to undefined | ### Return type @@ -347,6 +355,55 @@ const { status, data } = await apiInstance.downloadAppBundleFile( [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **downloadAppBundleZip** + +> File downloadAppBundleZip() + +Returns the full custom app bundle archive for the active version as `application/zip`. + +### Example + +```typescript +import { DefaultApi, Configuration } from './api'; + +const configuration = new Configuration(); +const apiInstance = new DefaultApi(configuration); + +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.downloadAppBundleZip(xOdeVersion); +``` + +### Parameters + +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | + +### Return type + +**File** + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/zip, application/json + +### HTTP response details + +| Status code | Description | Response headers | +| ----------- | ----------------------------- | ---------------- | +| **200** | ZIP archive of the app bundle | - | +| **401** | Unauthorized | - | +| **404** | Bundle zip not available | - | +| **500** | Internal server error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **downloadAttachment** > File downloadAttachment() @@ -362,10 +419,12 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let original: string; //Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. (optional) (default to undefined) const { status, data } = await apiInstance.downloadAttachment( attachmentId, + xOdeVersion, original, ); ``` @@ -375,6 +434,7 @@ const { status, data } = await apiInstance.downloadAttachment( | Name | Type | Description | Notes | | ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | | **attachmentId** | [**string**] | | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | | **original** | [**string**] | Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. | (optional) defaults to undefined | ### Return type @@ -400,6 +460,53 @@ const { status, data } = await apiInstance.downloadAttachment( [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **getAPIVersions** + +> APIVersionsResponse getAPIVersions() + +Returns version metadata for the public HTTP API (compatibility hints for clients). + +### Example + +```typescript +import { DefaultApi, Configuration } from './api'; + +const configuration = new Configuration(); +const apiInstance = new DefaultApi(configuration); + +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getAPIVersions(xOdeVersion); +``` + +### Parameters + +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | + +### Return type + +**APIVersionsResponse** + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details + +| Status code | Description | Response headers | +| ----------- | ---------------- | ---------------- | +| **200** | API version list | - | +| **401** | Unauthorized | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **getAppBundleChanges** > ChangeLog getAppBundleChanges() @@ -414,7 +521,7 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let current: string; //The current version (defaults to latest) (optional) (default to undefined) let target: string; //The target version to compare against (defaults to previous version) (optional) (default to undefined) @@ -427,11 +534,11 @@ const { status, data } = await apiInstance.getAppBundleChanges( ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **current** | [**string**] | The current version (defaults to latest) | (optional) defaults to undefined | -| **target** | [**string**] | The target version to compare against (defaults to previous version) | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **current** | [**string**] | The current version (defaults to latest) | (optional) defaults to undefined | +| **target** | [**string**] | The target version to compare against (defaults to previous version) | (optional) defaults to undefined | ### Return type @@ -469,7 +576,7 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let xOdeClientId: string; //Optional client instance id for correlating app bundle checks with presence. (optional) (default to undefined) const { status, data } = await apiInstance.getAppBundleManifest( @@ -480,10 +587,10 @@ const { status, data } = await apiInstance.getAppBundleManifest( ### Parameters -| Name | Type | Description | Notes | -| ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **xOdeClientId** | [**string**] | Optional client instance id for correlating app bundle checks with presence. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ---------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **xOdeClientId** | [**string**] | Optional client instance id for correlating app bundle checks with presence. | (optional) defaults to undefined | ### Return type @@ -518,16 +625,16 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.getAppBundleVersions(xOdeVersion); ``` ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -564,7 +671,7 @@ import { DefaultApi, Configuration, AttachmentManifestRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let attachmentManifestRequest: AttachmentManifestRequest; // let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) @@ -577,11 +684,11 @@ const { status, data } = await apiInstance.getAttachmentManifest( ### Parameters -| Name | Type | Description | Notes | -| ----------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **attachmentManifestRequest** | **AttachmentManifestRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ----------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **attachmentManifestRequest** | **AttachmentManifestRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | ### Return type @@ -622,12 +729,16 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -const { status, data } = await apiInstance.getVersion(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getVersion(xOdeVersion); ``` ### Parameters -This endpoint does not have any parameters. +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -635,7 +746,7 @@ This endpoint does not have any parameters. ### Authorization -No authorization required +[bearerAuth](../README.md#bearerAuth) ### HTTP request headers @@ -647,6 +758,7 @@ No authorization required | Status code | Description | Response headers | | ----------- | -------------------------------------------- | ---------------- | | **200** | Successful response with version information | - | +| **401** | Unauthorized | - | | **500** | Internal server error | - | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -665,7 +777,7 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let xOdeClientId: string; //Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. (optional) (default to undefined) const { status, data } = await apiInstance.listUsers(xOdeVersion, xOdeClientId); @@ -673,10 +785,10 @@ const { status, data } = await apiInstance.listUsers(xOdeVersion, xOdeClientId); ### Parameters -| Name | Type | Description | Notes | -| ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **xOdeClientId** | [**string**] | Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ---------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **xOdeClientId** | [**string**] | Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. | (optional) defaults to undefined | ### Return type @@ -715,7 +827,7 @@ import { DefaultApi, Configuration, LoginRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let loginRequest: LoginRequest; // const { status, data } = await apiInstance.login(xOdeVersion, loginRequest); @@ -723,10 +835,10 @@ const { status, data } = await apiInstance.login(xOdeVersion, loginRequest); ### Parameters -| Name | Type | Description | Notes | -| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **loginRequest** | **LoginRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| ---------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **loginRequest** | **LoginRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -763,7 +875,7 @@ import { DefaultApi, Configuration } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let bundle: File; //ZIP file containing the new app bundle (optional) (default to undefined) const { status, data } = await apiInstance.pushAppBundle(xOdeVersion, bundle); @@ -771,10 +883,10 @@ const { status, data } = await apiInstance.pushAppBundle(xOdeVersion, bundle); ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **bundle** | [**File**] | ZIP file containing the new app bundle | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **bundle** | [**File**] | ZIP file containing the new app bundle | (optional) defaults to undefined | ### Return type @@ -815,7 +927,7 @@ import { DefaultApi, Configuration, RefreshTokenRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let refreshTokenRequest: RefreshTokenRequest; // const { status, data } = await apiInstance.refreshToken( @@ -826,10 +938,10 @@ const { status, data } = await apiInstance.refreshToken( ### Parameters -| Name | Type | Description | Notes | -| ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **refreshTokenRequest** | **RefreshTokenRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| ----------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **refreshTokenRequest** | **RefreshTokenRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -868,7 +980,7 @@ import { DefaultApi, Configuration, ResetUserPasswordRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let resetUserPasswordRequest: ResetUserPasswordRequest; // const { status, data } = await apiInstance.resetUserPassword( @@ -879,10 +991,10 @@ const { status, data } = await apiInstance.resetUserPassword( ### Parameters -| Name | Type | Description | Notes | -| ---------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **resetUserPasswordRequest** | **ResetUserPasswordRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| ---------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **resetUserPasswordRequest** | **ResetUserPasswordRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -922,7 +1034,7 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let version: string; //Version identifier to switch to (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.switchAppBundleVersion( version, @@ -932,10 +1044,10 @@ const { status, data } = await apiInstance.switchAppBundleVersion( ### Parameters -| Name | Type | Description | Notes | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| **version** | [**string**] | Version identifier to switch to | defaults to undefined | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | +| Name | Type | Description | Notes | +| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| **version** | [**string**] | Version identifier to switch to | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | ### Return type @@ -976,12 +1088,12 @@ import { DefaultApi, Configuration, SyncPullRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let syncPullRequest: SyncPullRequest; // let schemaType: string; //Filter by schemaType (optional) (default to undefined) let limit: number; //Maximum number of records to return (optional) (default to 50) let xOdeClientId: string; //Optional client instance id; improves per-device presence when combined with sync body `client_id`. (optional) (default to undefined) -let xRepositoryGeneration: number; //Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. (optional) (default to undefined) +let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. (optional) (default to undefined) const { status, data } = await apiInstance.syncPull( xOdeVersion, @@ -995,14 +1107,14 @@ const { status, data } = await apiInstance.syncPull( ### Parameters -| Name | Type | Description | Notes | -| ------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **syncPullRequest** | **SyncPullRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **schemaType** | [**string**] | Filter by schemaType | (optional) defaults to undefined | -| **limit** | [**number**] | Maximum number of records to return | (optional) defaults to 50 | -| **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined | -| **xRepositoryGeneration** | [**number**] | Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **syncPullRequest** | **SyncPullRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **schemaType** | [**string**] | Filter by schemaType | (optional) defaults to undefined | +| **limit** | [**number**] | Maximum number of records to return | (optional) defaults to 50 | +| **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined | +| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. | (optional) defaults to undefined | ### Return type @@ -1019,9 +1131,10 @@ const { status, data } = await apiInstance.syncPull( ### HTTP response details -| Status code | Description | Response headers | -| ----------- | ----------- | ---------------- | -| **200** | Sync data | - | +| Status code | Description | Response headers | +| ----------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| **200** | Sync data | \* x-repository-generation -
| +| **409** | Repository epoch mismatch (e.g. after admin hard reset). Client must align repository_generation before pulling. | \* x-repository-generation -
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -1037,7 +1150,7 @@ import { DefaultApi, Configuration, SyncPushRequest } from './api'; const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let syncPushRequest: SyncPushRequest; // let xOdeClientId: string; //Optional client instance id; improves per-device presence when combined with sync body `client_id`. (optional) (default to undefined) let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) @@ -1052,12 +1165,12 @@ const { status, data } = await apiInstance.syncPush( ### Parameters -| Name | Type | Description | Notes | -| ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| **syncPushRequest** | **SyncPushRequest** | | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined | -| **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined | -| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **syncPushRequest** | **SyncPushRequest** | | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined | +| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | ### Return type @@ -1076,7 +1189,7 @@ const { status, data } = await apiInstance.syncPush( | Status code | Description | Response headers | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| **200** | Sync result | - | +| **200** | Sync result | \* x-repository-generation -
| | **409** | Repository epoch mismatch (e.g. after admin hard reset). Client must pull current state and align repository_generation before pushing. | \* x-repository-generation -
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -1094,11 +1207,13 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let file: File; //The binary file to upload (default to undefined) let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) const { status, data } = await apiInstance.uploadAttachment( attachmentId, + xOdeVersion, file, xRepositoryGeneration, ); @@ -1106,11 +1221,12 @@ const { status, data } = await apiInstance.uploadAttachment( ### Parameters -| Name | Type | Description | Notes | -| ------------------------- | ------------ | ------------------------------------------------------------------------------------------- | -------------------------------- | -| **attachmentId** | [**string**] | | defaults to undefined | -| **file** | [**File**] | The binary file to upload | defaults to undefined | -| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | +| Name | Type | Description | Notes | +| ------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| **attachmentId** | [**string**] | | defaults to undefined | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined | +| **file** | [**File**] | The binary file to upload | defaults to undefined | +| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined | ### Return type diff --git a/formulus/src/api/synkronus/generated/docs/SyncPullRequest.md b/formulus/src/api/synkronus/generated/docs/SyncPullRequest.md index af5acaebd..5871d200f 100644 --- a/formulus/src/api/synkronus/generated/docs/SyncPullRequest.md +++ b/formulus/src/api/synkronus/generated/docs/SyncPullRequest.md @@ -2,12 +2,12 @@ ## Properties -| Name | Type | Description | Notes | -| ------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------- | -| **client_id** | **string** | | [default to undefined] | -| **repository_generation** | **number** | Optional body copy of epoch; header x-repository-generation wins when both are sent. | [optional] [default to undefined] | -| **since** | [**SyncPullRequestSince**](SyncPullRequestSince.md) | | [optional] [default to undefined] | -| **schema_types** | **Array<string>** | | [optional] [default to undefined] | +| Name | Type | Description | Notes | +| ------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| **client_id** | **string** | | [default to undefined] | +| **repository_generation** | **number** | Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. | [optional] [default to undefined] | +| **since** | [**SyncPullRequestSince**](SyncPullRequestSince.md) | | [optional] [default to undefined] | +| **schema_types** | **Array<string>** | | [optional] [default to undefined] | ## Example diff --git a/formulus/src/api/synkronus/index.ts b/formulus/src/api/synkronus/index.ts index ea281a838..c47ca6455 100644 --- a/formulus/src/api/synkronus/index.ts +++ b/formulus/src/api/synkronus/index.ts @@ -4,6 +4,7 @@ import { AppBundleManifest, AttachmentOperation, DefaultApiSyncPushRequest, + SyncPullResponse, SyncPushRequest, } from './generated'; import { Observation } from '../../database/models/Observation'; @@ -16,15 +17,71 @@ import { SYNC_WRITE_FORBIDDEN_MESSAGE, } from './Auth'; import { databaseService } from '../../database/DatabaseService'; +import { database } from '../../database/database'; import randomId from '@nozbe/watermelondb/utils/common/randomId'; import { clientIdService } from '../../services/ClientIdService'; import { unzip } from 'react-native-zip-archive'; import { synkronusDownload } from './download'; import { ODE_VERSION } from '../../version'; -import { parseRepositoryResetFromAxios } from '../../errors/RepositoryResetRequiredError'; +import { + isRepositoryResetRequiredError, + parseRepositoryResetFromAxios, + RepositoryResetRequiredError, +} from '../../errors/RepositoryResetRequiredError'; +import type { AxiosError, AxiosResponse } from 'axios'; const REPOSITORY_GENERATION_STORAGE_KEY = '@repository_generation'; +/** Best-effort read of x-repository-generation from Axios response headers (case varies). */ +function headerRepositoryGeneration( + headers: AxiosResponse['headers'] | undefined, +): string | undefined { + if (!headers || typeof headers !== 'object') return undefined; + const h = headers as Record; + const direct = h['x-repository-generation'] ?? h['X-Repository-Generation']; + if (typeof direct === 'string') return direct; + if (Array.isArray(direct) && direct[0] != null) return String(direct[0]); + const maybeGet = (headers as { get?: (k: string) => string | undefined }).get; + if (typeof maybeGet === 'function') { + const v = maybeGet('x-repository-generation'); + if (v != null) return v; + } + return undefined; +} + +function logRepositoryGenerationSync( + message: string, + payload?: Record, +): void { + if (payload != null) { + console.log(`[RepositoryGeneration] ${message}`, payload); + } else { + console.log(`[RepositoryGeneration] ${message}`); + } +} + +function logAxiosErrorForRepoGen(operation: string, error: unknown): void { + const ax = error as AxiosError<{ + code?: string; + message?: string; + }>; + const status = ax.response?.status; + if (status == null) { + logRepositoryGenerationSync(`${operation} failed (no HTTP response)`, { + message: ax.message, + }); + return; + } + logRepositoryGenerationSync(`${operation} HTTP ${status}`, { + url: ax.config?.url, + method: ax.config?.method, + responseData: ax.response?.data, + headerXRepositoryGeneration: headerRepositoryGeneration( + ax.response?.headers, + ), + }); +} + interface DownloadResult { success: boolean; message: string; @@ -87,12 +144,55 @@ class SynkronusApi { gen: number | undefined, ): Promise { if (gen == null || !Number.isFinite(gen)) return; + const prev = await AsyncStorage.getItem(REPOSITORY_GENERATION_STORAGE_KEY); await AsyncStorage.setItem(REPOSITORY_GENERATION_STORAGE_KEY, String(gen)); + if (prev !== String(gen)) { + logRepositoryGenerationSync('persisted server repository_generation', { + previous: prev ?? '(missing)', + new: gen, + }); + } } private rethrowIfRepositoryResetConflict(error: unknown): void { const parsed = parseRepositoryResetFromAxios(error); - if (parsed) throw parsed; + if (parsed) { + logRepositoryGenerationSync( + 'repository_reset_required — mapped to RepositoryResetRequiredError', + { + serverRepositoryGeneration: parsed.serverRepositoryGeneration, + message: parsed.message, + }, + ); + throw parsed; + } + } + + /** + * Synkronus should return 409 when epochs mismatch; if we ever get HTTP 200 with a + * different repository_generation than we sent, treat it as reset required and do not persist. + */ + private ensureRepoGenResponseMatchesSent( + operation: string, + clientGenSent: number, + responseGen: number | undefined, + ): void { + if (responseGen == null || !Number.isFinite(Number(responseGen))) { + return; + } + const sent = Math.floor(Number(clientGenSent)); + const fromBody = Math.floor(Number(responseGen)); + if (sent === fromBody) { + return; + } + logRepositoryGenerationSync( + `${operation}: response repository_generation does not match client (defensive check)`, + { clientGenSent: sent, responseRepositoryGeneration: fromBody }, + ); + throw new RepositoryResetRequiredError( + 'The server repository epoch no longer matches this device. Clear local data and sync again.', + fromBody, + ); } /** @@ -291,16 +391,27 @@ class SynkronusApi { ); const api = await this.getApi(); + const manifestClientGen = await this.getRepositoryGenerationForRequest(); + logRepositoryGenerationSync('getAttachmentManifest request', { + clientXRepositoryGeneration: manifestClientGen, + sinceVersion: lastAttachmentVersion, + }); const manifestResponse = await api.getAttachmentManifest({ xOdeVersion: ODE_VERSION, attachmentManifestRequest: { client_id: clientId, since_version: lastAttachmentVersion, + repository_generation: manifestClientGen, }, - xRepositoryGeneration: await this.getRepositoryGenerationForRequest(), + xRepositoryGeneration: manifestClientGen, }); const manifest = manifestResponse.data; + this.ensureRepoGenResponseMatchesSent( + 'getAttachmentManifest', + manifestClientGen, + manifest.repository_generation, + ); await this.persistRepositoryGenerationFromResponse( manifest.repository_generation, ); @@ -344,6 +455,9 @@ class SynkronusApi { ); } catch (error: unknown) { this.rethrowIfRepositoryResetConflict(error); + if (isRepositoryResetRequiredError(error)) { + throw error; + } console.error('Failed to process attachment manifest:', error); throw error; // Let the error bubble up so we can fix the root cause } @@ -761,10 +875,8 @@ class SynkronusApi { } /** - * Save a new attachment for immediate use and queue it for upload - * This should be called when a new attachment is created (e.g., from camera) - * The file is saved to both the main attachments folder (for immediate use in forms) - * and the unsynced folder (as an upload queue) + * Save a new attachment under draft storage until the observation is saved; promotion + * to main + pending_upload runs when the observation is persisted (see attachmentStorage). */ async saveNewAttachment( attachmentId: string, @@ -772,28 +884,19 @@ class SynkronusApi { isBase64: boolean = true, ): Promise { const attachmentsDirectory = `${RNFS.DocumentDirectoryPath}/attachments`; - const pendingUploadDirectory = `${RNFS.DocumentDirectoryPath}/attachments/pending_upload`; + const draftDirectory = `${attachmentsDirectory}/draft`; - // Ensure both directories exist await RNFS.mkdir(attachmentsDirectory); - await RNFS.mkdir(pendingUploadDirectory); + await RNFS.mkdir(draftDirectory); - const mainFilePath = `${attachmentsDirectory}/${attachmentId}`; - const pendingFilePath = `${pendingUploadDirectory}/${attachmentId}`; + const draftFilePath = `${draftDirectory}/${attachmentId}`; const encoding = isBase64 ? 'base64' : 'utf8'; - // Save to both locations - await Promise.all([ - RNFS.writeFile(mainFilePath, fileData, encoding), - RNFS.writeFile(pendingFilePath, fileData, encoding), - ]); + await RNFS.writeFile(draftFilePath, fileData, encoding); - console.debug( - `Saved new attachment: ${attachmentId} (available immediately, queued for upload)`, - ); + console.debug(`Saved new draft attachment: ${attachmentId}`); - // Return the path that should be stored in observation data - return mainFilePath; + return draftFilePath; } /** @@ -838,22 +941,55 @@ class SynkronusApi { const schemaTypes = undefined; // TODO: Feature: Maybe allow partial sync let res; let currentSince = since; + let totalServerRecordsThisPull = 0; do { - res = await api.syncPull({ - xOdeVersion: ODE_VERSION, - syncPullRequest: { - client_id: clientId, - since: { - version: currentSince, + const clientGen = await this.getRepositoryGenerationForRequest(); + logRepositoryGenerationSync('syncPull request', { + clientXRepositoryGeneration: clientGen, + sinceVersion: currentSince, + }); + + try { + res = await api.syncPull({ + xOdeVersion: ODE_VERSION, + syncPullRequest: { + client_id: clientId, + repository_generation: clientGen, + since: { + version: currentSince, + }, + schema_types: schemaTypes, }, - schema_types: schemaTypes, - }, - xRepositoryGeneration: await this.getRepositoryGenerationForRequest(), + xRepositoryGeneration: clientGen, + }); + } catch (err: unknown) { + logAxiosErrorForRepoGen('syncPull', err); + this.rethrowIfRepositoryResetConflict(err); + throw err; + } + + const pullRes = res as AxiosResponse; + logRepositoryGenerationSync('syncPull response OK', { + clientSent: clientGen, + sinceVersion: currentSince, + bodyRepositoryGeneration: res.data.repository_generation, + bodyCurrentVersion: res.data.current_version, + bodyHasMore: res.data.has_more, + recordsInThisPage: res.data.records?.length ?? 0, + headerXRepositoryGeneration: headerRepositoryGeneration( + pullRes.headers, + ), + note: 'repository_generation = server epoch (resets); current_version = observation stream cursor — a 4 and a 1 here are not a mismatch.', }); console.debug('Pull response: ', res.data); + this.ensureRepoGenResponseMatchesSent( + 'syncPull', + clientGen, + res.data.repository_generation, + ); await this.persistRepositoryGenerationFromResponse( res.data.repository_generation, ); @@ -863,6 +999,8 @@ class SynkronusApi { ? res.data.records.map(ObservationMapper.fromApi) : []; + totalServerRecordsThisPull += domainObservations.length; + // 2. Apply to local db (local dirty records will not be applied = last update wins). // Skipped rows get a `last_write_won` tag (see syncConstants / WatermelonDBRepo). const pulledChanges = await repo.applyServerChanges(domainObservations); // ingest observations into WatermelonDB @@ -882,6 +1020,26 @@ class SynkronusApi { } } while (res.data.has_more); + logRepositoryGenerationSync('syncPull all pages done', { + totalServerRecordsReceived: totalServerRecordsThisPull, + finalBodyCurrentVersion: res.data.current_version, + finalBodyRepositoryGeneration: res.data.repository_generation, + }); + + if (totalServerRecordsThisPull === 0) { + const localObservationRows = await database + .get('observations') + .query() + .fetchCount(); + if (localObservationRows > 0) { + console.warn( + '[RepositoryGeneration] Pull returned 0 observation records across all pages, but the local DB still has', + localObservationRows, + 'row(s). An empty pull does not delete local rows (applyServerChanges is a no-op when there are no server records). This is normal if you only have offline/unsynced drafts. If the server was reset and repository_generation already matches the client, stale synced rows can remain until you use the repository-reset flow (Erase and sync) or clear local data.', + ); + } + } + // Only when all observations are pulled and ingested by WatermelonDB, update the last seen version await AsyncStorage.setItem( '@last_seen_version', @@ -942,6 +1100,16 @@ class SynkronusApi { // 3. Check if we have observations to push if (localChanges.length === 0) { console.debug('No local changes to push'); + const skipGen = await this.getRepositoryGenerationForRequest(); + const lastSeen = + (await AsyncStorage.getItem('@last_seen_version')) ?? '(missing)'; + logRepositoryGenerationSync( + 'syncPush skipped (no local observation rows to push)', + { + effectiveClientGen: skipGen, + lastSeenVersion: lastSeen, + }, + ); // If we uploaded attachments, report that if (includeAttachments && attachmentUploadResults.length > 0) { @@ -957,22 +1125,44 @@ class SynkronusApi { } // 3. Push observations to server + const pushClientGen = await this.getRepositoryGenerationForRequest(); const syncPushRequest: SyncPushRequest = { client_id: await clientIdService.getClientId(), records: localChanges.map(ObservationMapper.toApi), transmission_id: transmissionId, + repository_generation: pushClientGen, }; const request: DefaultApiSyncPushRequest = { xOdeVersion: ODE_VERSION, syncPushRequest, - xRepositoryGeneration: await this.getRepositoryGenerationForRequest(), + xRepositoryGeneration: pushClientGen, }; + logRepositoryGenerationSync('syncPush request', { + clientXRepositoryGeneration: pushClientGen, + observationCount: localChanges.length, + }); + console.debug( `Pushing ${localChanges.length} observations with transmission ID: ${transmissionId}`, ); const res = await api.syncPush(request); + + logRepositoryGenerationSync('syncPush response OK', { + clientSent: pushClientGen, + bodyRepositoryGeneration: res.data.repository_generation, + bodyCurrentVersion: res.data.current_version, + headerXRepositoryGeneration: headerRepositoryGeneration( + (res as AxiosResponse).headers, + ), + }); + + this.ensureRepoGenResponseMatchesSent( + 'syncPush', + pushClientGen, + res.data.repository_generation, + ); await this.persistRepositoryGenerationFromResponse( res.data.repository_generation, ); @@ -1009,7 +1199,11 @@ class SynkronusApi { return res.data.current_version; } catch (error: unknown) { + logAxiosErrorForRepoGen('syncPush', error); this.rethrowIfRepositoryResetConflict(error); + if (isRepositoryResetRequiredError(error)) { + throw error; + } console.error('Failed to push observations:', error); if (isForbiddenError(error)) { throw new Error(SYNC_WRITE_FORBIDDEN_MESSAGE); @@ -1027,10 +1221,26 @@ class SynkronusApi { ? 'Syncing observations with attachments' : 'Syncing observations', ); + const rawStored = await AsyncStorage.getItem( + REPOSITORY_GENERATION_STORAGE_KEY, + ); + const effectiveGen = await this.getRepositoryGenerationForRequest(); + logRepositoryGenerationSync('syncObservations start', { + storageRaw: rawStored ?? '(missing)', + effectiveClientGen: effectiveGen, + includeAttachments, + }); const version = await this.pullObservations(includeAttachments); console.debug('Pull completed @ data version ' + version); await this.pushObservations(includeAttachments); console.debug('Push completed'); + const storageAfter = await AsyncStorage.getItem( + REPOSITORY_GENERATION_STORAGE_KEY, + ); + logRepositoryGenerationSync('syncObservations finished', { + observationDataVersion: version, + storageRepositoryGeneration: storageAfter ?? '(missing)', + }); return version; } } diff --git a/formulus/src/errors/RepositoryResetRequiredError.ts b/formulus/src/errors/RepositoryResetRequiredError.ts index 719ec06ec..7050ffa43 100644 --- a/formulus/src/errors/RepositoryResetRequiredError.ts +++ b/formulus/src/errors/RepositoryResetRequiredError.ts @@ -39,7 +39,17 @@ export function parseRepositoryResetFromAxios( }>; if (ax.response?.status !== 409) return null; const code = ax.response?.data?.code; - if (code !== CODE_REPOSITORY_RESET_REQUIRED) return null; + if (code !== CODE_REPOSITORY_RESET_REQUIRED) { + console.warn( + '[RepositoryGeneration] HTTP 409 but sync error code is not repository_reset_required; client will not show repository-reset recovery.', + { + code, + message: ax.response?.data?.message, + data: ax.response?.data, + }, + ); + return null; + } const headerRaw = ax.response?.headers?.['x-repository-generation']; const headerStr = Array.isArray(headerRaw) ? headerRaw[0] : headerRaw; diff --git a/formulus/src/screens/SyncScreen.tsx b/formulus/src/screens/SyncScreen.tsx index 211e96955..f4b7f175a 100644 --- a/formulus/src/screens/SyncScreen.tsx +++ b/formulus/src/screens/SyncScreen.tsx @@ -23,7 +23,10 @@ import { getUserInfo, getUserFacingSyncErrorMessage, } from '../api/synkronus/Auth'; -import { isRepositoryResetRequiredError } from '../errors/RepositoryResetRequiredError'; +import { + isRepositoryResetRequiredError, + type RepositoryResetRequiredError, +} from '../errors/RepositoryResetRequiredError'; import { repositoryRecoveryService } from '../services/RepositoryRecoveryService'; import colors from '../theme/colors'; import { Button } from '../components/common'; @@ -43,6 +46,11 @@ import { type ActiveOperation = 'sync' | 'update' | 'sync_then_update' | null; +const REPOSITORY_RESET_ALERT_TITLE = 'Server data was reset'; + +const REPOSITORY_RESET_ALERT_MESSAGE = + 'The server replaced its observation dataset (a new data generation). To sync again, this device must delete all local observations and all attachment files, including items that have not been uploaded yet.\n\nThis cannot be undone. Erase everything on this device and sync again?'; + const SyncScreen = () => { const { themeColors, resolvedMode } = useAppTheme(); const shellStyle = useScreenShellStyle(); @@ -154,6 +162,47 @@ const SyncScreen = () => { } }, []); + const runRepositoryResetRecovery = useCallback( + ( + error: RepositoryResetRequiredError, + activeOp: ActiveOperation, + afterWipe: () => Promise, + failureTitle: string, + ) => { + Alert.alert( + REPOSITORY_RESET_ALERT_TITLE, + REPOSITORY_RESET_ALERT_MESSAGE, + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Erase and sync', + style: 'destructive', + onPress: () => { + void (async () => { + try { + startSync(true); + setActiveOperation(activeOp); + await repositoryRecoveryService.wipeLocalSyncState( + error.serverRepositoryGeneration, + ); + await afterWipe(); + finishSync(); + } catch (e) { + const msg = getUserFacingSyncErrorMessage(e); + finishSync(msg); + Alert.alert(failureTitle, msg); + } finally { + setActiveOperation(null); + } + })(); + }, + }, + ], + ); + }, + [startSync, finishSync], + ); + const handleSync = useCallback(async () => { if (syncState.isActive) return; @@ -176,34 +225,14 @@ const SyncScreen = () => { } catch (error) { if (isRepositoryResetRequiredError(error)) { syncError = getUserFacingSyncErrorMessage(error); - Alert.alert( - 'Server repository reset', - 'Your local data no longer matches this server. Clear local observations and attachments, then sync again?', - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Clear and sync', - style: 'destructive', - onPress: () => { - void (async () => { - try { - startSync(true); - setActiveOperation('sync'); - await repositoryRecoveryService.wipeLocalSyncState(); - await syncService.syncObservations(true); - await refreshAfterOperation(); - finishSync(); - } catch (e) { - const msg = getUserFacingSyncErrorMessage(e); - finishSync(msg); - Alert.alert('Sync failed', msg); - } finally { - setActiveOperation(null); - } - })(); - }, - }, - ], + runRepositoryResetRecovery( + error, + 'sync', + async () => { + await syncService.syncObservations(true); + await refreshAfterOperation(); + }, + 'Sync failed', ); } else { syncError = getUserFacingSyncErrorMessage(error); @@ -213,7 +242,13 @@ const SyncScreen = () => { finishSync(syncError); setActiveOperation(null); } - }, [syncState.isActive, startSync, finishSync, refreshAfterOperation]); + }, [ + syncState.isActive, + startSync, + finishSync, + refreshAfterOperation, + runRepositoryResetRecovery, + ]); const performAppBundleUpdate = useCallback(async () => { try { @@ -262,39 +297,19 @@ const SyncScreen = () => { if (isRepositoryResetRequiredError(error)) { const errorMessage = getUserFacingSyncErrorMessage(error); finishSync(errorMessage); - Alert.alert( - 'Server repository reset', - 'Your local data no longer matches this server. Clear local observations and attachments, then sync again?', - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Clear and sync', - style: 'destructive', - onPress: () => { - void (async () => { - try { - startSync(true); - setActiveOperation('sync_then_update'); - await repositoryRecoveryService.wipeLocalSyncState(); - await syncService.syncObservations(true); - await syncService.updateAppBundle(); - setUpdateAvailable(false); - await refreshAfterOperation(); - finishSync(); - const formService = await import('../services/FormService'); - const fs = await formService.FormService.getInstance(); - await fs.invalidateCache(); - } catch (e) { - const msg = getUserFacingSyncErrorMessage(e); - finishSync(msg); - Alert.alert('Operation failed', msg); - } finally { - setActiveOperation(null); - } - })(); - }, - }, - ], + runRepositoryResetRecovery( + error, + 'sync_then_update', + async () => { + await syncService.syncObservations(true); + await syncService.updateAppBundle(); + setUpdateAvailable(false); + await refreshAfterOperation(); + const formService = await import('../services/FormService'); + const fs = await formService.FormService.getInstance(); + await fs.invalidateCache(); + }, + 'Operation failed', ); } else { const errorMessage = getUserFacingSyncErrorMessage(error); @@ -304,7 +319,12 @@ const SyncScreen = () => { } finally { setActiveOperation(null); } - }, [startSync, finishSync, refreshAfterOperation]); + }, [ + startSync, + finishSync, + refreshAfterOperation, + runRepositoryResetRecovery, + ]); const handleCustomAppUpdate = useCallback(async () => { if (syncState.isActive) return; diff --git a/formulus/src/screens/WelcomeScreen.tsx b/formulus/src/screens/WelcomeScreen.tsx index 5953196df..1a1a2a8c2 100644 --- a/formulus/src/screens/WelcomeScreen.tsx +++ b/formulus/src/screens/WelcomeScreen.tsx @@ -57,7 +57,12 @@ const WelcomeScreen = () => { const handleGetStarted = () => { navigation.reset({ index: 0, - routes: [{ name: 'MainApp' }], + routes: [ + { + name: 'MainApp', + params: { screen: 'Settings' }, + }, + ], }); }; diff --git a/formulus/src/services/RepositoryRecoveryService.ts b/formulus/src/services/RepositoryRecoveryService.ts index a6dd445ff..bc2c1b918 100644 --- a/formulus/src/services/RepositoryRecoveryService.ts +++ b/formulus/src/services/RepositoryRecoveryService.ts @@ -11,7 +11,12 @@ const REPOSITORY_GENERATION_KEY = '@repository_generation'; * auth session, and app bundle/forms. */ class RepositoryRecoveryService { - async wipeLocalSyncState(): Promise { + /** + * @param serverRepositoryGeneration - When the server returned 409, pass the epoch from + * `x-repository-generation` so the next sync matches Synkronus (default client gen 1 would + * still conflict after a reset). + */ + async wipeLocalSyncState(serverRepositoryGeneration?: number): Promise { const attachmentsDirectory = `${RNFS.DocumentDirectoryPath}/attachments`; try { if (await RNFS.exists(attachmentsDirectory)) { @@ -22,6 +27,7 @@ class RepositoryRecoveryService { } await RNFS.mkdir(attachmentsDirectory); await RNFS.mkdir(`${attachmentsDirectory}/pending_upload`); + await RNFS.mkdir(`${attachmentsDirectory}/draft`); await database.write(async () => { await database.unsafeResetDatabase(); @@ -34,6 +40,17 @@ class RepositoryRecoveryService { '@lastSync', ]); + if ( + serverRepositoryGeneration != null && + Number.isFinite(serverRepositoryGeneration) && + serverRepositoryGeneration >= 1 + ) { + await AsyncStorage.setItem( + REPOSITORY_GENERATION_KEY, + String(Math.floor(serverRepositoryGeneration)), + ); + } + synkronusApi.clearTokenCache(); } } diff --git a/formulus/src/services/ServerSwitchService.ts b/formulus/src/services/ServerSwitchService.ts index 436ce5d67..0176db48c 100644 --- a/formulus/src/services/ServerSwitchService.ts +++ b/formulus/src/services/ServerSwitchService.ts @@ -42,6 +42,7 @@ class ServerSwitchService { } await RNFS.mkdir(attachmentsDirectory); await RNFS.mkdir(`${attachmentsDirectory}/pending_upload`); + await RNFS.mkdir(`${attachmentsDirectory}/draft`); // 2) Clear app bundle and forms (fail fast) await synkronusApi.removeAppBundleFiles(); diff --git a/formulus/src/services/SyncService.ts b/formulus/src/services/SyncService.ts index a1d2a2c29..1a10234a9 100644 --- a/formulus/src/services/SyncService.ts +++ b/formulus/src/services/SyncService.ts @@ -281,6 +281,16 @@ export class SyncService { 'sync observations', ); + const repoGenStorage = + (await AsyncStorage.getItem('@repository_generation')) ?? '(missing)'; + console.log( + '[RepositoryGeneration] SyncService: observations sync done', + { + finalObservationDataVersion: finalVersion, + repositoryGenerationStorage: repoGenStorage, + }, + ); + this.updateProgress({ current: 4, total: 4, diff --git a/formulus/src/services/WebViewFileUrlResolver.ts b/formulus/src/services/WebViewFileUrlResolver.ts index 154def06a..b8d8211bd 100644 --- a/formulus/src/services/WebViewFileUrlResolver.ts +++ b/formulus/src/services/WebViewFileUrlResolver.ts @@ -34,13 +34,16 @@ export function safeAttachmentBasename(raw: unknown): string | null { const attachmentsRoot = (): string => `${RNFS.DocumentDirectoryPath}/attachments`; +const draftRoot = (): string => + `${RNFS.DocumentDirectoryPath}/attachments/draft`; const pendingRoot = (): string => `${RNFS.DocumentDirectoryPath}/attachments/pending_upload`; const customAppRoot = (): string => `${RNFS.DocumentDirectoryPath}/app`; const formsRoot = (): string => `${RNFS.DocumentDirectoryPath}/forms`; /** - * Return file:// URL for an attachment file if it exists in main or pending_upload folder. + * Return file:// URL for an attachment file if it exists in draft (unsaved capture), + * committed folder, or pending_upload. */ export async function resolveAttachmentFileUrl( fileName: string, @@ -49,9 +52,13 @@ export async function resolveAttachmentFileUrl( if (!base) { return null; } + const draftPath = `${draftRoot()}/${base}`; const mainPath = `${attachmentsRoot()}/${base}`; const pendingPath = `${pendingRoot()}/${base}`; try { + if (await RNFS.exists(draftPath)) { + return pathToFileUrl(draftPath); + } if (await RNFS.exists(mainPath)) { return pathToFileUrl(mainPath); } diff --git a/formulus/src/services/attachmentStorage.ts b/formulus/src/services/attachmentStorage.ts new file mode 100644 index 000000000..d41fce032 --- /dev/null +++ b/formulus/src/services/attachmentStorage.ts @@ -0,0 +1,98 @@ +import RNFS from 'react-native-fs'; +import { safeAttachmentBasename } from './WebViewFileUrlResolver'; + +export const attachmentsRoot = (): string => + `${RNFS.DocumentDirectoryPath}/attachments`; + +export const pendingUploadRoot = (): string => + `${attachmentsRoot()}/pending_upload`; + +export const draftAttachmentsRoot = (): string => `${attachmentsRoot()}/draft`; + +/** Same rules as SynkronusApi.isAttachmentPath — basename / GUID-style refs in observation JSON. */ +export function isAttachmentBasename(value: string): boolean { + const guidPattern = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + const guidWithExtension = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx)$/i; + return guidPattern.test(value) || guidWithExtension.test(value); +} + +function collectAttachmentBasenames(data: unknown, out: Set): void { + if (typeof data === 'string') { + if (isAttachmentBasename(data)) { + out.add(data); + } + return; + } + if (!data || typeof data !== 'object') return; + if (Array.isArray(data)) { + for (const item of data) { + collectAttachmentBasenames(item, out); + } + return; + } + for (const value of Object.values(data)) { + collectAttachmentBasenames(value, out); + } +} + +export function collectAttachmentBasenamesFromData(data: unknown): string[] { + const out = new Set(); + collectAttachmentBasenames(data, out); + return [...out]; +} + +async function promoteOneDraftFile(base: string): Promise { + const baseSafe = safeAttachmentBasename(base); + if (!baseSafe || !isAttachmentBasename(baseSafe)) { + return; + } + const draftPath = `${draftAttachmentsRoot()}/${baseSafe}`; + const mainPath = `${attachmentsRoot()}/${baseSafe}`; + const pendingPath = `${pendingUploadRoot()}/${baseSafe}`; + if (!(await RNFS.exists(draftPath))) { + return; + } + await RNFS.mkdir(attachmentsRoot()); + await RNFS.mkdir(pendingUploadRoot()); + await RNFS.copyFile(draftPath, mainPath); + await RNFS.copyFile(draftPath, pendingPath); + await RNFS.unlink(draftPath); +} + +/** Deep rewrite: draft attachment paths become committed paths (same basename under `attachments/`). */ +export function rewriteDraftUrisInData(data: unknown): unknown { + if (data == null) return data; + if (typeof data === 'string') { + if (data.includes('/attachments/draft/')) { + return data.replace(/\/attachments\/draft\//g, '/attachments/'); + } + return data; + } + if (Array.isArray(data)) { + return data.map(rewriteDraftUrisInData); + } + if (typeof data === 'object') { + const o: Record = {}; + for (const [k, v] of Object.entries(data)) { + o[k] = rewriteDraftUrisInData(v); + } + return o; + } + return data; +} + +/** + * After an observation is persisted, promote any draft files referenced in `data` to + * `attachments/` + `pending_upload/`, and return updated JSON with `file://` paths fixed. + */ +export async function commitDraftAttachmentsAfterSave( + data: Record, +): Promise> { + const basenames = collectAttachmentBasenamesFromData(data); + for (const b of basenames) { + await promoteOneDraftFile(b); + } + return rewriteDraftUrisInData(data) as Record; +} diff --git a/formulus/src/types/NavigationTypes.ts b/formulus/src/types/NavigationTypes.ts index 9b14c0403..e58e35b48 100644 --- a/formulus/src/types/NavigationTypes.ts +++ b/formulus/src/types/NavigationTypes.ts @@ -1,3 +1,5 @@ +import { NavigatorScreenParams } from '@react-navigation/native'; + export type MainTabParamList = { Home: undefined; Forms: undefined; @@ -27,6 +29,6 @@ export type VisibleMainTab = (typeof VISIBLE_MAIN_TABS)[number]; export type MainAppStackParamList = { Welcome: undefined; - MainApp: undefined; + MainApp: NavigatorScreenParams | undefined; ObservationDetail: { observationId: string }; }; diff --git a/formulus/src/webview/FormulusInterfaceDefinition.ts b/formulus/src/webview/FormulusInterfaceDefinition.ts index 644cc0e39..8bc82be01 100644 --- a/formulus/src/webview/FormulusInterfaceDefinition.ts +++ b/formulus/src/webview/FormulusInterfaceDefinition.ts @@ -451,8 +451,9 @@ export interface FormulusInterface { /** * Resolve a synced or camera-saved attachment to a WebView-loadable `file://` URL. - * Checks `{DocumentDirectory}/attachments/` and `pending_upload/`. Pass the basename only - * (e.g. `photo.filename` from observation data); path segments and ".." are rejected. + * Checks `{DocumentDirectory}/attachments/draft/` (unsaved capture), then `attachments/`, + * then `pending_upload/`. Pass the basename only (e.g. `photo.filename` from observation + * data); path segments and ".." are rejected. * @param fileName - Attachment file basename * @returns `file://` URL if the file exists, otherwise `null` */ @@ -494,7 +495,7 @@ export interface FormulusCallbacks { /** * Current version of the interface */ -export const FORMULUS_INTERFACE_VERSION = '1.2.0'; +export const FORMULUS_INTERFACE_VERSION = '1.2.1'; /** Parses major.minor.patch from the start of a version string (ignores prerelease after `-`). */ function semverSegments(version: string): [number, number, number] { diff --git a/formulus/src/webview/FormulusMessageHandlers.ts b/formulus/src/webview/FormulusMessageHandlers.ts index 0bedfec03..2f460abc7 100644 --- a/formulus/src/webview/FormulusMessageHandlers.ts +++ b/formulus/src/webview/FormulusMessageHandlers.ts @@ -49,6 +49,7 @@ import { getFormSpecsDirectoryFileUrl, resolveAttachmentFileUrl, } from '../services/WebViewFileUrlResolver'; +import { commitDraftAttachmentsAfterSave } from '../services/attachmentStorage'; export type HandlerArgs = { data: unknown; @@ -249,6 +250,13 @@ const saveFormData = async ( ? await formService.updateObservation(observationId, data) : await formService.addNewObservation(formType, data); + if (id != null) { + const fixedData = await commitDraftAttachmentsAfterSave( + data as Record, + ); + await formService.updateObservation(id, fixedData); + } + return id; } catch (error) { console.error('Error saving form data:', error); @@ -410,36 +418,26 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { ); const attachmentsDirectory = `${RNFS.DocumentDirectoryPath}/attachments`; - const pendingUploadDirectory = `${RNFS.DocumentDirectoryPath}/attachments/pending_upload`; + const draftDirectory = `${attachmentsDirectory}/draft`; + const draftFilePath = `${draftDirectory}/${guidFilename}`; - const mainFilePath = `${attachmentsDirectory}/${guidFilename}`; - const pendingFilePath = `${pendingUploadDirectory}/${guidFilename}`; - - console.log('Copying camera image to attachment sync system:', { + console.log('Copying camera image to draft attachment storage:', { source: asset.uri, - mainPath: mainFilePath, - pendingPath: pendingFilePath, + draftPath: draftFilePath, }); - // Ensure both directories exist and copy file to both locations Promise.all([ RNFS.mkdir(attachmentsDirectory), - RNFS.mkdir(pendingUploadDirectory), + RNFS.mkdir(draftDirectory), ]) - .then(() => { - // Copy to both locations simultaneously - return Promise.all([ - RNFS.copyFile(asset.uri, mainFilePath), - RNFS.copyFile(asset.uri, pendingFilePath), - ]); - }) + .then(() => RNFS.copyFile(asset.uri, draftFilePath)) .then(() => { console.log( - 'Image saved to attachment sync system:', - mainFilePath, + 'Image saved to draft attachments:', + draftFilePath, ); - const webViewUrl = `file://${mainFilePath}`; + const webViewUrl = `file://${draftFilePath}`; resolve({ fieldId, @@ -448,8 +446,8 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { type: 'image', id: imageGuid, filename: guidFilename, - uri: mainFilePath, // Main attachment path for sync protocol - url: webViewUrl, // WebView-accessible URL for display + uri: draftFilePath, + url: webViewUrl, timestamp: new Date().toISOString(), metadata: { width: asset.width || 1920, @@ -460,8 +458,8 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { quality: 0.8, originalFileName: asset.fileName || guidFilename, persistentStorage: true, - storageLocation: 'attachments_with_upload_queue', - syncReady: true, + storageLocation: 'draft_attachments', + syncReady: false, }, }, }); diff --git a/synkronus-cli/pkg/client/client.go b/synkronus-cli/pkg/client/client.go index 71d69128d..81c5041db 100644 --- a/synkronus-cli/pkg/client/client.go +++ b/synkronus-cli/pkg/client/client.go @@ -139,8 +139,15 @@ func (c *Client) GetVersion() (*SystemVersionInfo, error) { if err := c.ensureReady(); err != nil { return nil, err } + apiVer, err := c.requiredVersion() + if err != nil { + return nil, err + } - resp, err := c.api.GetVersionWithResponse(context.Background()) + resp, err := c.api.GetVersionWithResponse( + context.Background(), + &generated.GetVersionParams{XOdeVersion: apiVer}, + ) if err != nil { return nil, fmt.Errorf("version request failed: %w", err) } @@ -148,44 +155,44 @@ func (c *Client) GetVersion() (*SystemVersionInfo, error) { if resp.JSON200 == nil { return nil, apiError(resp.StatusCode(), resp.Body) } - version := &SystemVersionInfo{} + out := &SystemVersionInfo{} if resp.JSON200.Server != nil && resp.JSON200.Server.Version != nil { - version.Server.Version = *resp.JSON200.Server.Version + out.Server.Version = *resp.JSON200.Server.Version } if resp.JSON200.Database != nil { if resp.JSON200.Database.Type != nil { - version.Database.Type = *resp.JSON200.Database.Type + out.Database.Type = *resp.JSON200.Database.Type } if resp.JSON200.Database.Version != nil { - version.Database.Version = *resp.JSON200.Database.Version + out.Database.Version = *resp.JSON200.Database.Version } if resp.JSON200.Database.DatabaseName != nil { - version.Database.DatabaseName = *resp.JSON200.Database.DatabaseName + out.Database.DatabaseName = *resp.JSON200.Database.DatabaseName } } if resp.JSON200.System != nil { if resp.JSON200.System.Os != nil { - version.System.OS = *resp.JSON200.System.Os + out.System.OS = *resp.JSON200.System.Os } if resp.JSON200.System.Architecture != nil { - version.System.Architecture = *resp.JSON200.System.Architecture + out.System.Architecture = *resp.JSON200.System.Architecture } if resp.JSON200.System.Cpus != nil { - version.System.CPUs = *resp.JSON200.System.Cpus + out.System.CPUs = *resp.JSON200.System.Cpus } } if resp.JSON200.Build != nil { if resp.JSON200.Build.Commit != nil { - version.Build.Commit = *resp.JSON200.Build.Commit + out.Build.Commit = *resp.JSON200.Build.Commit } if resp.JSON200.Build.BuildTime != nil { - version.Build.BuildTime = *resp.JSON200.Build.BuildTime + out.Build.BuildTime = *resp.JSON200.Build.BuildTime } if resp.JSON200.Build.GoVersion != nil { - version.Build.GoVersion = *resp.JSON200.Build.GoVersion + out.Build.GoVersion = *resp.JSON200.Build.GoVersion } } - return version, nil + return out, nil } // GetAppBundleManifest retrieves the app bundle manifest @@ -286,29 +293,41 @@ func (c *Client) downloadBinaryToFile(path string, destPath string) error { if err := c.ensureReady(); err != nil { return err } + apiVer, err := c.requiredVersion() + if err != nil { + return err + } var body []byte var status int - var err error switch path { case "/dataexport/parquet": var resp *generated.GetParquetExportZipHTTPResponse - resp, err = c.api.GetParquetExportZipWithResponse(context.Background()) + resp, err = c.api.GetParquetExportZipWithResponse( + context.Background(), + &generated.GetParquetExportZipParams{XOdeVersion: apiVer}, + ) if err == nil { body = resp.Body status = resp.StatusCode() } case "/dataexport/raw-json": var resp *generated.GetRawJsonExportZipHTTPResponse - resp, err = c.api.GetRawJsonExportZipWithResponse(context.Background()) + resp, err = c.api.GetRawJsonExportZipWithResponse( + context.Background(), + &generated.GetRawJsonExportZipParams{XOdeVersion: apiVer}, + ) if err == nil { body = resp.Body status = resp.StatusCode() } case "/attachments/export-zip": var resp *generated.GetAttachmentsExportZipHTTPResponse - resp, err = c.api.GetAttachmentsExportZipWithResponse(context.Background()) + resp, err = c.api.GetAttachmentsExportZipWithResponse( + context.Background(), + &generated.GetAttachmentsExportZipParams{XOdeVersion: apiVer}, + ) if err == nil { body = resp.Body status = resp.StatusCode() @@ -441,8 +460,13 @@ func (c *Client) AdminRepositoryReset() (*generated.RepositoryResetResponse, err if err := c.ensureReady(); err != nil { return nil, err } + apiVer, err := c.requiredVersion() + if err != nil { + return nil, err + } resp, err := c.api.AdminRepositoryResetWithResponse( context.Background(), + &generated.AdminRepositoryResetParams{XOdeVersion: apiVer}, generated.RepositoryResetRequest{Confirm: generated.RESETREPOSITORY}, ) if err != nil { diff --git a/synkronus-cli/pkg/client/generated/client.gen.go b/synkronus-cli/pkg/client/generated/client.gen.go index 88b79a028..2e543eaf6 100644 --- a/synkronus-cli/pkg/client/generated/client.gen.go +++ b/synkronus-cli/pkg/client/generated/client.gen.go @@ -118,6 +118,20 @@ func (e CreateUserJSONBodyRole) Valid() bool { } } +// APIVersionInfo defines model for APIVersionInfo. +type APIVersionInfo struct { + Deprecated bool `json:"deprecated"` + ReleaseDate openapi_types.Date `json:"releaseDate"` + Version string `json:"version"` +} + +// APIVersionsResponse defines model for APIVersionsResponse. +type APIVersionsResponse struct { + // Current Identifier of the current API contract version + Current string `json:"current"` + Versions []APIVersionInfo `json:"versions"` +} + // AppBundleChangeLog defines model for AppBundleChangeLog. type AppBundleChangeLog struct { CompareVersionA string `json:"compare_version_a"` @@ -359,7 +373,7 @@ type ServerInfo struct { type SyncPullRequest struct { ClientId string `json:"client_id"` - // RepositoryGeneration Optional body copy of epoch; header x-repository-generation wins when both are sent. + // RepositoryGeneration Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. RepositoryGeneration *int64 `json:"repository_generation,omitempty"` SchemaTypes *[]string `json:"schema_types,omitempty"` @@ -474,6 +488,9 @@ type UserResponse struct { // UserResponseRole defines model for UserResponse.Role. type UserResponseRole string +// XOdeVersion defines model for XOdeVersion. +type XOdeVersion = string + // Forbidden defines model for Forbidden. type Forbidden = ErrorResponse @@ -483,6 +500,12 @@ type InternalServerError = ErrorResponse // Unauthorized defines model for Unauthorized. type Unauthorized = ErrorResponse +// AdminRepositoryResetParams defines parameters for AdminRepositoryReset. +type AdminRepositoryResetParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + // GetAppBundleChangesParams defines parameters for GetAppBundleChanges. type GetAppBundleChangesParams struct { // Current The current version (defaults to latest) @@ -491,8 +514,14 @@ type GetAppBundleChangesParams struct { // Target The target version to compare against (defaults to previous version) Target *string `form:"target,omitempty" json:"target,omitempty"` - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// DownloadAppBundleZipParams defines parameters for DownloadAppBundleZip. +type DownloadAppBundleZipParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // DownloadAppBundleFileParams defines parameters for DownloadAppBundleFile. @@ -501,14 +530,14 @@ type DownloadAppBundleFileParams struct { Preview *bool `form:"preview,omitempty" json:"preview,omitempty"` IfNoneMatch *string `json:"if-none-match,omitempty"` - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // GetAppBundleManifestParams defines parameters for GetAppBundleManifest. type GetAppBundleManifestParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` // XOdeClientId Optional client instance id for correlating app bundle checks with presence. XOdeClientId *string `json:"x-ode-client-id,omitempty"` @@ -522,26 +551,32 @@ type PushAppBundleMultipartBody struct { // PushAppBundleParams defines parameters for PushAppBundle. type PushAppBundleParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // SwitchAppBundleVersionParams defines parameters for SwitchAppBundleVersion. type SwitchAppBundleVersionParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // GetAppBundleVersionsParams defines parameters for GetAppBundleVersions. type GetAppBundleVersionsParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// GetAttachmentsExportZipParams defines parameters for GetAttachmentsExportZip. +type GetAttachmentsExportZipParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // GetAttachmentManifestParams defines parameters for GetAttachmentManifest. type GetAttachmentManifestParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` // XRepositoryGeneration Client repository epoch; must match the server. Omitted or invalid values are treated as 1. XRepositoryGeneration *int64 `json:"x-repository-generation,omitempty"` @@ -551,12 +586,18 @@ type GetAttachmentManifestParams struct { type DownloadAttachmentParams struct { // Original Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. Original *string `form:"original,omitempty" json:"original,omitempty"` + + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // CheckAttachmentExistsParams defines parameters for CheckAttachmentExists. type CheckAttachmentExistsParams struct { // Original Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. Original *string `form:"original,omitempty" json:"original,omitempty"` + + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // UploadAttachmentMultipartBody defines parameters for UploadAttachment. @@ -567,6 +608,9 @@ type UploadAttachmentMultipartBody struct { // UploadAttachmentParams defines parameters for UploadAttachment. type UploadAttachmentParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` + // XRepositoryGeneration Client repository epoch; must match the server. Omitted or invalid values are treated as 1. XRepositoryGeneration *int64 `json:"x-repository-generation,omitempty"` } @@ -582,8 +626,8 @@ type LoginJSONBody struct { // LoginParams defines parameters for Login. type LoginParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // RefreshTokenJSONBody defines parameters for RefreshToken. @@ -594,8 +638,20 @@ type RefreshTokenJSONBody struct { // RefreshTokenParams defines parameters for RefreshToken. type RefreshTokenParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// GetParquetExportZipParams defines parameters for GetParquetExportZip. +type GetParquetExportZipParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// GetRawJsonExportZipParams defines parameters for GetRawJsonExportZip. +type GetRawJsonExportZipParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // SyncPullParams defines parameters for SyncPull. @@ -606,20 +662,20 @@ type SyncPullParams struct { // Limit Maximum number of records to return Limit *int `form:"limit,omitempty" json:"limit,omitempty"` - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` // XOdeClientId Optional client instance id; improves per-device presence when combined with sync body `client_id`. XOdeClientId *string `json:"x-ode-client-id,omitempty"` - // XRepositoryGeneration Client's repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + // XRepositoryGeneration Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. XRepositoryGeneration *int64 `json:"x-repository-generation,omitempty"` } // SyncPushParams defines parameters for SyncPush. type SyncPushParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` // XOdeClientId Optional client instance id; improves per-device presence when combined with sync body `client_id`. XOdeClientId *string `json:"x-ode-client-id,omitempty"` @@ -630,8 +686,8 @@ type SyncPushParams struct { // ListUsersParams defines parameters for ListUsers. type ListUsersParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` // XOdeClientId Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. XOdeClientId *string `json:"x-ode-client-id,omitempty"` @@ -648,8 +704,8 @@ type ChangePasswordJSONBody struct { // ChangePasswordParams defines parameters for ChangePassword. type ChangePasswordParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // CreateUserJSONBody defines parameters for CreateUser. @@ -666,8 +722,8 @@ type CreateUserJSONBody struct { // CreateUserParams defines parameters for CreateUser. type CreateUserParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // CreateUserJSONBodyRole defines parameters for CreateUser. @@ -684,14 +740,26 @@ type ResetUserPasswordJSONBody struct { // ResetUserPasswordParams defines parameters for ResetUserPassword. type ResetUserPasswordParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // DeleteUserParams defines parameters for DeleteUser. type DeleteUserParams struct { - // XOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. - XOdeVersion string `json:"x-ode-version"` + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// GetVersionParams defines parameters for GetVersion. +type GetVersionParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` +} + +// GetAPIVersionsParams defines parameters for GetAPIVersions. +type GetAPIVersionsParams struct { + // XOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + XOdeVersion XOdeVersion `json:"x-ode-version"` } // AdminRepositoryResetJSONRequestBody defines body for AdminRepositoryReset for application/json ContentType. @@ -801,13 +869,16 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { // AdminRepositoryResetWithBody request with any body - AdminRepositoryResetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + AdminRepositoryResetWithBody(ctx context.Context, params *AdminRepositoryResetParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - AdminRepositoryReset(ctx context.Context, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + AdminRepositoryReset(ctx context.Context, params *AdminRepositoryResetParams, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetAppBundleChanges request GetAppBundleChanges(ctx context.Context, params *GetAppBundleChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DownloadAppBundleZip request + DownloadAppBundleZip(ctx context.Context, params *DownloadAppBundleZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DownloadAppBundleFile request DownloadAppBundleFile(ctx context.Context, path string, params *DownloadAppBundleFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -824,7 +895,7 @@ type ClientInterface interface { GetAppBundleVersions(ctx context.Context, params *GetAppBundleVersionsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetAttachmentsExportZip request - GetAttachmentsExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + GetAttachmentsExportZip(ctx context.Context, params *GetAttachmentsExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetAttachmentManifestWithBody request with any body GetAttachmentManifestWithBody(ctx context.Context, params *GetAttachmentManifestParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -851,10 +922,10 @@ type ClientInterface interface { RefreshToken(ctx context.Context, params *RefreshTokenParams, body RefreshTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetParquetExportZip request - GetParquetExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + GetParquetExportZip(ctx context.Context, params *GetParquetExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetRawJsonExportZip request - GetRawJsonExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + GetRawJsonExportZip(ctx context.Context, params *GetRawJsonExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // SyncPullWithBody request with any body SyncPullWithBody(ctx context.Context, params *SyncPullParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -888,14 +959,17 @@ type ClientInterface interface { DeleteUser(ctx context.Context, username string, params *DeleteUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetVersion request - GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + GetVersion(ctx context.Context, params *GetVersionParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAPIVersions request + GetAPIVersions(ctx context.Context, params *GetAPIVersionsParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetHealth request GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) AdminRepositoryResetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAdminRepositoryResetRequestWithBody(c.Server, contentType, body) +func (c *Client) AdminRepositoryResetWithBody(ctx context.Context, params *AdminRepositoryResetParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAdminRepositoryResetRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -906,8 +980,8 @@ func (c *Client) AdminRepositoryResetWithBody(ctx context.Context, contentType s return c.Client.Do(req) } -func (c *Client) AdminRepositoryReset(ctx context.Context, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAdminRepositoryResetRequest(c.Server, body) +func (c *Client) AdminRepositoryReset(ctx context.Context, params *AdminRepositoryResetParams, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAdminRepositoryResetRequest(c.Server, params, body) if err != nil { return nil, err } @@ -930,6 +1004,18 @@ func (c *Client) GetAppBundleChanges(ctx context.Context, params *GetAppBundleCh return c.Client.Do(req) } +func (c *Client) DownloadAppBundleZip(ctx context.Context, params *DownloadAppBundleZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDownloadAppBundleZipRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DownloadAppBundleFile(ctx context.Context, path string, params *DownloadAppBundleFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDownloadAppBundleFileRequest(c.Server, path, params) if err != nil { @@ -990,8 +1076,8 @@ func (c *Client) GetAppBundleVersions(ctx context.Context, params *GetAppBundleV return c.Client.Do(req) } -func (c *Client) GetAttachmentsExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetAttachmentsExportZipRequest(c.Server) +func (c *Client) GetAttachmentsExportZip(ctx context.Context, params *GetAttachmentsExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAttachmentsExportZipRequest(c.Server, params) if err != nil { return nil, err } @@ -1110,8 +1196,8 @@ func (c *Client) RefreshToken(ctx context.Context, params *RefreshTokenParams, b return c.Client.Do(req) } -func (c *Client) GetParquetExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetParquetExportZipRequest(c.Server) +func (c *Client) GetParquetExportZip(ctx context.Context, params *GetParquetExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetParquetExportZipRequest(c.Server, params) if err != nil { return nil, err } @@ -1122,8 +1208,8 @@ func (c *Client) GetParquetExportZip(ctx context.Context, reqEditors ...RequestE return c.Client.Do(req) } -func (c *Client) GetRawJsonExportZip(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetRawJsonExportZipRequest(c.Server) +func (c *Client) GetRawJsonExportZip(ctx context.Context, params *GetRawJsonExportZipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRawJsonExportZipRequest(c.Server, params) if err != nil { return nil, err } @@ -1278,8 +1364,20 @@ func (c *Client) DeleteUser(ctx context.Context, username string, params *Delete return c.Client.Do(req) } -func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetVersionRequest(c.Server) +func (c *Client) GetVersion(ctx context.Context, params *GetVersionParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVersionRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAPIVersions(ctx context.Context, params *GetAPIVersionsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAPIVersionsRequest(c.Server, params) if err != nil { return nil, err } @@ -1303,18 +1401,18 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) ( } // NewAdminRepositoryResetRequest calls the generic AdminRepositoryReset builder with application/json body -func NewAdminRepositoryResetRequest(server string, body AdminRepositoryResetJSONRequestBody) (*http.Request, error) { +func NewAdminRepositoryResetRequest(server string, params *AdminRepositoryResetParams, body AdminRepositoryResetJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewAdminRepositoryResetRequestWithBody(server, "application/json", bodyReader) + return NewAdminRepositoryResetRequestWithBody(server, params, "application/json", bodyReader) } // NewAdminRepositoryResetRequestWithBody generates requests for AdminRepositoryReset with any type of body -func NewAdminRepositoryResetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +func NewAdminRepositoryResetRequestWithBody(server string, params *AdminRepositoryResetParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1339,6 +1437,19 @@ func NewAdminRepositoryResetRequestWithBody(server string, contentType string, b req.Header.Add("Content-Type", contentType) + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -1420,6 +1531,46 @@ func NewGetAppBundleChangesRequest(server string, params *GetAppBundleChangesPar return req, nil } +// NewDownloadAppBundleZipRequest generates requests for DownloadAppBundleZip +func NewDownloadAppBundleZipRequest(server string, params *DownloadAppBundleZipParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/app-bundle/download-zip") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + + return req, nil +} + // NewDownloadAppBundleFileRequest generates requests for DownloadAppBundleFile func NewDownloadAppBundleFileRequest(server string, path string, params *DownloadAppBundleFileParams) (*http.Request, error) { var err error @@ -1681,7 +1832,7 @@ func NewGetAppBundleVersionsRequest(server string, params *GetAppBundleVersionsP } // NewGetAttachmentsExportZipRequest generates requests for GetAttachmentsExportZip -func NewGetAttachmentsExportZipRequest(server string) (*http.Request, error) { +func NewGetAttachmentsExportZipRequest(server string, params *GetAttachmentsExportZipParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1704,6 +1855,19 @@ func NewGetAttachmentsExportZipRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -1824,6 +1988,19 @@ func NewDownloadAttachmentRequest(server string, attachmentId string, params *Do return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -1880,6 +2057,19 @@ func NewCheckAttachmentExistsRequest(server string, attachmentId string, params return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -1918,15 +2108,24 @@ func NewUploadAttachmentRequestWithBody(server string, attachmentId string, para if params != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + if params.XRepositoryGeneration != nil { - var headerParam0 string + var headerParam1 string - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-repository-generation", *params.XRepositoryGeneration, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "integer", Format: "int64"}) + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "x-repository-generation", *params.XRepositoryGeneration, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "integer", Format: "int64"}) if err != nil { return nil, err } - req.Header.Set("x-repository-generation", headerParam0) + req.Header.Set("x-repository-generation", headerParam1) } } @@ -2041,7 +2240,7 @@ func NewRefreshTokenRequestWithBody(server string, params *RefreshTokenParams, c } // NewGetParquetExportZipRequest generates requests for GetParquetExportZip -func NewGetParquetExportZipRequest(server string) (*http.Request, error) { +func NewGetParquetExportZipRequest(server string, params *GetParquetExportZipParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -2064,11 +2263,24 @@ func NewGetParquetExportZipRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } // NewGetRawJsonExportZipRequest generates requests for GetRawJsonExportZip -func NewGetRawJsonExportZipRequest(server string) (*http.Request, error) { +func NewGetRawJsonExportZipRequest(server string, params *GetRawJsonExportZipParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -2091,6 +2303,19 @@ func NewGetRawJsonExportZipRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -2540,7 +2765,7 @@ func NewDeleteUserRequest(server string, username string, params *DeleteUserPara } // NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { +func NewGetVersionRequest(server string, params *GetVersionParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -2563,6 +2788,59 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + + return req, nil +} + +// NewGetAPIVersionsRequest generates requests for GetAPIVersions +func NewGetAPIVersionsRequest(server string, params *GetAPIVersionsParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/versions") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "x-ode-version", params.XOdeVersion, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("x-ode-version", headerParam0) + + } + return req, nil } @@ -2637,13 +2915,16 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { // AdminRepositoryResetWithBodyWithResponse request with any body - AdminRepositoryResetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) + AdminRepositoryResetWithBodyWithResponse(ctx context.Context, params *AdminRepositoryResetParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) - AdminRepositoryResetWithResponse(ctx context.Context, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) + AdminRepositoryResetWithResponse(ctx context.Context, params *AdminRepositoryResetParams, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) // GetAppBundleChangesWithResponse request GetAppBundleChangesWithResponse(ctx context.Context, params *GetAppBundleChangesParams, reqEditors ...RequestEditorFn) (*GetAppBundleChangesHTTPResponse, error) + // DownloadAppBundleZipWithResponse request + DownloadAppBundleZipWithResponse(ctx context.Context, params *DownloadAppBundleZipParams, reqEditors ...RequestEditorFn) (*DownloadAppBundleZipHTTPResponse, error) + // DownloadAppBundleFileWithResponse request DownloadAppBundleFileWithResponse(ctx context.Context, path string, params *DownloadAppBundleFileParams, reqEditors ...RequestEditorFn) (*DownloadAppBundleFileHTTPResponse, error) @@ -2660,7 +2941,7 @@ type ClientWithResponsesInterface interface { GetAppBundleVersionsWithResponse(ctx context.Context, params *GetAppBundleVersionsParams, reqEditors ...RequestEditorFn) (*GetAppBundleVersionsHTTPResponse, error) // GetAttachmentsExportZipWithResponse request - GetAttachmentsExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAttachmentsExportZipHTTPResponse, error) + GetAttachmentsExportZipWithResponse(ctx context.Context, params *GetAttachmentsExportZipParams, reqEditors ...RequestEditorFn) (*GetAttachmentsExportZipHTTPResponse, error) // GetAttachmentManifestWithBodyWithResponse request with any body GetAttachmentManifestWithBodyWithResponse(ctx context.Context, params *GetAttachmentManifestParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*GetAttachmentManifestHTTPResponse, error) @@ -2687,10 +2968,10 @@ type ClientWithResponsesInterface interface { RefreshTokenWithResponse(ctx context.Context, params *RefreshTokenParams, body RefreshTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*RefreshTokenHTTPResponse, error) // GetParquetExportZipWithResponse request - GetParquetExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetParquetExportZipHTTPResponse, error) + GetParquetExportZipWithResponse(ctx context.Context, params *GetParquetExportZipParams, reqEditors ...RequestEditorFn) (*GetParquetExportZipHTTPResponse, error) // GetRawJsonExportZipWithResponse request - GetRawJsonExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetRawJsonExportZipHTTPResponse, error) + GetRawJsonExportZipWithResponse(ctx context.Context, params *GetRawJsonExportZipParams, reqEditors ...RequestEditorFn) (*GetRawJsonExportZipHTTPResponse, error) // SyncPullWithBodyWithResponse request with any body SyncPullWithBodyWithResponse(ctx context.Context, params *SyncPullParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SyncPullHTTPResponse, error) @@ -2724,7 +3005,10 @@ type ClientWithResponsesInterface interface { DeleteUserWithResponse(ctx context.Context, username string, params *DeleteUserParams, reqEditors ...RequestEditorFn) (*DeleteUserHTTPResponse, error) // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionHTTPResponse, error) + GetVersionWithResponse(ctx context.Context, params *GetVersionParams, reqEditors ...RequestEditorFn) (*GetVersionHTTPResponse, error) + + // GetAPIVersionsWithResponse request + GetAPIVersionsWithResponse(ctx context.Context, params *GetAPIVersionsParams, reqEditors ...RequestEditorFn) (*GetAPIVersionsHTTPResponse, error) // GetHealthWithResponse request GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthHTTPResponse, error) @@ -2781,6 +3065,30 @@ func (r GetAppBundleChangesHTTPResponse) StatusCode() int { return 0 } +type DownloadAppBundleZipHTTPResponse struct { + Body []byte + HTTPResponse *http.Response + JSON401 *Unauthorized + JSON404 *ErrorResponse + JSON500 *InternalServerError +} + +// Status returns HTTPResponse.Status +func (r DownloadAppBundleZipHTTPResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DownloadAppBundleZipHTTPResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DownloadAppBundleFileHTTPResponse struct { Body []byte HTTPResponse *http.Response @@ -3118,6 +3426,7 @@ type SyncPullHTTPResponse struct { Body []byte HTTPResponse *http.Response JSON200 *SyncPullResponse + JSON409 *ErrorResponse } // Status returns HTTPResponse.Status @@ -3295,6 +3604,7 @@ type GetVersionHTTPResponse struct { Body []byte HTTPResponse *http.Response JSON200 *SystemVersionInfo + JSON401 *Unauthorized JSON500 *ErrorResponse } @@ -3314,6 +3624,29 @@ func (r GetVersionHTTPResponse) StatusCode() int { return 0 } +type GetAPIVersionsHTTPResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *APIVersionsResponse + JSON401 *Unauthorized +} + +// Status returns HTTPResponse.Status +func (r GetAPIVersionsHTTPResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAPIVersionsHTTPResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetHealthHTTPResponse struct { Body []byte HTTPResponse *http.Response @@ -3355,16 +3688,16 @@ func (r GetHealthHTTPResponse) StatusCode() int { } // AdminRepositoryResetWithBodyWithResponse request with arbitrary body returning *AdminRepositoryResetHTTPResponse -func (c *ClientWithResponses) AdminRepositoryResetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) { - rsp, err := c.AdminRepositoryResetWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) AdminRepositoryResetWithBodyWithResponse(ctx context.Context, params *AdminRepositoryResetParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) { + rsp, err := c.AdminRepositoryResetWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseAdminRepositoryResetHTTPResponse(rsp) } -func (c *ClientWithResponses) AdminRepositoryResetWithResponse(ctx context.Context, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) { - rsp, err := c.AdminRepositoryReset(ctx, body, reqEditors...) +func (c *ClientWithResponses) AdminRepositoryResetWithResponse(ctx context.Context, params *AdminRepositoryResetParams, body AdminRepositoryResetJSONRequestBody, reqEditors ...RequestEditorFn) (*AdminRepositoryResetHTTPResponse, error) { + rsp, err := c.AdminRepositoryReset(ctx, params, body, reqEditors...) if err != nil { return nil, err } @@ -3380,6 +3713,15 @@ func (c *ClientWithResponses) GetAppBundleChangesWithResponse(ctx context.Contex return ParseGetAppBundleChangesHTTPResponse(rsp) } +// DownloadAppBundleZipWithResponse request returning *DownloadAppBundleZipHTTPResponse +func (c *ClientWithResponses) DownloadAppBundleZipWithResponse(ctx context.Context, params *DownloadAppBundleZipParams, reqEditors ...RequestEditorFn) (*DownloadAppBundleZipHTTPResponse, error) { + rsp, err := c.DownloadAppBundleZip(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDownloadAppBundleZipHTTPResponse(rsp) +} + // DownloadAppBundleFileWithResponse request returning *DownloadAppBundleFileHTTPResponse func (c *ClientWithResponses) DownloadAppBundleFileWithResponse(ctx context.Context, path string, params *DownloadAppBundleFileParams, reqEditors ...RequestEditorFn) (*DownloadAppBundleFileHTTPResponse, error) { rsp, err := c.DownloadAppBundleFile(ctx, path, params, reqEditors...) @@ -3426,8 +3768,8 @@ func (c *ClientWithResponses) GetAppBundleVersionsWithResponse(ctx context.Conte } // GetAttachmentsExportZipWithResponse request returning *GetAttachmentsExportZipHTTPResponse -func (c *ClientWithResponses) GetAttachmentsExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAttachmentsExportZipHTTPResponse, error) { - rsp, err := c.GetAttachmentsExportZip(ctx, reqEditors...) +func (c *ClientWithResponses) GetAttachmentsExportZipWithResponse(ctx context.Context, params *GetAttachmentsExportZipParams, reqEditors ...RequestEditorFn) (*GetAttachmentsExportZipHTTPResponse, error) { + rsp, err := c.GetAttachmentsExportZip(ctx, params, reqEditors...) if err != nil { return nil, err } @@ -3513,8 +3855,8 @@ func (c *ClientWithResponses) RefreshTokenWithResponse(ctx context.Context, para } // GetParquetExportZipWithResponse request returning *GetParquetExportZipHTTPResponse -func (c *ClientWithResponses) GetParquetExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetParquetExportZipHTTPResponse, error) { - rsp, err := c.GetParquetExportZip(ctx, reqEditors...) +func (c *ClientWithResponses) GetParquetExportZipWithResponse(ctx context.Context, params *GetParquetExportZipParams, reqEditors ...RequestEditorFn) (*GetParquetExportZipHTTPResponse, error) { + rsp, err := c.GetParquetExportZip(ctx, params, reqEditors...) if err != nil { return nil, err } @@ -3522,8 +3864,8 @@ func (c *ClientWithResponses) GetParquetExportZipWithResponse(ctx context.Contex } // GetRawJsonExportZipWithResponse request returning *GetRawJsonExportZipHTTPResponse -func (c *ClientWithResponses) GetRawJsonExportZipWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetRawJsonExportZipHTTPResponse, error) { - rsp, err := c.GetRawJsonExportZip(ctx, reqEditors...) +func (c *ClientWithResponses) GetRawJsonExportZipWithResponse(ctx context.Context, params *GetRawJsonExportZipParams, reqEditors ...RequestEditorFn) (*GetRawJsonExportZipHTTPResponse, error) { + rsp, err := c.GetRawJsonExportZip(ctx, params, reqEditors...) if err != nil { return nil, err } @@ -3634,14 +3976,23 @@ func (c *ClientWithResponses) DeleteUserWithResponse(ctx context.Context, userna } // GetVersionWithResponse request returning *GetVersionHTTPResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionHTTPResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, params *GetVersionParams, reqEditors ...RequestEditorFn) (*GetVersionHTTPResponse, error) { + rsp, err := c.GetVersion(ctx, params, reqEditors...) if err != nil { return nil, err } return ParseGetVersionHTTPResponse(rsp) } +// GetAPIVersionsWithResponse request returning *GetAPIVersionsHTTPResponse +func (c *ClientWithResponses) GetAPIVersionsWithResponse(ctx context.Context, params *GetAPIVersionsParams, reqEditors ...RequestEditorFn) (*GetAPIVersionsHTTPResponse, error) { + rsp, err := c.GetAPIVersions(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAPIVersionsHTTPResponse(rsp) +} + // GetHealthWithResponse request returning *GetHealthHTTPResponse func (c *ClientWithResponses) GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthHTTPResponse, error) { rsp, err := c.GetHealth(ctx, reqEditors...) @@ -3752,6 +4103,46 @@ func ParseGetAppBundleChangesHTTPResponse(rsp *http.Response) (*GetAppBundleChan return response, nil } +// ParseDownloadAppBundleZipHTTPResponse parses an HTTP response from a DownloadAppBundleZipWithResponse call +func ParseDownloadAppBundleZipHTTPResponse(rsp *http.Response) (*DownloadAppBundleZipHTTPResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DownloadAppBundleZipHTTPResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Unauthorized + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseDownloadAppBundleFileHTTPResponse parses an HTTP response from a DownloadAppBundleFileWithResponse call func ParseDownloadAppBundleFileHTTPResponse(rsp *http.Response) (*DownloadAppBundleFileHTTPResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4279,6 +4670,13 @@ func ParseSyncPullHTTPResponse(rsp *http.Response) (*SyncPullHTTPResponse, error } response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + } return response, nil @@ -4586,6 +4984,13 @@ func ParseGetVersionHTTPResponse(rsp *http.Response) (*GetVersionHTTPResponse, e } response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Unauthorized + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest ErrorResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -4598,6 +5003,39 @@ func ParseGetVersionHTTPResponse(rsp *http.Response) (*GetVersionHTTPResponse, e return response, nil } +// ParseGetAPIVersionsHTTPResponse parses an HTTP response from a GetAPIVersionsWithResponse call +func ParseGetAPIVersionsHTTPResponse(rsp *http.Response) (*GetAPIVersionsHTTPResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAPIVersionsHTTPResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest APIVersionsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Unauthorized + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + } + + return response, nil +} + // ParseGetHealthHTTPResponse parses an HTTP response from a GetHealthWithResponse call func ParseGetHealthHTTPResponse(rsp *http.Response) (*GetHealthHTTPResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/synkronus-portal/src/api/synkronus/generated/.openapi-generator/FILES b/synkronus-portal/src/api/synkronus/generated/.openapi-generator/FILES index 6e35b1463..03184907e 100644 --- a/synkronus-portal/src/api/synkronus/generated/.openapi-generator/FILES +++ b/synkronus-portal/src/api/synkronus/generated/.openapi-generator/FILES @@ -4,6 +4,8 @@ api.ts base.ts common.ts configuration.ts +docs/APIVersionInfo.md +docs/APIVersionsResponse.md docs/AppBundleChangeLog.md docs/AppBundleFile.md docs/AppBundleManifest.md diff --git a/synkronus-portal/src/api/synkronus/generated/api.ts b/synkronus-portal/src/api/synkronus/generated/api.ts index 3b53651e6..953bba28f 100644 --- a/synkronus-portal/src/api/synkronus/generated/api.ts +++ b/synkronus-portal/src/api/synkronus/generated/api.ts @@ -40,6 +40,18 @@ import { operationServerMap, } from './base'; +export interface APIVersionInfo { + version: string; + releaseDate: string; + deprecated: boolean; +} +export interface APIVersionsResponse { + versions: Array; + /** + * Identifier of the current API contract version + */ + current: string; +} export interface AppBundleChangeLog { compare_version_a: string; compare_version_b: string; @@ -398,7 +410,7 @@ export interface SwitchAppBundleVersion200Response { export interface SyncPullRequest { client_id: string; /** - * Optional body copy of epoch; header x-repository-generation wins when both are sent. + * Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. */ repository_generation?: number; since?: SyncPullRequestSince; @@ -539,12 +551,16 @@ export const AttachmentsApiAxiosParamCreator = function ( /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getAttachmentsExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getAttachmentsExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/export-zip`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -567,6 +583,9 @@ export const AttachmentsApiAxiosParamCreator = function ( localVarHeaderParameter['Accept'] = 'application/zip,application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -594,16 +613,21 @@ export const AttachmentsApiFp = function (configuration?: Configuration) { /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getAttachmentsExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getAttachmentsExportZip(options); + await localVarAxiosParamCreator.getAttachmentsExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['AttachmentsApi.getAttachmentsExportZip']?.[ @@ -633,19 +657,31 @@ export const AttachmentsApiFactory = function ( /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {AttachmentsApiGetAttachmentsExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ getAttachmentsExportZip( + requestParameters: AttachmentsApiGetAttachmentsExportZipRequest, options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .getAttachmentsExportZip(options) + .getAttachmentsExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, }; }; +/** + * Request parameters for getAttachmentsExportZip operation in AttachmentsApi. + */ +export interface AttachmentsApiGetAttachmentsExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + /** * AttachmentsApi - object-oriented interface */ @@ -653,12 +689,16 @@ export class AttachmentsApi extends BaseAPI { /** * Returns a ZIP containing every attachment whose latest manifest operation is create or update. Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. * @summary Download all attachments as a streamed ZIP + * @param {AttachmentsApiGetAttachmentsExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public getAttachmentsExportZip(options?: RawAxiosRequestConfig) { + public getAttachmentsExportZip( + requestParameters: AttachmentsApiGetAttachmentsExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return AttachmentsApiFp(this.configuration) - .getAttachmentsExportZip(options) + .getAttachmentsExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } } @@ -673,12 +713,16 @@ export const DataExportApiAxiosParamCreator = function ( /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getParquetExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getParquetExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/dataexport/parquet`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -701,6 +745,9 @@ export const DataExportApiAxiosParamCreator = function ( localVarHeaderParameter['Accept'] = 'application/zip,application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -718,12 +765,16 @@ export const DataExportApiAxiosParamCreator = function ( /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getRawJsonExportZip: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getRawJsonExportZip', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/dataexport/raw-json`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -746,6 +797,9 @@ export const DataExportApiAxiosParamCreator = function ( localVarHeaderParameter['Accept'] = 'application/zip,application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -773,16 +827,21 @@ export const DataExportApiFp = function (configuration?: Configuration) { /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getParquetExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getParquetExportZip(options); + await localVarAxiosParamCreator.getParquetExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DataExportApi.getParquetExportZip']?.[ @@ -799,16 +858,21 @@ export const DataExportApiFp = function (configuration?: Configuration) { /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getRawJsonExportZip( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.getRawJsonExportZip(options); + await localVarAxiosParamCreator.getRawJsonExportZip( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DataExportApi.getRawJsonExportZip']?.[ @@ -838,28 +902,56 @@ export const DataExportApiFactory = function ( /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {DataExportApiGetParquetExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getParquetExportZip(options?: RawAxiosRequestConfig): AxiosPromise { + getParquetExportZip( + requestParameters: DataExportApiGetParquetExportZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { return localVarFp - .getParquetExportZip(options) + .getParquetExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {DataExportApiGetRawJsonExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getRawJsonExportZip(options?: RawAxiosRequestConfig): AxiosPromise { + getRawJsonExportZip( + requestParameters: DataExportApiGetRawJsonExportZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { return localVarFp - .getRawJsonExportZip(options) + .getRawJsonExportZip(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, }; }; +/** + * Request parameters for getParquetExportZip operation in DataExportApi. + */ +export interface DataExportApiGetParquetExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + +/** + * Request parameters for getRawJsonExportZip operation in DataExportApi. + */ +export interface DataExportApiGetRawJsonExportZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + /** * DataExportApi - object-oriented interface */ @@ -867,24 +959,32 @@ export class DataExportApi extends BaseAPI { /** * Returns a ZIP file containing multiple Parquet files, each representing a flattened export of observations per form type. Supports downloading the entire dataset as separate Parquet files bundled together. * @summary Download a ZIP archive of Parquet exports + * @param {DataExportApiGetParquetExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public getParquetExportZip(options?: RawAxiosRequestConfig) { + public getParquetExportZip( + requestParameters: DataExportApiGetParquetExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return DataExportApiFp(this.configuration) - .getParquetExportZip(options) + .getParquetExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } /** * Returns a ZIP archive where each non-deleted observation is one JSON file, grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. * @summary Download a ZIP archive of per-observation JSON files + * @param {DataExportApiGetRawJsonExportZipRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public getRawJsonExportZip(options?: RawAxiosRequestConfig) { + public getRawJsonExportZip( + requestParameters: DataExportApiGetRawJsonExportZipRequest, + options?: RawAxiosRequestConfig, + ) { return DataExportApiFp(this.configuration) - .getRawJsonExportZip(options) + .getRawJsonExportZip(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } } @@ -899,14 +999,18 @@ export const DefaultApiAxiosParamCreator = function ( /** * Destructive operation: deletes all observations and attachment manifest rows, resets the observation stream cursor, increments repository_generation, and clears attachment files on disk. App bundles are not removed. Requires body `{ \"confirm\": \"RESET_REPOSITORY\" }`. * @summary Irreversibly wipe server observation and attachment sync data (admin only) + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RepositoryResetRequest} repositoryResetRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ adminRepositoryReset: async ( + xOdeVersion: string, repositoryResetRequest: RepositoryResetRequest, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('adminRepositoryReset', 'xOdeVersion', xOdeVersion); // verify required parameter 'repositoryResetRequest' is not null or undefined assertParamExists( 'adminRepositoryReset', @@ -936,6 +1040,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarHeaderParameter['Content-Type'] = 'application/json'; localVarHeaderParameter['Accept'] = 'application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -958,7 +1065,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Change password for the currently authenticated user * @summary Change user password (authenticated user)\'s password - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ChangePasswordRequest} changePasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1026,17 +1133,21 @@ export const DefaultApiAxiosParamCreator = function ( * Checks whether the attachment is available for download. If `original=true` (or `1` / `yes`), existence is checked against the original file first, with fallback to the processed file. * @summary Check if an attachment exists * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ checkAttachmentExists: async ( attachmentId: string, + xOdeVersion: string, original?: string, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('checkAttachmentExists', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('checkAttachmentExists', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/{attachment_id}`.replace( `{${'attachment_id'}}`, encodeURIComponent(String(attachmentId)), @@ -1064,6 +1175,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarQueryParameter['original'] = original; } + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1081,7 +1195,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Create a new user with specified username, password, and role * @summary Create a new user (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {CreateUserRequest} createUserRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1145,7 +1259,7 @@ export const DefaultApiAxiosParamCreator = function ( * Delete a user by username * @summary Delete a user (admin only) * @param {string} username Username of the user to delete - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1205,7 +1319,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Download a specific file from the app bundle * @param {string} path - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {boolean} [preview] If true, returns the file from the latest version including unreleased changes * @param {string} [ifNoneMatch] * @param {*} [options] Override http request option. @@ -1271,21 +1385,77 @@ export const DefaultApiAxiosParamCreator = function ( options: localVarRequestOptions, }; }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + downloadAppBundleZip: async ( + xOdeVersion: string, + options: RawAxiosRequestConfig = {}, + ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('downloadAppBundleZip', 'xOdeVersion', xOdeVersion); + const localVarPath = `/api/app-bundle/download-zip`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: 'GET', + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + localVarHeaderParameter['Accept'] = 'application/zip,application/json'; + + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ downloadAttachment: async ( attachmentId: string, + xOdeVersion: string, original?: string, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('downloadAttachment', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('downloadAttachment', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/attachments/{attachment_id}`.replace( `{${'attachment_id'}}`, encodeURIComponent(String(attachmentId)), @@ -1315,6 +1485,61 @@ export const DefaultApiAxiosParamCreator = function ( localVarHeaderParameter['Accept'] = 'application/octet-stream'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAPIVersions: async ( + xOdeVersion: string, + options: RawAxiosRequestConfig = {}, + ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getAPIVersions', 'xOdeVersion', xOdeVersion); + const localVarPath = `/api/versions`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: 'GET', + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + localVarHeaderParameter['Accept'] = 'application/json'; + + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1332,7 +1557,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [current] The current version (defaults to latest) * @param {string} [target] The target version to compare against (defaults to previous version) * @param {*} [options] Override http request option. @@ -1396,7 +1621,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Get the current custom app bundle manifest - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id for correlating app bundle checks with presence. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1453,7 +1678,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Get a list of available app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1505,7 +1730,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Returns a manifest of attachment changes (new, updated, deleted) since a specified data version * @summary Get attachment manifest for incremental sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {AttachmentManifestRequest} attachmentManifestRequest * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -1582,12 +1807,16 @@ export const DefaultApiAxiosParamCreator = function ( /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ getVersion: async ( + xOdeVersion: string, options: RawAxiosRequestConfig = {}, ): Promise => { + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('getVersion', 'xOdeVersion', xOdeVersion); const localVarPath = `/api/version`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -1604,8 +1833,15 @@ export const DefaultApiAxiosParamCreator = function ( const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + localVarHeaderParameter['Accept'] = 'application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1623,7 +1859,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Retrieve a list of all users in the system. Admin access required. Each item may include optional `presence` (last-seen per client, bundle/Ode hints) when the server has recorded activity. * @summary List all users (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1681,7 +1917,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Obtain a JWT token by providing username and password * @summary Authenticate user and return JWT tokens - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {LoginRequest} loginRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1740,7 +1976,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Upload a new app bundle (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} [bundle] ZIP file containing the new app bundle * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1804,7 +2040,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Obtain a new JWT token using a refresh token * @summary Refresh JWT token - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RefreshTokenRequest} refreshTokenRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1867,7 +2103,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * Reset password for a specified user * @summary Reset user password (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ResetUserPasswordRequest} resetUserPasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1935,7 +2171,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Switch to a specific app bundle version (admin only) * @param {string} version Version identifier to switch to - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1994,12 +2230,12 @@ export const DefaultApiAxiosParamCreator = function ( /** * Retrieves records that have changed since a specified version. **Pagination Pattern:** 1. Send initial request with `since.version` (or omit for all records) 2. Process returned records 3. If `has_more` is true, make next request using `change_cutoff` as the new `since.version` 4. Repeat until `has_more` is false Example pagination flow: - Request 1: `since: {version: 100}` → Response: `change_cutoff: 150, has_more: true` - Request 2: `since: {version: 150}` → Response: `change_cutoff: 200, has_more: false` * @summary Pull updated records since last sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPullRequest} syncPullRequest * @param {string} [schemaType] Filter by schemaType * @param {number} [limit] Maximum number of records to return * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. - * @param {number} [xRepositoryGeneration] Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2084,7 +2320,7 @@ export const DefaultApiAxiosParamCreator = function ( /** * * @summary Push new or updated records to the server - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPushRequest} syncPushRequest * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. @@ -2163,6 +2399,7 @@ export const DefaultApiAxiosParamCreator = function ( * * @summary Upload a new attachment with specified ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} file The binary file to upload * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -2170,12 +2407,15 @@ export const DefaultApiAxiosParamCreator = function ( */ uploadAttachment: async ( attachmentId: string, + xOdeVersion: string, file: File, xRepositoryGeneration?: number, options: RawAxiosRequestConfig = {}, ): Promise => { // verify required parameter 'attachmentId' is not null or undefined assertParamExists('uploadAttachment', 'attachmentId', attachmentId); + // verify required parameter 'xOdeVersion' is not null or undefined + assertParamExists('uploadAttachment', 'xOdeVersion', xOdeVersion); // verify required parameter 'file' is not null or undefined assertParamExists('uploadAttachment', 'file', file); const localVarPath = `/api/attachments/{attachment_id}`.replace( @@ -2211,6 +2451,9 @@ export const DefaultApiAxiosParamCreator = function ( localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; localVarHeaderParameter['Accept'] = 'application/json'; + if (xOdeVersion != null) { + localVarHeaderParameter['x-ode-version'] = String(xOdeVersion); + } if (xRepositoryGeneration != null) { localVarHeaderParameter['x-repository-generation'] = typeof xRepositoryGeneration === 'string' @@ -2247,11 +2490,13 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Destructive operation: deletes all observations and attachment manifest rows, resets the observation stream cursor, increments repository_generation, and clears attachment files on disk. App bundles are not removed. Requires body `{ \"confirm\": \"RESET_REPOSITORY\" }`. * @summary Irreversibly wipe server observation and attachment sync data (admin only) + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RepositoryResetRequest} repositoryResetRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ async adminRepositoryReset( + xOdeVersion: string, repositoryResetRequest: RepositoryResetRequest, options?: RawAxiosRequestConfig, ): Promise< @@ -2262,6 +2507,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { > { const localVarAxiosArgs = await localVarAxiosParamCreator.adminRepositoryReset( + xOdeVersion, repositoryResetRequest, options, ); @@ -2281,7 +2527,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Change password for the currently authenticated user * @summary Change user password (authenticated user)\'s password - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ChangePasswordRequest} changePasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2318,12 +2564,14 @@ export const DefaultApiFp = function (configuration?: Configuration) { * Checks whether the attachment is available for download. If `original=true` (or `1` / `yes`), existence is checked against the original file first, with fallback to the processed file. * @summary Check if an attachment exists * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ async checkAttachmentExists( attachmentId: string, + xOdeVersion: string, original?: string, options?: RawAxiosRequestConfig, ): Promise< @@ -2332,6 +2580,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.checkAttachmentExists( attachmentId, + xOdeVersion, original, options, ); @@ -2351,7 +2600,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Create a new user with specified username, password, and role * @summary Create a new user (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {CreateUserRequest} createUserRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2385,7 +2634,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * Delete a user by username * @summary Delete a user (admin only) * @param {string} username Username of the user to delete - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2421,7 +2670,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Download a specific file from the app bundle * @param {string} path - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {boolean} [preview] If true, returns the file from the latest version including unreleased changes * @param {string} [ifNoneMatch] * @param {*} [options] Override http request option. @@ -2457,16 +2706,49 @@ export const DefaultApiFp = function (configuration?: Configuration) { configuration, )(axios, localVarOperationServerBasePath || basePath); }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async downloadAppBundleZip( + xOdeVersion: string, + options?: RawAxiosRequestConfig, + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = + await localVarAxiosParamCreator.downloadAppBundleZip( + xOdeVersion, + options, + ); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = + operationServerMap['DefaultApi.downloadAppBundleZip']?.[ + localVarOperationServerIndex + ]?.url; + return (axios, basePath) => + createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration, + )(axios, localVarOperationServerBasePath || basePath); + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [original] Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. * @param {*} [options] Override http request option. * @throws {RequiredError} */ async downloadAttachment( attachmentId: string, + xOdeVersion: string, original?: string, options?: RawAxiosRequestConfig, ): Promise< @@ -2475,6 +2757,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.downloadAttachment( attachmentId, + xOdeVersion, original, options, ); @@ -2491,10 +2774,43 @@ export const DefaultApiFp = function (configuration?: Configuration) { configuration, )(axios, localVarOperationServerBasePath || basePath); }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAPIVersions( + xOdeVersion: string, + options?: RawAxiosRequestConfig, + ): Promise< + ( + axios?: AxiosInstance, + basePath?: string, + ) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAPIVersions( + xOdeVersion, + options, + ); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = + operationServerMap['DefaultApi.getAPIVersions']?.[ + localVarOperationServerIndex + ]?.url; + return (axios, basePath) => + createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration, + )(axios, localVarOperationServerBasePath || basePath); + }, /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [current] The current version (defaults to latest) * @param {string} [target] The target version to compare against (defaults to previous version) * @param {*} [options] Override http request option. @@ -2531,7 +2847,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Get the current custom app bundle manifest - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id for correlating app bundle checks with presence. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2568,7 +2884,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Get a list of available app bundle versions - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2602,7 +2918,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Returns a manifest of attachment changes (new, updated, deleted) since a specified data version * @summary Get attachment manifest for incremental sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {AttachmentManifestRequest} attachmentManifestRequest * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -2642,10 +2958,12 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ async getVersion( + xOdeVersion: string, options?: RawAxiosRequestConfig, ): Promise< ( @@ -2653,8 +2971,10 @@ export const DefaultApiFp = function (configuration?: Configuration) { basePath?: string, ) => AxiosPromise > { - const localVarAxiosArgs = - await localVarAxiosParamCreator.getVersion(options); + const localVarAxiosArgs = await localVarAxiosParamCreator.getVersion( + xOdeVersion, + options, + ); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['DefaultApi.getVersion']?.[ @@ -2671,7 +2991,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Retrieve a list of all users in the system. Admin access required. Each item may include optional `presence` (last-seen per client, bundle/Ode hints) when the server has recorded activity. * @summary List all users (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {string} [xOdeClientId] Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2707,7 +3027,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Obtain a JWT token by providing username and password * @summary Authenticate user and return JWT tokens - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {LoginRequest} loginRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2739,7 +3059,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Upload a new app bundle (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} [bundle] ZIP file containing the new app bundle * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2775,7 +3095,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Obtain a new JWT token using a refresh token * @summary Refresh JWT token - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {RefreshTokenRequest} refreshTokenRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2808,7 +3128,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Reset password for a specified user * @summary Reset user password (admin only) - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {ResetUserPasswordRequest} resetUserPasswordRequest * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -2846,7 +3166,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Switch to a specific app bundle version (admin only) * @param {string} version Version identifier to switch to - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2882,12 +3202,12 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * Retrieves records that have changed since a specified version. **Pagination Pattern:** 1. Send initial request with `since.version` (or omit for all records) 2. Process returned records 3. If `has_more` is true, make next request using `change_cutoff` as the new `since.version` 4. Repeat until `has_more` is false Example pagination flow: - Request 1: `since: {version: 100}` → Response: `change_cutoff: 150, has_more: true` - Request 2: `since: {version: 150}` → Response: `change_cutoff: 200, has_more: false` * @summary Pull updated records since last sync - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPullRequest} syncPullRequest * @param {string} [schemaType] Filter by schemaType * @param {number} [limit] Maximum number of records to return * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. - * @param {number} [xRepositoryGeneration] Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -2930,7 +3250,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { /** * * @summary Push new or updated records to the server - * @param {string} xOdeVersion Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {SyncPushRequest} syncPushRequest * @param {string} [xOdeClientId] Optional client instance id; improves per-device presence when combined with sync body `client_id`. * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. @@ -2973,6 +3293,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { * * @summary Upload a new attachment with specified ID * @param {string} attachmentId + * @param {string} xOdeVersion Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). * @param {File} file The binary file to upload * @param {number} [xRepositoryGeneration] Client repository epoch; must match the server. Omitted or invalid values are treated as 1. * @param {*} [options] Override http request option. @@ -2980,6 +3301,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { */ async uploadAttachment( attachmentId: string, + xOdeVersion: string, file: File, xRepositoryGeneration?: number, options?: RawAxiosRequestConfig, @@ -2992,6 +3314,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.uploadAttachment( attachmentId, + xOdeVersion, file, xRepositoryGeneration, options, @@ -3034,7 +3357,11 @@ export const DefaultApiFactory = function ( options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .adminRepositoryReset(requestParameters.repositoryResetRequest, options) + .adminRepositoryReset( + requestParameters.xOdeVersion, + requestParameters.repositoryResetRequest, + options, + ) .then(request => request(axios, basePath)); }, /** @@ -3070,6 +3397,7 @@ export const DefaultApiFactory = function ( return localVarFp .checkAttachmentExists( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) @@ -3134,6 +3462,21 @@ export const DefaultApiFactory = function ( ) .then(request => request(axios, basePath)); }, + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {DefaultApiDownloadAppBundleZipRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + downloadAppBundleZip( + requestParameters: DefaultApiDownloadAppBundleZipRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { + return localVarFp + .downloadAppBundleZip(requestParameters.xOdeVersion, options) + .then(request => request(axios, basePath)); + }, /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID @@ -3148,11 +3491,27 @@ export const DefaultApiFactory = function ( return localVarFp .downloadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) .then(request => request(axios, basePath)); }, + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {DefaultApiGetAPIVersionsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAPIVersions( + requestParameters: DefaultApiGetAPIVersionsRequest, + options?: RawAxiosRequestConfig, + ): AxiosPromise { + return localVarFp + .getAPIVersions(requestParameters.xOdeVersion, options) + .then(request => request(axios, basePath)); + }, /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions @@ -3230,14 +3589,16 @@ export const DefaultApiFactory = function ( /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {DefaultApiGetVersionRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ getVersion( + requestParameters: DefaultApiGetVersionRequest, options?: RawAxiosRequestConfig, ): AxiosPromise { return localVarFp - .getVersion(options) + .getVersion(requestParameters.xOdeVersion, options) .then(request => request(axios, basePath)); }, /** @@ -3412,6 +3773,7 @@ export const DefaultApiFactory = function ( return localVarFp .uploadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.file, requestParameters.xRepositoryGeneration, options, @@ -3425,6 +3787,11 @@ export const DefaultApiFactory = function ( * Request parameters for adminRepositoryReset operation in DefaultApi. */ export interface DefaultApiAdminRepositoryResetRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; + readonly repositoryResetRequest: RepositoryResetRequest; } @@ -3433,7 +3800,7 @@ export interface DefaultApiAdminRepositoryResetRequest { */ export interface DefaultApiChangePasswordRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3446,6 +3813,11 @@ export interface DefaultApiChangePasswordRequest { export interface DefaultApiCheckAttachmentExistsRequest { readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; + /** * Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. */ @@ -3457,7 +3829,7 @@ export interface DefaultApiCheckAttachmentExistsRequest { */ export interface DefaultApiCreateUserRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3474,7 +3846,7 @@ export interface DefaultApiDeleteUserRequest { readonly username: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; } @@ -3486,7 +3858,7 @@ export interface DefaultApiDownloadAppBundleFileRequest { readonly path: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3498,24 +3870,49 @@ export interface DefaultApiDownloadAppBundleFileRequest { readonly ifNoneMatch?: string; } +/** + * Request parameters for downloadAppBundleZip operation in DefaultApi. + */ +export interface DefaultApiDownloadAppBundleZipRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + /** * Request parameters for downloadAttachment operation in DefaultApi. */ export interface DefaultApiDownloadAttachmentRequest { readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; + /** * Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. */ readonly original?: string; } +/** + * Request parameters for getAPIVersions operation in DefaultApi. + */ +export interface DefaultApiGetAPIVersionsRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + /** * Request parameters for getAppBundleChanges operation in DefaultApi. */ export interface DefaultApiGetAppBundleChangesRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3535,7 +3932,7 @@ export interface DefaultApiGetAppBundleChangesRequest { */ export interface DefaultApiGetAppBundleManifestRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3550,7 +3947,7 @@ export interface DefaultApiGetAppBundleManifestRequest { */ export interface DefaultApiGetAppBundleVersionsRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; } @@ -3560,7 +3957,7 @@ export interface DefaultApiGetAppBundleVersionsRequest { */ export interface DefaultApiGetAttachmentManifestRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3572,12 +3969,22 @@ export interface DefaultApiGetAttachmentManifestRequest { readonly xRepositoryGeneration?: number; } +/** + * Request parameters for getVersion operation in DefaultApi. + */ +export interface DefaultApiGetVersionRequest { + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; +} + /** * Request parameters for listUsers operation in DefaultApi. */ export interface DefaultApiListUsersRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3592,7 +3999,7 @@ export interface DefaultApiListUsersRequest { */ export interface DefaultApiLoginRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3604,7 +4011,7 @@ export interface DefaultApiLoginRequest { */ export interface DefaultApiPushAppBundleRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3619,7 +4026,7 @@ export interface DefaultApiPushAppBundleRequest { */ export interface DefaultApiRefreshTokenRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3631,7 +4038,7 @@ export interface DefaultApiRefreshTokenRequest { */ export interface DefaultApiResetUserPasswordRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3648,7 +4055,7 @@ export interface DefaultApiSwitchAppBundleVersionRequest { readonly version: string; /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; } @@ -3658,7 +4065,7 @@ export interface DefaultApiSwitchAppBundleVersionRequest { */ export interface DefaultApiSyncPullRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3680,7 +4087,7 @@ export interface DefaultApiSyncPullRequest { readonly xOdeClientId?: string; /** - * Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + * Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. */ readonly xRepositoryGeneration?: number; } @@ -3690,7 +4097,7 @@ export interface DefaultApiSyncPullRequest { */ export interface DefaultApiSyncPushRequest { /** - * Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). */ readonly xOdeVersion: string; @@ -3713,6 +4120,11 @@ export interface DefaultApiSyncPushRequest { export interface DefaultApiUploadAttachmentRequest { readonly attachmentId: string; + /** + * Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). + */ + readonly xOdeVersion: string; + /** * The binary file to upload */ @@ -3740,7 +4152,11 @@ export class DefaultApi extends BaseAPI { options?: RawAxiosRequestConfig, ) { return DefaultApiFp(this.configuration) - .adminRepositoryReset(requestParameters.repositoryResetRequest, options) + .adminRepositoryReset( + requestParameters.xOdeVersion, + requestParameters.repositoryResetRequest, + options, + ) .then(request => request(this.axios, this.basePath)); } @@ -3778,6 +4194,7 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .checkAttachmentExists( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) @@ -3846,6 +4263,22 @@ export class DefaultApi extends BaseAPI { .then(request => request(this.axios, this.basePath)); } + /** + * Returns the full custom app bundle archive for the active version as `application/zip`. + * @summary Download the active app bundle as a single ZIP + * @param {DefaultApiDownloadAppBundleZipRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public downloadAppBundleZip( + requestParameters: DefaultApiDownloadAppBundleZipRequest, + options?: RawAxiosRequestConfig, + ) { + return DefaultApiFp(this.configuration) + .downloadAppBundleZip(requestParameters.xOdeVersion, options) + .then(request => request(this.axios, this.basePath)); + } + /** * Downloads the processed attachment by default. If `original=true` (or `1` / `yes`) and an uncompressed sibling exists, the original file is returned. If no original exists, the endpoint falls back to the processed attachment. * @summary Download an attachment by ID @@ -3860,12 +4293,29 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .downloadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.original, options, ) .then(request => request(this.axios, this.basePath)); } + /** + * Returns version metadata for the public HTTP API (compatibility hints for clients). + * @summary List supported API contract versions + * @param {DefaultApiGetAPIVersionsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public getAPIVersions( + requestParameters: DefaultApiGetAPIVersionsRequest, + options?: RawAxiosRequestConfig, + ) { + return DefaultApiFp(this.configuration) + .getAPIVersions(requestParameters.xOdeVersion, options) + .then(request => request(this.axios, this.basePath)); + } + /** * Compares two versions of the app bundle and returns detailed changes * @summary Get changes between two app bundle versions @@ -3947,12 +4397,16 @@ export class DefaultApi extends BaseAPI { /** * Returns detailed version information about the server, including build information and system details * @summary Get server version and system information + * @param {DefaultApiGetVersionRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public getVersion(options?: RawAxiosRequestConfig) { + public getVersion( + requestParameters: DefaultApiGetVersionRequest, + options?: RawAxiosRequestConfig, + ) { return DefaultApiFp(this.configuration) - .getVersion(options) + .getVersion(requestParameters.xOdeVersion, options) .then(request => request(this.axios, this.basePath)); } @@ -4136,6 +4590,7 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration) .uploadAttachment( requestParameters.attachmentId, + requestParameters.xOdeVersion, requestParameters.file, requestParameters.xRepositoryGeneration, options, diff --git a/synkronus-portal/src/api/synkronus/generated/docs/APIVersionInfo.md b/synkronus-portal/src/api/synkronus/generated/docs/APIVersionInfo.md new file mode 100644 index 000000000..04dae8ec5 --- /dev/null +++ b/synkronus-portal/src/api/synkronus/generated/docs/APIVersionInfo.md @@ -0,0 +1,24 @@ +# APIVersionInfo + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**version** | **string** | | [default to undefined] +**releaseDate** | **string** | | [default to undefined] +**deprecated** | **boolean** | | [default to undefined] + +## Example + +```typescript +import { APIVersionInfo } from './api'; + +const instance: APIVersionInfo = { + version, + releaseDate, + deprecated, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/synkronus-portal/src/api/synkronus/generated/docs/APIVersionsResponse.md b/synkronus-portal/src/api/synkronus/generated/docs/APIVersionsResponse.md new file mode 100644 index 000000000..3f249a4a5 --- /dev/null +++ b/synkronus-portal/src/api/synkronus/generated/docs/APIVersionsResponse.md @@ -0,0 +1,22 @@ +# APIVersionsResponse + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**versions** | [**Array<APIVersionInfo>**](APIVersionInfo.md) | | [default to undefined] +**current** | **string** | Identifier of the current API contract version | [default to undefined] + +## Example + +```typescript +import { APIVersionsResponse } from './api'; + +const instance: APIVersionsResponse = { + versions, + current, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/synkronus-portal/src/api/synkronus/generated/docs/AttachmentsApi.md b/synkronus-portal/src/api/synkronus/generated/docs/AttachmentsApi.md index 81f7a7188..f2e2ebd50 100644 --- a/synkronus-portal/src/api/synkronus/generated/docs/AttachmentsApi.md +++ b/synkronus-portal/src/api/synkronus/generated/docs/AttachmentsApi.md @@ -22,11 +22,18 @@ import { const configuration = new Configuration(); const apiInstance = new AttachmentsApi(configuration); -const { status, data } = await apiInstance.getAttachmentsExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getAttachmentsExportZip( + xOdeVersion +); ``` ### Parameters -This endpoint does not have any parameters. + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type diff --git a/synkronus-portal/src/api/synkronus/generated/docs/DataExportApi.md b/synkronus-portal/src/api/synkronus/generated/docs/DataExportApi.md index 8e7c7fbb4..d3265f503 100644 --- a/synkronus-portal/src/api/synkronus/generated/docs/DataExportApi.md +++ b/synkronus-portal/src/api/synkronus/generated/docs/DataExportApi.md @@ -23,11 +23,18 @@ import { const configuration = new Configuration(); const apiInstance = new DataExportApi(configuration); -const { status, data } = await apiInstance.getParquetExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getParquetExportZip( + xOdeVersion +); ``` ### Parameters -This endpoint does not have any parameters. + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -70,11 +77,18 @@ import { const configuration = new Configuration(); const apiInstance = new DataExportApi(configuration); -const { status, data } = await apiInstance.getRawJsonExportZip(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getRawJsonExportZip( + xOdeVersion +); ``` ### Parameters -This endpoint does not have any parameters. + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type diff --git a/synkronus-portal/src/api/synkronus/generated/docs/DefaultApi.md b/synkronus-portal/src/api/synkronus/generated/docs/DefaultApi.md index b9e16457a..c79d73223 100644 --- a/synkronus-portal/src/api/synkronus/generated/docs/DefaultApi.md +++ b/synkronus-portal/src/api/synkronus/generated/docs/DefaultApi.md @@ -10,7 +10,9 @@ All URIs are relative to *http://localhost* |[**createUser**](#createuser) | **POST** /api/users/create | Create a new user (admin only)| |[**deleteUser**](#deleteuser) | **DELETE** /api/users/{username} | Delete a user (admin only)| |[**downloadAppBundleFile**](#downloadappbundlefile) | **GET** /api/app-bundle/download/{path} | Download a specific file from the app bundle| +|[**downloadAppBundleZip**](#downloadappbundlezip) | **GET** /api/app-bundle/download-zip | Download the active app bundle as a single ZIP| |[**downloadAttachment**](#downloadattachment) | **GET** /api/attachments/{attachment_id} | Download an attachment by ID| +|[**getAPIVersions**](#getapiversions) | **GET** /api/versions | List supported API contract versions| |[**getAppBundleChanges**](#getappbundlechanges) | **GET** /api/app-bundle/changes | Get changes between two app bundle versions| |[**getAppBundleManifest**](#getappbundlemanifest) | **GET** /api/app-bundle/manifest | Get the current custom app bundle manifest| |[**getAppBundleVersions**](#getappbundleversions) | **GET** /api/app-bundle/versions | Get a list of available app bundle versions| @@ -43,9 +45,11 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let repositoryResetRequest: RepositoryResetRequest; // const { status, data } = await apiInstance.adminRepositoryReset( + xOdeVersion, repositoryResetRequest ); ``` @@ -55,6 +59,7 @@ const { status, data } = await apiInstance.adminRepositoryReset( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **repositoryResetRequest** | **RepositoryResetRequest**| | | +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -74,7 +79,7 @@ const { status, data } = await apiInstance.adminRepositoryReset( ### HTTP response details | Status code | Description | Response headers | |-------------|-------------|------------------| -|**200** | Reset completed | - | +|**200** | Reset completed | * x-repository-generation -
| |**400** | Invalid confirmation body | - | |**401** | Unauthorized | - | |**403** | Forbidden (non-admin) | - | @@ -99,7 +104,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let changePasswordRequest: ChangePasswordRequest; // const { status, data } = await apiInstance.changePassword( @@ -113,7 +118,7 @@ const { status, data } = await apiInstance.changePassword( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **changePasswordRequest** | **ChangePasswordRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -156,10 +161,12 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let original: string; //Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. (optional) (default to undefined) const { status, data } = await apiInstance.checkAttachmentExists( attachmentId, + xOdeVersion, original ); ``` @@ -169,6 +176,7 @@ const { status, data } = await apiInstance.checkAttachmentExists( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **attachmentId** | [**string**] | | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **original** | [**string**] | Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. | (optional) defaults to undefined| @@ -212,7 +220,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let createUserRequest: CreateUserRequest; // const { status, data } = await apiInstance.createUser( @@ -226,7 +234,7 @@ const { status, data } = await apiInstance.createUser( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **createUserRequest** | **CreateUserRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -271,7 +279,7 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let username: string; //Username of the user to delete (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.deleteUser( username, @@ -284,7 +292,7 @@ const { status, data } = await apiInstance.deleteUser( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **username** | [**string**] | Username of the user to delete | defaults to undefined| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -328,7 +336,7 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let path: string; // (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let preview: boolean; //If true, returns the file from the latest version including unreleased changes (optional) (default to false) let ifNoneMatch: string; // (optional) (default to undefined) @@ -345,7 +353,7 @@ const { status, data } = await apiInstance.downloadAppBundleFile( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **path** | [**string**] | | defaults to undefined| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **preview** | [**boolean**] | If true, returns the file from the latest version including unreleased changes | (optional) defaults to false| | **ifNoneMatch** | [**string**] | | (optional) defaults to undefined| @@ -372,6 +380,60 @@ const { status, data } = await apiInstance.downloadAppBundleFile( [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **downloadAppBundleZip** +> File downloadAppBundleZip() + +Returns the full custom app bundle archive for the active version as `application/zip`. + +### Example + +```typescript +import { + DefaultApi, + Configuration +} from './api'; + +const configuration = new Configuration(); +const apiInstance = new DefaultApi(configuration); + +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.downloadAppBundleZip( + xOdeVersion +); +``` + +### Parameters + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| + + +### Return type + +**File** + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/zip, application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +|**200** | ZIP archive of the app bundle | - | +|**401** | Unauthorized | - | +|**404** | Bundle zip not available | - | +|**500** | Internal server error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **downloadAttachment** > File downloadAttachment() @@ -389,10 +451,12 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let original: string; //Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. (optional) (default to undefined) const { status, data } = await apiInstance.downloadAttachment( attachmentId, + xOdeVersion, original ); ``` @@ -402,6 +466,7 @@ const { status, data } = await apiInstance.downloadAttachment( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **attachmentId** | [**string**] | | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **original** | [**string**] | Prefer the original (uncompressed) attachment when available. Truthy values: `true`, `1`, `yes` (case-insensitive). Falls back to processed file when no original exists. | (optional) defaults to undefined| @@ -428,6 +493,58 @@ const { status, data } = await apiInstance.downloadAttachment( [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **getAPIVersions** +> APIVersionsResponse getAPIVersions() + +Returns version metadata for the public HTTP API (compatibility hints for clients). + +### Example + +```typescript +import { + DefaultApi, + Configuration +} from './api'; + +const configuration = new Configuration(); +const apiInstance = new DefaultApi(configuration); + +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getAPIVersions( + xOdeVersion +); +``` + +### Parameters + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| + + +### Return type + +**APIVersionsResponse** + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +|**200** | API version list | - | +|**401** | Unauthorized | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **getAppBundleChanges** > ChangeLog getAppBundleChanges() @@ -444,7 +561,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let current: string; //The current version (defaults to latest) (optional) (default to undefined) let target: string; //The target version to compare against (defaults to previous version) (optional) (default to undefined) @@ -459,7 +576,7 @@ const { status, data } = await apiInstance.getAppBundleChanges( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **current** | [**string**] | The current version (defaults to latest) | (optional) defaults to undefined| | **target** | [**string**] | The target version to compare against (defaults to previous version) | (optional) defaults to undefined| @@ -503,7 +620,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let xOdeClientId: string; //Optional client instance id for correlating app bundle checks with presence. (optional) (default to undefined) const { status, data } = await apiInstance.getAppBundleManifest( @@ -516,7 +633,7 @@ const { status, data } = await apiInstance.getAppBundleManifest( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **xOdeClientId** | [**string**] | Optional client instance id for correlating app bundle checks with presence. | (optional) defaults to undefined| @@ -556,7 +673,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.getAppBundleVersions( xOdeVersion @@ -567,7 +684,7 @@ const { status, data } = await apiInstance.getAppBundleVersions( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -608,7 +725,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let attachmentManifestRequest: AttachmentManifestRequest; // let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) @@ -624,7 +741,7 @@ const { status, data } = await apiInstance.getAttachmentManifest( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **attachmentManifestRequest** | **AttachmentManifestRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined| @@ -669,11 +786,18 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -const { status, data } = await apiInstance.getVersion(); +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) + +const { status, data } = await apiInstance.getVersion( + xOdeVersion +); ``` ### Parameters -This endpoint does not have any parameters. + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -682,7 +806,7 @@ This endpoint does not have any parameters. ### Authorization -No authorization required +[bearerAuth](../README.md#bearerAuth) ### HTTP request headers @@ -694,6 +818,7 @@ No authorization required | Status code | Description | Response headers | |-------------|-------------|------------------| |**200** | Successful response with version information | - | +|**401** | Unauthorized | - | |**500** | Internal server error | - | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -714,7 +839,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let xOdeClientId: string; //Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. (optional) (default to undefined) const { status, data } = await apiInstance.listUsers( @@ -727,7 +852,7 @@ const { status, data } = await apiInstance.listUsers( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **xOdeClientId** | [**string**] | Optional client instance id (browser/CLI); used for presence when sent with authenticated requests. | (optional) defaults to undefined| @@ -771,7 +896,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let loginRequest: LoginRequest; // const { status, data } = await apiInstance.login( @@ -785,7 +910,7 @@ const { status, data } = await apiInstance.login( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **loginRequest** | **LoginRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -826,7 +951,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let bundle: File; //ZIP file containing the new app bundle (optional) (default to undefined) const { status, data } = await apiInstance.pushAppBundle( @@ -839,7 +964,7 @@ const { status, data } = await apiInstance.pushAppBundle( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **bundle** | [**File**] | ZIP file containing the new app bundle | (optional) defaults to undefined| @@ -885,7 +1010,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let refreshTokenRequest: RefreshTokenRequest; // const { status, data } = await apiInstance.refreshToken( @@ -899,7 +1024,7 @@ const { status, data } = await apiInstance.refreshToken( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **refreshTokenRequest** | **RefreshTokenRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -942,7 +1067,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let resetUserPasswordRequest: ResetUserPasswordRequest; // const { status, data } = await apiInstance.resetUserPassword( @@ -956,7 +1081,7 @@ const { status, data } = await apiInstance.resetUserPassword( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **resetUserPasswordRequest** | **ResetUserPasswordRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -1000,7 +1125,7 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let version: string; //Version identifier to switch to (default to undefined) -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) const { status, data } = await apiInstance.switchAppBundleVersion( version, @@ -1013,7 +1138,7 @@ const { status, data } = await apiInstance.switchAppBundleVersion( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **version** | [**string**] | Version identifier to switch to | defaults to undefined| -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| ### Return type @@ -1058,12 +1183,12 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let syncPullRequest: SyncPullRequest; // let schemaType: string; //Filter by schemaType (optional) (default to undefined) let limit: number; //Maximum number of records to return (optional) (default to 50) let xOdeClientId: string; //Optional client instance id; improves per-device presence when combined with sync body `client_id`. (optional) (default to undefined) -let xRepositoryGeneration: number; //Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. (optional) (default to undefined) +let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. (optional) (default to undefined) const { status, data } = await apiInstance.syncPull( xOdeVersion, @@ -1080,11 +1205,11 @@ const { status, data } = await apiInstance.syncPull( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **syncPullRequest** | **SyncPullRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **schemaType** | [**string**] | Filter by schemaType | (optional) defaults to undefined| | **limit** | [**number**] | Maximum number of records to return | (optional) defaults to 50| | **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined| -| **xRepositoryGeneration** | [**number**] | Client\'s repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. | (optional) defaults to undefined| +| **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. | (optional) defaults to undefined| ### Return type @@ -1104,7 +1229,8 @@ const { status, data } = await apiInstance.syncPull( ### HTTP response details | Status code | Description | Response headers | |-------------|-------------|------------------| -|**200** | Sync data | - | +|**200** | Sync data | * x-repository-generation -
| +|**409** | Repository epoch mismatch (e.g. after admin hard reset). Client must align repository_generation before pulling. | * x-repository-generation -
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -1124,7 +1250,7 @@ import { const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); -let xOdeVersion: string; //Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let syncPushRequest: SyncPushRequest; // let xOdeClientId: string; //Optional client instance id; improves per-device presence when combined with sync body `client_id`. (optional) (default to undefined) let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) @@ -1142,7 +1268,7 @@ const { status, data } = await apiInstance.syncPush( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **syncPushRequest** | **SyncPushRequest**| | | -| **xOdeVersion** | [**string**] | Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **xOdeClientId** | [**string**] | Optional client instance id; improves per-device presence when combined with sync body `client_id`. | (optional) defaults to undefined| | **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined| @@ -1164,7 +1290,7 @@ const { status, data } = await apiInstance.syncPush( ### HTTP response details | Status code | Description | Response headers | |-------------|-------------|------------------| -|**200** | Sync result | - | +|**200** | Sync result | * x-repository-generation -
| |**409** | Repository epoch mismatch (e.g. after admin hard reset). Client must pull current state and align repository_generation before pushing. | * x-repository-generation -
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -1185,11 +1311,13 @@ const configuration = new Configuration(); const apiInstance = new DefaultApi(configuration); let attachmentId: string; // (default to undefined) +let xOdeVersion: string; //Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). (default to undefined) let file: File; //The binary file to upload (default to undefined) let xRepositoryGeneration: number; //Client repository epoch; must match the server. Omitted or invalid values are treated as 1. (optional) (default to undefined) const { status, data } = await apiInstance.uploadAttachment( attachmentId, + xOdeVersion, file, xRepositoryGeneration ); @@ -1200,6 +1328,7 @@ const { status, data } = await apiInstance.uploadAttachment( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| | **attachmentId** | [**string**] | | defaults to undefined| +| **xOdeVersion** | [**string**] | Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). | defaults to undefined| | **file** | [**File**] | The binary file to upload | defaults to undefined| | **xRepositoryGeneration** | [**number**] | Client repository epoch; must match the server. Omitted or invalid values are treated as 1. | (optional) defaults to undefined| diff --git a/synkronus-portal/src/api/synkronus/generated/docs/SyncPullRequest.md b/synkronus-portal/src/api/synkronus/generated/docs/SyncPullRequest.md index ab61a5ec3..fc03df403 100644 --- a/synkronus-portal/src/api/synkronus/generated/docs/SyncPullRequest.md +++ b/synkronus-portal/src/api/synkronus/generated/docs/SyncPullRequest.md @@ -6,7 +6,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **client_id** | **string** | | [default to undefined] -**repository_generation** | **number** | Optional body copy of epoch; header x-repository-generation wins when both are sent. | [optional] [default to undefined] +**repository_generation** | **number** | Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. | [optional] [default to undefined] **since** | [**SyncPullRequestSince**](SyncPullRequestSince.md) | | [optional] [default to undefined] **schema_types** | **Array<string>** | | [optional] [default to undefined] diff --git a/synkronus/documentation/sync-protocol.md b/synkronus/documentation/sync-protocol.md index 71294d971..b8a13dd70 100644 --- a/synkronus/documentation/sync-protocol.md +++ b/synkronus/documentation/sync-protocol.md @@ -13,7 +13,7 @@ - Pull → Push model: client pulls recent changes, then pushes local changes - **Repository generation (epoch)** is separate from the **change stream cursor** (`since.version` / `change_id`): - `repository_generation` is a monotonic integer on the server; it increments only when an administrator performs a **hard repository reset** (wiping observation/attachment sync state). - - Clients must send `X-Repository-Generation` on push, attachment manifest, and attachment upload; responses include the current epoch in JSON (and may repeat it in the header). + - Clients must send `x-repository-generation` on sync pull, push, attachment manifest, and attachment upload; responses include the current epoch in JSON (and may repeat it in the header). - If the client’s epoch lags or diverges, the server responds with **409 Conflict** and stable error `code: repository_reset_required`. Clients must align (typically by pulling fresh state after wiping local observation/attachment data that no longer matches the server). - Each record contains: - `id` diff --git a/synkronus/documentation/user-presence.md b/synkronus/documentation/user-presence.md index 2aa518b1d..78fdde6c2 100644 --- a/synkronus/documentation/user-presence.md +++ b/synkronus/documentation/user-presence.md @@ -4,7 +4,7 @@ Synkronus records **best-effort** per-user, per-client activity for operators. W ## Behavior -- **Protected routes** (after JWT): optional heartbeat via middleware using `x-ode-client-id` (may be empty; stored as an empty string key) and `x-ode-version` / `x-formulus-version` for client version hints. Heartbeats are **throttled** per `(username, client_id)` to limit load. +- **Protected routes** (after JWT): optional heartbeat via middleware using `x-ode-client-id` (may be empty; stored as an empty string key) and `x-ode-version` for client version hints. Heartbeats are **throttled** per `(username, client_id)` to limit load. - **Sync**: successful `POST /api/sync/pull` and `POST /api/sync/push` enqueue richer updates (sync cursor / `current_version`) with **throttle bypass** so version metadata is not dropped. - **App bundle**: successful `GET /api/app-bundle/manifest` records the resolved manifest `version` for that user and client header. - **Queue**: if the internal queue is full, events are **dropped** and logged; API responses still succeed. diff --git a/synkronus/internal/api/api.go b/synkronus/internal/api/api.go index 1ec870afb..6ae22dd0e 100644 --- a/synkronus/internal/api/api.go +++ b/synkronus/internal/api/api.go @@ -47,7 +47,7 @@ func NewRouter(log *logger.Logger, h *handlers.Handler) http.Handler { r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"accept", "authorization", "content-type", "x-csrf-token", "x-ode-version", "x-formulus-version", "x-ode-client-id", "x-repository-generation", "if-none-match"}, + AllowedHeaders: []string{"accept", "authorization", "content-type", "x-csrf-token", "x-ode-version", "x-ode-client-id", "x-repository-generation", "if-none-match"}, ExposedHeaders: []string{"link", "etag", "x-repository-generation"}, AllowCredentials: true, MaxAge: 300, diff --git a/synkronus/internal/handlers/common.go b/synkronus/internal/handlers/common.go index 28a508906..fbb557a42 100644 --- a/synkronus/internal/handlers/common.go +++ b/synkronus/internal/handlers/common.go @@ -69,9 +69,6 @@ func sendErrorPayload(w http.ResponseWriter, status int, err error, message, cod func odeVersionFromRequest(r *http.Request) *string { v := strings.TrimSpace(r.Header.Get("x-ode-version")) - if v == "" { - v = strings.TrimSpace(r.Header.Get("x-formulus-version")) - } if v == "" { return nil } diff --git a/synkronus/internal/handlers/mocks/sync_service.go b/synkronus/internal/handlers/mocks/sync_service.go index 093747827..8d1838c85 100644 --- a/synkronus/internal/handlers/mocks/sync_service.go +++ b/synkronus/internal/handlers/mocks/sync_service.go @@ -47,6 +47,11 @@ func (m *MockSyncService) GetRepositoryGeneration(ctx context.Context) (int64, e return m.repositoryGeneration, nil } +// SetRepositoryGeneration sets the mock server epoch (for handler tests). +func (m *MockSyncService) SetRepositoryGeneration(gen int64) { + m.repositoryGeneration = gen +} + // HardResetRepository mocks an admin reset (increments epoch and clears observations). func (m *MockSyncService) HardResetRepository(ctx context.Context, adminUsername string) (int64, error) { if !m.initialized { diff --git a/synkronus/internal/handlers/sync.go b/synkronus/internal/handlers/sync.go index 37c9610de..a527d5bd3 100644 --- a/synkronus/internal/handlers/sync.go +++ b/synkronus/internal/handlers/sync.go @@ -50,9 +50,24 @@ func (h *Handler) Pull(w http.ResponseWriter, r *http.Request) { return } + clientGen := sync.ParseClientRepositoryGeneration(r, req.RepositoryGeneration) + serverGen, genErr := h.syncService.GetRepositoryGeneration(r.Context()) + if genErr != nil { + h.log.Error("Failed to read repository generation", "error", genErr) + SendErrorResponse(w, http.StatusInternalServerError, genErr, "Failed to verify repository generation") + return + } + if clientGen != serverGen { + w.Header().Set(sync.HeaderRepositoryGeneration, strconv.FormatInt(serverGen, 10)) + SendErrorResponseWithCode(w, http.StatusConflict, sync.ErrRepositoryGenerationMismatch, + "Client repository_generation does not match the server; pull sync state and align generation before pulling.", + CodeRepositoryResetRequired) + return + } + // Parse query parameters limitStr := r.URL.Query().Get("limit") - limit := 100 // default limit + limit := 50 // default limit (matches OpenAPI) if limitStr != "" { if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 { limit = parsedLimit diff --git a/synkronus/internal/handlers/sync_repository_generation_test.go b/synkronus/internal/handlers/sync_repository_generation_test.go new file mode 100644 index 000000000..f971531fb --- /dev/null +++ b/synkronus/internal/handlers/sync_repository_generation_test.go @@ -0,0 +1,158 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/opendataensemble/synkronus/pkg/sync" +) + +func TestPull_repositoryGenerationMismatch_returns409(t *testing.T) { + h, mockSync, _ := createTestHandlerWithSync() + mockSync.SetRepositoryGeneration(5) + + body, err := json.Marshal(SyncPullRequest{ + ClientID: "test-client", + Since: &SyncPullRequestSince{ + Version: 0, + }, + RepositoryGeneration: int64Ptr(4), + }) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPost, "/api/sync/pull", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + // No x-repository-generation header: parser uses body (4) vs server (5) + w := httptest.NewRecorder() + h.Pull(w, req) + + resp := w.Result() + t.Cleanup(func() { _ = resp.Body.Close() }) + + if resp.StatusCode != http.StatusConflict { + t.Fatalf("expected HTTP 409 Conflict, got %d", resp.StatusCode) + } + var er ErrorResponse + if err := json.NewDecoder(resp.Body).Decode(&er); err != nil { + t.Fatalf("decode error body: %v", err) + } + if er.Code != CodeRepositoryResetRequired { + t.Fatalf("expected code %q, got %q", CodeRepositoryResetRequired, er.Code) + } + hdr := resp.Header.Get(sync.HeaderRepositoryGeneration) + if hdr != "5" { + t.Fatalf("expected %s header %q, got %q", sync.HeaderRepositoryGeneration, "5", hdr) + } +} + +func TestPull_repositoryGenerationMatch_headerWinsOverBody_ok(t *testing.T) { + h, mockSync, _ := createTestHandlerWithSync() + mockSync.SetRepositoryGeneration(5) + + body, err := json.Marshal(SyncPullRequest{ + ClientID: "test-client", + // Intentionally inconsistent with header — header must win (OpenAPI contract). + RepositoryGeneration: int64Ptr(1), + }) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPost, "/api/sync/pull", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set(sync.HeaderRepositoryGeneration, "5") + w := httptest.NewRecorder() + h.Pull(w, req) + + resp := w.Result() + t.Cleanup(func() { _ = resp.Body.Close() }) + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected HTTP 200, got %d", resp.StatusCode) + } +} + +func TestPull_repositoryGenerationMismatch_header_returns409(t *testing.T) { + h, mockSync, _ := createTestHandlerWithSync() + mockSync.SetRepositoryGeneration(5) + + body, err := json.Marshal(SyncPullRequest{ + ClientID: "test-client", + RepositoryGeneration: int64Ptr(5), + }) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPost, "/api/sync/pull", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set(sync.HeaderRepositoryGeneration, "4") + w := httptest.NewRecorder() + h.Pull(w, req) + + resp := w.Result() + t.Cleanup(func() { _ = resp.Body.Close() }) + + if resp.StatusCode != http.StatusConflict { + t.Fatalf("expected HTTP 409, got %d", resp.StatusCode) + } +} + +func TestPush_repositoryGenerationMismatch_returns409(t *testing.T) { + h, mockSync, _ := createTestHandlerWithSync() + mockSync.SetRepositoryGeneration(5) + + reqBody := SyncPushRequest{ + TransmissionID: "tx-1", + ClientID: "test-client", + Records: []sync.Observation{ + { + ObservationID: "obs-1", + FormType: "survey", + FormVersion: "1.0", + Data: json.RawMessage(`{}`), + CreatedAt: "2025-06-25T12:00:00Z", + UpdatedAt: "2025-06-25T12:00:00Z", + Deleted: false, + }, + }, + RepositoryGeneration: int64Ptr(4), + } + + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPost, "/api/sync/push", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + h.Push(w, req) + + resp := w.Result() + t.Cleanup(func() { _ = resp.Body.Close() }) + + if resp.StatusCode != http.StatusConflict { + t.Fatalf("expected HTTP 409 Conflict, got %d", resp.StatusCode) + } + var er ErrorResponse + if err := json.NewDecoder(resp.Body).Decode(&er); err != nil { + t.Fatalf("decode error body: %v", err) + } + if er.Code != CodeRepositoryResetRequired { + t.Fatalf("expected code %q, got %q", CodeRepositoryResetRequired, er.Code) + } + if resp.Header.Get(sync.HeaderRepositoryGeneration) != strconv.FormatInt(5, 10) { + t.Fatalf("expected x-repository-generation 5 on response") + } +} + +func int64Ptr(v int64) *int64 { + return &v +} diff --git a/synkronus/internal/handlers/test_helpers.go b/synkronus/internal/handlers/test_helpers.go index daa54d090..760255049 100644 --- a/synkronus/internal/handlers/test_helpers.go +++ b/synkronus/internal/handlers/test_helpers.go @@ -50,3 +50,32 @@ func createTestHandler() (*Handler, *mocks.MockAppBundleService) { return h, mockAppBundleService } + +// createTestHandlerWithSync is like createTestHandler but exposes the mock sync service +// so tests can set repository_generation and other sync state. +func createTestHandlerWithSync() (*Handler, *mocks.MockSyncService, *mocks.MockAppBundleService) { + log := logger.NewLogger() + testConfig := mocks.NewTestConfig() + mockAuthService := mocks.NewMockAuthService() + mockAppBundleService := mocks.NewMockAppBundleService() + mockSyncService := mocks.NewMockSyncService() + if err := mockSyncService.Initialize(context.Background()); err != nil { + panic("Failed to initialize mock sync service: " + err.Error()) + } + mockVersionService := mocks.NewMockVersionService() + mockAttachmentManifestService := &mocks.MockAttachmentManifestService{} + mockDataExportService := mocks.NewMockDataExportService() + h := NewHandler( + log, + testConfig, + mockAuthService, + mockAppBundleService, + mockSyncService, + mocks.NewMockUserService(), + mockVersionService, + mockAttachmentManifestService, + mockDataExportService, + nil, + ) + return h, mockSyncService, mockAppBundleService +} diff --git a/synkronus/openapi/synkronus.yaml b/synkronus/openapi/synkronus.yaml index 631f07cc6..c4f5dfa0a 100644 --- a/synkronus/openapi/synkronus.yaml +++ b/synkronus/openapi/synkronus.yaml @@ -53,6 +53,10 @@ paths: operationId: getVersion summary: Get server version and system information description: Returns detailed version information about the server, including build information and system details + security: + - bearerAuth: [read-only, read-write, admin] + parameters: + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: Successful response with version information @@ -60,6 +64,8 @@ paths: application/json: schema: $ref: '#/components/schemas/SystemVersionInfo' + '401': + $ref: '#/components/responses/Unauthorized' '500': description: Internal server error content: @@ -67,6 +73,25 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' + /api/versions: + get: + operationId: getAPIVersions + summary: List supported API contract versions + description: Returns version metadata for the public HTTP API (compatibility hints for clients). + security: + - bearerAuth: [read-only, read-write, admin] + parameters: + - $ref: '#/components/parameters/XOdeVersion' + responses: + '200': + description: API version list + content: + application/json: + schema: + $ref: '#/components/schemas/APIVersionsResponse' + '401': + $ref: '#/components/responses/Unauthorized' + /api/app-bundle/changes: get: operationId: getAppBundleChanges @@ -87,14 +112,7 @@ paths: schema: type: string description: The target version to compare against (defaults to previous version) - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: Successfully retrieved changes between versions @@ -128,14 +146,7 @@ paths: security: - bearerAuth: [read-only, read-write] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' - name: x-ode-client-id in: header required: false @@ -178,14 +189,7 @@ paths: in: header schema: type: string - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: File content @@ -201,6 +205,34 @@ paths: '304': description: Not Modified + /api/app-bundle/download-zip: + get: + operationId: downloadAppBundleZip + summary: Download the active app bundle as a single ZIP + description: Returns the full custom app bundle archive for the active version as `application/zip`. + security: + - bearerAuth: [read-only, read-write] + parameters: + - $ref: '#/components/parameters/XOdeVersion' + responses: + '200': + description: ZIP archive of the app bundle + content: + application/zip: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Bundle zip not available + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + $ref: '#/components/responses/InternalServerError' + /api/app-bundle/versions: get: operationId: getAppBundleVersions @@ -208,14 +240,7 @@ paths: security: - bearerAuth: [read-only, read-write] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: List of available app bundle versions @@ -231,14 +256,7 @@ paths: security: - bearerAuth: [admin] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -295,14 +313,7 @@ paths: schema: type: string description: Version identifier to switch to - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: Successfully switched to the specified version @@ -345,14 +356,7 @@ paths: summary: Authenticate user and return JWT tokens description: Obtain a JWT token by providing username and password parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -394,14 +398,7 @@ paths: summary: Refresh JWT token description: Obtain a new JWT token using a refresh token parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -441,14 +438,7 @@ paths: security: - bearerAuth: [admin] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -510,14 +500,7 @@ paths: security: - bearerAuth: [admin] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' - name: x-ode-client-id in: header required: false @@ -560,14 +543,7 @@ paths: schema: type: string description: Username of the user to delete - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: User deleted successfully @@ -612,14 +588,7 @@ paths: security: - bearerAuth: [admin] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -679,14 +648,7 @@ paths: security: - bearerAuth: [read-only, read-write, admin] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -760,14 +722,7 @@ paths: default: 50 description: Maximum number of records to return - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' - name: x-ode-client-id in: header required: false @@ -781,7 +736,7 @@ paths: type: integer format: int64 minimum: 1 - description: Client's repository epoch. Omitted or invalid values are treated as 1. Responses include the current epoch in JSON and may expose it in this header. + description: Client repository epoch; must match the server. Omitted or invalid values are treated as 1. Successful responses include the current epoch in JSON and in this header. requestBody: required: true content: @@ -791,10 +746,27 @@ paths: responses: '200': description: Sync data + headers: + x-repository-generation: + schema: + type: integer + format: int64 + description: Current repository epoch (same as response body `repository_generation`). content: application/json: schema: $ref: '#/components/schemas/SyncPullResponse' + '409': + description: Repository epoch mismatch (e.g. after admin hard reset). Client must align repository_generation before pulling. + headers: + x-repository-generation: + schema: + type: integer + format: int64 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /api/sync/push: post: @@ -803,14 +775,7 @@ paths: security: - bearerAuth: [read-write] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' - name: x-ode-client-id in: header required: false @@ -834,6 +799,12 @@ paths: responses: '200': description: Sync result + headers: + x-repository-generation: + schema: + type: integer + format: int64 + description: Current repository epoch (same as response body `repository_generation`). content: application/json: schema: @@ -860,6 +831,8 @@ paths: Requires body `{ "confirm": "RESET_REPOSITORY" }`. security: - bearerAuth: [admin] + parameters: + - $ref: '#/components/parameters/XOdeVersion' requestBody: required: true content: @@ -869,6 +842,12 @@ paths: responses: '200': description: Reset completed + headers: + x-repository-generation: + schema: + type: integer + format: int64 + description: New repository epoch after reset (same as response body `repository_generation`). content: application/json: schema: @@ -906,14 +885,7 @@ paths: security: - bearerAuth: [read-only, read-write] parameters: - - name: x-ode-version - in: header - required: true - schema: - type: string - pattern: '^\d+\.\d+\.\d+$' - example: '1.0.0' - description: Required client version header using semantic versioning (MAJOR.MINOR.PATCH). Major version must be compatible with the server. + - $ref: '#/components/parameters/XOdeVersion' - name: x-repository-generation in: header required: false @@ -974,6 +946,8 @@ paths: Entry paths correspond to attachment IDs. Large exports stream without buffering the full archive in memory. tags: - Attachments + parameters: + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: ZIP archive stream @@ -1010,6 +984,7 @@ paths: schema: type: string example: "abc123.jpg" + - $ref: '#/components/parameters/XOdeVersion' - name: x-repository-generation in: header required: false @@ -1069,6 +1044,7 @@ paths: schema: type: string example: "abc123.jpg" + - $ref: '#/components/parameters/XOdeVersion' - name: original in: query required: false @@ -1107,6 +1083,7 @@ paths: schema: type: string example: "abc123.jpg" + - $ref: '#/components/parameters/XOdeVersion' - name: original in: query required: false @@ -1135,6 +1112,8 @@ paths: operationId: getParquetExportZip tags: - DataExport + parameters: + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: ZIP archive stream containing Parquet files @@ -1161,6 +1140,8 @@ paths: grouped by form type folder. Each file contains metadata fields and a nested `data` object with the form payload. tags: - DataExport + parameters: + - $ref: '#/components/parameters/XOdeVersion' responses: '200': description: ZIP archive stream containing JSON files @@ -1179,7 +1160,43 @@ paths: - bearerAuth: [read-only, read-write, admin] components: + parameters: + XOdeVersion: + name: x-ode-version + in: header + required: true + schema: + type: string + pattern: '^([vV])?\d+\.\d+\.\d+([+.-].*)?$' + example: '1.0.0' + description: >- + Client semantic version; the major segment must match the server. Optional leading v/V and semver pre-release/build suffixes are accepted (same rules as Synkronus). schemas: + APIVersionsResponse: + type: object + required: [versions, current] + properties: + versions: + type: array + items: + $ref: '#/components/schemas/APIVersionInfo' + current: + type: string + description: Identifier of the current API contract version + example: '1.0.0' + APIVersionInfo: + type: object + required: [version, releaseDate, deprecated] + properties: + version: + type: string + example: '1.0.0' + releaseDate: + type: string + format: date + example: '2025-01-01' + deprecated: + type: boolean SystemVersionInfo: type: object properties: @@ -1478,7 +1495,7 @@ components: type: integer format: int64 minimum: 1 - description: Optional body copy of epoch; header x-repository-generation wins when both are sent. + description: Optional body copy of epoch; header x-repository-generation wins when both are sent. Must match the server or the request returns 409. since: type: object description: Optional pagination cursor indicating the last seen change diff --git a/synkronus/pkg/middleware/formulusversion/formulusversion.go b/synkronus/pkg/middleware/formulusversion/formulusversion.go index 9987eb0ca..d3e94122b 100644 --- a/synkronus/pkg/middleware/formulusversion/formulusversion.go +++ b/synkronus/pkg/middleware/formulusversion/formulusversion.go @@ -11,10 +11,7 @@ import ( "github.com/opendataensemble/synkronus/pkg/version" ) -const ( - headerODEVersion = "x-ode-version" - headerFormulusVersion = "x-formulus-version" -) +const headerODEVersion = "x-ode-version" // VersionMismatchResponse is the JSON body returned when version check fails (mismatch or invalid/missing version). // Uses HTTP 426 Upgrade Required status code - the standard HTTP status for version incompatibility. @@ -24,27 +21,20 @@ type VersionMismatchResponse struct { } // Middleware returns a middleware that requires x-ode-version and checks major version match. -// Transitional fallback: if x-ode-version is missing, x-formulus-version is accepted. -// TODO: remove x-formulus-version fallback in an upcoming major version. func Middleware(log *logger.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { serverVer := version.BuildVersion() - clientHeaderName := headerODEVersion clientVer := strings.TrimSpace(r.Header.Get(headerODEVersion)) if clientVer == "" { - clientVer = strings.TrimSpace(r.Header.Get(headerFormulusVersion)) - clientHeaderName = headerFormulusVersion - } - if clientVer == "" { - log.Warn("Missing version header", "required_header", headerODEVersion, "fallback_header", headerFormulusVersion) + log.Warn("Missing version header", "required_header", headerODEVersion) writeVersionError(w, "missing x-ode-version header. client must send a valid semantic version.", serverVer) return } clientMajor, ok := parseMajor(clientVer) if !ok { - log.Warn("Client version header unparseable", "header", clientHeaderName, "value", clientVer) - writeVersionError(w, fmt.Sprintf("%s must be a valid semantic version (e.g. 1.0.0).", clientHeaderName), serverVer) + log.Warn("Client version header unparseable", "header", headerODEVersion, "value", clientVer) + writeVersionError(w, fmt.Sprintf("%s must be a valid semantic version (e.g. 1.0.0).", headerODEVersion), serverVer) return } serverMajor, ok := parseMajor(serverVer) @@ -55,7 +45,7 @@ func Middleware(log *logger.Logger) func(http.Handler) http.Handler { } if clientMajor != serverMajor { log.Warn("Client-Synkronus version mismatch", - "client_header", clientHeaderName, + "client_header", headerODEVersion, "client_version", clientVer, "synkronus_version", serverVer, "client_major", clientMajor, diff --git a/synkronus/pkg/middleware/formulusversion/formulusversion_test.go b/synkronus/pkg/middleware/formulusversion/formulusversion_test.go index 54666d805..cfee9a471 100644 --- a/synkronus/pkg/middleware/formulusversion/formulusversion_test.go +++ b/synkronus/pkg/middleware/formulusversion/formulusversion_test.go @@ -114,13 +114,13 @@ func TestMiddleware(t *testing.T) { } }) - t.Run("fallback_to_x_formulus_version_passes_during_transition", func(t *testing.T) { + t.Run("x_formulus_version_alone_returns_426", func(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil) req.Header.Set("x-formulus-version", "1.0.0") rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Errorf("expected 200 when fallback header is used, got %d body %s", rec.Code, rec.Body.String()) + if rec.Code != http.StatusUpgradeRequired { + t.Errorf("expected 426 when only x-formulus-version is sent, got %d", rec.Code) } }) } diff --git a/synkronus/pkg/middleware/presencemw/middleware.go b/synkronus/pkg/middleware/presencemw/middleware.go index eabba713a..90cbf64c4 100644 --- a/synkronus/pkg/middleware/presencemw/middleware.go +++ b/synkronus/pkg/middleware/presencemw/middleware.go @@ -10,9 +10,8 @@ import ( ) const ( - headerODEClientID = "x-ode-client-id" - headerODEVersion = "x-ode-version" - headerFormulusVersion = "x-formulus-version" + headerODEClientID = "x-ode-client-id" + headerODEVersion = "x-ode-version" ) // Middleware records throttled presence for authenticated requests (after AuthMiddleware). @@ -29,9 +28,6 @@ func Middleware(rec *presence.Recorder) func(http.Handler) http.Handler { } clientID := strings.TrimSpace(r.Header.Get(headerODEClientID)) odeVer := strings.TrimSpace(r.Header.Get(headerODEVersion)) - if odeVer == "" { - odeVer = strings.TrimSpace(r.Header.Get(headerFormulusVersion)) - } var odePtr *string if odeVer != "" { odePtr = &odeVer diff --git a/synkronus/pkg/sync/generation.go b/synkronus/pkg/sync/generation.go index f0432d15a..9437c8120 100644 --- a/synkronus/pkg/sync/generation.go +++ b/synkronus/pkg/sync/generation.go @@ -8,7 +8,7 @@ import ( const ( // HeaderRepositoryGeneration is the HTTP header for the repository epoch (synkronus ↔ clients). - HeaderRepositoryGeneration = "X-Repository-Generation" + HeaderRepositoryGeneration = "x-repository-generation" // DefaultRepositoryGeneration is the effective generation when the client omits header/body (backwards compatibility). DefaultRepositoryGeneration int64 = 1 ) diff --git a/synkronus/pkg/sync/generation_test.go b/synkronus/pkg/sync/generation_test.go new file mode 100644 index 000000000..c24d1de22 --- /dev/null +++ b/synkronus/pkg/sync/generation_test.go @@ -0,0 +1,75 @@ +package sync + +import ( + "net/http/httptest" + "testing" +) + +func TestParseClientRepositoryGeneration(t *testing.T) { + body5 := int64(5) + body2 := int64(2) + bodyNeg := int64(-1) + + tests := []struct { + name string + headerValue string + body *int64 + want int64 + }{ + { + name: "omitted header and body defaults to 1", + headerValue: "", + body: nil, + want: DefaultRepositoryGeneration, + }, + { + name: "body only", + headerValue: "", + body: &body5, + want: 5, + }, + { + name: "header only", + headerValue: "7", + body: nil, + want: 7, + }, + { + name: "header wins over body when both present", + headerValue: "3", + body: &body5, + want: 3, + }, + { + name: "invalid header string falls back to default 1 (body ignored)", + headerValue: "not-a-number", + body: &body2, + want: DefaultRepositoryGeneration, + }, + { + name: "header zero is invalid uses default 1", + headerValue: "0", + body: &body5, + want: DefaultRepositoryGeneration, + }, + { + name: "negative body ignored uses default when no valid header", + headerValue: "", + body: &bodyNeg, + want: DefaultRepositoryGeneration, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest("POST", "/", nil) + if tc.headerValue != "" { + req.Header.Set(HeaderRepositoryGeneration, tc.headerValue) + } + got := ParseClientRepositoryGeneration(req, tc.body) + if got != tc.want { + t.Fatalf("ParseClientRepositoryGeneration() = %d, want %d", got, tc.want) + } + }) + } +}