initial
This commit is contained in:
326
ts/api/handlers.s3.ts
Normal file
326
ts/api/handlers.s3.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
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<void> {
|
||||
// List all buckets
|
||||
typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.IReq_ListBuckets>(
|
||||
'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<interfaces.IReq_CreateBucket>(
|
||||
'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<interfaces.IReq_DeleteBucket>(
|
||||
'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<interfaces.IReq_ListObjects>(
|
||||
'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<string>();
|
||||
|
||||
// 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<interfaces.IReq_GetObject>(
|
||||
'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<string, string> = {
|
||||
'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<interfaces.IReq_GetObjectMetadata>(
|
||||
'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<string, string> = {
|
||||
'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<interfaces.IReq_PutObject>(
|
||||
'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<interfaces.IReq_DeleteObject>(
|
||||
'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<interfaces.IReq_CopyObject>(
|
||||
'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 };
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user