missions api2

This commit is contained in:
alma 2025-05-06 16:14:09 +02:00
parent 20fc307a59
commit 1bd2673ac9
3 changed files with 194 additions and 71 deletions

View File

@ -2,6 +2,7 @@ import { prisma } from '@/lib/prisma';
import { LeantimeService } from './leantime-service';
import { OutlineService } from './outline-service';
import { RocketChatService } from './rocketchat-service';
import axios from 'axios';
interface IntegrationResult {
success: boolean;
@ -54,44 +55,88 @@ export class IntegrationService {
let leantimeProjectId: number | undefined;
let outlineCollectionId: string | undefined;
let rocketChatChannelId: string | undefined;
// Track which integrations succeeded and which failed
const integrationStatus = {
leantime: { success: false, id: undefined as number | undefined, error: undefined as string | undefined },
outline: { success: false, id: undefined as string | undefined, error: undefined as string | undefined },
rocketchat: { success: false, id: undefined as string | undefined, error: undefined as string | undefined }
};
// A flag to determine if we should consider this a success or failure overall
let criticalFailure = false;
try {
// Step 1: Create Leantime project
leantimeProjectId = await this.leantimeService.createProject(mission);
console.log(`Leantime project created with ID: ${leantimeProjectId}`);
// Step 2: Create Outline collection
outlineCollectionId = await this.outlineService.createCollection(mission);
console.log(`Outline collection created with ID: ${outlineCollectionId}`);
// Step 3: Create Rocket.Chat channel
rocketChatChannelId = await this.rocketChatService.createChannel(mission);
console.log(`Rocket.Chat channel created with ID: ${rocketChatChannelId}`);
// Add integrations for specific services
if (mission.services.includes('gite')) {
// TODO: Add Gitea integration when API docs are available
console.log('Gitea service requested but integration not implemented yet');
// Step 1: Create Leantime project (Consider this a critical integration)
try {
leantimeProjectId = await this.leantimeService.createProject(mission);
console.log(`Leantime project created with ID: ${leantimeProjectId}`);
integrationStatus.leantime.success = true;
integrationStatus.leantime.id = leantimeProjectId;
} catch (leantimeError) {
console.error('Error creating Leantime project:', leantimeError);
integrationStatus.leantime.success = false;
integrationStatus.leantime.error = leantimeError instanceof Error ? leantimeError.message : String(leantimeError);
criticalFailure = true;
throw leantimeError; // Leantime is critical, so we rethrow
}
if (mission.services.includes('artlab')) {
// TODO: Add Penpot integration when API docs are available
console.log('Artlab service requested but integration not implemented yet');
// Add a delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 500));
// Step 2: Create Outline collection (Consider this non-critical)
try {
outlineCollectionId = await this.outlineService.createCollection(mission);
console.log(`Outline collection created with ID: ${outlineCollectionId}`);
integrationStatus.outline.success = true;
integrationStatus.outline.id = outlineCollectionId;
} catch (outlineError) {
console.error('Error creating Outline collection:', outlineError);
integrationStatus.outline.success = false;
integrationStatus.outline.error = outlineError instanceof Error ? outlineError.message : String(outlineError);
// Check if it's an authentication error (401)
if (axios.isAxiosError(outlineError) && outlineError.response?.status === 401) {
console.log('⚠️ Outline authentication error. Please check your API credentials.');
}
// Don't set criticalFailure - Outline is non-critical
}
// Update the mission with the integration IDs
// Add a delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 500));
// Step 3: Create Rocket.Chat channel (Consider this non-critical)
try {
rocketChatChannelId = await this.rocketChatService.createChannel(mission);
console.log(`Rocket.Chat channel created with ID: ${rocketChatChannelId}`);
integrationStatus.rocketchat.success = true;
integrationStatus.rocketchat.id = rocketChatChannelId;
} catch (rocketChatError) {
console.error('Error creating Rocket.Chat channel:', rocketChatError);
integrationStatus.rocketchat.success = false;
integrationStatus.rocketchat.error = rocketChatError instanceof Error ? rocketChatError.message : String(rocketChatError);
// Don't set criticalFailure - Rocket.Chat is non-critical
}
// Update the mission with the integration IDs (only for successful integrations)
await prisma.mission.update({
where: { id: missionId },
data: {
leantimeProjectId: leantimeProjectId.toString(),
outlineCollectionId: outlineCollectionId,
rocketChatChannelId: rocketChatChannelId,
leantimeProjectId: integrationStatus.leantime.success ? leantimeProjectId?.toString() : undefined,
outlineCollectionId: integrationStatus.outline.success ? outlineCollectionId : undefined,
rocketChatChannelId: integrationStatus.rocketchat.success ? rocketChatChannelId : undefined,
// giteaRepositoryUrl and penpotProjectId will be added when implemented
}
});
// Output a summary of integration results
console.log('Integration results:', JSON.stringify(integrationStatus, null, 2));
// If we get here without a critical failure, we consider it a success
// even if some non-critical integrations failed
return {
success: true,
success: !criticalFailure,
data: {
leantimeProjectId,
outlineCollectionId,
@ -104,10 +149,15 @@ export class IntegrationService {
// Rollback any created resources
await this.rollbackIntegrations(leantimeProjectId, outlineCollectionId, rocketChatChannelId);
// Delete the mission itself since an integration failed
await prisma.mission.delete({
where: { id: missionId }
});
// Delete the mission itself since a critical integration failed
try {
await prisma.mission.delete({
where: { id: missionId }
});
console.log(`Mission ${missionId} deleted due to critical integration failure.`);
} catch (deleteError) {
console.error('Error deleting mission after integration failure:', deleteError);
}
return {
success: false,
@ -134,24 +184,71 @@ export class IntegrationService {
outlineCollectionId?: string,
rocketChatChannelId?: string
): Promise<void> {
console.log('⚠️ Rolling back integrations due to an error...');
// Track what we've successfully rolled back
const rollbackStatuses = {
leantime: false,
outline: false,
rocketchat: false
};
try {
// Attempt to delete Leantime project
if (leantimeProjectId) {
await this.leantimeService.deleteProject(leantimeProjectId);
try {
const leantimeSuccess = await this.leantimeService.deleteProject(leantimeProjectId);
rollbackStatuses.leantime = leantimeSuccess;
console.log(`Leantime project deletion ${leantimeSuccess ? 'successful' : 'failed'}: ${leantimeProjectId}`);
// Add a delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (leantimeError) {
console.error('Error during Leantime rollback:', leantimeError);
console.log(`⚠️ Note: Leantime project ${leantimeProjectId} may need to be deleted manually`);
}
}
// Attempt to delete Outline collection
if (outlineCollectionId) {
await this.outlineService.deleteCollection(outlineCollectionId);
try {
const outlineSuccess = await this.outlineService.deleteCollection(outlineCollectionId);
rollbackStatuses.outline = outlineSuccess;
console.log(`Outline collection deletion ${outlineSuccess ? 'successful' : 'failed'}: ${outlineCollectionId}`);
// Add a delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (outlineError) {
console.error('Error during Outline rollback:', outlineError);
console.log(`⚠️ Note: Outline collection ${outlineCollectionId} may need to be deleted manually`);
}
}
// Attempt to delete Rocket.Chat channel
if (rocketChatChannelId) {
await this.rocketChatService.deleteChannel(rocketChatChannelId);
try {
const rocketChatSuccess = await this.rocketChatService.deleteChannel(rocketChatChannelId);
rollbackStatuses.rocketchat = rocketChatSuccess;
console.log(`Rocket.Chat channel deletion ${rocketChatSuccess ? 'successful' : 'failed'}: ${rocketChatChannelId}`);
} catch (rocketChatError) {
console.error('Error during Rocket.Chat rollback:', rocketChatError);
console.log(`⚠️ Note: Rocket.Chat channel ${rocketChatChannelId} may need to be deleted manually`);
}
}
// Provide a summary of rollback operations
console.log('Rollback summary:', JSON.stringify(rollbackStatuses));
// If any rollbacks failed, provide a note
if (!rollbackStatuses.leantime || !rollbackStatuses.outline || !rollbackStatuses.rocketchat) {
console.log('⚠️ Some resources may need to be deleted manually.');
}
} catch (error) {
console.error('Error during rollback:', error);
// Even if rollback fails, we continue with mission deletion
console.log('⚠️ Resources may need to be deleted manually:');
if (leantimeProjectId) console.log(`- Leantime project: ${leantimeProjectId}`);
if (outlineCollectionId) console.log(`- Outline collection: ${outlineCollectionId}`);
if (rocketChatChannelId) console.log(`- Rocket.Chat channel: ${rocketChatChannelId}`);
}
}
}

View File

@ -576,51 +576,66 @@ export class LeantimeService {
}
} catch (directError) {
console.log('Direct email lookup failed, trying alternate method...');
// Check if the error is rate limiting (429)
if (axios.isAxiosError(directError) && directError.response?.status === 429) {
console.log('Rate limiting detected (429). Waiting before retry...');
// Wait 1 second before next API call to respect rate limits
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 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
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiToken
try {
const response = await axios.post(
this.getApiEndpoint(),
{
method: 'leantime.rpc.Users.Users.getAll',
jsonrpc: '2.0',
id: 1
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiToken
}
}
}
);
if (!response.data || !response.data.result) {
throw new Error(`Failed to get users: ${JSON.stringify(response.data)}`);
}
const users = response.data.result;
// 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)
);
}
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;
if (!response.data || !response.data.result) {
throw new Error(`Failed to get users: ${JSON.stringify(response.data)}`);
}
const users = response.data.result;
// 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)
);
}
if (user) {
console.log(`Found user with email ${email}: ID ${user.id}`);
return user.id;
}
} catch (fetchError) {
// Check if this is a rate limiting error
if (axios.isAxiosError(fetchError) && fetchError.response?.status === 429) {
console.log('Rate limiting detected (429). Consider user assignment through manual UI.');
} else {
console.error('Error fetching users:', fetchError);
}
}
// If user is still not found or error occurred
console.log(`No user found with email ${email}`);
return null;
} catch (error) {
console.error('Error getting user by email:', error);
return null;

View File

@ -16,6 +16,10 @@ export class OutlineService {
*/
async createCollection(mission: any): Promise<string> {
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`,
{
@ -46,6 +50,13 @@ export class OutlineService {
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)}`);
}