missions finition
This commit is contained in:
parent
18143c73db
commit
1638dfec50
@ -2,6 +2,29 @@ import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/app/api/auth/options";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { N8nService } from '@/lib/services/n8n-service';
|
||||
import { deleteMissionLogo, deleteMissionAttachment } from '@/lib/mission-uploads';
|
||||
import { S3Client, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
// S3 Configuration for missions - uses environment variables
|
||||
const MISSIONS_S3_CONFIG = {
|
||||
endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''),
|
||||
region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1',
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '',
|
||||
secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '',
|
||||
bucket: 'missions'
|
||||
};
|
||||
|
||||
const missionsS3Client = new S3Client({
|
||||
region: MISSIONS_S3_CONFIG.region,
|
||||
endpoint: MISSIONS_S3_CONFIG.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: MISSIONS_S3_CONFIG.accessKey,
|
||||
secretAccessKey: MISSIONS_S3_CONFIG.secretKey
|
||||
},
|
||||
forcePathStyle: true
|
||||
});
|
||||
|
||||
// GET endpoint to get mission details including creator and missionUsers
|
||||
export async function GET(
|
||||
@ -66,3 +89,161 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE endpoint to delete a mission
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ missionId: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { missionId } = await params;
|
||||
const userId = session.user.id;
|
||||
|
||||
// Get mission with all related data
|
||||
const mission = await prisma.mission.findUnique({
|
||||
where: { id: missionId },
|
||||
include: {
|
||||
attachments: true,
|
||||
missionUsers: true,
|
||||
calendars: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!mission) {
|
||||
return NextResponse.json({ error: 'Mission not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user has permission (only creator can delete)
|
||||
if (mission.creatorId !== userId) {
|
||||
return NextResponse.json({ error: 'Forbidden: Only the mission creator can delete the mission' }, { status: 403 });
|
||||
}
|
||||
|
||||
logger.debug('Starting mission deletion', {
|
||||
missionId,
|
||||
missionName: mission.name,
|
||||
hasLogo: !!mission.logo,
|
||||
attachmentsCount: mission.attachments.length
|
||||
});
|
||||
|
||||
// Step 1: Delete all files from S3/MinIO
|
||||
try {
|
||||
// Delete logo if exists
|
||||
if (mission.logo) {
|
||||
try {
|
||||
await deleteMissionLogo(missionId, mission.logo);
|
||||
logger.debug('Mission logo deleted from S3');
|
||||
} catch (logoError) {
|
||||
logger.warn('Error deleting logo (continuing)', {
|
||||
error: logoError instanceof Error ? logoError.message : String(logoError)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all attachments
|
||||
for (const attachment of mission.attachments) {
|
||||
try {
|
||||
await deleteMissionAttachment(attachment.filePath);
|
||||
logger.debug('Attachment deleted from S3', { filePath: attachment.filePath });
|
||||
} catch (attachmentError) {
|
||||
logger.warn('Error deleting attachment (continuing)', {
|
||||
filePath: attachment.filePath,
|
||||
error: attachmentError instanceof Error ? attachmentError.message : String(attachmentError)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files in the mission folder (cleanup any orphaned files)
|
||||
try {
|
||||
const listCommand = new ListObjectsV2Command({
|
||||
Bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
Prefix: `${missionId}/`
|
||||
});
|
||||
const listResponse = await missionsS3Client.send(listCommand);
|
||||
|
||||
if (listResponse.Contents && listResponse.Contents.length > 0) {
|
||||
const deletePromises = listResponse.Contents.map(obj => {
|
||||
if (obj.Key) {
|
||||
return missionsS3Client.send(new DeleteObjectCommand({
|
||||
Bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
Key: obj.Key
|
||||
}));
|
||||
}
|
||||
});
|
||||
await Promise.all(deletePromises);
|
||||
logger.debug('Cleaned up all files in mission folder', { count: listResponse.Contents.length });
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
logger.warn('Error cleaning up mission folder (continuing)', {
|
||||
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
||||
});
|
||||
}
|
||||
} catch (s3Error) {
|
||||
logger.error('Error deleting files from S3 (continuing with database deletion)', {
|
||||
error: s3Error instanceof Error ? s3Error.message : String(s3Error)
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2: Extract repo name for N8N
|
||||
let repoName = '';
|
||||
if (mission.giteaRepositoryUrl) {
|
||||
try {
|
||||
const url = new URL(mission.giteaRepositoryUrl);
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
repoName = pathParts[pathParts.length - 1] || '';
|
||||
} catch (error) {
|
||||
const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/);
|
||||
repoName = match ? match[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Call N8N deletion webhook (non-blocking)
|
||||
const n8nService = new N8nService();
|
||||
const n8nData = {
|
||||
missionId: mission.id,
|
||||
name: mission.name,
|
||||
repoName: repoName,
|
||||
leantimeProjectId: mission.leantimeProjectId || 0,
|
||||
documentationCollectionId: mission.outlineCollectionId || '',
|
||||
rocketchatChannelId: mission.rocketChatChannelId || '',
|
||||
giteaRepositoryUrl: mission.giteaRepositoryUrl,
|
||||
outlineCollectionId: mission.outlineCollectionId,
|
||||
rocketChatChannelId: mission.rocketChatChannelId,
|
||||
penpotProjectId: mission.penpotProjectId
|
||||
};
|
||||
|
||||
// Call N8N but don't fail if it errors
|
||||
n8nService.triggerMissionDeletion(n8nData).catch(n8nError => {
|
||||
logger.error('N8N deletion webhook failed (mission still deleted from database)', {
|
||||
error: n8nError instanceof Error ? n8nError.message : String(n8nError),
|
||||
missionId
|
||||
});
|
||||
});
|
||||
|
||||
// Step 4: Delete from database (cascade will handle related records)
|
||||
await prisma.mission.delete({
|
||||
where: { id: missionId }
|
||||
});
|
||||
|
||||
logger.debug('Mission deleted successfully', { missionId });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Mission deleted successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
const { missionId: errorMissionId } = await params;
|
||||
logger.error('Error deleting mission', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
missionId: errorMissionId
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete mission', details: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,15 +91,29 @@ export async function GET(
|
||||
|
||||
return new NextResponse(response.Body as any, { headers });
|
||||
} catch (error) {
|
||||
// Check if it's a NoSuchKey error (file doesn't exist)
|
||||
const isNoSuchKey = error instanceof NoSuchKey ||
|
||||
(error instanceof Error && error.name === 'NoSuchKey') ||
|
||||
(error instanceof Error && error.message.includes('does not exist'));
|
||||
|
||||
logger.error('Error fetching file from Minio', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
path: filePath,
|
||||
minioPath,
|
||||
bucket: 'missions',
|
||||
errorType: error instanceof NoSuchKey ? 'NoSuchKey' : 'Unknown'
|
||||
bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
errorType: isNoSuchKey ? 'NoSuchKey' : 'Unknown',
|
||||
errorName: error instanceof Error ? error.name : typeof error
|
||||
});
|
||||
if (error instanceof NoSuchKey) {
|
||||
return new NextResponse('File not found', { status: 404 });
|
||||
|
||||
if (isNoSuchKey) {
|
||||
// Return 404 with proper headers for image requests
|
||||
return new NextResponse('File not found', {
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
}
|
||||
return new NextResponse('Internal Server Error', { status: 500 });
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user