feat(files): add per-channel storage quota [CI]
ci/woodpecker/pr/web-build Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
justme committed 2026-10-14 20:42:08 +08:00
1 parent 441ed0e5f0
commit a6f078d009
19 files changed
+172 -15

No files matched your search

+4 -1
View File
@@ -1,5 +1,5 @@
// Generated from backend DTOs; do not edit.
// backend-contract-sha256: 654ec761e3481b423296b402497a97f4092ce167e7a2ca51a47d25f0fca4466e
// backend-contract-sha256: ba787115471d5d33fb0c33c95af014b5cd0d535455bd4bea037cfdcc4f048828
export interface AccountChannelBody {
channel_id: string;
@@ -299,6 +299,8 @@ export interface LimitsBody {
max_seats: number;
max_ttl_seconds: number;
max_visitors: number;
max_workspace_bytes: number;
max_workspace_channel_bytes: number;
min_ttl_seconds: number;
}
export interface SettingChoicesBody {
@@ -365,6 +367,7 @@ export interface WorkspaceChannel {
chatHistory: boolean;
createdAt: number;
createdBy: string;
fileBytes: number;
filesEpoch: number;
filesHistory: boolean;
groupId?: string | null;
@@ -55,6 +55,7 @@ export async function applyChannelRecord(resource: SavedResource, session: Chann
const signedEvent = await objectEventProjection(resource, session, event.item.itemId);
if (signedEvent?.kind === 'chat.edit' && signedEvent.targetItemId)
publishWorkspace(resource.id, { t: 'removed', id: signedEvent.targetItemId });
if (signedEvent?.kind.startsWith('file.')) void loadWorkspace(resource.id).catch(() => {});
const control = readResourceRecord(event.item);
if (control !== false) {
if (control && control.t === 'file-catalog') {
@@ -108,6 +109,7 @@ export async function applyChannelRecord(resource: SavedResource, session: Chann
fileCatalogs.delete(`${resource.id}:${event.itemId}`);
blobs.delete(`${resource.id}:${event.itemId}`);
publishWorkspace(resource.id, { t: 'removed', id: event.itemId });
void loadWorkspace(resource.id).catch(() => {});
break;
case 'rejected':
showRejected(resource.id, event);
@@ -3,6 +3,7 @@ import { active, getSession } from './sessions';
import { apiFetch, type ApiError } from './http';
import { publishWorkspace } from './presentation';
import { channelMetadata, processChannelBytes, syncChannel } from './channelEvents';
import { loadWorkspace } from './workspaceStructure';
import { flushOutbox } from './transfer';
import { socketStream, sseStream, delay, type StreamConsumer } from './transports';
import { errorMessage } from '../errors';
@@ -31,7 +32,11 @@ export async function connectChannel(id: string): Promise<void> {
const stream: StreamConsumer = {
cursor: 0,
async record(bytes) { stream.cursor = Math.max(stream.cursor, await processChannelBytes(resource, session, bytes)); },
async control(value) { if (value.t === 'settings' || value.t === 'seats') await channelMetadata(resource, session); },
async control(value) {
if (value.t === 'settings') {
await Promise.all([channelMetadata(resource, session), loadWorkspace(id)]);
} else if (value.t === 'seats') await channelMetadata(resource, session);
},
online() { if (!signal.aborted) publishWorkspace(id, { t: 'connection', state: 'online' }); },
};
let initialized = false;