The Telegram-like group file contract (packages/core shared-files.ts) becomes a working subsystem, built in three parallel tracks: API (NestJS): migration 029 group_files (chat_media_archive metadata-only index with partial replay-unique + RLS, group_file_shares with sha256 token persistence, archive_sync_state/outbox durable-claim seam incl. a unique claim_key index), group-files module (list/search with cursor pagination, direct upload ≤20 MiB streamed+hashed to oCIS WebDAV, download with ACL + retention rechecks, share links with expiry, retention modes incl. legalHold) — RBAC group-membership authz + audit chain events. Web: Files section per group — group picker, drag-drop upload (20 MiB cap), search + MIME/origin filters, cursor pagination, download via authenticated blob fetch, share dialog with expiry + shown-once token, admin retention dialog; bilingual EN/ES (wiki i18n pattern); nav sidebar/palette/bottom-nav wiring; MIME classes aligned to the DB CHECK (document/other). Archive bot (apps/archive-bot): plain sync-loop Matrix bot mirroring attachments from explicitly-mapped unencrypted archive rooms — raw /sync with durable since-cursor, outbox-claimed ingestion (hash-verified stream, no temp files), magic-byte MIME sniffing, safe-name normalization, WebDAV upload, indexed + replied with link, redaction→unavailable; health endpoint; Dockerfile. Tests 74/74, typecheck clean across api/web/bot.
43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import type { LocalizedText } from "./i18n";
|
|
import type { FileOrigin, MimeClass, RetentionMode } from "./types";
|
|
|
|
/** Bilingual display labels for the group-files enums (values are fixed by the API). */
|
|
|
|
export const ORIGIN_LABEL: Record<FileOrigin, LocalizedText> = {
|
|
matrix: { en: "Matrix", es: "Matrix" },
|
|
bridge: { en: "Bridge", es: "Puente" },
|
|
upload: { en: "Upload", es: "Subido" },
|
|
"pad-export": { en: "Pad export", es: "Export. de pad" },
|
|
};
|
|
|
|
export const ORIGIN_TONE: Record<FileOrigin, "neutral" | "accent" | "live" | "warn" | "danger"> = {
|
|
matrix: "accent",
|
|
bridge: "live",
|
|
upload: "neutral",
|
|
"pad-export": "warn",
|
|
};
|
|
|
|
export const MIME_CLASS_LABEL: Record<MimeClass, LocalizedText> = {
|
|
image: { en: "Image", es: "Imagen" },
|
|
video: { en: "Video", es: "Vídeo" },
|
|
audio: { en: "Audio", es: "Audio" },
|
|
document: { en: "Document", es: "Documento" },
|
|
other: { en: "File", es: "Archivo" },
|
|
};
|
|
|
|
export const RETENTION_LABEL: Record<RetentionMode, LocalizedText> = {
|
|
keep: { en: "Keep", es: "Conservar" },
|
|
until: { en: "Until", es: "Hasta" },
|
|
legalHold: { en: "Legal hold", es: "Retención legal" },
|
|
};
|
|
|
|
export const RETENTION_TONE: Record<RetentionMode, "neutral" | "accent" | "live" | "warn" | "danger"> = {
|
|
keep: "neutral",
|
|
until: "accent",
|
|
legalHold: "danger",
|
|
};
|
|
|
|
/** Human label for a MIME class, tolerating an unknown value from the server. */
|
|
export function mimeClassLabel(mimeClass: MimeClass): LocalizedText {
|
|
return MIME_CLASS_LABEL[mimeClass] ?? MIME_CLASS_LABEL.other;
|
|
}
|