NeahStable/lib/mission-uploads.ts
2026-01-21 11:40:40 +01:00

255 lines
7.8 KiB
TypeScript

/**
* Utilities for mission-related file paths and uploads
*/
import { S3Client, PutObjectCommand } 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' // Missions bucket is always 'missions'
};
// Validate required S3 configuration
if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) {
const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.';
console.error(errorMsg);
if (process.env.NODE_ENV === 'production') {
throw new Error('S3 credentials are required in production environment');
}
}
// Initialize S3 client for Minio
const s3Client = 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 // Required for MinIO
});
// Generate the mission logo path in Minio
export function getMissionLogoPath(userId: string, missionId: string, fileExtension: string): string {
// Use a consistent path structure: missions/{missionId}/logo{extension}
return `missions/${missionId}/logo${fileExtension}`;
}
// Generate the mission attachment path in Minio
export function getMissionAttachmentPath(userId: string, missionId: string, filename: string): string {
// Use a consistent path structure: missions/{missionId}/attachments/{filename}
return `missions/${missionId}/attachments/${filename}`;
}
// Helper function to ensure a path has the missions/ prefix
export function ensureMissionsPrefix(path: string): string {
return path.startsWith('missions/') ? path : `missions/${path}`;
}
// Helper function to construct a public URL for a mission file
export function getMissionFileUrl(path: string): string {
const normalizedPath = ensureMissionsPrefix(path);
return `/api/missions/image/${normalizedPath}`;
}
// Helper function to delete a mission logo
export async function deleteMissionLogo(missionId: string, logoPath: string): Promise<void> {
const normalizedPath = ensureMissionsPrefix(logoPath);
const minioPath = normalizedPath.replace(/^missions\//, ''); // Remove prefix for Minio
try {
const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
logger.debug('Deleting mission logo', {
missionId,
minioPath
});
const command = new DeleteObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket,
Key: minioPath,
});
await s3Client.send(command);
logger.debug('Mission logo deleted successfully', { minioPath });
} catch (error) {
logger.error('Error deleting mission logo', {
error: error instanceof Error ? error.message : String(error),
missionId,
minioPath
});
throw error;
}
}
// Helper function to delete a mission attachment
export async function deleteMissionAttachment(filePath: string): Promise<void> {
const normalizedPath = ensureMissionsPrefix(filePath);
const minioPath = normalizedPath.replace(/^missions\//, ''); // Remove prefix for Minio
try {
const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
logger.debug('Deleting mission attachment', {
minioPath
});
const command = new DeleteObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket,
Key: minioPath,
});
await s3Client.send(command);
logger.debug('Mission attachment deleted successfully', { minioPath });
} catch (error) {
logger.error('Error deleting mission attachment', {
error: error instanceof Error ? error.message : String(error),
minioPath
});
throw error;
}
}
// Upload a mission logo to Minio
export async function uploadMissionLogo(userId: string, missionId: string, file: File): Promise<{ filePath: string }> {
try {
logger.debug('Starting logo upload', {
missionId,
fileName: file.name,
fileSize: file.size,
fileType: file.type
});
const fileExtension = file.name.substring(file.name.lastIndexOf('.'));
const filePath = getMissionLogoPath(userId, missionId, fileExtension);
const minioPath = filePath.replace(/^missions\//, ''); // Remove prefix for Minio
// Convert File to Buffer
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
logger.debug('Uploading to Minio', {
bucket: MISSIONS_S3_CONFIG.bucket,
key: minioPath,
contentType: file.type
});
await s3Client.send(new PutObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket,
Key: minioPath,
Body: buffer,
ContentType: file.type,
ACL: 'public-read'
}));
logger.debug('Logo upload successful', {
filePath,
minioPath
});
return { filePath };
} catch (error) {
logger.error('Error uploading mission logo', {
error: error instanceof Error ? error.message : String(error),
missionId,
fileName: file.name
});
throw error;
}
}
// Upload a mission attachment to Minio
export async function uploadMissionAttachment(
userId: string,
missionId: string,
file: File
): Promise<{
filename: string;
filePath: string;
fileType: string;
fileSize: number;
}> {
try {
logger.debug('Starting attachment upload', {
missionId,
fileName: file.name,
fileSize: file.size,
fileType: file.type
});
const filePath = getMissionAttachmentPath(userId, missionId, file.name);
const minioPath = filePath.replace(/^missions\//, ''); // Remove prefix for Minio
// Convert File to Buffer
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
logger.debug('Uploading to Minio', {
bucket: MISSIONS_S3_CONFIG.bucket,
key: minioPath,
contentType: file.type
});
await s3Client.send(new PutObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket,
Key: minioPath,
Body: buffer,
ContentType: file.type,
ACL: 'public-read'
}));
logger.debug('Attachment upload successful', {
filePath,
minioPath
});
return {
filename: file.name,
filePath,
fileType: file.type,
fileSize: file.size
};
} catch (error) {
logger.error('Error uploading mission attachment', {
error: error instanceof Error ? error.message : String(error),
missionId,
fileName: file.name
});
throw error;
}
}
// Generate a presigned URL for direct upload to Minio (for logo)
export async function generateMissionLogoUploadUrl(
userId: string,
missionId: string,
fileExtension: string
): Promise<{ uploadUrl: string; filePath: string }> {
const filePath = getMissionLogoPath(userId, missionId, fileExtension);
const minioPath = filePath.replace(/^missions\//, '');
// TODO: Implement presigned URL generation
// This would require additional AWS SDK functionality
throw new Error('Presigned URL generation not implemented');
}
// Generate a presigned URL for direct upload to Minio (for attachment)
export async function generateMissionAttachmentUploadUrl(
userId: string,
missionId: string,
filename: string
): Promise<{ uploadUrl: string; filePath: string }> {
const filePath = getMissionAttachmentPath(userId, missionId, filename);
const minioPath = filePath.replace(/^missions\//, '');
// TODO: Implement presigned URL generation
// This would require additional AWS SDK functionality
throw new Error('Presigned URL generation not implemented');
}