mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-18 02:31:11 +10:00
feat: spaces - WIP
This commit is contained in:
@ -16,4 +16,7 @@ export class CreatePageDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
parentPageId?: string;
|
||||
|
||||
@IsString()
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||
import { Page } from './page.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity('page_history')
|
||||
export class PageHistory {
|
||||
@ -48,6 +49,13 @@ export class PageHistory {
|
||||
@JoinColumn({ name: 'lastUpdatedById' })
|
||||
lastUpdatedBy: User;
|
||||
|
||||
@Column()
|
||||
spaceId: string;
|
||||
|
||||
@ManyToOne(() => Space, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'spaceId' })
|
||||
space: Space;
|
||||
|
||||
@Column()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
DeleteDateColumn,
|
||||
} from 'typeorm';
|
||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity('page_ordering')
|
||||
@Unique(['entityId', 'entityType'])
|
||||
@ -23,9 +24,12 @@ export class PageOrdering {
|
||||
@Column({ type: 'varchar', length: 50, nullable: false })
|
||||
entityType: string;
|
||||
|
||||
@Column('uuid', { array: true })
|
||||
@Column('uuid', { array: true, default: '{}' })
|
||||
childrenIds: string[];
|
||||
|
||||
@Column('uuid')
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.id, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@ -33,7 +37,13 @@ export class PageOrdering {
|
||||
workspace: Workspace;
|
||||
|
||||
@Column('uuid')
|
||||
workspaceId: string;
|
||||
spaceId: string;
|
||||
|
||||
@ManyToOne(() => Space, (space) => space.id, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'spaceId' })
|
||||
space: Space;
|
||||
|
||||
@DeleteDateColumn({ nullable: true })
|
||||
deletedAt: Date;
|
||||
|
||||
@ -14,6 +14,7 @@ import { User } from '../../user/entities/user.entity';
|
||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||
import { Comment } from '../../comment/entities/comment.entity';
|
||||
import { PageHistory } from './page-history.entity';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity('pages')
|
||||
@Index('pages_tsv_idx', ['tsv'])
|
||||
@ -82,6 +83,13 @@ export class Page {
|
||||
@JoinColumn({ name: 'deletedById' })
|
||||
deletedBy: User;
|
||||
|
||||
@Column()
|
||||
spaceId: string;
|
||||
|
||||
@ManyToOne(() => Space, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'spaceId' })
|
||||
space: Space;
|
||||
|
||||
@Column()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import { PageService } from './services/page.service';
|
||||
import { CreatePageDto } from './dto/create-page.dto';
|
||||
import { UpdatePageDto } from './dto/update-page.dto';
|
||||
import { JwtGuard } from '../auth/guards/JwtGuard';
|
||||
import { JwtGuard } from '../auth/guards/jwt.guard';
|
||||
import { WorkspaceService } from '../workspace/services/workspace.service';
|
||||
import { MovePageDto } from './dto/move-page.dto';
|
||||
import { PageDetailsDto } from './dto/page-details.dto';
|
||||
@ -71,39 +71,27 @@ export class PageController {
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('recent')
|
||||
async getRecentWorkspacePages(@JwtUser() jwtUser) {
|
||||
const workspaceId = (
|
||||
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
|
||||
).id;
|
||||
return this.pageService.getRecentWorkspacePages(workspaceId);
|
||||
async getRecentSpacePages(@Body() { spaceId }) {
|
||||
console.log(spaceId);
|
||||
return this.pageService.getRecentSpacePages(spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post()
|
||||
async getWorkspacePages(@JwtUser() jwtUser) {
|
||||
const workspaceId = (
|
||||
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
|
||||
).id;
|
||||
return this.pageService.getSidebarPagesByWorkspaceId(workspaceId);
|
||||
async getSpacePages(spaceId: string) {
|
||||
return this.pageService.getSidebarPagesBySpaceId(spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('ordering')
|
||||
async getWorkspacePageOrder(@JwtUser() jwtUser) {
|
||||
const workspaceId = (
|
||||
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
|
||||
).id;
|
||||
return this.pageOrderService.getWorkspacePageOrder(workspaceId);
|
||||
async getSpacePageOrder(spaceId: string) {
|
||||
return this.pageOrderService.getSpacePageOrder(spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('tree')
|
||||
async workspacePageTree(@JwtUser() jwtUser) {
|
||||
const workspaceId = (
|
||||
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
|
||||
).id;
|
||||
|
||||
return this.pageOrderService.convertToTree(workspaceId);
|
||||
async spacePageTree(@Body() { spaceId }) {
|
||||
return this.pageOrderService.convertToTree(spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@ -2,7 +2,8 @@ import { MovePageDto } from './dto/move-page.dto';
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
export enum OrderingEntity {
|
||||
workspace = 'WORKSPACE',
|
||||
workspace = 'SPACE',
|
||||
space = 'SPACE',
|
||||
page = 'PAGE',
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ export class PageRepository extends Repository<Page> {
|
||||
'page.parentPageId',
|
||||
'page.creatorId',
|
||||
'page.lastUpdatedById',
|
||||
'page.spaceId',
|
||||
'page.workspaceId',
|
||||
'page.isLocked',
|
||||
'page.status',
|
||||
|
||||
@ -34,7 +34,7 @@ export class PageOrderingService {
|
||||
const movedPage = await manager
|
||||
.createQueryBuilder(Page, 'page')
|
||||
.where('page.id = :movedPageId', { movedPageId })
|
||||
.select(['page.id', 'page.workspaceId', 'page.parentPageId'])
|
||||
.select(['page.id', 'page.spaceId', 'page.parentPageId'])
|
||||
.getOne();
|
||||
|
||||
if (!movedPage) throw new BadRequestException('Moved page not found');
|
||||
@ -43,15 +43,15 @@ export class PageOrderingService {
|
||||
if (movedPage.parentPageId) {
|
||||
await this.removeFromParent(movedPage.parentPageId, dto.id, manager);
|
||||
}
|
||||
const workspaceOrdering = await this.getEntityOrdering(
|
||||
movedPage.workspaceId,
|
||||
OrderingEntity.workspace,
|
||||
const spaceOrdering = await this.getEntityOrdering(
|
||||
movedPage.spaceId,
|
||||
OrderingEntity.space,
|
||||
manager,
|
||||
);
|
||||
|
||||
orderPageList(workspaceOrdering.childrenIds, dto);
|
||||
orderPageList(spaceOrdering.childrenIds, dto);
|
||||
|
||||
await manager.save(workspaceOrdering);
|
||||
await manager.save(spaceOrdering);
|
||||
} else {
|
||||
const parentPageId = dto.parentId;
|
||||
|
||||
@ -65,7 +65,7 @@ export class PageOrderingService {
|
||||
parentPageOrdering = await this.createPageOrdering(
|
||||
parentPageId,
|
||||
OrderingEntity.page,
|
||||
movedPage.workspaceId,
|
||||
movedPage.spaceId,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
@ -78,8 +78,8 @@ export class PageOrderingService {
|
||||
|
||||
// If movedPage didn't have a parent initially (was at root level), update the root level
|
||||
if (!movedPage.parentPageId) {
|
||||
await this.removeFromWorkspacePageOrder(
|
||||
movedPage.workspaceId,
|
||||
await this.removeFromSpacePageOrder(
|
||||
movedPage.spaceId,
|
||||
dto.id,
|
||||
manager,
|
||||
);
|
||||
@ -95,36 +95,32 @@ export class PageOrderingService {
|
||||
});
|
||||
}
|
||||
|
||||
async addPageToOrder(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
parentPageId?: string,
|
||||
) {
|
||||
async addPageToOrder(spaceId: string, pageId: string, parentPageId?: string) {
|
||||
await this.dataSource.transaction(async (manager: EntityManager) => {
|
||||
if (parentPageId) {
|
||||
await this.upsertOrdering(
|
||||
parentPageId,
|
||||
OrderingEntity.page,
|
||||
pageId,
|
||||
workspaceId,
|
||||
spaceId,
|
||||
manager,
|
||||
);
|
||||
} else {
|
||||
await this.addToWorkspacePageOrder(workspaceId, pageId, manager);
|
||||
await this.addToSpacePageOrder(spaceId, pageId, manager);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async addToWorkspacePageOrder(
|
||||
workspaceId: string,
|
||||
async addToSpacePageOrder(
|
||||
spaceId: string,
|
||||
pageId: string,
|
||||
manager: EntityManager,
|
||||
) {
|
||||
await this.upsertOrdering(
|
||||
workspaceId,
|
||||
OrderingEntity.workspace,
|
||||
spaceId,
|
||||
OrderingEntity.space,
|
||||
pageId,
|
||||
workspaceId,
|
||||
spaceId,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
@ -142,14 +138,14 @@ export class PageOrderingService {
|
||||
);
|
||||
}
|
||||
|
||||
async removeFromWorkspacePageOrder(
|
||||
workspaceId: string,
|
||||
async removeFromSpacePageOrder(
|
||||
spaceId: string,
|
||||
pageId: string,
|
||||
manager: EntityManager,
|
||||
) {
|
||||
await this.removeChildFromOrdering(
|
||||
workspaceId,
|
||||
OrderingEntity.workspace,
|
||||
spaceId,
|
||||
OrderingEntity.space,
|
||||
pageId,
|
||||
manager,
|
||||
);
|
||||
@ -179,11 +175,7 @@ export class PageOrderingService {
|
||||
if (page.parentPageId) {
|
||||
await this.removeFromParent(page.parentPageId, page.id, manager);
|
||||
} else {
|
||||
await this.removeFromWorkspacePageOrder(
|
||||
page.workspaceId,
|
||||
page.id,
|
||||
manager,
|
||||
);
|
||||
await this.removeFromSpacePageOrder(page.spaceId, page.id, manager);
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +183,7 @@ export class PageOrderingService {
|
||||
entityId: string,
|
||||
entityType: string,
|
||||
childId: string,
|
||||
workspaceId: string,
|
||||
spaceId: string,
|
||||
manager: EntityManager,
|
||||
) {
|
||||
let ordering = await this.getEntityOrdering(entityId, entityType, manager);
|
||||
@ -200,7 +192,7 @@ export class PageOrderingService {
|
||||
ordering = await this.createPageOrdering(
|
||||
entityId,
|
||||
entityType,
|
||||
workspaceId,
|
||||
spaceId,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
@ -229,35 +221,35 @@ export class PageOrderingService {
|
||||
async createPageOrdering(
|
||||
entityId: string,
|
||||
entityType: string,
|
||||
workspaceId: string,
|
||||
spaceId: string,
|
||||
manager: EntityManager,
|
||||
): Promise<PageOrdering> {
|
||||
await manager.query(
|
||||
`INSERT INTO page_ordering ("entityId", "entityType", "workspaceId", "childrenIds")
|
||||
`INSERT INTO page_ordering ("entityId", "entityType", "spaceId", "childrenIds")
|
||||
VALUES ($1, $2, $3, '{}')
|
||||
ON CONFLICT ("entityId", "entityType") DO NOTHING`,
|
||||
[entityId, entityType, workspaceId],
|
||||
[entityId, entityType, spaceId],
|
||||
);
|
||||
|
||||
return await this.getEntityOrdering(entityId, entityType, manager);
|
||||
}
|
||||
|
||||
async getWorkspacePageOrder(workspaceId: string): Promise<PageOrdering> {
|
||||
async getSpacePageOrder(spaceId: string): Promise<PageOrdering> {
|
||||
return await this.dataSource
|
||||
.createQueryBuilder(PageOrdering, 'ordering')
|
||||
.select(['ordering.id', 'ordering.childrenIds', 'ordering.workspaceId'])
|
||||
.where('ordering.entityId = :workspaceId', { workspaceId })
|
||||
.select(['ordering.id', 'ordering.childrenIds', 'ordering.spaceId'])
|
||||
.where('ordering.entityId = :spaceId', { spaceId })
|
||||
.andWhere('ordering.entityType = :entityType', {
|
||||
entityType: OrderingEntity.workspace,
|
||||
entityType: OrderingEntity.space,
|
||||
})
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async convertToTree(workspaceId: string): Promise<TreeNode[]> {
|
||||
const workspaceOrder = await this.getWorkspacePageOrder(workspaceId);
|
||||
async convertToTree(spaceId: string): Promise<TreeNode[]> {
|
||||
const spaceOrder = await this.getSpacePageOrder(spaceId);
|
||||
|
||||
const pageOrder = workspaceOrder ? workspaceOrder.childrenIds : undefined;
|
||||
const pages = await this.pageService.getSidebarPagesByWorkspaceId(workspaceId);
|
||||
const pageOrder = spaceOrder ? spaceOrder.childrenIds : undefined;
|
||||
const pages = await this.pageService.getSidebarPagesBySpaceId(spaceId);
|
||||
|
||||
const pageMap: { [id: string]: PageWithOrderingDto } = {};
|
||||
pages.forEach((page) => {
|
||||
|
||||
@ -67,7 +67,7 @@ export class PageService {
|
||||
page.lastUpdatedById = userId;
|
||||
|
||||
if (createPageDto.parentPageId) {
|
||||
// TODO: make sure parent page belongs to same workspace and user has permissions
|
||||
// TODO: make sure parent page belongs to same space and user has permissions
|
||||
const parentPage = await this.pageRepository.findOne({
|
||||
where: { id: createPageDto.parentPageId },
|
||||
select: ['id'],
|
||||
@ -79,7 +79,7 @@ export class PageService {
|
||||
const createdPage = await this.pageRepository.save(page);
|
||||
|
||||
await this.pageOrderingService.addPageToOrder(
|
||||
workspaceId,
|
||||
createPageDto.spaceId,
|
||||
createPageDto.id,
|
||||
createPageDto.parentPageId,
|
||||
);
|
||||
@ -174,12 +174,7 @@ export class PageService {
|
||||
const restoredPage = await manager
|
||||
.createQueryBuilder(Page, 'page')
|
||||
.where('page.id = :pageId', { pageId })
|
||||
.select([
|
||||
'page.id',
|
||||
'page.title',
|
||||
'page.workspaceId',
|
||||
'page.parentPageId',
|
||||
])
|
||||
.select(['page.id', 'page.title', 'page.spaceId', 'page.parentPageId'])
|
||||
.getOne();
|
||||
|
||||
if (!restoredPage) {
|
||||
@ -188,7 +183,7 @@ export class PageService {
|
||||
|
||||
// add page back to its hierarchy
|
||||
await this.pageOrderingService.addPageToOrder(
|
||||
restoredPage.workspaceId,
|
||||
restoredPage.spaceId,
|
||||
pageId,
|
||||
restoredPage.parentPageId,
|
||||
);
|
||||
@ -222,8 +217,8 @@ export class PageService {
|
||||
return await this.pageRepository.findById(pageId);
|
||||
}
|
||||
|
||||
async getSidebarPagesByWorkspaceId(
|
||||
workspaceId: string,
|
||||
async getSidebarPagesBySpaceId(
|
||||
spaceId: string,
|
||||
limit = 200,
|
||||
): Promise<PageWithOrderingDto[]> {
|
||||
const pages = await this.pageRepository
|
||||
@ -234,12 +229,13 @@ export class PageService {
|
||||
'ordering.entityId = page.id AND ordering.entityType = :entityType',
|
||||
{ entityType: OrderingEntity.page },
|
||||
)
|
||||
.where('page.workspaceId = :workspaceId', { workspaceId })
|
||||
.where('page.spaceId = :spaceId', { spaceId })
|
||||
.select([
|
||||
'page.id',
|
||||
'page.title',
|
||||
'page.icon',
|
||||
'page.parentPageId',
|
||||
'page.spaceId',
|
||||
'ordering.childrenIds',
|
||||
'page.creatorId',
|
||||
'page.createdAt',
|
||||
@ -251,14 +247,14 @@ export class PageService {
|
||||
return transformPageResult(pages);
|
||||
}
|
||||
|
||||
async getRecentWorkspacePages(
|
||||
workspaceId: string,
|
||||
async getRecentSpacePages(
|
||||
spaceId: string,
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
): Promise<Page[]> {
|
||||
const pages = await this.pageRepository
|
||||
.createQueryBuilder('page')
|
||||
.where('page.workspaceId = :workspaceId', { workspaceId })
|
||||
.where('page.spaceId = :spaceId', { spaceId })
|
||||
.select(this.pageRepository.baseFields)
|
||||
.orderBy('page.updatedAt', 'DESC')
|
||||
.offset(offset)
|
||||
|
||||
Reference in New Issue
Block a user