diff --git a/lib/services/leantime-service.ts b/lib/services/leantime-service.ts index 97be7273..24e4c289 100644 --- a/lib/services/leantime-service.ts +++ b/lib/services/leantime-service.ts @@ -658,43 +658,107 @@ export class LeantimeService { */ async deleteProject(projectId: number): Promise { try { - console.log(`Attempting to delete Leantime project ${projectId} with method: leantime.rpc.Projects.Projects.deleteProject`); + console.log(`Attempting to delete Leantime project ${projectId} using REST API approach`); - const response = await axios.post( - this.getApiEndpoint(), - { - method: 'leantime.rpc.Projects.Projects.deleteProject', - jsonrpc: '2.0', - id: 1, - params: { - id: projectId - } - }, - { - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': this.apiToken + // Get base URL by removing any API path + const baseUrl = this.apiUrl + .replace('/api/jsonrpc', '') + .replace('/api/jsonrpc.php', ''); + + // Construct REST API URL for project deletion + // Attempt different patterns that might work: + + // 1. First, try the standard REST API approach + const restApiUrl = baseUrl.endsWith('/') + ? `${baseUrl}api/v1/projects/${projectId}` + : `${baseUrl}/api/v1/projects/${projectId}`; + + console.log(`Trying REST API deletion via DELETE to: ${restApiUrl}`); + + try { + const restResponse = await axios.delete( + restApiUrl, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + } } + ); + + console.log(`REST API Delete response:`, JSON.stringify(restResponse.data, null, 2)); + + if (restResponse.status >= 200 && restResponse.status < 300) { + console.log(`Successfully deleted project ${projectId} via REST API`); + return true; } - ); - - // Log the full response for debugging - console.log(`Leantime delete response for project ${projectId}:`, JSON.stringify(response.data, null, 2)); - - if (!response.data || response.data.result !== true) { - console.error(`Failed to delete Leantime project ${projectId} with first method. API Response:`, JSON.stringify(response.data, null, 2)); + } catch (restError) { + console.error('REST API error:', restError); + if (axios.isAxiosError(restError)) { + console.error('REST API error details:', { + status: restError.response?.status, + data: restError.response?.data, + message: restError.message + }); + } + } + + // 2. Try the web interface approach (simulate a form submission) + console.log(`Trying web interface simulation for project deletion...`); + + // Assuming a standard web interface form submission + const formUrl = baseUrl.endsWith('/') + ? `${baseUrl}projects/deleteProject/${projectId}` + : `${baseUrl}/projects/deleteProject/${projectId}`; + + console.log(`Simulating form submission to: ${formUrl}`); + + try { + // Some systems require a POST to the delete endpoint + const formResponse = await axios.post( + formUrl, + {}, // Empty payload or { confirm: true } depending on the system + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + } + } + ); - // Try alternative method (without double "Projects") - console.log(`Trying alternative method: leantime.rpc.Projects.deleteProject for project ${projectId}`); + console.log(`Form submission response:`, JSON.stringify(formResponse.data, null, 2)); - const alternativeResponse = await axios.post( + if (formResponse.status >= 200 && formResponse.status < 300) { + console.log(`Successfully deleted project ${projectId} via web interface simulation`); + return true; + } + } catch (formError) { + console.error('Form submission error:', formError); + if (axios.isAxiosError(formError)) { + console.error('Form submission error details:', { + status: formError.response?.status, + data: formError.response?.data, + message: formError.message + }); + } + } + + // 3. Last resort: Try to mark the project as archived/inactive if deletion isn't supported + console.log(`Attempting to mark project as archived/inactive as a fallback...`); + + try { + // Use the JSON-RPC API to update the project status + const updateResponse = await axios.post( this.getApiEndpoint(), { - method: 'leantime.rpc.Projects.deleteProject', + method: 'leantime.rpc.Projects.Projects.updateProject', jsonrpc: '2.0', id: 1, params: { - id: projectId + id: projectId, + values: { + status: 'archived' // Try 'archived', 'deleted', or 'inactive' + } } }, { @@ -705,76 +769,35 @@ export class LeantimeService { } ); - // Log the alternative response - console.log(`Alternative method response for project ${projectId}:`, JSON.stringify(alternativeResponse.data, null, 2)); + console.log(`Project status update response:`, JSON.stringify(updateResponse.data, null, 2)); - // Check if alternative method worked - if (alternativeResponse.data && alternativeResponse.data.result === true) { - console.log(`Successfully deleted project ${projectId} with alternative method`); + if (updateResponse.data && updateResponse.data.result) { + console.log(`Successfully marked project ${projectId} as archived`); return true; } - - // If we get here, both methods failed - console.error(`Failed to delete Leantime project ${projectId} with both methods.`); - return false; - } - - console.log(`Successfully deleted Leantime project ${projectId}`); - return true; - } catch (error) { - // Check if this is a rate limiting error - if (axios.isAxiosError(error) && error.response?.status === 429) { - console.log('Rate limiting detected (429) on delete. Waiting and retrying...'); - - // Wait 3 seconds and then retry - await new Promise(resolve => setTimeout(resolve, 3000)); - - try { - // Retry the delete - console.log(`Retrying deletion after rate limit for project ${projectId}`); - const retryResponse = await axios.post( - this.getApiEndpoint(), - { - method: 'leantime.rpc.Projects.Projects.deleteProject', - jsonrpc: '2.0', - id: 1, - params: { - id: projectId - } - }, - { - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': this.apiToken - } - } - ); - - // Log the retry response - console.log(`Retry response for project ${projectId}:`, JSON.stringify(retryResponse.data, null, 2)); - - if (retryResponse.data && retryResponse.data.result === true) { - console.log(`Successfully deleted project ${projectId} on retry`); - return true; - } else { - console.error(`Retry failed to delete project ${projectId}. Response:`, JSON.stringify(retryResponse.data, null, 2)); - return false; - } - } catch (retryError) { - console.error(`Error on retry delete for project ${projectId}:`, retryError); - if (axios.isAxiosError(retryError)) { - console.error('Retry error details:', { - status: retryError.response?.status, - data: retryError.response?.data, - message: retryError.message - }); - } - return false; + } catch (updateError) { + console.error('Project status update error:', updateError); + if (axios.isAxiosError(updateError)) { + console.error('Update error details:', { + status: updateError.response?.status, + data: updateError.response?.data, + message: updateError.message + }); } } + // If we get here, all methods failed + console.error(`Failed to delete or archive Leantime project ${projectId} with all methods.`); + console.log(`Please manually delete/archive project ${projectId} from Leantime.`); + + // Get the project URL for manual deletion + const projectUrl = this.getProjectUrl(projectId); + console.log(`Project URL for manual cleanup: ${projectUrl}`); + + return false; + } catch (error) { // Log detailed error information - console.error(`Error deleting Leantime project ${projectId}:`, error); + console.error(`Error in deleteProject for Leantime project ${projectId}:`, error); if (axios.isAxiosError(error)) { console.error('Error details:', { status: error.response?.status,