From f11f9f62106682691f91ff053ea8359a77507384 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Aug 2023 19:48:57 +0100 Subject: [PATCH] Add new page module and services; refactor existing entities - Introduced a new page module with associated services. - Refactored TypeORM entities in user and workspace modules. --- server/src/core/core.module.ts | 3 +- server/src/core/page/dto/create-page.dto.ts | 18 ++++ server/src/core/page/dto/update-page.dto.ts | 4 + server/src/core/page/entities/page.entity.ts | 88 +++++++++++++++++++ server/src/core/page/page.controller.spec.ts | 20 +++++ server/src/core/page/page.controller.ts | 23 +++++ server/src/core/page/page.module.ts | 13 +++ server/src/core/page/page.service.spec.ts | 18 ++++ server/src/core/page/page.service.ts | 24 +++++ .../core/page/repositories/page.repository.ts | 14 +++ server/src/core/user/entities/user.entity.ts | 24 ++--- .../entities/workspace-invitation.entity.ts | 22 ++--- .../entities/workspace-user.entity.ts | 16 ++-- .../workspace/entities/workspace.entity.ts | 47 +++++----- 14 files changed, 282 insertions(+), 52 deletions(-) create mode 100644 server/src/core/page/dto/create-page.dto.ts create mode 100644 server/src/core/page/dto/update-page.dto.ts create mode 100644 server/src/core/page/entities/page.entity.ts create mode 100644 server/src/core/page/page.controller.spec.ts create mode 100644 server/src/core/page/page.controller.ts create mode 100644 server/src/core/page/page.module.ts create mode 100644 server/src/core/page/page.service.spec.ts create mode 100644 server/src/core/page/page.service.ts create mode 100644 server/src/core/page/repositories/page.repository.ts diff --git a/server/src/core/core.module.ts b/server/src/core/core.module.ts index 5985ed7f..2389bbf1 100644 --- a/server/src/core/core.module.ts +++ b/server/src/core/core.module.ts @@ -2,8 +2,9 @@ import { Module } from '@nestjs/common'; import { UserModule } from './user/user.module'; import { AuthModule } from './auth/auth.module'; import { WorkspaceModule } from './workspace/workspace.module'; +import { PageModule } from './page/page.module'; @Module({ - imports: [UserModule, AuthModule, WorkspaceModule], + imports: [UserModule, AuthModule, WorkspaceModule, PageModule], }) export class CoreModule {} diff --git a/server/src/core/page/dto/create-page.dto.ts b/server/src/core/page/dto/create-page.dto.ts new file mode 100644 index 00000000..7d37df0b --- /dev/null +++ b/server/src/core/page/dto/create-page.dto.ts @@ -0,0 +1,18 @@ +import { IsOptional, IsString } from 'class-validator'; + +export class CreatePageDto { + @IsOptional() + title?: string; + + @IsOptional() + content?: string; + + @IsOptional() + parentId?: string; + + @IsString() + creatorId: string; + + @IsString() + workspaceId: string; +} diff --git a/server/src/core/page/dto/update-page.dto.ts b/server/src/core/page/dto/update-page.dto.ts new file mode 100644 index 00000000..c9f3d9c7 --- /dev/null +++ b/server/src/core/page/dto/update-page.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreatePageDto } from './create-page.dto'; + +export class UpdatePageDto extends PartialType(CreatePageDto) {} diff --git a/server/src/core/page/entities/page.entity.ts b/server/src/core/page/entities/page.entity.ts new file mode 100644 index 00000000..511a704d --- /dev/null +++ b/server/src/core/page/entities/page.entity.ts @@ -0,0 +1,88 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + OneToMany, + DeleteDateColumn, +} from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { Workspace } from '../../workspace/entities/workspace.entity'; + +@Entity('pages') +export class Page { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ length: 500, nullable: true }) + title: string; + + @Column({ type: 'text', nullable: true }) + content: string; + + @Column({ type: 'text', nullable: true }) + html: string; + + @Column({ type: 'jsonb', nullable: true }) + json: any; + + @Column({ nullable: true }) + slug: string; + + @Column({ nullable: true }) + icon: string; + + @Column({ nullable: true }) + coverPhoto: string; + + @Column({ length: 255, nullable: true }) + editor: string; + + @Column({ length: 255, nullable: true }) + shareId: string; + + @Column({ type: 'uuid', nullable: true }) + parentPageId: string; + + @Column() + creatorId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'creatorId' }) + creator: User; + + @Column() + workspaceId: string; + + @ManyToOne(() => Workspace, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Workspace; + + @Column({ type: 'boolean', default: false }) + isLocked: boolean; + + @Column({ length: 255, nullable: true }) + status: string; + + @Column({ type: 'date', nullable: true }) + publishedAt: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @DeleteDateColumn({ nullable: true }) + deletedAt: Date; + + @ManyToOne(() => Page, (page) => page.childPages) + @JoinColumn({ name: 'parentPageId' }) + parentPage: Page; + + @OneToMany(() => Page, (page) => page.parentPage, { onDelete: 'CASCADE' }) + childPages: Page[]; +} diff --git a/server/src/core/page/page.controller.spec.ts b/server/src/core/page/page.controller.spec.ts new file mode 100644 index 00000000..ac0c5e16 --- /dev/null +++ b/server/src/core/page/page.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PageController } from './page.controller'; +import { PageService } from './page.service'; + +describe('PageController', () => { + let controller: PageController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [PageController], + providers: [PageService], + }).compile(); + + controller = module.get(PageController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/core/page/page.controller.ts b/server/src/core/page/page.controller.ts new file mode 100644 index 00000000..dbdb5526 --- /dev/null +++ b/server/src/core/page/page.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Post, Body, Delete, Get, Param } from "@nestjs/common"; +import { PageService } from './page.service'; +import { CreatePageDto } from './dto/create-page.dto'; + +@Controller('page') +export class PageController { + constructor(private readonly pageService: PageService) {} + + @Post('create') + async create(@Body() createPageDto: CreatePageDto) { + return this.pageService.create(createPageDto); + } + + @Get('page/:id') + async getPage(@Param('id') pageId: string) { + return this.pageService.findById(pageId); + } + + @Delete('delete/:id') + async delete(@Param('id') pageId: string) { + await this.pageService.delete(pageId); + } +} diff --git a/server/src/core/page/page.module.ts b/server/src/core/page/page.module.ts new file mode 100644 index 00000000..a730e249 --- /dev/null +++ b/server/src/core/page/page.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { PageService } from './page.service'; +import { PageController } from './page.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Page } from './entities/page.entity'; +import { PageRepository } from './repositories/page.repository'; + +@Module({ + imports: [TypeOrmModule.forFeature([Page])], + controllers: [PageController], + providers: [PageService, PageRepository], +}) +export class PageModule {} diff --git a/server/src/core/page/page.service.spec.ts b/server/src/core/page/page.service.spec.ts new file mode 100644 index 00000000..7cadcb7f --- /dev/null +++ b/server/src/core/page/page.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PageService } from './page.service'; + +describe('PageService', () => { + let service: PageService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PageService], + }).compile(); + + service = module.get(PageService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/core/page/page.service.ts b/server/src/core/page/page.service.ts new file mode 100644 index 00000000..c11a2cad --- /dev/null +++ b/server/src/core/page/page.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { PageRepository } from './repositories/page.repository'; +import { CreatePageDto } from './dto/create-page.dto'; + +@Injectable() +export class PageService { + constructor(private pageRepository: PageRepository) {} + + async create(createPageDto: CreatePageDto) { + await this.pageRepository.save(createPageDto); + } + + async findById(pageId: string) { + return this.pageRepository.findById(pageId); + } + + async delete(pageId: string) { + return this.pageRepository.softDelete(pageId); + } + + async forceDelete(pageId: string) { + return this.pageRepository.delete(pageId); + } +} diff --git a/server/src/core/page/repositories/page.repository.ts b/server/src/core/page/repositories/page.repository.ts new file mode 100644 index 00000000..bb021022 --- /dev/null +++ b/server/src/core/page/repositories/page.repository.ts @@ -0,0 +1,14 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { Page } from '../entities/page.entity'; + +@Injectable() +export class PageRepository extends Repository { + constructor(private dataSource: DataSource) { + super(Page, dataSource.createEntityManager()); + } + + async findById(pageId: string) { + return this.findOneBy({ id: pageId }); + } +} diff --git a/server/src/core/user/entities/user.entity.ts b/server/src/core/user/entities/user.entity.ts index e001d3ca..c480a3ac 100644 --- a/server/src/core/user/entities/user.entity.ts +++ b/server/src/core/user/entities/user.entity.ts @@ -10,16 +10,17 @@ import { import * as bcrypt from 'bcrypt'; import { Workspace } from '../../workspace/entities/workspace.entity'; import { WorkspaceUser } from '../../workspace/entities/workspace-user.entity'; +import { Page } from '../../page/entities/page.entity'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ length: 255, nullable: true }) name: string; - @Column({ unique: true }) + @Column({ length: 255, unique: true }) email: string; @Column({ nullable: true }) @@ -29,12 +30,12 @@ export class User { password: string; @Column({ nullable: true }) - avatar_url: string; + avatarUrl: string; - @Column({ nullable: true }) + @Column({ length: 100, nullable: true }) locale: string; - @Column({ nullable: true }) + @Column({ length: 300, nullable: true }) timezone: string; @Column({ type: 'jsonb', nullable: true }) @@ -43,7 +44,7 @@ export class User { @Column({ nullable: true }) lastLoginAt: Date; - @Column({ nullable: true }) + @Column({ length: 100, nullable: true }) lastLoginIp: string; @CreateDateColumn() @@ -52,16 +53,15 @@ export class User { @UpdateDateColumn() updatedAt: Date; - @OneToMany(() => Workspace, (workspace) => workspace.creator, { - createForeignKeyConstraints: false, - }) + @OneToMany(() => Workspace, (workspace) => workspace.creator) workspaces: Workspace[]; - @OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user, { - createForeignKeyConstraints: false, - }) + @OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user) workspaceUser: WorkspaceUser[]; + @OneToMany(() => Page, (page) => page.creator) + createdPages; + toJSON() { delete this.password; return this; diff --git a/server/src/core/workspace/entities/workspace-invitation.entity.ts b/server/src/core/workspace/entities/workspace-invitation.entity.ts index c3a0942a..77e64a98 100644 --- a/server/src/core/workspace/entities/workspace-invitation.entity.ts +++ b/server/src/core/workspace/entities/workspace-invitation.entity.ts @@ -15,28 +15,30 @@ export class WorkspaceInvitation { @PrimaryGeneratedColumn('uuid') id: string; + @Column() + workspaceId: string; + @ManyToOne(() => Workspace, { onDelete: 'CASCADE', - createForeignKeyConstraints: false, }) @JoinColumn({ name: 'workspaceId' }) workspace: Workspace; - @ManyToOne(() => User, { - onDelete: 'SET NULL', - createForeignKeyConstraints: false, - }) + @Column() + invitedById: string; + + @ManyToOne(() => User) @JoinColumn({ name: 'invitedById' }) invitedBy: User; - @Column({ type: 'varchar', length: 255 }) + @Column({ length: 255 }) email: string; - @Column({ type: 'varchar', length: 100, nullable: true }) - role?: string; + @Column({ length: 100, nullable: true }) + role: string; - @Column({ type: 'varchar', length: 100, nullable: true }) - status?: string; + @Column({ length: 100, nullable: true }) + status: string; @CreateDateColumn() createdAt: Date; diff --git a/server/src/core/workspace/entities/workspace-user.entity.ts b/server/src/core/workspace/entities/workspace-user.entity.ts index 90a78e6d..b14c3b89 100644 --- a/server/src/core/workspace/entities/workspace-user.entity.ts +++ b/server/src/core/workspace/entities/workspace-user.entity.ts @@ -17,28 +17,26 @@ export class WorkspaceUser { @PrimaryGeneratedColumn('uuid') id: string; + @Column() + userId: string; + @ManyToOne(() => User, (user) => user.workspaceUser, { onDelete: 'CASCADE', - createForeignKeyConstraints: false, }) @JoinColumn({ name: 'userId' }) user: User; @Column() - userId: string; + workspaceId: string; - @ManyToOne(() => Workspace, (workspace) => workspace.workspaceUser, { + @ManyToOne(() => Workspace, (workspace) => workspace.workspaceUsers, { onDelete: 'CASCADE', - createForeignKeyConstraints: false, }) @JoinColumn({ name: 'workspaceId' }) workspace: Workspace; - @Column() - workspaceId: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - role?: string; + @Column({ length: 100, nullable: true }) + role: string; @CreateDateColumn() createdAt: Date; diff --git a/server/src/core/workspace/entities/workspace.entity.ts b/server/src/core/workspace/entities/workspace.entity.ts index d5374399..88d17b2d 100644 --- a/server/src/core/workspace/entities/workspace.entity.ts +++ b/server/src/core/workspace/entities/workspace.entity.ts @@ -10,53 +10,60 @@ import { } from 'typeorm'; import { User } from '../../user/entities/user.entity'; import { WorkspaceUser } from './workspace-user.entity'; +import { Page } from '../../page/entities/page.entity'; +import { WorkspaceInvitation } from './workspace-invitation.entity'; @Entity('workspaces') export class Workspace { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ length: 255, nullable: true }) name: string; @Column({ type: 'text', nullable: true }) - description?: string; + description: string; - @Column({ nullable: true }) - logo?: string; + @Column({ length: 255, nullable: true }) + logo: string; - @Column({ unique: true }) + @Column({ length: 255, unique: true }) hostname: string; - @Column({ nullable: true }) - customDomain?: string; + @Column({ length: 255, nullable: true }) + customDomain: string; @Column({ type: 'boolean', default: true }) enableInvite: boolean; - @Column({ type: 'text', unique: true, nullable: true }) - inviteCode?: string; + @Column({ length: 255, unique: true, nullable: true }) + inviteCode: string; @Column({ type: 'jsonb', nullable: true }) - settings?: any; - - @ManyToOne(() => User, (user) => user.workspaces, { - createForeignKeyConstraints: false, - }) - @JoinColumn({ name: 'creatorId' }) - creator: User; + settings: any; @Column() creatorId: string; + @ManyToOne(() => User, (user) => user.workspaces) + @JoinColumn({ name: 'creatorId' }) + creator: User; + @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; - @OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.workspace, { - createForeignKeyConstraints: false, - }) - workspaceUser: WorkspaceUser[]; + @OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.workspace) + workspaceUsers: WorkspaceUser[]; + + @OneToMany( + () => WorkspaceInvitation, + (workspaceInvitation) => workspaceInvitation.workspace, + ) + workspaceInvitations: WorkspaceInvitation[]; + + @OneToMany(() => Page, (page) => page.workspace) + pages: Page[]; }