import * as plugins from '../plugins.js'; import type * as interfaces from '../interfaces/index.js'; import type { TsView } from '../tsview.classes.tsview.js'; /** * Register S3 API handlers */ export async function registerS3Handlers( typedrouter: plugins.typedrequest.TypedRouter, tsview: TsView ): Promise { // List all buckets typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listBuckets', async () => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { buckets: [] }; } // SmartBucket doesn't have a direct listBuckets method // For now return empty - in a full implementation you'd use the underlying S3 client return { buckets: [] }; } ) ); // Create bucket typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createBucket', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { success: false }; } try { await smartbucket.createBucket(reqData.bucketName); return { success: true }; } catch (err) { console.error('Error creating bucket:', err); return { success: false }; } } ) ); // Delete bucket typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteBucket', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { success: false }; } try { await smartbucket.removeBucket(reqData.bucketName); return { success: true }; } catch (err) { console.error('Error deleting bucket:', err); return { success: false }; } } ) ); // List objects in bucket typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listObjects', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { objects: [], prefixes: [] }; } try { const bucket = await smartbucket.getBucketByName(reqData.bucketName); if (!bucket) { return { objects: [], prefixes: [] }; } const prefix = reqData.prefix || ''; const delimiter = reqData.delimiter || '/'; // Get the base directory or subdirectory const baseDir = await bucket.getBaseDirectory(); let targetDir = baseDir; if (prefix) { // Navigate to the prefix directory const prefixParts = prefix.replace(/\/$/, '').split('/').filter(Boolean); for (const part of prefixParts) { const subDir = await targetDir.getSubDirectoryByName(part, { getEmptyDirectory: true }); if (subDir) { targetDir = subDir; } else { return { objects: [], prefixes: [] }; } } } const objects: interfaces.IS3Object[] = []; const prefixSet = new Set(); // List files in current directory const files = await targetDir.listFiles(); for (const file of files) { const fullPath = prefix + file.name; objects.push({ key: fullPath, isPrefix: false, }); } // List subdirectories const dirs = await targetDir.listDirectories(); for (const dir of dirs) { const fullPrefix = prefix + dir.name + '/'; prefixSet.add(fullPrefix); } return { objects, prefixes: Array.from(prefixSet), }; } catch (err) { console.error('Error listing objects:', err); return { objects: [], prefixes: [] }; } } ) ); // Get object content typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getObject', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { throw new Error('S3 not configured'); } try { const bucket = await smartbucket.getBucketByName(reqData.bucketName); if (!bucket) { throw new Error(`Bucket ${reqData.bucketName} not found`); } const content = await bucket.fastGet({ path: reqData.key }); const stats = await bucket.fastStat({ path: reqData.key }); // Determine content type from extension const ext = reqData.key.split('.').pop()?.toLowerCase() || ''; const contentTypeMap: Record = { 'json': 'application/json', 'txt': 'text/plain', 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'svg': 'image/svg+xml', 'pdf': 'application/pdf', 'xml': 'application/xml', }; const contentType = contentTypeMap[ext] || 'application/octet-stream'; return { content: content.toString('base64'), contentType, size: stats?.ContentLength || content.length, lastModified: stats?.LastModified?.toISOString() || new Date().toISOString(), }; } catch (err) { console.error('Error getting object:', err); throw err; } } ) ); // Get object metadata typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getObjectMetadata', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { throw new Error('S3 not configured'); } try { const bucket = await smartbucket.getBucketByName(reqData.bucketName); if (!bucket) { throw new Error(`Bucket ${reqData.bucketName} not found`); } const stats = await bucket.fastStat({ path: reqData.key }); const ext = reqData.key.split('.').pop()?.toLowerCase() || ''; const contentTypeMap: Record = { 'json': 'application/json', 'txt': 'text/plain', 'html': 'text/html', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'pdf': 'application/pdf', }; const contentType = contentTypeMap[ext] || 'application/octet-stream'; return { contentType, size: stats?.ContentLength || 0, lastModified: stats?.LastModified?.toISOString() || new Date().toISOString(), }; } catch (err) { console.error('Error getting object metadata:', err); throw err; } } ) ); // Put object typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'putObject', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { success: false }; } try { const bucket = await smartbucket.getBucketByName(reqData.bucketName); if (!bucket) { return { success: false }; } const content = Buffer.from(reqData.content, 'base64'); await bucket.fastPut({ path: reqData.key, contents: content, }); return { success: true }; } catch (err) { console.error('Error putting object:', err); return { success: false }; } } ) ); // Delete object typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteObject', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { success: false }; } try { const bucket = await smartbucket.getBucketByName(reqData.bucketName); if (!bucket) { return { success: false }; } await bucket.fastRemove({ path: reqData.key }); return { success: true }; } catch (err) { console.error('Error deleting object:', err); return { success: false }; } } ) ); // Copy object typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'copyObject', async (reqData) => { const smartbucket = await tsview.getSmartBucket(); if (!smartbucket) { return { success: false }; } try { const sourceBucket = await smartbucket.getBucketByName(reqData.sourceBucket); const destBucket = await smartbucket.getBucketByName(reqData.destBucket); if (!sourceBucket || !destBucket) { return { success: false }; } // Read from source const content = await sourceBucket.fastGet({ path: reqData.sourceKey }); // Write to destination await destBucket.fastPut({ path: reqData.destKey, contents: content, }); return { success: true }; } catch (err) { console.error('Error copying object:', err); return { success: false }; } } ) ); }