Integrate group files: nullable room for uploads, archive class, claim idempotency, upload route
Wires the three parallel group-cloud tracks to one surface and fixes the seams between them: - chat_media_archive.room_id becomes nullable (direct web uploads have no Matrix room yet — the contract's null-event pattern extends to the room); core GroupFile.roomId and ArchiveEntryInput.roomId follow. - mime_class gains 'archive' (zip) across the 029 CHECK, validation MIME_CLASSES and the bot classifier — one vocabulary everywhere. - registerArchiveEntry claim keys are null-safe for room-less uploads. - New: POST /files/groups/:groupId/upload — FileInterceptor (20 MiB) streams through magic-byte sniffing, library-safe naming, oCIS upload and the metadata index, behind group-membership ACL (Owner/Admin bypass). - safeFileName/sniffMime promoted to packages/core (file-names.ts, file-mime.ts) so the bot and the api share one implementation; the bot's local modules become re-export shims; @types/node added to core. Verified: api typecheck clean, group-files 22/22, bot 74/74, web typecheck clean; 029 re-applied clean on a fresh scratch DB (room_id nullable, archive class, claim_key unique all confirmed in the catalog).
This commit is contained in:
parent
688a2ce996
commit
e7e703c105
21 changed files with 473 additions and 227 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, Inject, Param, ParseUUIDPipe, Post, Query, Req, Res, UseGuards } from "@nestjs/common";
|
||||
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, Inject, Param, ParseUUIDPipe, Post, Query, Req, Res, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import type { Request, Response } from "express";
|
||||
import { RbacGuard, RequirePermission, type AuthenticatedRequest } from "../rbac/rbac.guard.js";
|
||||
import { requireObject, requirePositiveInt, requireSession, requireString } from "../http/validation.js";
|
||||
|
|
@ -6,11 +7,20 @@ import { GroupFilesService, type FilePage } from "./group-files.service.js";
|
|||
import type { GroupFile } from "@tactical-media-broadcast-hub/core";
|
||||
import { verifyWhileStreaming } from "./ocis-webdav.js";
|
||||
|
||||
const MAX_FILE_UPLOAD_BYTES = 20 * 1024 * 1024;
|
||||
|
||||
interface FileUpload {
|
||||
buffer: Buffer;
|
||||
mimetype: string;
|
||||
originalname: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group files API — the Telegram-like group file surface.
|
||||
*
|
||||
* GET /files list/search, keyset (groupId, senderMxid,
|
||||
* mimeClass, name, from, to, cursor, limit)
|
||||
* POST /files/groups/:groupId/upload direct web upload (≤20 MiB, multipart)
|
||||
* GET /files/:id one GroupFile
|
||||
* GET /files/:id/download stream payload (sha256 re-verified)
|
||||
* POST /files/:id/retention set retention (keep/until/legalHold)
|
||||
|
|
@ -39,6 +49,26 @@ export class GroupFilesController {
|
|||
return this.files.listFiles(session.workspaceId, session.userId, session.role, query);
|
||||
}
|
||||
|
||||
@Post("groups/:groupId/upload")
|
||||
@RequirePermission("media.view")
|
||||
@UseInterceptors(FileInterceptor("file", {
|
||||
limits: { fileSize: MAX_FILE_UPLOAD_BYTES, files: 1, fields: 3, fieldSize: 1024, parts: 4 },
|
||||
}))
|
||||
async upload(
|
||||
@Req() request: Request & AuthenticatedRequest,
|
||||
@Param("groupId", ParseUUIDPipe) groupId: string,
|
||||
@UploadedFile() file?: FileUpload,
|
||||
): Promise<GroupFile> {
|
||||
const session = requireSession(request);
|
||||
if (file === undefined) throw new BadRequestException("a file is required (field: file)");
|
||||
return this.files.uploadForGroup(
|
||||
session.workspaceId,
|
||||
groupId,
|
||||
{ userId: session.userId, role: session.role },
|
||||
{ buffer: file.buffer, mimetype: file.mimetype, originalName: file.originalname },
|
||||
);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@RequirePermission("media.view")
|
||||
async get(@Req() request: Request & AuthenticatedRequest, @Param("id", ParseUUIDPipe) id: string): Promise<GroupFile> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue