This commit is contained in:
Philipinho
2025-04-16 20:19:16 +01:00
parent 418e61614c
commit 5bdefda9c7
16 changed files with 412 additions and 151 deletions

View File

@ -1,10 +0,0 @@
import { IsBoolean, IsOptional, IsString } from 'class-validator';
export class CreateShareDto {
@IsString()
pageId: string;
@IsBoolean()
@IsOptional()
includeSubPages: boolean;
}

View File

@ -6,6 +6,30 @@ import {
IsUUID,
} from 'class-validator';
export class CreateShareDto {
@IsString()
@IsNotEmpty()
pageId: string;
@IsBoolean()
@IsOptional()
includeSubPages: boolean;
@IsOptional()
@IsBoolean()
searchIndexing: boolean;
}
export class UpdateShareDto extends CreateShareDto {
@IsString()
@IsNotEmpty()
shareId: string;
@IsString()
@IsOptional()
pageId: string;
}
export class ShareIdDto {
@IsString()
@IsNotEmpty()

View File

@ -1,7 +0,0 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdateShareDto {
@IsString()
@IsNotEmpty()
shareId: string;
}

View File

@ -18,9 +18,13 @@ import {
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
import { ShareService } from './share.service';
import { UpdateShareDto } from './dto/update-page.dto';
import { CreateShareDto } from './dto/create-share.dto';
import { ShareIdDto, ShareInfoDto, SharePageIdDto } from './dto/share.dto';
import {
CreateShareDto,
ShareIdDto,
ShareInfoDto,
SharePageIdDto,
UpdateShareDto,
} from './dto/share.dto';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { Public } from '../../common/decorators/public.decorator';
@ -48,8 +52,8 @@ export class ShareController {
@Public()
@HttpCode(HttpStatus.OK)
@Post('/info')
async getShare(
@Post('/page-info')
async getSharedPageInfo(
@Body() dto: ShareInfoDto,
@AuthWorkspace() workspace: Workspace,
) {
@ -61,24 +65,40 @@ export class ShareController {
}
@HttpCode(HttpStatus.OK)
@Post('/status')
async getShareStatus(
@Post('/info')
async getShare(@Body() dto: ShareIdDto, @AuthUser() user: User) {
const share = await this.shareRepo.findById(dto.shareId);
if (!share) {
throw new NotFoundException('Share not found');
}
const ability = await this.spaceAbility.createForUser(user, share.spaceId);
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Share)) {
throw new ForbiddenException();
}
return share;
}
@HttpCode(HttpStatus.OK)
@Post('/for-page')
async getShareForPage(
@Body() dto: SharePageIdDto,
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const page = await this.pageRepo.findById(dto.pageId);
if (!page || workspace.id !== page.workspaceId) {
throw new NotFoundException('Page not found');
if (!page) {
throw new NotFoundException('Shared page not found');
}
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Share)) {
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Share)) {
throw new ForbiddenException();
}
return this.shareService.getShareStatus(page.id, workspace.id);
return this.shareService.getShareForPage(page.id, workspace.id);
}
@HttpCode(HttpStatus.OK)
@ -103,7 +123,7 @@ export class ShareController {
page,
authUserId: user.id,
workspaceId: workspace.id,
includeSubPages: createShareDto.includeSubPages,
createShareDto,
});
}
@ -121,7 +141,7 @@ export class ShareController {
throw new ForbiddenException();
}
//return this.shareService.update(page, updatePageDto, user.id);
return this.shareService.updateShare(share.id, updateShareDto);
}
@HttpCode(HttpStatus.OK)

View File

