mirror of
https://github.com/docmost/docmost.git
synced 2026-09-01 11:26:54 +08:00
refactor(base): fold /bases/inline-embed into /bases/create with parentPageId
The inline-embed endpoint was a thin wrapper around baseService.create:
its only differences from /bases/create were that it derived the
spaceId/workspaceId from a parent page, gated permission via
pageAccessService.validateCanEdit on that parent, and seeded inline
defaults (Title + Text 1 + Text 2 + one row). All of that fits
naturally on /bases/create as a parentPageId branch.
- CreateBaseDto: spaceId is now optional. Either spaceId or
parentPageId must be supplied; the controller validates the
cross-field requirement and 400s otherwise.
- /bases/create: with parentPageId, derive workspaceId/spaceId from
the parent, validateCanEdit on the parent, apply inline defaults.
Without parentPageId, gate on space-level Create, Page (the
standalone path).
- InlineEmbedBaseDto + createInlineEmbed deleted.
- Slash command in the editor now POSTs /bases/create with
{ parentPageId } — the server picks up the inline branch.
This commit is contained in:
@@ -529,7 +529,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
.run();
|
.run();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.post<{ id: string }>("/bases/inline-embed", {
|
const res = await api.post<{ id: string }>("/bases/create", {
|
||||||
parentPageId,
|
parentPageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
BadRequestException,
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
@@ -19,7 +20,6 @@ import { UpdateBaseDto } from '../dto/update-base.dto';
|
|||||||
import { BaseIdDto } from '../dto/base.dto';
|
import { BaseIdDto } from '../dto/base.dto';
|
||||||
import { ExportBaseCsvDto } from '../dto/export-base.dto';
|
import { ExportBaseCsvDto } from '../dto/export-base.dto';
|
||||||
import { ResolvePagesDto } from '../dto/resolve-pages.dto';
|
import { ResolvePagesDto } from '../dto/resolve-pages.dto';
|
||||||
import { InlineEmbedBaseDto } from '../dto/inline-embed-base.dto';
|
|
||||||
import { AuthUser } from '../../../common/decorators/auth-user.decorator';
|
import { AuthUser } from '../../../common/decorators/auth-user.decorator';
|
||||||
import { AuthWorkspace } from '../../../common/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from '../../../common/decorators/auth-workspace.decorator';
|
||||||
import { JwtAuthGuard } from '../../../common/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../../common/guards/jwt-auth.guard';
|
||||||
@@ -56,9 +56,38 @@ export class BaseController {
|
|||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
) {
|
) {
|
||||||
// Bases are pages — use the same SpaceCaslSubject.Page check the
|
// One endpoint, two modes:
|
||||||
// page controller uses for its own create endpoint, so a single
|
// - parentPageId set → inline embed inside that page. Derive
|
||||||
// role definition (admin/writer/reader) governs both.
|
// spaceId/workspaceId from the parent and gate on Edit access
|
||||||
|
// to the parent (same check the editor uses for any other
|
||||||
|
// content the user adds while editing). Seed two extra text
|
||||||
|
// columns + one row so the freshly-inserted base looks like
|
||||||
|
// a typical database on first paint.
|
||||||
|
// - parentPageId unset → standalone base. Gate on space-level
|
||||||
|
// Create, Page (the same check the page controller uses for
|
||||||
|
// its own create endpoint).
|
||||||
|
if (dto.parentPageId) {
|
||||||
|
const parent = await this.pageRepo.findById(dto.parentPageId);
|
||||||
|
if (!parent) {
|
||||||
|
throw new NotFoundException('Parent page not found');
|
||||||
|
}
|
||||||
|
await this.pageAccessService.validateCanEdit(parent, user);
|
||||||
|
|
||||||
|
return this.baseService.create(
|
||||||
|
user.id,
|
||||||
|
parent.workspaceId,
|
||||||
|
{
|
||||||
|
...dto,
|
||||||
|
spaceId: parent.spaceId,
|
||||||
|
},
|
||||||
|
{ extraTextProperties: 2, defaultRows: 1 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dto.spaceId) {
|
||||||
|
throw new BadRequestException('spaceId or parentPageId is required');
|
||||||
|
}
|
||||||
|
|
||||||
const ability = await this.spaceAbility.createForUser(user, dto.spaceId);
|
const ability = await this.spaceAbility.createForUser(user, dto.spaceId);
|
||||||
if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Page)) {
|
if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Page)) {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
@@ -169,33 +198,4 @@ export class BaseController {
|
|||||||
return { items };
|
return { items };
|
||||||
}
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@Post('inline-embed')
|
|
||||||
async createInlineEmbed(
|
|
||||||
@Body() dto: InlineEmbedBaseDto,
|
|
||||||
@AuthUser() user: User,
|
|
||||||
) {
|
|
||||||
const parent = await this.pageRepo.findById(dto.parentPageId);
|
|
||||||
if (!parent) {
|
|
||||||
throw new NotFoundException('Parent page not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.pageAccessService.validateCanEdit(parent, user);
|
|
||||||
|
|
||||||
// Inline embeds land mid-document and need to be visually meaningful
|
|
||||||
// on first paint — a single "Title" column with no rows looks broken.
|
|
||||||
// Seed two extra text columns and one empty row so the freshly-
|
|
||||||
// created base looks like a typical database (Title + Text 1 + Text 2,
|
|
||||||
// one ready-to-fill row).
|
|
||||||
return this.baseService.create(
|
|
||||||
user.id,
|
|
||||||
parent.workspaceId,
|
|
||||||
{
|
|
||||||
spaceId: parent.spaceId,
|
|
||||||
parentPageId: dto.parentPageId,
|
|
||||||
name: 'Untitled',
|
|
||||||
},
|
|
||||||
{ extraTextProperties: 2, defaultRows: 1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,16 @@ export class CreateBaseDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
// Either `spaceId` or `parentPageId` must be supplied. When
|
||||||
|
// parentPageId is given, the base is being inserted as an inline
|
||||||
|
// embed inside that page — spaceId/workspaceId are derived from
|
||||||
|
// the parent and the seeded shape includes a couple of default
|
||||||
|
// columns + one row. When only spaceId is supplied, the base is a
|
||||||
|
// standalone page in that space's tree. The controller validates
|
||||||
|
// the cross-field requirement and picks the right permission gate.
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
spaceId!: string;
|
@IsOptional()
|
||||||
|
spaceId?: string;
|
||||||
|
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { IsUUID } from 'class-validator';
|
|
||||||
|
|
||||||
export class InlineEmbedBaseDto {
|
|
||||||
@IsUUID()
|
|
||||||
parentPageId!: string;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user