authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 20:52:38-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-27 21:30:46-08:00
log8b538e28021f73da137c8d83117f9cb391a6ec0b
tree5e635fa5ad81ce32ae978590756d3131ee0e43e4
parenta6c57e169aa8e06189d2a8e88672549731b305c0

dedupe


3 files changed, 34 insertions(+), 33 deletions(-)

apps/server/src/orchestration/Layers/ProjectionPipeline.ts+7-30
...@@ -38,6 +38,11 @@ import {...@@ -38,6 +38,11 @@ import {
38 OrchestrationProjectionPipeline,38 OrchestrationProjectionPipeline,
39 type OrchestrationProjectionPipelineShape,39 type OrchestrationProjectionPipelineShape,
40} from "../Services/ProjectionPipeline.ts";40} from "../Services/ProjectionPipeline.ts";
41import {
42 ATTACHMENTS_ROUTE_PREFIX,
43 IMAGE_EXTENSION_BY_MIME_TYPE,
44 SAFE_IMAGE_FILE_EXTENSIONS,
45} from "../../projectFaviconRoute.ts";
4146
42export const ORCHESTRATION_PROJECTOR_NAMES = {47export const ORCHESTRATION_PROJECTOR_NAMES = {
43 projects: "projection.projects",48 projects: "projection.projects",
...@@ -61,31 +66,6 @@ interface ProjectorDefinition {...@@ -61,31 +66,6 @@ interface ProjectorDefinition {
61 ) => Effect.Effect<void, ProjectionRepositoryError>;66 ) => Effect.Effect<void, ProjectionRepositoryError>;
62}67}
6368
64const ATTACHMENTS_ROUTE_PREFIX = "/attachments";
65const ATTACHMENTS_STATE_SUBDIRECTORY = "attachments";
66const IMAGE_EXTENSION_BY_MIME_TYPE: Record<string, string> = {
67 "image/png": ".png",
68 "image/jpeg": ".jpg",
69 "image/jpg": ".jpg",
70 "image/gif": ".gif",
71 "image/webp": ".webp",
72 "image/svg+xml": ".svg",
73 "image/bmp": ".bmp",
74 "image/tiff": ".tiff",
75 "image/heic": ".heic",
76};
77const SAFE_IMAGE_FILE_EXTENSIONS = new Set([
78 ".jpg",
79 ".jpeg",
80 ".png",
81 ".gif",
82 ".webp",
83 ".bmp",
84 ".tiff",
85 ".svg",
86 ".ico",
87]);
88
89interface PendingAttachmentWrite {69interface PendingAttachmentWrite {
90 readonly absolutePath: string;70 readonly absolutePath: string;
91 readonly bytes: Uint8Array;71 readonly bytes: Uint8Array;
...@@ -133,7 +113,7 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {...@@ -133,7 +113,7 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {
133 if (Option.isNone(serverConfig)) return input.attachments;113 if (Option.isNone(serverConfig)) return input.attachments;
134114
135 const path = yield* Path.Path;115 const path = yield* Path.Path;
136 const attachmentsRootDir = path.join(serverConfig.value.stateDir, ATTACHMENTS_STATE_SUBDIRECTORY);116 const attachmentsRootDir = path.join(serverConfig.value.stateDir, "attachments");
137 const threadSegment = encodeURIComponent(input.threadId);117 const threadSegment = encodeURIComponent(input.threadId);
138 const messageSegment = encodeURIComponent(input.messageId);118 const messageSegment = encodeURIComponent(input.messageId);
139119
...@@ -332,10 +312,7 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {...@@ -332,10 +312,7 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {
332 return;312 return;
333 }313 }
334314
335 const attachmentsRootDir = input.path.join(315 const attachmentsRootDir = input.path.join(serverConfig.value.stateDir, "attachments");
336 serverConfig.value.stateDir,
337 ATTACHMENTS_STATE_SUBDIRECTORY,
338 );
339316
340 yield* Effect.forEach(317 yield* Effect.forEach(
341 input.sideEffects.deletedThreadIds,318 input.sideEffects.deletedThreadIds,
apps/server/src/projectFaviconRoute.ts+26
...@@ -2,6 +2,32 @@ import fs from "node:fs";...@@ -2,6 +2,32 @@ import fs from "node:fs";
2import http from "node:http";2import http from "node:http";
3import path from "node:path";3import path from "node:path";
44
5export const ATTACHMENTS_ROUTE_PREFIX = "/attachments";
6
7export const IMAGE_EXTENSION_BY_MIME_TYPE: Record<string, string> = {
8 "image/png": ".png",
9 "image/jpeg": ".jpg",
10 "image/jpg": ".jpg",
11 "image/gif": ".gif",
12 "image/webp": ".webp",
13 "image/svg+xml": ".svg",
14 "image/bmp": ".bmp",
15 "image/tiff": ".tiff",
16 "image/heic": ".heic",
17};
18
19export const SAFE_IMAGE_FILE_EXTENSIONS = new Set([
20 ".jpg",
21 ".jpeg",
22 ".png",
23 ".gif",
24 ".webp",
25 ".bmp",
26 ".tiff",
27 ".svg",
28 ".ico",
29]);
30
5const FAVICON_MIME_TYPES: Record<string, string> = {31const FAVICON_MIME_TYPES: Record<string, string> = {
6 ".png": "image/png",32 ".png": "image/png",
7 ".jpg": "image/jpeg",33 ".jpg": "image/jpeg",
apps/server/src/wsServer.ts+1-3
...@@ -55,9 +55,7 @@ import { clamp } from "effect/Number";...@@ -55,9 +55,7 @@ import { clamp } from "effect/Number";
55import { Open } from "./open";55import { Open } from "./open";
56import { ServerConfig } from "./config";56import { ServerConfig } from "./config";
57import { GitCore } from "./git/Services/GitCore.ts";57import { GitCore } from "./git/Services/GitCore.ts";
58import { tryHandleProjectFaviconRequest } from "./projectFaviconRoute";58import { ATTACHMENTS_ROUTE_PREFIX, tryHandleProjectFaviconRequest } from "./projectFaviconRoute";
59
60const ATTACHMENTS_ROUTE_PREFIX = "/attachments/";
6159
62/**60/**
63 * ServerShape - Service API for server lifecycle control.61 * ServerShape - Service API for server lifecycle control.