authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 21:12:01-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-27 21:30:46-08:00
logb52518f85fa3f11a6a405e3bebaf908ebdc5add7
treee80b24ccd8d5421fc4a2717e2cda06192b50b7a7
parent3e99019af85e67cdbc5be0ef95b71662ebe683e7

Flatten attachment paths with message-prefixed filenames

- Write projected attachments under the thread directory using `messageSegment-fileName` - Avoid nested per-message folders while preserving unique, deterministic file paths - Update projection pipeline tests to match new attachment URLs and on-disk paths

2 files changed, 12 insertions(+), 18 deletions(-)

apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+9-16
...@@ -230,7 +230,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -230,7 +230,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
230 name: "example.png",230 name: "example.png",
231 mimeType: "image/png",231 mimeType: "image/png",
232 sizeBytes: 5,232 sizeBytes: 5,
233 dataUrl: "/attachments/thread-attachments/message-attachments/0.png",233 dataUrl: "/attachments/thread-attachments/message-attachments-0.png",
234 },234 },
235 ]);235 ]);
236236
...@@ -238,8 +238,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -238,8 +238,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
238 stateDir,238 stateDir,
239 "attachments",239 "attachments",
240 "thread-attachments",240 "thread-attachments",
241 "message-attachments",241 "message-attachments-0.png",
242 "0.png",
243 );242 );
244 assert.equal(fs.existsSync(attachmentPath), true);243 assert.equal(fs.existsSync(attachmentPath), true);
245 assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("SGVsbG8=", "base64"));244 assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("SGVsbG8=", "base64"));
...@@ -325,7 +324,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -325,7 +324,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
325 name: "untrusted.exe",324 name: "untrusted.exe",
326 mimeType: "image/x-unknown",325 mimeType: "image/x-unknown",
327 sizeBytes: 5,326 sizeBytes: 5,
328 dataUrl: "/attachments/thread-attachments-safe/message-attachments-safe/0.bin",327 dataUrl: "/attachments/thread-attachments-safe/message-attachments-safe-0.bin",
329 },328 },
330 {329 {
331 type: "image",330 type: "image",
...@@ -340,8 +339,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -340,8 +339,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
340 stateDir,339 stateDir,
341 "attachments",340 "attachments",
342 "thread-attachments-safe",341 "thread-attachments-safe",
343 "message-attachments-safe",342 "message-attachments-safe-0.bin",
344 "0.bin",
345 );343 );
346 assert.equal(fs.existsSync(firstAttachmentPath), true);344 assert.equal(fs.existsSync(firstAttachmentPath), true);
347 assert.deepEqual(fs.readFileSync(firstAttachmentPath), Buffer.from("SGVsbG8=", "base64"));345 assert.deepEqual(fs.readFileSync(firstAttachmentPath), Buffer.from("SGVsbG8=", "base64"));
...@@ -350,8 +348,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -350,8 +348,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
350 stateDir,348 stateDir,
351 "attachments",349 "attachments",
352 "thread-attachments-safe",350 "thread-attachments-safe",
353 "message-attachments-safe",351 "message-attachments-safe-1.png",
354 "1.png",
355 );352 );
356 assert.equal(fs.existsSync(secondAttachmentPath), false);353 assert.equal(fs.existsSync(secondAttachmentPath), false);
357 fs.rmSync(stateDir, { recursive: true, force: true });354 fs.rmSync(stateDir, { recursive: true, force: true });
...@@ -614,8 +611,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -614,8 +611,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
614 stateDir,611 stateDir,
615 "attachments",612 "attachments",
616 "thread-overwrite",613 "thread-overwrite",
617 "message-overwrite",614 "message-overwrite-0.png",
618 "0.png",
619 );615 );
620 assert.equal(fs.existsSync(attachmentPath), true);616 assert.equal(fs.existsSync(attachmentPath), true);
621 assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("V29ybGQ=", "base64"));617 assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("V29ybGQ=", "base64"));
...@@ -753,8 +749,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -753,8 +749,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
753 stateDir,749 stateDir,
754 "attachments",750 "attachments",
755 "thread-rollback",751 "thread-rollback",
756 "message-rollback",752 "message-rollback-0.png",
757 "0.png",
758 );753 );
759 assert.equal(fs.existsSync(attachmentPath), false);754 assert.equal(fs.existsSync(attachmentPath), false);
760 yield* sql`DROP TRIGGER IF EXISTS fail_thread_messages_projection_state_update`;755 yield* sql`DROP TRIGGER IF EXISTS fail_thread_messages_projection_state_update`;
...@@ -945,15 +940,13 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -945,15 +940,13 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
945 stateDir,940 stateDir,
946 "attachments",941 "attachments",
947 "thread-revert-files",942 "thread-revert-files",
948 "message-keep",943 "message-keep-0.png",
949 "0.png",
950 );944 );
951 const removePath = path.join(945 const removePath = path.join(
952 stateDir,946 stateDir,
953 "attachments",947 "attachments",
954 "thread-revert-files",948 "thread-revert-files",
955 "message-remove",949 "message-remove-0.png",
956 "0.png",
957 );950 );
958 assert.equal(fs.existsSync(keepPath), true);951 assert.equal(fs.existsSync(keepPath), true);
959 assert.equal(fs.existsSync(removePath), true);952 assert.equal(fs.existsSync(removePath), true);
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+3-2
...@@ -140,8 +140,9 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {...@@ -140,8 +140,9 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {
140 ...attachment,140 ...attachment,
141 mimeType: parsed.mimeType,141 mimeType: parsed.mimeType,
142 })}`;142 })}`;
143 const relativePath = `${threadSegment}/${messageSegment}/${fileName}`;143 const uniqueFileName = `${messageSegment}-${fileName}`;
144 const absolutePath = path.join(attachmentsRootDir, threadSegment, messageSegment, fileName);144 const relativePath = `${threadSegment}/${uniqueFileName}`;
145 const absolutePath = path.join(attachmentsRootDir, threadSegment, uniqueFileName);
145 input.stageFileWrite({ absolutePath, bytes });146 input.stageFileWrite({ absolutePath, bytes });
146147
147 return {148 return {