mcp-memory-libsql
by spences10
Verified
import {
AttachmentMetadata,
AttachmentResult,
AttachmentServiceConfig,
AttachmentSource,
AttachmentValidationResult,
ATTACHMENT_FOLDERS,
AttachmentFolderType
} from './types.js';
import fs from 'fs/promises';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
const DEFAULT_CONFIG: AttachmentServiceConfig = {
maxSizeBytes: 25 * 1024 * 1024, // 25MB
allowedMimeTypes: ['*/*'],
quotaLimitBytes: 1024 * 1024 * 1024, // 1GB
basePath: process.env.WORKSPACE_BASE_PATH ?
path.join(process.env.WORKSPACE_BASE_PATH, ATTACHMENT_FOLDERS.ROOT) :
'/app/workspace/attachments'
};
export class AttachmentService {
private static instance: AttachmentService;
private config: AttachmentServiceConfig;
private initialized = false;
private constructor(config: AttachmentServiceConfig = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
}
/**
* Get the singleton instance
*/
public static getInstance(config: AttachmentServiceConfig = {}): AttachmentService {
if (!AttachmentService.instance) {
AttachmentService.instance = new AttachmentService(config);
}
return AttachmentService.instance;
}
/**
* Initialize attachment folders in local storage
*/
async initialize(email: string): Promise<void> {
try {
// Create base attachment directory
await fs.mkdir(this.config.basePath!, { recursive: true });
// Create email directory structure
const emailPath = path.join(this.config.basePath!, ATTACHMENT_FOLDERS.EMAIL);
await fs.mkdir(emailPath, { recursive: true });
await fs.mkdir(path.join(emailPath, ATTACHMENT_FOLDERS.INCOMING), { recursive: true });
await fs.mkdir(path.join(emailPath, ATTACHMENT_FOLDERS.OUTGOING), { recursive: true });
// Create calendar directory structure
const calendarPath = path.join(this.config.basePath!, ATTACHMENT_FOLDERS.CALENDAR);
await fs.mkdir(calendarPath, { recursive: true });
await fs.mkdir(path.join(calendarPath, ATTACHMENT_FOLDERS.EVENT_FILES), { recursive: true });
this.initialized = true;
} catch (error) {
throw new Error(`Failed to initialize attachment directories: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Validate attachment against configured limits
*/
private validateAttachment(source: AttachmentSource): AttachmentValidationResult {
// Check size if available
if (source.metadata.size && this.config.maxSizeBytes) {
if (source.metadata.size > this.config.maxSizeBytes) {
return {
valid: false,
error: `File size ${source.metadata.size} exceeds maximum allowed size ${this.config.maxSizeBytes}`
};
}
}
// Check MIME type if restricted
if (this.config.allowedMimeTypes &&
this.config.allowedMimeTypes[0] !== '*/*' &&
!this.config.allowedMimeTypes.includes(source.metadata.mimeType)) {
return {
valid: false,
error: `MIME type ${source.metadata.mimeType} is not allowed`
};
}
return { valid: true };
}
/**
* Process attachment and store in local filesystem
*/
async processAttachment(
email: string,
source: AttachmentSource,
parentFolder: string
): Promise<AttachmentResult> {
if (!this.initialized) {
await this.initialize(email);
}
// Validate attachment
const validation = this.validateAttachment(source);
if (!validation.valid) {
return {
success: false,
error: validation.error
};
}
try {
if (!source.content) {
throw new Error('File content not provided');
}
// Generate unique ID and create file path
const id = uuidv4();
const folderPath = path.join(this.config.basePath!, parentFolder);
const filePath = path.join(folderPath, `${id}_${source.metadata.name}`);
// Write file content
const content = Buffer.from(source.content, 'base64');
await fs.writeFile(filePath, content);
// Get actual file size
const stats = await fs.stat(filePath);
return {
success: true,
attachment: {
id,
name: source.metadata.name,
mimeType: source.metadata.mimeType,
size: stats.size,
path: filePath
}
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
/**
* Download attachment from local storage
*/
async downloadAttachment(
email: string,
attachmentId: string,
filePath: string
): Promise<AttachmentResult> {
if (!this.initialized) {
await this.initialize(email);
}
try {
// Verify file exists and is within workspace
if (!filePath.startsWith(this.config.basePath!)) {
throw new Error('Invalid file path');
}
const content = await fs.readFile(filePath);
const stats = await fs.stat(filePath);
return {
success: true,
attachment: {
id: attachmentId,
name: path.basename(filePath).substring(37), // Remove UUID prefix
mimeType: path.extname(filePath) ?
`application/${path.extname(filePath).substring(1)}` :
'application/octet-stream',
size: stats.size,
path: filePath
}
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
/**
* Delete attachment from local storage
*/
async deleteAttachment(
email: string,
attachmentId: string,
filePath: string
): Promise<AttachmentResult> {
if (!this.initialized) {
await this.initialize(email);
}
try {
// Verify file exists and is within workspace
if (!filePath.startsWith(this.config.basePath!)) {
throw new Error('Invalid file path');
}
// Get file stats before deletion
const stats = await fs.stat(filePath);
const name = path.basename(filePath).substring(37); // Remove UUID prefix
const mimeType = path.extname(filePath) ?
`application/${path.extname(filePath).substring(1)}` :
'application/octet-stream';
// Delete the file
await fs.unlink(filePath);
return {
success: true,
attachment: {
id: attachmentId,
name,
mimeType,
size: stats.size,
path: filePath
}
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
/**
* Get full path for a specific attachment category
*/
getAttachmentPath(folder: AttachmentFolderType): string {
return path.join(this.config.basePath!, folder);
}
}