Files
docmost/apps/server/src/core/page/page.controller.ts
Philip Okugbe de7982fe30 feat: copy page to different space (#1118)
* Add copy page to space endpoint
* copy storage function
* copy function
* feat: copy attachments too
* Copy page - WIP
* fix type
* sync
* cleanup
2025-04-30 14:43:16 +01:00

305 lines
8.9 KiB
TypeScript

import {
Controller,
Post,
Body,
HttpCode,
HttpStatus,
UseGuards,
ForbiddenException,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import { PageService } from './services/page.service';
import { CreatePageDto } from './dto/create-page.dto';
import { UpdatePageDto } from './dto/update-page.dto';
import { MovePageDto, MovePageToSpaceDto } from './dto/move-page.dto';
import { PageHistoryIdDto, PageIdDto, PageInfoDto } from './dto/page.dto';
import { PageHistoryService } from './services/page-history.service';
import { AuthUser } from '../../common/decorators/auth-user.decorator';
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { User, Workspace } from '@docmost/db/types/entity.types';
import { SidebarPageDto } from './dto/sidebar-page.dto';
import {
SpaceCaslAction,
SpaceCaslSubject,
} from '../casl/interfaces/space-ability.type';
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { RecentPageDto } from './dto/recent-page.dto';
import { CopyPageToSpaceDto } from './dto/copy-page.dto';
@UseGuards(JwtAuthGuard)
@Controller('pages')
export class PageController {
constructor(
private readonly pageService: PageService,
private readonly pageRepo: PageRepo,
private readonly pageHistoryService: PageHistoryService,
private readonly spaceAbility: SpaceAbilityFactory,
) {}
@HttpCode(HttpStatus.OK)
@Post('/info')
async getPage(@Body() dto: PageInfoDto, @AuthUser() user: User) {
const page = await this.pageRepo.findById(dto.pageId, {
includeSpace: true,
includeContent: true,
includeCreator: true,
includeLastUpdatedBy: true,
includeContributors: true,
});
if (!page) {
throw new NotFoundException('Page not found');
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return page;
}
@HttpCode(HttpStatus.OK)
@Post('create')
async create(
@Body() createPageDto: CreatePageDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const ability = await this.spaceAbility.createForUser(
user,
createPageDto.spaceId,
);
if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageService.create(user.id, workspace.id, createPageDto);
}
@HttpCode(HttpStatus.OK)
@Post('update')
async update(@Body() updatePageDto: UpdatePageDto, @AuthUser() user: User) {
const page = await this.pageRepo.findById(updatePageDto.pageId);
if (!page) {
throw new NotFoundException('Page not found');
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageService.update(page, updatePageDto, user.id);
}
@HttpCode(HttpStatus.OK)
@Post('delete')
async delete(@Body() pageIdDto: PageIdDto, @AuthUser() user: User) {
const page = await this.pageRepo.findById(pageIdDto.pageId);
if (!page) {
throw new NotFoundException('Page not found');
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
await this.pageService.forceDelete(pageIdDto.pageId);
}
@HttpCode(HttpStatus.OK)
@Post('restore')
async restore(@Body() pageIdDto: PageIdDto) {
// await this.pageService.restore(deletePageDto.id);
}
@HttpCode(HttpStatus.OK)
@Post('recent')
async getRecentPages(
@Body() recentPageDto: RecentPageDto,
@Body() pagination: PaginationOptions,
@AuthUser() user: User,
) {
if (recentPageDto.spaceId) {
const ability = await this.spaceAbility.createForUser(
user,
recentPageDto.spaceId,
);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageService.getRecentSpacePages(
recentPageDto.spaceId,
pagination,
);
}
return this.pageService.getRecentPages(user.id, pagination);
}
// TODO: scope to workspaces
@HttpCode(HttpStatus.OK)
@Post('/history')
async getPageHistory(
@Body() dto: PageIdDto,
@Body() pagination: PaginationOptions,
@AuthUser() user: User,
) {
const page = await this.pageRepo.findById(dto.pageId);
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageHistoryService.findHistoryByPageId(page.id, pagination);
}
@HttpCode(HttpStatus.OK)
@Post('/history/info')
async getPageHistoryInfo(
@Body() dto: PageHistoryIdDto,
@AuthUser() user: User,
) {
const history = await this.pageHistoryService.findById(dto.historyId);
if (!history) {
throw new NotFoundException('Page history not found');
}
const ability = await this.spaceAbility.createForUser(
user,
history.spaceId,
);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return history;
}
@HttpCode(HttpStatus.OK)
@Post('/sidebar-pages')
async getSidebarPages(
@Body() dto: SidebarPageDto,
@Body() pagination: PaginationOptions,
@AuthUser() user: User,
) {
const ability = await this.spaceAbility.createForUser(user, dto.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
let pageId = null;
if (dto.pageId) {
const page = await this.pageRepo.findById(dto.pageId);
if (page.spaceId !== dto.spaceId) {
throw new ForbiddenException();
}
pageId = page.id;
}
return this.pageService.getSidebarPages(dto.spaceId, pagination, pageId);
}
@HttpCode(HttpStatus.OK)
@Post('move-to-space')
async movePageToSpace(
@Body() dto: MovePageToSpaceDto,
@AuthUser() user: User,
) {
const movedPage = await this.pageRepo.findById(dto.pageId);
if (!movedPage) {
throw new NotFoundException('Page to move not found');
}
if (movedPage.spaceId === dto.spaceId) {
throw new BadRequestException('Page is already in this space');
}
const abilities = await Promise.all([
this.spaceAbility.createForUser(user, movedPage.spaceId),
this.spaceAbility.createForUser(user, dto.spaceId),
]);
if (
abilities.some((ability) =>
ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page),
)
) {
throw new ForbiddenException();
}
return this.pageService.movePageToSpace(movedPage, dto.spaceId);
}
@HttpCode(HttpStatus.OK)
@Post('copy-to-space')
async copyPageToSpace(
@Body() dto: CopyPageToSpaceDto,
@AuthUser() user: User,
) {
const copiedPage = await this.pageRepo.findById(dto.pageId);
if (!copiedPage) {
throw new NotFoundException('Page to copy not found');
}
if (copiedPage.spaceId === dto.spaceId) {
throw new BadRequestException('Page is already in this space');
}
const abilities = await Promise.all([
this.spaceAbility.createForUser(user, copiedPage.spaceId),
this.spaceAbility.createForUser(user, dto.spaceId),
]);
if (
abilities.some((ability) =>
ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page),
)
) {
throw new ForbiddenException();
}
return this.pageService.copyPageToSpace(copiedPage, dto.spaceId, user);
}
@HttpCode(HttpStatus.OK)
@Post('move')
async movePage(@Body() dto: MovePageDto, @AuthUser() user: User) {
const movedPage = await this.pageRepo.findById(dto.pageId);
if (!movedPage) {
throw new NotFoundException('Moved page not found');
}
const ability = await this.spaceAbility.createForUser(
user,
movedPage.spaceId,
);
if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageService.movePage(dto, movedPage);
}
@HttpCode(HttpStatus.OK)
@Post('/breadcrumbs')
async getPageBreadcrumbs(@Body() dto: PageIdDto, @AuthUser() user: User) {
const page = await this.pageRepo.findById(dto.pageId);
if (!page) {
throw new NotFoundException('Page not found');
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
}
return this.pageService.getPageBreadCrumbs(page.id);
}
}