feat: retire oCIS — the hub runs entirely on R2 object storage
Files payloads moved to R2 in 041; this removes the remaining oCIS coupling: - delete the oCIS WebDAV client; verifyWhileStreaming moves to its own storage-agnostic module (verify-stream.ts) - group-files: storage_key is the only payload source; legacy rows answer 'retired storage' instead of touching oCIS - collective share: the ocis-resource item kind is gone (peertube links stay); share-into-library was R2-backed since the previous commit - import-drive endpoint removed (no web caller); import-chat (homeserver) stays - web: Drive tab + embedded oCIS page removed; Library is the Files view Legacy e2e rows (2) deleted; every remaining archive row is R2-backed.
This commit is contained in:
parent
4cbb63920a
commit
e7bf870de9
13 changed files with 88 additions and 750 deletions
|
|
@ -28,8 +28,8 @@ import { appendTenantAudit, type AuditEvent } from "../audit/audit.service.js";
|
|||
import { withSystem, withTenant } from "../db/client.js";
|
||||
import { isUuid, parseLimit } from "../http/validation.js";
|
||||
import { decodeFileListCursor, encodeFileListCursor } from "./file-cursor.js";
|
||||
import { ocisConfigFromEnv, OcisWebdavClient, type FetchLike, type OcisDownload } from "./ocis-webdav.js";
|
||||
import { S3Storage, s3ConfigFromEnv, type StorageService } from "../storage/storage.js";
|
||||
import type { PayloadDownload } from "./verify-stream.js";
|
||||
import { hashShareToken, mintShareToken } from "./share-token.js";
|
||||
import { MIME_CLASSES, ORIGIN_KINDS, parseRetentionInput, type MimeClass, type OriginKind } from "./group-files.validation.js";
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ export const GROUP_FILES_DEPENDENCIES: unique symbol = Symbol("GROUP_FILES_DEPEN
|
|||
/** Wraps the raw payload handle the controller streams out. */
|
||||
export interface FileDownloadHandle {
|
||||
item: GroupFile;
|
||||
download: OcisDownload;
|
||||
download: PayloadDownload;
|
||||
}
|
||||
|
||||
export interface ShareCreated {
|
||||
|
|
@ -187,21 +187,11 @@ function rowToGroupFile(row: ArchiveRow, libraryUrl: (resourceId: string) => str
|
|||
|
||||
@Injectable()
|
||||
export class GroupFilesService {
|
||||
private client: OcisWebdavClient | null = null;
|
||||
private store: StorageService | null = null;
|
||||
|
||||
constructor(@Optional() @Inject(GROUP_FILES_DEPENDENCIES) private readonly deps?: { fetcher?: FetchLike; storage?: StorageService }) {}
|
||||
constructor(@Optional() @Inject(GROUP_FILES_DEPENDENCIES) private readonly deps?: { storage?: StorageService }) {}
|
||||
|
||||
/** Lazily bound: the oCIS env may be absent in dev/test contexts. Legacy rows only. */
|
||||
private webdav(): OcisWebdavClient {
|
||||
if (this.client === null) {
|
||||
const fetcher: FetchLike = this.deps?.fetcher ?? ((url, init) => fetch(url, init));
|
||||
this.client = new OcisWebdavClient(ocisConfigFromEnv(), fetcher);
|
||||
}
|
||||
return this.client;
|
||||
}
|
||||
|
||||
/** R2 object storage — the primary payload store (041). */
|
||||
/** R2 object storage — the only payload store (041; oCIS retired). */
|
||||
private storage(): StorageService {
|
||||
if (this.store === null) {
|
||||
this.store = this.deps?.storage ?? new S3Storage(s3ConfigFromEnv());
|
||||
|
|
@ -318,20 +308,18 @@ export class GroupFilesService {
|
|||
async openDownload(workspaceId: string, userId: string, role: string, itemId: string): Promise<FileDownloadHandle> {
|
||||
const item = await this.getFile(workspaceId, userId, role, itemId);
|
||||
if (item.status !== "available") throw new GoneException(`file is ${item.status}`);
|
||||
const download = await this.openPayload(item.file.storageKey ?? null, item.file.resourceId, item.mimeType).catch(() => {
|
||||
const download = await this.openPayload(item.file.storageKey, item.mimeType).catch(() => {
|
||||
throw new ServiceUnavailableException("file storage is unavailable");
|
||||
});
|
||||
return { item, download };
|
||||
}
|
||||
|
||||
/** R2 for rows with a storage key (041+); oCIS WebDAV for legacy rows. */
|
||||
private async openPayload(storageKey: string | null | undefined, resourceId: string, mime: string): Promise<OcisDownload> {
|
||||
const key = storageKey ?? null;
|
||||
if (key !== null) {
|
||||
const bytes = await this.storage().getObject(key);
|
||||
return { stream: Readable.from(bytes), sizeBytes: bytes.byteLength, contentType: mime };
|
||||
private async openPayload(storageKey: string | null | undefined, mime: string): Promise<PayloadDownload> {
|
||||
if (storageKey === null || storageKey === undefined) {
|
||||
throw new ServiceUnavailableException("file was stored on retired storage and is no longer served");
|
||||
}
|
||||
return this.webdav().download(resourceId);
|
||||
const bytes = await this.storage().getObject(storageKey);
|
||||
return { stream: Readable.from(bytes), sizeBytes: bytes.byteLength, contentType: mime };
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -358,7 +346,7 @@ export class GroupFilesService {
|
|||
},
|
||||
});
|
||||
const after = await this.requireRow(client, itemId);
|
||||
return rowToGroupFile(after, (resourceId) => this.webdav().libraryUrl(resourceId));
|
||||
return rowToGroupFile(after, (resourceId) => this.libraryUrlFor(resourceId));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +363,7 @@ export class GroupFilesService {
|
|||
diff: { name: before.safe_name, roomId: before.room_id, eventId: before.event_id },
|
||||
});
|
||||
const after = await this.requireRow(client, itemId);
|
||||
return rowToGroupFile(after, (resourceId) => this.webdav().libraryUrl(resourceId));
|
||||
return rowToGroupFile(after, (resourceId) => this.libraryUrlFor(resourceId));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -470,7 +458,7 @@ export class GroupFilesService {
|
|||
throw new GoneException("file retention has expired");
|
||||
}
|
||||
const item = rowToGroupFile(row, (resourceId) => this.libraryUrlFor(resourceId));
|
||||
const download = await this.openPayload(row.storage_key, item.file.resourceId, item.mimeType).catch(() => {
|
||||
const download = await this.openPayload(row.storage_key, item.mimeType).catch(() => {
|
||||
throw new ServiceUnavailableException("file storage is unavailable");
|
||||
});
|
||||
return { item, download };
|
||||
|
|
@ -590,7 +578,7 @@ export class GroupFilesService {
|
|||
const safe = safeFileName(file.originalName, sniff.extension);
|
||||
// Object storage is the only payload path (user directive 2026-10-06):
|
||||
// SPENC1-sealed, content-addressed, deduplicated. ocis_resource_id keeps
|
||||
// carrying the resource handle (now the R2 key) for legacy readers.
|
||||
// carrying the resource handle (the R2 key) for legacy readers.
|
||||
const stored = await this.storage().putContent(file.buffer, sniff.mime).catch(() => {
|
||||
throw new ServiceUnavailableException("file storage is unavailable");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue