import type { GroupFile } from "@tactical-media-broadcast-hub/core"; import { toGroupFileMapping, type RoomMapping } from "./config.js"; import type { AttachmentWork } from "./events.js"; import { inspectStream } from "./inspector.js"; import { replyTxnId } from "./keys.js"; import type { MatrixPort } from "./matrix.js"; import { safeFileName } from "./names.js"; import type { ArchiveStore, OutboxRow } from "./store.js"; import type { LibraryPort } from "./webdav.js"; export interface ArchivedAttachment { file: GroupFile; safeName: string; } /** * Steps 2–3 of the processing contract for one claimed attachment: * authenticated download → in-flight size/hash/MIME → staged WebDAV PUT → * content-addressed MOVE → index row + outbox done in one transaction. * The payload is never written to local disk. */ export class Archiver { constructor( private readonly matrix: MatrixPort, private readonly library: LibraryPort, private readonly store: ArchiveStore, ) {} async archive( mapping: RoomMapping, work: AttachmentWork, outbox: OutboxRow, claimKey: string, signal: AbortSignal, ): Promise { const { candidate, event } = work; const download = await this.matrix.downloadMedia(candidate.mxcUri, signal); const { stream, result } = inspectStream(download.body, download.contentLength); const dir = ["Library", mapping.workspaceId, mapping.groupId]; // Deterministic per claim: a retried upload overwrites its own partial staging copy. const stagingName = `${replyTxnId(claimKey)}.part`; await this.library.putStaging(dir, stagingName, stream, signal); const payload = await result; const safeName = safeFileName(work.displayName, payload.sniffed.extension); const resource = await this.library.commit(dir, stagingName, `${payload.sha256}-${safeName}`, signal); const createdAt = new Date(event.origin_server_ts ?? Date.now()); const indexed = await this.store.commitArchived({ workspaceId: mapping.workspaceId, groupId: mapping.groupId, roomId: mapping.roomId, eventId: event.event_id, senderMxid: event.sender, matrixEventJson: event, originalName: work.displayName, safeName, mime: payload.sniffed.mime, mimeClass: payload.sniffed.mimeClass, byteSize: payload.byteSize, sha256: payload.sha256, ocisResourceId: resource.resourceId, ocisResourceVersion: resource.version, createdAt, }, outbox.id); const group = toGroupFileMapping(mapping); return { safeName, file: { id: indexed.id, workspaceId: mapping.workspaceId, groupId: mapping.groupId, roomId: mapping.roomId, senderId: event.sender, eventId: event.event_id, origin: { kind: "matrix", mxcUri: candidate.mxcUri }, name: safeName, mimeType: payload.sniffed.mime, sizeBytes: payload.byteSize, checksum: { algorithm: "sha256", value: payload.sha256 }, createdAt: createdAt.toISOString(), indexedAt: indexed.indexedAt.toISOString(), file: { spaceId: group.spaceId, resourceId: resource.resourceId, version: resource.version }, shareLink: { url: resource.url, access: "members", expiresAt: null }, retention: group.retention, status: "available", }, }; } }