import { ApiRequestOptions } from '../hooks/useApi'; // ============================================================================ // TYPES & INTERFACES // ============================================================================ export interface KnowledgePreferences { schemaVersion?: number; neutralizeBeforeEmbed?: boolean; mailContentDepth?: 'metadata' | 'snippet' | 'full'; mailIndexAttachments?: boolean; filesIndexBinaries?: boolean; mimeAllowlist?: string[]; clickupScope?: 'titles' | 'title_description' | 'with_comments'; clickupIndexAttachments?: boolean; surfaceToggles?: { google?: { gmail?: boolean; drive?: boolean }; msft?: { sharepoint?: boolean; outlook?: boolean }; }; maxAgeDays?: number; } export interface Connection { id: string; userId: string; authority: 'local' | 'google' | 'msft' | 'clickup' | 'infomaniak'; externalId: string; externalUsername: string; externalEmail?: string; status: 'active' | 'expired' | 'revoked' | 'pending'; connectedAt: number; // Backend uses float for UTC timestamp in seconds lastChecked: number; // Backend uses float for UTC timestamp in seconds expiresAt?: number; // Backend uses Optional[float] for UTC timestamp in seconds knowledgeIngestionEnabled?: boolean; knowledgePreferences?: KnowledgePreferences | null; [key: string]: any; // Allow additional properties } export interface AttributeDefinition { name: string; label: string; type: 'string' | 'number' | 'date' | 'boolean' | 'enum'; sortable?: boolean; filterable?: boolean; searchable?: boolean; width?: number; minWidth?: number; maxWidth?: number; filterOptions?: string[]; } export interface PaginationParams { page?: number; pageSize?: number; sort?: Array<{ field: string; direction: 'asc' | 'desc' }>; filters?: Record; search?: string; /** Key of a saved view to apply (server loads groupByLevels, filters, sort from DB). */ viewKey?: string; /** Explicit grouping levels; when sent (incl. []), overrides the view for this request. */ groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>; } export interface GroupBand { path: string[]; label: string; startRowIndex: number; rowCount: number; } export interface GroupLayout { levels: string[]; bands: GroupBand[]; } export interface PaginatedResponse { items: T[]; pagination?: { currentPage: number; pageSize: number; totalItems: number; totalPages: number; }; groupLayout?: GroupLayout; appliedView?: { viewKey?: string; displayName?: string }; } export interface CreateConnectionData { id?: string; userId?: string; authority?: 'msft' | 'google' | 'clickup' | 'infomaniak'; type?: 'msft' | 'google' | 'clickup' | 'infomaniak'; // Backend maps type → authority externalId?: string; externalUsername?: string; externalEmail?: string; status?: 'active' | 'expired' | 'revoked' | 'pending'; knowledgeIngestionEnabled?: boolean; knowledgePreferences?: KnowledgePreferences | null; connectedAt?: number; lastChecked?: number; expiresAt?: number; } export interface ConnectResponse { authUrl: string; } // Type for the request function passed to API functions export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; // ============================================================================ // API REQUEST FUNCTIONS // ============================================================================ /** * Fetch connection attributes from backend * Endpoint: GET /api/attributes/UserConnection */ export async function fetchConnectionAttributes(_request: ApiRequestFunction): Promise { // Note: This uses api.get directly due to response format handling // For now, we'll use api.get directly in the hook as well throw new Error('fetchConnectionAttributes should use api instance directly for response format handling'); } /** * Fetch list of connections with optional pagination * Endpoint: GET /api/connections/ */ export async function fetchConnections( request: ApiRequestFunction, params?: PaginationParams ): Promise | Connection[]> { const requestParams: any = {}; // Build pagination object if provided if (params) { const paginationObj: any = {}; if (params.page !== undefined) paginationObj.page = params.page; if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize; if (params.sort) paginationObj.sort = params.sort; if (params.filters) paginationObj.filters = params.filters; if (params.search) paginationObj.search = params.search; if (params.viewKey) paginationObj.viewKey = params.viewKey; if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels; if (Object.keys(paginationObj).length > 0) { requestParams.pagination = JSON.stringify(paginationObj); } } const data = await request({ url: '/api/connections/', method: 'get', params: requestParams }); return data; } /** * Create a new connection * Endpoint: POST /api/connections/ */ export async function createConnection( request: ApiRequestFunction, connectionData: CreateConnectionData ): Promise { return await request({ url: '/api/connections/', method: 'post', data: connectionData }); } /** * Connect to a service (initiate OAuth) * Endpoint: POST /api/connections/{connectionId}/connect * * @param reauth If true, forces the OAuth provider to re-show the consent screen. * Required when newly added scopes (e.g. Calendar/Contacts after a * feature rollout) need to be granted on top of the existing token. */ export async function connectService( request: ApiRequestFunction, connectionId: string, reauth: boolean = false ): Promise { return await request({ url: `/api/connections/${connectionId}/connect`, method: 'post', data: reauth ? { reauth: true } : undefined, }); } /** * Disconnect from a service * Endpoint: POST /api/connections/{connectionId}/disconnect */ export async function disconnectService( request: ApiRequestFunction, connectionId: string ): Promise<{ message: string }> { return await request({ url: `/api/connections/${connectionId}/disconnect`, method: 'post' }); } /** * Delete a connection * Endpoint: DELETE /api/connections/{connectionId} */ export async function deleteConnection( request: ApiRequestFunction, connectionId: string ): Promise<{ message: string }> { return await request({ url: `/api/connections/${connectionId}`, method: 'delete' }); } /** * Update a connection * Endpoint: PUT /api/connections/{connectionId} */ export async function updateConnection( request: ApiRequestFunction, connectionId: string, updateData: Partial ): Promise { return await request({ url: `/api/connections/${connectionId}`, method: 'put', data: updateData }); } /** * Refresh Microsoft token * Endpoint: POST /api/msft/refresh */ export async function refreshMicrosoftToken( request: ApiRequestFunction, connectionId: string ): Promise { return await request({ url: '/api/msft/refresh', method: 'post', data: { connectionId } }); } /** * Refresh Google token * Endpoint: POST /api/google/refresh */ export async function refreshGoogleToken( request: ApiRequestFunction, connectionId: string ): Promise { return await request({ url: '/api/google/refresh', method: 'post', data: { connectionId } }); } /** * Submit an Infomaniak Personal Access Token (kdrive + mail) for an existing * UserConnection. The backend validates the token via /1/profile and stores it * as the connection's data-access bearer token. * Endpoint: POST /api/infomaniak/connections/{connectionId}/token */ export async function submitInfomaniakToken( request: ApiRequestFunction, connectionId: string, token: string ): Promise<{ id: string; status: string; type: string; externalUsername: string; externalEmail?: string | null; lastChecked: number; }> { return await request({ url: `/api/infomaniak/connections/${connectionId}/token`, method: 'post', data: { token } }); }