Compare commits
No commits in common. "731d7f5e93c74486eb6a74cf8fbbf5419bb7d42e" and "5e0f5cf49edf4762c7595d1052b164db9bb82273" have entirely different histories.
731d7f5e93
...
5e0f5cf49e
3 changed files with 1 additions and 112 deletions
|
|
@ -1,43 +0,0 @@
|
|||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { getBridgeClient, resolveBridgeUrl } from "../services/bridge-client";
|
||||
import type { BridgeRow } from "../types/database-view.types";
|
||||
import { VIEW_DATA_QUERY_KEY } from "./use-view-data";
|
||||
|
||||
interface UseCreateRowOptions {
|
||||
tableId: string;
|
||||
viewId: string;
|
||||
bridgeUrl?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new row in the table via the bridge, then invalidate the view
|
||||
* cache so the row appears.
|
||||
*
|
||||
* Server-first (no optimistic insert): the bridge assigns the row id, and the
|
||||
* inline editor PATCHes by id afterwards — fabricating a temporary id here
|
||||
* would desync the first edit. An empty payload creates a blank row (Baserow
|
||||
* fills defaults); callers may pass initial field values.
|
||||
*/
|
||||
export function useCreateRow({ tableId, viewId, bridgeUrl }: UseCreateRowOptions) {
|
||||
const queryClient = useQueryClient();
|
||||
const url = resolveBridgeUrl(bridgeUrl);
|
||||
|
||||
return useMutation<BridgeRow, Error, Record<string, unknown> | void>({
|
||||
mutationFn: async (fields) => {
|
||||
const client = getBridgeClient(url);
|
||||
return (await (client.post(
|
||||
`/api/v1/tables/${tableId}/rows`,
|
||||
fields ?? {},
|
||||
) as unknown)) as BridgeRow;
|
||||
},
|
||||
|
||||
onSettled: () => {
|
||||
// Reconcile with the server. The bridge also emits an SSE row.created
|
||||
// event which triggers the same invalidation — idempotent.
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [VIEW_DATA_QUERY_KEY, viewId],
|
||||
exact: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import { modals } from "@mantine/modals";
|
|||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useViewData } from "../hooks/use-view-data";
|
||||
import { useUpdateRow } from "../hooks/use-update-row";
|
||||
import { useCreateRow } from "../hooks/use-create-row";
|
||||
import { useDatabaseRealtimeUpdates } from "../hooks/use-database-realtime-updates";
|
||||
import { usePermissions } from "../hooks/use-permissions";
|
||||
import { useAcadenicePermissions } from "@/features/acadenice/rbac/hooks/use-acadenice-permissions";
|
||||
|
|
@ -187,7 +186,6 @@ export function TableRenderer({ tableId, viewId, bridgeUrl }: TableRendererProps
|
|||
const canAdminTables = acadenicePerms.hasPermission("tables:write");
|
||||
const queryClient = useQueryClient();
|
||||
const updateRow = useUpdateRow({ tableId, viewId, bridgeUrl });
|
||||
const createRow = useCreateRow({ tableId, viewId, bridgeUrl });
|
||||
|
||||
// Field admin modal state.
|
||||
const [fieldModalOpen, setFieldModalOpen] = useState(false);
|
||||
|
|
@ -354,19 +352,6 @@ export function TableRenderer({ tableId, viewId, bridgeUrl }: TableRendererProps
|
|||
</table>
|
||||
</div>
|
||||
|
||||
{canWriteRows && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
leftSection={<IconPlus size={14} />}
|
||||
onClick={() => createRow.mutate()}
|
||||
loading={createRow.isPending}
|
||||
mt="xs"
|
||||
>
|
||||
{t("database_view.table.add_row", "Ajouter une ligne")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Pagination — only shown when there is more than one page. */}
|
||||
{(page > 1 || hasNextPage) && (
|
||||
<div className={styles.pagination}>
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@ import {
|
|||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
type MessageEvent,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Sse,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { fromEvent, interval, merge, type Observable } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
|
|
@ -29,10 +24,6 @@ import { AuthUser } from '../../../../common/decorators/auth-user.decorator';
|
|||
import { AuthWorkspace } from '../../../../common/decorators/auth-workspace.decorator';
|
||||
import { User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import { SyncBlocksService } from '../services/sync-blocks.service';
|
||||
import {
|
||||
SYNC_BLOCK_UPDATED_EVENT,
|
||||
type SyncBlockUpdatedPayload,
|
||||
} from '../services/sync-block-broadcast.service';
|
||||
import {
|
||||
CreateSyncBlockDto,
|
||||
UpdateSyncBlockDto,
|
||||
|
|
@ -59,51 +50,7 @@ import {
|
|||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('v1/sync-blocks')
|
||||
export class SyncBlocksController {
|
||||
constructor(
|
||||
private readonly syncBlocksService: SyncBlocksService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* SSE stream of updates for a single sync block.
|
||||
*
|
||||
* The client (use-sync-block-realtime) opens an EventSource here and
|
||||
* invalidates its cache on `sync-block.updated`. Auth is the controller's
|
||||
* JwtAuthGuard — EventSource cannot set headers but the JWT strategy reads
|
||||
* the `authToken` cookie, sent automatically same-origin.
|
||||
*
|
||||
* A 25s heartbeat keeps the connection alive through proxies that drop idle
|
||||
* streams (~30s). The client ignores the unknown `ping` event.
|
||||
*/
|
||||
@ApiOperation({
|
||||
summary: 'Sync block realtime stream (SSE)',
|
||||
description:
|
||||
'Server-sent events: emits `sync-block.updated` when this block changes.',
|
||||
})
|
||||
@ApiParam({ name: 'id', description: 'Sync block UUID', type: 'string' })
|
||||
@Sse(':id/events')
|
||||
events(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
): Observable<MessageEvent> {
|
||||
const updates = fromEvent<SyncBlockUpdatedPayload>(
|
||||
this.eventEmitter,
|
||||
SYNC_BLOCK_UPDATED_EVENT,
|
||||
).pipe(
|
||||
filter((payload) => payload?.masterId === id),
|
||||
map(
|
||||
(payload): MessageEvent => ({
|
||||
type: 'sync-block.updated',
|
||||
data: { masterId: payload.masterId },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const heartbeat = interval(25_000).pipe(
|
||||
map((): MessageEvent => ({ type: 'ping', data: {} })),
|
||||
);
|
||||
|
||||
return merge(updates, heartbeat);
|
||||
}
|
||||
constructor(private readonly syncBlocksService: SyncBlocksService) {}
|
||||
|
||||
@ApiOperation({ summary: 'Create sync block', description: 'Creates a master sync block that can be embedded by reference in multiple pages.' })
|
||||
@ApiBody({ schema: { type: 'object', properties: { content: { type: 'object', description: 'ProseMirror JSON content' } } }, description: 'Initial content (optional)' })
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue