feat: spaces - WIP

This commit is contained in:
Philipinho
2024-02-28 02:39:46 +00:00
parent 1d620eba49
commit 40251aef7d
32 changed files with 512 additions and 119 deletions

View File

@ -16,4 +16,7 @@ export class CreatePageDto {
@IsOptional()
@IsString()
parentPageId?: string;
@IsString()
spaceId: string;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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',
}

View File

@ -18,6 +18,7 @@ export class PageRepository extends Repository<Page> {
'page.parentPageId',
'page.creatorId',
'page.lastUpdatedById',
'page.spaceId',
'page.workspaceId',
'page.isLocked',
'page.status',

View File

@ -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) => {

View File

@ -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)