diff --git a/lib/services/leantime-service.ts b/lib/services/leantime-service.ts index f01492ef..ad8da0cd 100644 --- a/lib/services/leantime-service.ts +++ b/lib/services/leantime-service.ts @@ -48,46 +48,79 @@ export class LeantimeService { console.log(`Creating project with dates: start=${formattedStartDate}, end=${formattedEndDate}`); - // Create the project - Follow the exact same pattern from user creation which works - // This pattern matches the Leantime PHP backend expectations + // Create project values object + const projectData = { + name: mission.name, + clientId: clientId, + details: mission.intention || '', + type: 'project', + start: formattedStartDate, + end: formattedEndDate, + status: 'open', + psettings: 'restricted' + }; + + console.log('Creating project with data:', JSON.stringify(projectData, null, 2)); + + // Create with direct call to createProject + const createResponse = await axios.post( + this.getApiEndpoint(), + { + method: 'leantime.rpc.Projects.createProject', + jsonrpc: '2.0', + id: 1, + params: projectData + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + } + } + ); + + // Log the response + console.log('Create response status:', createResponse.status); + console.log('Create response data:', JSON.stringify(createResponse.data, null, 2)); + + if (createResponse.data && createResponse.data.result) { + const projectId = createResponse.data.result; + console.log(`Created Leantime project with ID: ${projectId}`); + + // If the mission has a logo, set it as project avatar + if (mission.logo) { + await this.setProjectAvatar(projectId, mission.logo); + } + + // Assign users to the project + if (mission.missionUsers && mission.missionUsers.length > 0) { + await this.assignUsersToProject(projectId, mission.missionUsers); + } + + return projectId; + } + + // Fall back to addProject if createProject fails + console.log('createProject failed, trying addProject...'); + const payload = { - method: 'leantime.rpc.Projects.Projects.addProject', + method: 'leantime.rpc.Projects.addProject', jsonrpc: '2.0', id: 1, params: { - // Use the same pattern of indexed properties + named properties that works in createLeantimeUser - values: { - '0': 0, // project ID will be set by Leantime - '1': mission.name, // name - '2': mission.intention || '', // details - '3': clientId, // clientId - '4': 'project', // type - '5': formattedStartDate, // start - '6': formattedEndDate, // end - '7': 'open', // status - '8': 0, // hourBudget - '9': 0, // dollarBudget - '10': 'restricted', // psettings - - // Also include named keys for robustness - clientId: clientId, - name: mission.name, - details: mission.intention || '', - type: 'project', - start: formattedStartDate, - end: formattedEndDate, - status: 'open', - hourBudget: 0, - dollarBudget: 0, - psettings: 'restricted' - } + name: mission.name, + clientId: clientId, + details: mission.intention || '', + type: 'project', + start: formattedStartDate, + end: formattedEndDate, + status: 'open', + psettings: 'restricted' } }; - // Log the full payload - console.log('Project creation payload:', JSON.stringify(payload, null, 2)); + console.log('addProject payload:', JSON.stringify(payload, null, 2)); - // Create the project const response = await axios.post( this.getApiEndpoint(), payload, @@ -99,9 +132,8 @@ export class LeantimeService { } ); - // Log the response - console.log('Leantime response status:', response.status); - console.log('Leantime response:', JSON.stringify(response.data, null, 2)); + console.log('addProject response status:', response.status); + console.log('addProject response data:', JSON.stringify(response.data, null, 2)); if (!response.data || !response.data.result) { throw new Error(`Failed to create Leantime project: ${JSON.stringify(response.data)}`); @@ -224,16 +256,19 @@ export class LeantimeService { */ async assignUserToProject(projectId: number, userId: string, role: string): Promise { try { + console.log(`Assigning user ${userId} to project ${projectId} with role ${role}`); + + // Try with Projects.addUser first const response = await axios.post( this.getApiEndpoint(), { - method: 'leantime.rpc.Projects.Projects.assignUserToProject', + method: 'leantime.rpc.Projects.addUser', jsonrpc: '2.0', id: 1, params: { - projectId, - userId, - role + projectId: projectId, + userId: userId, + role: role } }, { @@ -244,11 +279,39 @@ export class LeantimeService { } ); - if (!response.data || !response.data.result) { - throw new Error(`Failed to assign user to project: ${JSON.stringify(response.data)}`); + if (response.data && response.data.result) { + console.log(`Assigned user ${userId} to project ${projectId} with role ${role}`); + return; } - - console.log(`Assigned user ${userId} to project ${projectId} with role ${role}`); + + // If that fails, try with the alternative format + console.log('First method failed, trying Projects.Projects.assignUserToProject...'); + + const altResponse = await axios.post( + this.getApiEndpoint(), + { + method: 'leantime.rpc.Projects.Projects.assignUserToProject', + jsonrpc: '2.0', + id: 1, + params: { + projectId: projectId, + userId: userId, + role: role + } + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + } + } + ); + + if (!altResponse.data || !altResponse.data.result) { + throw new Error(`Failed to assign user to project with both methods: ${JSON.stringify(altResponse.data)}`); + } + + console.log(`Assigned user ${userId} to project ${projectId} with role ${role} using alternative method`); } catch (error) { console.error(`Error assigning user ${userId} to project ${projectId}:`, error); // Don't fail if individual user assignment fails @@ -366,12 +429,46 @@ export class LeantimeService { */ async getUserByEmail(email: string): Promise { try { + console.log(`Looking up user with email: ${email}`); + + // Try getUserByEmail first (direct lookup method) + try { + const directResponse = await axios.post( + this.getApiEndpoint(), + { + method: 'leantime.rpc.Users.Users.getUserByEmail', + jsonrpc: '2.0', + id: 1, + params: { + email: email + } + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + } + } + ); + + if (directResponse.data && + directResponse.data.result && + directResponse.data.result !== false && + directResponse.data.result.id) { + console.log(`Found user by direct email lookup: ${directResponse.data.result.id}`); + return directResponse.data.result.id; + } + } catch (directError) { + console.log('Direct email lookup failed, trying alternate method...'); + } + + // Fall back to get all users and filter const response = await axios.post( this.getApiEndpoint(), { method: 'leantime.rpc.Users.Users.getAll', jsonrpc: '2.0', - id: 1, + id: 1 }, { headers: { @@ -386,9 +483,28 @@ export class LeantimeService { } const users = response.data.result; - const user = users.find((u: any) => u.email === email); + + // First try exact email match + let user = users.find((u: any) => u.email === email || u.username === email); + + // If that fails, try case-insensitive match + if (!user) { + const lowerEmail = email.toLowerCase(); + user = users.find((u: any) => + (u.email && u.email.toLowerCase() === lowerEmail) || + (u.username && u.username.toLowerCase() === lowerEmail) + ); + } - return user ? user.id : null; + if (user) { + console.log(`Found user with email ${email}: ID ${user.id}`); + return user.id; + } else { + // If user is still not found, we might need to create them + // This would require additional code to create a user if needed + console.log(`No user found with email ${email}`); + return null; + } } catch (error) { console.error('Error getting user by email:', error); return null;