@ -4,7 +4,7 @@ import {
Logger,
NotFoundException,
} from '@nestjs/common';
import { ShareInfoDto } from './dto/share.dto';
import { CreateShareDto, ShareInfoDto, UpdateShareDto } from './dto/share.dto';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
import { generateSlugId } from '../../common/helpers';
@ -55,9 +55,9 @@ export class ShareService {
authUserId: string;
workspaceId: string;
page: Page;
includeSubPages: boolean;
createShareDto: CreateShareDto;
}) {
const { authUserId, workspaceId, page, includeSubPages } = opts;
const { authUserId, workspaceId, page, createShareDto } = opts;
try {
const shares = await this.shareRepo.findByPageId(page.id);
@ -68,19 +68,35 @@ export class ShareService {
return await this.shareRepo.insertShare({
key: generateSlugId(),
pageId: page.id,
includeSubPages: includeSubPages,
includeSubPages: createShareDto.includeSubPages,
searchIndexing: true,
creatorId: authUserId,
spaceId: page.spaceId,
workspaceId,
});
} catch (err) {
this.logger.error(err);
throw new BadRequestException('Failed to create page');
throw new BadRequestException('Failed to share page');
}
}
async updateShare(shareId: string, updateShareDto: UpdateShareDto) {
try {
return this.shareRepo.updateShare(
{
includeSubPages: updateShareDto.includeSubPages,
searchIndexing: updateShareDto.searchIndexing,
},
shareId,
);
} catch (err) {
this.logger.error(err);
throw new BadRequestException('Failed to update share');
}
}
async getSharedPage(dto: ShareInfoDto, workspaceId: string) {
const share = await this.getShareStatus(dto.pageId, workspaceId);
const share = await this.getShareForPage(dto.pageId, workspaceId);
if (!share) {
throw new NotFoundException('Shared page not found');
@ -94,25 +110,33 @@ export class ShareService {
page.content = await this.updatePublicAttachments(page);
if (!page) {
throw new NotFoundException('Page not found');
throw new NotFoundException('Shared page not found');
}
return page;
return { page, share };
}
async getShareStatus(pageId: string, workspaceId: string) {
async getShareForPage(pageId: string, workspaceId: string) {
// here we try to check if a page was shared directly or if it inherits the share from its closest shared ancestor
const share = await this.db
.withRecursive('page_hierarchy', (cte) =>
cte
.selectFrom('pages')
.select(['id', 'parentPageId', sql`0`.as('level')])
.select([
'id',
'slugId',
'pages.title',
'parentPageId',
sql`0`.as('level'),
])
.where(isValidUUID(pageId) ? 'id' : 'slugId', '=', pageId)
.unionAll((union) =>
union
.selectFrom('pages as p')
.select([
'p.id',
'p.slugId',
'p.title',
'p.parentPageId',
// Increase the level by 1 for each ancestor.
sql`ph.level + 1`.as('level'),
@ -124,10 +148,13 @@ export class ShareService {
.leftJoin('shares', 'shares.pageId', 'page_hierarchy.id')
.select([
'page_hierarchy.id as sharedPageId',
'page_hierarchy.slugId as sharedPageSlugId',
'page_hierarchy.title as sharedPageTitle',
'page_hierarchy.level as level',
'shares.id as shareId',
'shares.key as shareKey',
'shares.includeSubPages as includeSubPages',
'shares.id',
'shares.key',
'shares.pageId',
'shares.includeSubPages',
'shares.creatorId',
'shares.spaceId',
'shares.workspaceId',
@ -147,7 +174,22 @@ export class ShareService {
throw new NotFoundException('Shared page not found');
}
return share;
return {
id: share.id,
key: share.key,
includeSubPages: share.includeSubPages,
pageId: share.pageId,
creatorId: share.creatorId,
spaceId: share.spaceId,
workspaceId: share.workspaceId,
createdAt: share.createdAt,
level: share.level,
sharedPage: {
id: share.sharedPageId,
slugId: share.sharedPageSlugId,
title: share.sharedPageTitle,
},
};
}
async getShareAncestorPage(

View File

@ -98,6 +98,7 @@ export class ShareRepo {
.updateTable('shares')
.set({ ...updatableShare, updatedAt: new Date() })
.where(!isValidUUID(shareId) ? 'key' : 'id', '=', shareId)
.returning(this.baseFields)
.executeTakeFirst();
}