missions api2
This commit is contained in:
parent
20fc307a59
commit
1bd2673ac9
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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)}`);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user