179 lines
5.0 KiB
TypeScript
179 lines
5.0 KiB
TypeScript
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
|
|
// S3 Configuration
|
|
export const S3_CONFIG = {
|
|
endpoint: 'https://dome-api.slm-lab.net',
|
|
region: 'us-east-1',
|
|
bucket: process.env.S3_BUCKET || 'pages',
|
|
accessKey: '4aBT4CMb7JIMMyUtp4Pl',
|
|
secretKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg'
|
|
};
|
|
|
|
// Initialize S3 client for Minio
|
|
export const s3Client = new S3Client({
|
|
region: S3_CONFIG.region,
|
|
endpoint: S3_CONFIG.endpoint,
|
|
credentials: {
|
|
accessKeyId: S3_CONFIG.accessKey,
|
|
secretAccessKey: S3_CONFIG.secretKey
|
|
},
|
|
forcePathStyle: true // Required for MinIO
|
|
});
|
|
|
|
/**
|
|
* Upload a file to S3
|
|
*/
|
|
export async function putObject(
|
|
key: string,
|
|
content: string | Buffer,
|
|
contentType?: string
|
|
): Promise<{ key: string; url?: string }> {
|
|
const command = new PutObjectCommand({
|
|
Bucket: S3_CONFIG.bucket,
|
|
Key: key,
|
|
Body: typeof content === 'string' ? Buffer.from(content, 'utf-8') : content,
|
|
ContentType: contentType || 'text/plain',
|
|
});
|
|
|
|
await s3Client.send(command);
|
|
return { key };
|
|
}
|
|
|
|
/**
|
|
* Get object content from S3
|
|
*/
|
|
export async function getObjectContent(key: string): Promise<string | null> {
|
|
try {
|
|
const command = new GetObjectCommand({
|
|
Bucket: S3_CONFIG.bucket,
|
|
Key: key,
|
|
});
|
|
|
|
const response = await s3Client.send(command);
|
|
if (!response.Body) {
|
|
return null;
|
|
}
|
|
|
|
const chunks: Uint8Array[] = [];
|
|
for await (const chunk of response.Body as any) {
|
|
chunks.push(chunk);
|
|
}
|
|
|
|
const buffer = Buffer.concat(chunks);
|
|
return buffer.toString('utf-8');
|
|
} catch (error) {
|
|
console.error('Error getting object content:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete an object from S3
|
|
*/
|
|
export async function deleteObject(key: string): Promise<void> {
|
|
const command = new DeleteObjectCommand({
|
|
Bucket: S3_CONFIG.bucket,
|
|
Key: key,
|
|
});
|
|
|
|
await s3Client.send(command);
|
|
}
|
|
|
|
/**
|
|
* List objects for a user in a specific folder
|
|
*/
|
|
export async function listUserObjects(userId: string, folder: string): Promise<Array<{ key: string; name: string; size?: number; lastModified?: Date }>> {
|
|
const prefix = `user-${userId}/${folder}/`;
|
|
const command = new ListObjectsV2Command({
|
|
Bucket: S3_CONFIG.bucket,
|
|
Prefix: prefix,
|
|
Delimiter: '/'
|
|
});
|
|
|
|
const response = await s3Client.send(command);
|
|
const objects = response.Contents || [];
|
|
|
|
return objects
|
|
.filter(obj => obj.Key && !obj.Key.endsWith('/') && !obj.Key.includes('.placeholder'))
|
|
.map(obj => ({
|
|
key: obj.Key!,
|
|
name: obj.Key!.split('/').pop() || obj.Key!,
|
|
size: obj.Size,
|
|
lastModified: obj.LastModified
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get public URL for an object
|
|
* Simple URL construction for MinIO/S3 objects
|
|
*/
|
|
export function getPublicUrl(filePath: string, bucket?: string): string {
|
|
if (!filePath) return '';
|
|
if (filePath.startsWith('http')) return filePath; // Already a full URL
|
|
|
|
// Remove leading slash if present
|
|
const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
|
|
|
|
// Construct the full URL
|
|
const endpoint = S3_CONFIG.endpoint?.replace(/\/$/, ''); // Remove trailing slash if present
|
|
const bucketName = bucket || S3_CONFIG.bucket;
|
|
|
|
// Return original path if no endpoint is configured
|
|
if (!endpoint) return cleanPath;
|
|
|
|
// Construct and return the full URL
|
|
return `${endpoint}/${bucketName}/${cleanPath}`;
|
|
}
|
|
|
|
/**
|
|
* Create standard folder structure for a user
|
|
*/
|
|
export async function createUserFolderStructure(userId: string): Promise<void> {
|
|
const folders = ['notes', 'diary', 'health', 'contacts'];
|
|
|
|
for (const folder of folders) {
|
|
try {
|
|
// Create folder path (prefix in S3)
|
|
const folderKey = `user-${userId}/${folder}/`;
|
|
await putObject(folderKey, '', 'application/x-directory');
|
|
|
|
// Create placeholder file to ensure folder is visible
|
|
const placeholderKey = `user-${userId}/${folder}/.placeholder`;
|
|
await putObject(placeholderKey, 'Folder placeholder', 'text/plain');
|
|
|
|
console.log(`Created folder: ${folderKey}`);
|
|
} catch (error) {
|
|
console.error(`Error creating folder ${folder} for user ${userId}:`, error);
|
|
// Continue with other folders even if one fails
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function uploadMissionFile({
|
|
missionId,
|
|
file,
|
|
type, // 'logo' or 'attachment'
|
|
}: {
|
|
missionId: string;
|
|
file: File;
|
|
type: 'logo' | 'attachment';
|
|
}): Promise<{ success: boolean; data?: any; error?: string }> {
|
|
const formData = new FormData();
|
|
formData.append('missionId', missionId);
|
|
formData.append('type', type);
|
|
formData.append('file', file);
|
|
|
|
const res = await fetch('/api/missions/upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}));
|
|
return { success: false, error: err.error || 'Upload failed' };
|
|
}
|
|
|
|
const data = await res.json();
|
|
return { success: true, data };
|
|
}
|