refactor: stricter domain: attachments
This commit is contained in:
@@ -8,6 +8,7 @@ Handles file sharing between peers over WebRTC data channels. Files are announce
|
|||||||
attachment/
|
attachment/
|
||||||
├── application/
|
├── application/
|
||||||
│ ├── attachment.facade.ts Thin entry point, delegates to manager
|
│ ├── attachment.facade.ts Thin entry point, delegates to manager
|
||||||
|
│ └── services/
|
||||||
│ ├── attachment-manager.service.ts Orchestrates lifecycle, auto-download, peer listeners
|
│ ├── attachment-manager.service.ts Orchestrates lifecycle, auto-download, peer listeners
|
||||||
│ ├── attachment-transfer.service.ts P2P file transfer protocol (announce/request/chunk/cancel)
|
│ ├── attachment-transfer.service.ts P2P file transfer protocol (announce/request/chunk/cancel)
|
||||||
│ ├── attachment-transfer-transport.service.ts Base64 encode/decode, chunked streaming
|
│ ├── attachment-transfer-transport.service.ts Base64 encode/decode, chunked streaming
|
||||||
@@ -15,15 +16,17 @@ attachment/
|
|||||||
│ └── attachment-runtime.store.ts In-memory signal-based state (Maps for attachments, chunks, pending)
|
│ └── attachment-runtime.store.ts In-memory signal-based state (Maps for attachments, chunks, pending)
|
||||||
│
|
│
|
||||||
├── domain/
|
├── domain/
|
||||||
│ ├── attachment.models.ts Attachment type extending AttachmentMeta with runtime state
|
|
||||||
│ ├── attachment.logic.ts isAttachmentMedia, shouldAutoRequestWhenWatched, shouldPersistDownloadedAttachment
|
│ ├── attachment.logic.ts isAttachmentMedia, shouldAutoRequestWhenWatched, shouldPersistDownloadedAttachment
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── attachment.models.ts Attachment type extending AttachmentMeta with runtime state
|
||||||
|
│ │ └── attachment-transfer.models.ts Protocol event types (file-announce, file-chunk, file-request, ...)
|
||||||
|
│ └── constants/
|
||||||
│ ├── attachment.constants.ts MAX_AUTO_SAVE_SIZE_BYTES = 10 MB
|
│ ├── attachment.constants.ts MAX_AUTO_SAVE_SIZE_BYTES = 10 MB
|
||||||
│ ├── attachment-transfer.models.ts Protocol event types (file-announce, file-chunk, file-request, ...)
|
|
||||||
│ └── attachment-transfer.constants.ts FILE_CHUNK_SIZE_BYTES = 64 KB, EWMA weights, error messages
|
│ └── attachment-transfer.constants.ts FILE_CHUNK_SIZE_BYTES = 64 KB, EWMA weights, error messages
|
||||||
│
|
│
|
||||||
├── infrastructure/
|
├── infrastructure/
|
||||||
│ ├── attachment-storage.service.ts Electron filesystem access (save / read / delete)
|
│ ├── attachment-storage.service.ts Electron filesystem access (save / read / delete)
|
||||||
│ └── attachment-storage.helpers.ts sanitizeAttachmentRoomName, resolveAttachmentStorageBucket
|
│ └── attachment-storage.util.ts sanitizeAttachmentRoomName, resolveAttachmentStorageBucket
|
||||||
│
|
│
|
||||||
└── index.ts Barrel exports
|
└── index.ts Barrel exports
|
||||||
```
|
```
|
||||||
@@ -52,16 +55,16 @@ graph TD
|
|||||||
Transfer --> Store
|
Transfer --> Store
|
||||||
Persistence --> Storage
|
Persistence --> Storage
|
||||||
Persistence --> Store
|
Persistence --> Store
|
||||||
Storage --> Helpers[attachment-storage.helpers]
|
Storage --> Helpers[attachment-storage.util]
|
||||||
|
|
||||||
click Facade "application/attachment.facade.ts" "Thin entry point" _blank
|
click Facade "application/attachment.facade.ts" "Thin entry point" _blank
|
||||||
click Manager "application/attachment-manager.service.ts" "Orchestrates lifecycle" _blank
|
click Manager "application/services/attachment-manager.service.ts" "Orchestrates lifecycle" _blank
|
||||||
click Transfer "application/attachment-transfer.service.ts" "P2P file transfer protocol" _blank
|
click Transfer "application/services/attachment-transfer.service.ts" "P2P file transfer protocol" _blank
|
||||||
click Transport "application/attachment-transfer-transport.service.ts" "Base64 encode/decode, chunked streaming" _blank
|
click Transport "application/services/attachment-transfer-transport.service.ts" "Base64 encode/decode, chunked streaming" _blank
|
||||||
click Persistence "application/attachment-persistence.service.ts" "DB + filesystem persistence" _blank
|
click Persistence "application/services/attachment-persistence.service.ts" "DB + filesystem persistence" _blank
|
||||||
click Store "application/attachment-runtime.store.ts" "In-memory signal-based state" _blank
|
click Store "application/services/attachment-runtime.store.ts" "In-memory signal-based state" _blank
|
||||||
click Storage "infrastructure/attachment-storage.service.ts" "Electron filesystem access" _blank
|
click Storage "infrastructure/attachment-storage.service.ts" "Electron filesystem access" _blank
|
||||||
click Helpers "infrastructure/attachment-storage.helpers.ts" "Path helpers" _blank
|
click Helpers "infrastructure/attachment-storage.util.ts" "Path helpers" _blank
|
||||||
click Logic "domain/attachment.logic.ts" "Pure decision functions" _blank
|
click Logic "domain/attachment.logic.ts" "Pure decision functions" _blank
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { AttachmentManagerService } from './attachment-manager.service';
|
import { AttachmentManagerService } from './services/attachment-manager.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttachmentFacade {
|
export class AttachmentFacade {
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import {
|
|||||||
inject
|
inject
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
import { RealtimeSessionFacade } from '../../../core/realtime';
|
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||||
import { DatabaseService } from '../../../infrastructure/persistence';
|
import { DatabaseService } from '../../../../infrastructure/persistence';
|
||||||
import { ROOM_URL_PATTERN } from '../../../core/constants';
|
import { ROOM_URL_PATTERN } from '../../../../core/constants';
|
||||||
import { shouldAutoRequestWhenWatched } from '../domain/attachment.logic';
|
import { shouldAutoRequestWhenWatched } from '../../domain/attachment.logic';
|
||||||
import type { Attachment, AttachmentMeta } from '../domain/attachment.models';
|
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.models';
|
||||||
import type {
|
import type {
|
||||||
FileAnnouncePayload,
|
FileAnnouncePayload,
|
||||||
FileCancelPayload,
|
FileCancelPayload,
|
||||||
FileChunkPayload,
|
FileChunkPayload,
|
||||||
FileNotFoundPayload,
|
FileNotFoundPayload,
|
||||||
FileRequestPayload
|
FileRequestPayload
|
||||||
} from '../domain/attachment-transfer.models';
|
} from '../../domain/models/attachment-transfer.models';
|
||||||
import { AttachmentPersistenceService } from './attachment-persistence.service';
|
import { AttachmentPersistenceService } from './attachment-persistence.service';
|
||||||
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
||||||
import { AttachmentTransferService } from './attachment-transfer.service';
|
import { AttachmentTransferService } from './attachment-transfer.service';
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { take } from 'rxjs';
|
import { take } from 'rxjs';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { selectCurrentRoomName } from '../../../store/rooms/rooms.selectors';
|
import { selectCurrentRoomName } from '../../../../store/rooms/rooms.selectors';
|
||||||
import { DatabaseService } from '../../../infrastructure/persistence';
|
import { DatabaseService } from '../../../../infrastructure/persistence';
|
||||||
import { AttachmentStorageService } from '../infrastructure/attachment-storage.service';
|
import { AttachmentStorageService } from '../../infrastructure/services/attachment-storage.service';
|
||||||
import type { Attachment, AttachmentMeta } from '../domain/attachment.models';
|
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.models';
|
||||||
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../domain/attachment.constants';
|
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../../domain/constants/attachment.constants';
|
||||||
import { LEGACY_ATTACHMENTS_STORAGE_KEY } from '../domain/attachment-transfer.constants';
|
import { LEGACY_ATTACHMENTS_STORAGE_KEY } from '../../domain/constants/attachment-transfer.constants';
|
||||||
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, signal } from '@angular/core';
|
import { Injectable, signal } from '@angular/core';
|
||||||
import type { Attachment } from '../domain/attachment.models';
|
import type { Attachment } from '../../domain/models/attachment.models';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttachmentRuntimeStore {
|
export class AttachmentRuntimeStore {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { RealtimeSessionFacade } from '../../../core/realtime';
|
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||||
import { AttachmentStorageService } from '../infrastructure/attachment-storage.service';
|
import { AttachmentStorageService } from '../../infrastructure/services/attachment-storage.service';
|
||||||
import { FILE_CHUNK_SIZE_BYTES } from '../domain/attachment-transfer.constants';
|
import { FILE_CHUNK_SIZE_BYTES } from '../../domain/constants/attachment-transfer.constants';
|
||||||
import { FileChunkEvent } from '../domain/attachment-transfer.models';
|
import { FileChunkEvent } from '../../domain/models/attachment-transfer.models';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttachmentTransferTransportService {
|
export class AttachmentTransferTransportService {
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { recordDebugNetworkFileChunk } from '../../../infrastructure/realtime/logging/debug-network-metrics';
|
import { recordDebugNetworkFileChunk } from '../../../../infrastructure/realtime/logging/debug-network-metrics';
|
||||||
import { RealtimeSessionFacade } from '../../../core/realtime';
|
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||||
import { AttachmentStorageService } from '../infrastructure/attachment-storage.service';
|
import { AttachmentStorageService } from '../../infrastructure/services/attachment-storage.service';
|
||||||
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../domain/attachment.constants';
|
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../../domain/constants/attachment.constants';
|
||||||
import { shouldPersistDownloadedAttachment } from '../domain/attachment.logic';
|
import { shouldPersistDownloadedAttachment } from '../../domain/attachment.logic';
|
||||||
import type { Attachment, AttachmentMeta } from '../domain/attachment.models';
|
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.models';
|
||||||
import {
|
import {
|
||||||
ATTACHMENT_TRANSFER_EWMA_CURRENT_WEIGHT,
|
ATTACHMENT_TRANSFER_EWMA_CURRENT_WEIGHT,
|
||||||
ATTACHMENT_TRANSFER_EWMA_PREVIOUS_WEIGHT,
|
ATTACHMENT_TRANSFER_EWMA_PREVIOUS_WEIGHT,
|
||||||
DEFAULT_ATTACHMENT_MIME_TYPE,
|
DEFAULT_ATTACHMENT_MIME_TYPE,
|
||||||
FILE_NOT_FOUND_REQUEST_ERROR,
|
FILE_NOT_FOUND_REQUEST_ERROR,
|
||||||
NO_CONNECTED_PEERS_REQUEST_ERROR
|
NO_CONNECTED_PEERS_REQUEST_ERROR
|
||||||
} from '../domain/attachment-transfer.constants';
|
} from '../../domain/constants/attachment-transfer.constants';
|
||||||
import {
|
import {
|
||||||
type FileAnnounceEvent,
|
type FileAnnounceEvent,
|
||||||
type FileAnnouncePayload,
|
type FileAnnouncePayload,
|
||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
type FileRequestEvent,
|
type FileRequestEvent,
|
||||||
type FileRequestPayload,
|
type FileRequestPayload,
|
||||||
type LocalFileWithPath
|
type LocalFileWithPath
|
||||||
} from '../domain/attachment-transfer.models';
|
} from '../../domain/models/attachment-transfer.models';
|
||||||
import { AttachmentPersistenceService } from './attachment-persistence.service';
|
import { AttachmentPersistenceService } from './attachment-persistence.service';
|
||||||
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
import { AttachmentRuntimeStore } from './attachment-runtime.store';
|
||||||
import { AttachmentTransferTransportService } from './attachment-transfer-transport.service';
|
import { AttachmentTransferTransportService } from './attachment-transfer-transport.service';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MAX_AUTO_SAVE_SIZE_BYTES } from './attachment.constants';
|
import { MAX_AUTO_SAVE_SIZE_BYTES } from './constants/attachment.constants';
|
||||||
import type { Attachment } from './attachment.models';
|
import type { Attachment } from './models/attachment.models';
|
||||||
|
|
||||||
export function isAttachmentMedia(attachment: Pick<Attachment, 'mime'>): boolean {
|
export function isAttachmentMedia(attachment: Pick<Attachment, 'mime'>): boolean {
|
||||||
return attachment.mime.startsWith('image/') ||
|
return attachment.mime.startsWith('image/') ||
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ChatEvent } from '../../../shared-kernel';
|
import type { ChatEvent } from '../../../../shared-kernel';
|
||||||
import type { ChatAttachmentAnnouncement } from '../../../shared-kernel';
|
import type { ChatAttachmentAnnouncement } from '../../../../shared-kernel';
|
||||||
|
|
||||||
export type FileAnnounceEvent = ChatEvent & {
|
export type FileAnnounceEvent = ChatEvent & {
|
||||||
type: 'file-announce';
|
type: 'file-announce';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ChatAttachmentMeta } from '../../../shared-kernel';
|
import type { ChatAttachmentMeta } from '../../../../shared-kernel';
|
||||||
|
|
||||||
export type AttachmentMeta = ChatAttachmentMeta;
|
export type AttachmentMeta = ChatAttachmentMeta;
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './application/attachment.facade';
|
export * from './application/attachment.facade';
|
||||||
export * from './domain/attachment.constants';
|
export * from './domain/constants/attachment.constants';
|
||||||
export * from './domain/attachment.models';
|
export * from './domain/models/attachment.models';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
|
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
||||||
import type { Attachment } from '../domain/attachment.models';
|
import type { Attachment } from '../../domain/models/attachment.models';
|
||||||
import {
|
import {
|
||||||
resolveAttachmentStorageBucket,
|
resolveAttachmentStorageBucket,
|
||||||
resolveAttachmentStoredFilename,
|
resolveAttachmentStoredFilename,
|
||||||
sanitizeAttachmentRoomName
|
sanitizeAttachmentRoomName
|
||||||
} from './attachment-storage.helpers';
|
} from '../util/attachment-storage.util';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttachmentStorageService {
|
export class AttachmentStorageService {
|
||||||
Reference in New Issue
Block a user