55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { getAppDataPath } from '@main/utils/pathDecoder';
|
|
import { assertSafeAttachmentStorageId } from '@features/agent-attachments/core/domain';
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
|
|
export type AgentAttachmentArtifactFileName =
|
|
| 'original.png'
|
|
| 'original.jpg'
|
|
| 'optimized.png'
|
|
| 'optimized.jpg'
|
|
| 'thumb.jpg'
|
|
| 'meta.json';
|
|
|
|
export interface ResolveAgentAttachmentArtifactPathInput {
|
|
teamName: string;
|
|
messageId: string;
|
|
attachmentId: string;
|
|
fileName: AgentAttachmentArtifactFileName;
|
|
appDataPath?: string;
|
|
}
|
|
|
|
export function resolveAgentAttachmentArtifactPath(
|
|
input: ResolveAgentAttachmentArtifactPathInput
|
|
): string {
|
|
assertSafeAttachmentStorageId('teamName', input.teamName);
|
|
assertSafeAttachmentStorageId('messageId', input.messageId);
|
|
assertSafeAttachmentStorageId('attachmentId', input.attachmentId);
|
|
|
|
const root = input.appDataPath ?? getAppDataPath();
|
|
const base = path.resolve(
|
|
root,
|
|
'attachments',
|
|
input.teamName,
|
|
input.messageId,
|
|
input.attachmentId
|
|
);
|
|
const resolved = path.resolve(base, input.fileName);
|
|
if (!resolved.startsWith(base + path.sep)) {
|
|
throw new Error('Attachment artifact path escaped managed directory');
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
export async function writeFileAtomic(filePath: string, bytes: Buffer | string): Promise<void> {
|
|
const dir = path.dirname(filePath);
|
|
await fs.mkdir(dir, { recursive: true });
|
|
const tmpPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
try {
|
|
await fs.writeFile(tmpPath, bytes);
|
|
await fs.rename(tmpPath, filePath);
|
|
} catch (error) {
|
|
await fs.rm(tmpPath, { force: true }).catch(() => undefined);
|
|
throw error;
|
|
}
|
|
}
|