import axios from 'axios'; export class OutlineService { private apiUrl: string; private apiToken: string; constructor() { this.apiUrl = process.env.OUTLINE_API_URL || 'https://app.getoutline.com/api'; this.apiToken = process.env.OUTLINE_API_KEY || ''; } /** * Create a new collection in Outline * @param mission The mission data * @returns Collection ID or throws error */ async createCollection(mission: any): Promise { try { // Log the API details for debugging console.log('Creating Outline collection with token length:', this.apiToken ? this.apiToken.length : 0); console.log('Outline API URL:', this.apiUrl); const response = await axios.post( `${this.apiUrl}/collections.create`, { name: mission.name, description: mission.intention || '', permission: 'read', private: true }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiToken}` } } ); if (!response.data || !response.data.data || !response.data.data.id) { throw new Error(`Failed to create Outline collection: ${JSON.stringify(response.data)}`); } const collectionId = response.data.data.id; console.log(`Created Outline collection with ID: ${collectionId}`); // Assign users to the collection if (mission.missionUsers && mission.missionUsers.length > 0) { await this.assignUsersToCollection(collectionId, mission.missionUsers); } return collectionId; } catch (error) { if (axios.isAxiosError(error) && error.response) { console.error('Outline API Error Details:', { status: error.response.status, statusText: error.response.statusText, data: error.response.data }); } console.error('Error creating Outline collection:', error); throw new Error(`Outline integration failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Assign mission users to the Outline collection * @param collectionId The Outline collection ID * @param missionUsers The mission users with roles */ async assignUsersToCollection(collectionId: string, missionUsers: any[]): Promise { try { for (const missionUser of missionUsers) { // Get the user in Outline const outlineUserId = await this.getUserByEmail(missionUser.user.email); if (!outlineUserId) { console.warn(`User not found in Outline: ${missionUser.user.email}`); continue; } // Determine permission (Gardien de la Mémoire gets admin, others get read) const permission = missionUser.role === 'gardien-memoire' ? 'admin' : 'read'; // Add the user to the collection await this.addUserToCollection(collectionId, outlineUserId, permission); } } catch (error) { console.error('Error assigning users to collection:', error); // Continue even if some user assignments fail } } /** * Add a user to a collection with the specified permission * @param collectionId The Outline collection ID * @param userId The Outline user ID * @param permission The permission to assign */ async addUserToCollection(collectionId: string, userId: string, permission: 'read' | 'admin'): Promise { try { const response = await axios.post( `${this.apiUrl}/collections.add_user`, { id: collectionId, userId: userId, permission: permission }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiToken}` } } ); if (!response.data || !response.data.success) { throw new Error(`Failed to add user to collection: ${JSON.stringify(response.data)}`); } console.log(`Added user ${userId} to collection ${collectionId} with permission ${permission}`); } catch (error) { console.error(`Error adding user ${userId} to collection ${collectionId}:`, error); // Don't fail if individual user assignment fails } } /** * Get a user ID by email * @param email The user email to search for * @returns The user ID or null if not found */ async getUserByEmail(email: string): Promise { try { const response = await axios.post( `${this.apiUrl}/users.info`, { email: email }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiToken}` } } ); if (!response.data || !response.data.data || !response.data.data.id) { return null; } return response.data.data.id; } catch (error) { console.error('Error getting user by email:', error); return null; } } /** * Delete a collection from Outline * @param collectionId The Outline collection ID to delete * @returns True if successful, false otherwise */ async deleteCollection(collectionId: string): Promise { try { const response = await axios.post( `${this.apiUrl}/collections.delete`, { id: collectionId }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiToken}` } } ); return response.data && response.data.success === true; } catch (error) { console.error(`Error deleting Outline collection ${collectionId}:`, error); return false; } } }