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 { LeantimeService } from './leantime-service';
|
||||||
import { OutlineService } from './outline-service';
|
import { OutlineService } from './outline-service';
|
||||||
import { RocketChatService } from './rocketchat-service';
|
import { RocketChatService } from './rocketchat-service';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
interface IntegrationResult {
|
interface IntegrationResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@ -54,44 +55,88 @@ export class IntegrationService {
|
|||||||
let leantimeProjectId: number | undefined;
|
let leantimeProjectId: number | undefined;
|
||||||
let outlineCollectionId: string | undefined;
|
let outlineCollectionId: string | undefined;
|
||||||
let rocketChatChannelId: 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 {
|
try {
|
||||||
// Step 1: Create Leantime project
|
// Step 1: Create Leantime project (Consider this a critical integration)
|
||||||
leantimeProjectId = await this.leantimeService.createProject(mission);
|
try {
|
||||||
console.log(`Leantime project created with ID: ${leantimeProjectId}`);
|
leantimeProjectId = await this.leantimeService.createProject(mission);
|
||||||
|
console.log(`Leantime project created with ID: ${leantimeProjectId}`);
|
||||||
// Step 2: Create Outline collection
|
integrationStatus.leantime.success = true;
|
||||||
outlineCollectionId = await this.outlineService.createCollection(mission);
|
integrationStatus.leantime.id = leantimeProjectId;
|
||||||
console.log(`Outline collection created with ID: ${outlineCollectionId}`);
|
} catch (leantimeError) {
|
||||||
|
console.error('Error creating Leantime project:', leantimeError);
|
||||||
// Step 3: Create Rocket.Chat channel
|
integrationStatus.leantime.success = false;
|
||||||
rocketChatChannelId = await this.rocketChatService.createChannel(mission);
|
integrationStatus.leantime.error = leantimeError instanceof Error ? leantimeError.message : String(leantimeError);
|
||||||
console.log(`Rocket.Chat channel created with ID: ${rocketChatChannelId}`);
|
criticalFailure = true;
|
||||||
|
throw leantimeError; // Leantime is critical, so we rethrow
|
||||||
// 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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mission.services.includes('artlab')) {
|
// Add a delay to avoid rate limits
|
||||||
// TODO: Add Penpot integration when API docs are available
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
console.log('Artlab service requested but integration not implemented yet');
|
|
||||||
|
// 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({
|
await prisma.mission.update({
|
||||||
where: { id: missionId },
|
where: { id: missionId },
|
||||||
data: {
|
data: {
|
||||||
leantimeProjectId: leantimeProjectId.toString(),
|
leantimeProjectId: integrationStatus.leantime.success ? leantimeProjectId?.toString() : undefined,
|
||||||
outlineCollectionId: outlineCollectionId,
|
outlineCollectionId: integrationStatus.outline.success ? outlineCollectionId : undefined,
|
||||||
rocketChatChannelId: rocketChatChannelId,
|
rocketChatChannelId: integrationStatus.rocketchat.success ? rocketChatChannelId : undefined,
|
||||||
// giteaRepositoryUrl and penpotProjectId will be added when implemented
|
// 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 {
|
return {
|
||||||
success: true,
|
success: !criticalFailure,
|
||||||
data: {
|
data: {
|
||||||
leantimeProjectId,
|
leantimeProjectId,
|
||||||
outlineCollectionId,
|
outlineCollectionId,
|
||||||
@ -104,10 +149,15 @@ export class IntegrationService {
|
|||||||
// Rollback any created resources
|
// Rollback any created resources
|
||||||
await this.rollbackIntegrations(leantimeProjectId, outlineCollectionId, rocketChatChannelId);
|
await this.rollbackIntegrations(leantimeProjectId, outlineCollectionId, rocketChatChannelId);
|
||||||
|
|
||||||
// Delete the mission itself since an integration failed
|
// Delete the mission itself since a critical integration failed
|
||||||
await prisma.mission.delete({
|
try {
|
||||||
where: { id: missionId }
|
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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@ -134,24 +184,71 @@ export class IntegrationService {
|
|||||||
outlineCollectionId?: string,
|
outlineCollectionId?: string,
|
||||||
rocketChatChannelId?: string
|
rocketChatChannelId?: string
|
||||||
): Promise<void> {
|
): 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 {
|
try {
|
||||||
// Attempt to delete Leantime project
|
// Attempt to delete Leantime project
|
||||||
if (leantimeProjectId) {
|
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
|
// Attempt to delete Outline collection
|
||||||
if (outlineCollectionId) {
|
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
|
// Attempt to delete Rocket.Chat channel
|
||||||
if (rocketChatChannelId) {
|
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) {
|
} catch (error) {
|
||||||
console.error('Error during rollback:', 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) {
|
} catch (directError) {
|
||||||
console.log('Direct email lookup failed, trying alternate method...');
|
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
|
// Fall back to get all users and filter
|
||||||
const response = await axios.post(
|
try {
|
||||||
this.getApiEndpoint(),
|
const response = await axios.post(
|
||||||
{
|
this.getApiEndpoint(),
|
||||||
method: 'leantime.rpc.Users.Users.getAll',
|
{
|
||||||
jsonrpc: '2.0',
|
method: 'leantime.rpc.Users.Users.getAll',
|
||||||
id: 1
|
jsonrpc: '2.0',
|
||||||
},
|
id: 1
|
||||||
{
|
},
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
'X-API-Key': this.apiToken
|
'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) {
|
if (!response.data || !response.data.result) {
|
||||||
console.log(`Found user with email ${email}: ID ${user.id}`);
|
throw new Error(`Failed to get users: ${JSON.stringify(response.data)}`);
|
||||||
return user.id;
|
}
|
||||||
} else {
|
|
||||||
// If user is still not found, we might need to create them
|
const users = response.data.result;
|
||||||
// This would require additional code to create a user if needed
|
|
||||||
console.log(`No user found with email ${email}`);
|
// First try exact email match
|
||||||
return null;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error getting user by email:', error);
|
console.error('Error getting user by email:', error);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -16,6 +16,10 @@ export class OutlineService {
|
|||||||
*/
|
*/
|
||||||
async createCollection(mission: any): Promise<string> {
|
async createCollection(mission: any): Promise<string> {
|
||||||
try {
|
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(
|
const response = await axios.post(
|
||||||
`${this.apiUrl}/collections.create`,
|
`${this.apiUrl}/collections.create`,
|
||||||
{
|
{
|
||||||
@ -46,6 +50,13 @@ export class OutlineService {
|
|||||||
|
|
||||||
return collectionId;
|
return collectionId;
|
||||||
} catch (error) {
|
} 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);
|
console.error('Error creating Outline collection:', error);
|
||||||
throw new Error(`Outline integration failed: ${error instanceof Error ? error.message : String(error)}`);
|
throw new Error(`Outline integration failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user