diff --git a/apps/server/src/core/auth/guards/jwt.guard.ts b/apps/server/src/core/auth/guards/jwt.guard.ts index cdfba1c..f849077 100644 --- a/apps/server/src/core/auth/guards/jwt.guard.ts +++ b/apps/server/src/core/auth/guards/jwt.guard.ts @@ -35,13 +35,19 @@ export class JwtGuard implements CanActivate { throw new UnauthorizedException('Invalid jwt token'); } - try { - const payload = await this.tokenService.verifyJwt(token); + let payload; + try { + payload = await this.tokenService.verifyJwt(token); + } catch (error) { + throw new UnauthorizedException('Could not verify jwt token'); + } + + try { //fetch user and current workspace data from db request['user'] = await this.userService.getUserInstance(payload.sub); } catch (error) { - throw new UnauthorizedException('Could not verify jwt token'); + throw new UnauthorizedException(error.message); } return true; diff --git a/apps/server/src/core/space/entities/space.entity.ts b/apps/server/src/core/space/entities/space.entity.ts index 350e8a0..6083281 100644 --- a/apps/server/src/core/space/entities/space.entity.ts +++ b/apps/server/src/core/space/entities/space.entity.ts @@ -11,6 +11,7 @@ import { import { User } from '../../user/entities/user.entity'; import { Workspace } from '../../workspace/entities/workspace.entity'; import { SpaceUser } from './space-user.entity'; +import { Page } from '../../page/entities/page.entity'; @Entity('spaces') export class Space { @@ -39,13 +40,18 @@ export class Space { @Column() workspaceId: string; - @ManyToOne(() => Workspace, { onDelete: 'CASCADE' }) + @ManyToOne(() => Workspace, (workspace) => workspace.spaces, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'workspaceId' }) workspace: Workspace; @OneToMany(() => SpaceUser, (workspaceUser) => workspaceUser.space) spaceUsers: SpaceUser[]; + @OneToMany(() => Page, (page) => page.space) + pages: Page[]; + @CreateDateColumn() createdAt: Date; diff --git a/apps/server/src/core/space/space.service.ts b/apps/server/src/core/space/space.service.ts index 4e36902..b3ca5bb 100644 --- a/apps/server/src/core/space/space.service.ts +++ b/apps/server/src/core/space/space.service.ts @@ -5,30 +5,45 @@ import { plainToInstance } from 'class-transformer'; import { SpaceRepository } from './repositories/space.repository'; import { SpaceUserRepository } from './repositories/space-user.repository'; import { SpaceUser } from './entities/space-user.entity'; +import { transactionWrapper } from '../../helpers/db.helper'; +import { DataSource, EntityManager } from 'typeorm'; @Injectable() export class SpaceService { constructor( private spaceRepository: SpaceRepository, private spaceUserRepository: SpaceUserRepository, + private dataSource: DataSource, ) {} - async create(userId: string, workspaceId, createSpaceDto?: CreateSpaceDto) { + async create( + userId: string, + workspaceId, + createSpaceDto?: CreateSpaceDto, + manager?: EntityManager, + ) { let space: Space; - if (createSpaceDto) { - space = plainToInstance(Space, createSpaceDto); - } else { - space = new Space(); - } + await transactionWrapper( + async (manager: EntityManager) => { + if (createSpaceDto) { + space = plainToInstance(Space, createSpaceDto); + } else { + space = new Space(); + } - space.creatorId = userId; - space.workspaceId = workspaceId; + space.creatorId = userId; + space.workspaceId = workspaceId; - space.name = createSpaceDto?.name ?? 'untitled space'; - space.description = createSpaceDto?.description ?? null; + space.name = createSpaceDto?.name ?? 'untitled space'; + space.description = createSpaceDto?.description ?? null; + + space = await manager.save(space); + }, + this.dataSource, + manager, + ); - space = await this.spaceRepository.save(space); return space; } @@ -36,21 +51,32 @@ export class SpaceService { userId: string, spaceId: string, role: string, + manager?: EntityManager, ): Promise { - const existingSpaceUser = await this.spaceUserRepository.findOne({ - where: { userId: userId, spaceId: spaceId }, - }); + let addedUser: SpaceUser; - if (existingSpaceUser) { - throw new BadRequestException('User already added to this space'); - } + await transactionWrapper( + async (manager: EntityManager) => { + const existingSpaceUser = await manager.findOne(SpaceUser, { + where: { userId: userId, spaceId: spaceId }, + }); - const spaceUser = new SpaceUser(); - spaceUser.userId = userId; - spaceUser.spaceId = spaceId; - spaceUser.role = role; + if (existingSpaceUser) { + throw new BadRequestException('User already added to this space'); + } - return this.spaceUserRepository.save(spaceUser); + const spaceUser = new SpaceUser(); + spaceUser.userId = userId; + spaceUser.spaceId = spaceId; + spaceUser.role = role; + + addedUser = await manager.save(spaceUser); + }, + this.dataSource, + manager, + ); + + return addedUser; } async getUserSpacesInWorkspace(userId: string, workspaceId: string) { diff --git a/apps/server/src/core/user/entities/user.entity.ts b/apps/server/src/core/user/entities/user.entity.ts index 1062e20..18a8daa 100644 --- a/apps/server/src/core/user/entities/user.entity.ts +++ b/apps/server/src/core/user/entities/user.entity.ts @@ -85,3 +85,8 @@ export class User { this.password = await bcrypt.hash(this.password, saltRounds); } } + +export type UserRole = { + role: string; +}; +export type UserWithRole = User & UserRole; diff --git a/apps/server/src/core/user/user.service.ts b/apps/server/src/core/user/user.service.ts index 7fe8846..03fabe9 100644 --- a/apps/server/src/core/user/user.service.ts +++ b/apps/server/src/core/user/user.service.ts @@ -11,27 +11,51 @@ import { plainToInstance } from 'class-transformer'; import * as bcrypt from 'bcrypt'; import { WorkspaceService } from '../workspace/services/workspace.service'; import { Workspace } from '../workspace/entities/workspace.entity'; +import { DataSource, EntityManager } from 'typeorm'; +import { transactionWrapper } from '../../helpers/db.helper'; +import { CreateWorkspaceDto } from '../workspace/dto/create-workspace.dto'; @Injectable() export class UserService { constructor( private userRepository: UserRepository, private workspaceService: WorkspaceService, + private dataSource: DataSource, ) {} - async create(createUserDto: CreateUserDto): Promise { + async create( + createUserDto: CreateUserDto, + manager?: EntityManager, + ): Promise { + let user: User; + const existingUser: User = await this.findByEmail(createUserDto.email); if (existingUser) { throw new BadRequestException('A user with this email already exists'); } - let user: User = plainToInstance(User, createUserDto); - user.locale = 'en'; - user.lastLoginAt = new Date(); + await transactionWrapper( + async (manager: EntityManager) => { + user = plainToInstance(User, createUserDto); + user.locale = 'en'; + user.lastLoginAt = new Date(); + user.name = createUserDto.email.split('@')[0]; - user = await this.userRepository.save(user); + user = await manager.save(User, user); - await this.workspaceService.createOrJoinWorkspace(user.id); + const createWorkspaceDto: CreateWorkspaceDto = { + name: 'My Workspace', + }; + + await this.workspaceService.createOrJoinWorkspace( + user.id, + createWorkspaceDto, + manager, + ); + }, + this.dataSource, + manager, + ); return user; } @@ -43,12 +67,14 @@ export class UserService { throw new NotFoundException('User not found'); } - const workspace: Workspace = - await this.workspaceService.getUserCurrentWorkspace(userId); + let workspace; - if (!workspace) { - throw new NotFoundException('Workspace not found'); + try { + workspace = await this.workspaceService.getUserCurrentWorkspace(userId); + } catch (error) { + //console.log(error); } + return { user, workspace }; } diff --git a/apps/server/src/core/workspace/controllers/workspace.controller.ts b/apps/server/src/core/workspace/controllers/workspace.controller.ts index a9e1462..97c1358 100644 --- a/apps/server/src/core/workspace/controllers/workspace.controller.ts +++ b/apps/server/src/core/workspace/controllers/workspace.controller.ts @@ -1,20 +1,14 @@ import { Body, Controller, - DefaultValuePipe, - Delete, - Get, HttpCode, HttpStatus, - ParseIntPipe, Post, - Query, UseGuards, } from '@nestjs/common'; import { WorkspaceService } from '../services/workspace.service'; import { JwtGuard } from '../../auth/guards/jwt.guard'; import { UpdateWorkspaceDto } from '../dto/update-workspace.dto'; -import { CreateWorkspaceDto } from '../dto/create-workspace.dto'; import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto'; @@ -30,6 +24,17 @@ import { PaginationOptions } from '../../../helpers/pagination/pagination-option export class WorkspaceController { constructor(private readonly workspaceService: WorkspaceService) {} + @HttpCode(HttpStatus.OK) + @Post('/') + async getUserWorkspaces( + @Body() + pagination: PaginationOptions, + @AuthUser() user: User, + ) { + return this.workspaceService.getUserWorkspaces(user.id, pagination); + } + + /* @HttpCode(HttpStatus.OK) @Post('create') async createWorkspace( @@ -38,6 +43,7 @@ export class WorkspaceController { ) { return this.workspaceService.create(user.id, createWorkspaceDto); } + */ @HttpCode(HttpStatus.OK) @Post('update') @@ -78,7 +84,7 @@ export class WorkspaceController { } @HttpCode(HttpStatus.OK) - @Delete('members/delete') + @Post('members/remove') async removeWorkspaceMember( @Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto, @CurrentWorkspace() workspace: Workspace, @@ -93,9 +99,11 @@ export class WorkspaceController { @Post('members/role') async updateWorkspaceMemberRole( @Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto, + @AuthUser() authUser: User, @CurrentWorkspace() workspace: Workspace, ) { return this.workspaceService.updateWorkspaceUserRole( + authUser, workspaceUserRoleDto, workspace.id, ); diff --git a/apps/server/src/core/workspace/entities/workspace-user.entity.ts b/apps/server/src/core/workspace/entities/workspace-user.entity.ts index bdcf7e4..353a390 100644 --- a/apps/server/src/core/workspace/entities/workspace-user.entity.ts +++ b/apps/server/src/core/workspace/entities/workspace-user.entity.ts @@ -44,3 +44,9 @@ export class WorkspaceUser { @UpdateDateColumn() updatedAt: Date; } + +export enum WorkspaceUserRole { + OWNER = 'owner', + ADMIN = 'admin', + MEMBER = 'member', +} diff --git a/apps/server/src/core/workspace/entities/workspace.entity.ts b/apps/server/src/core/workspace/entities/workspace.entity.ts index f49b94b..02cb650 100644 --- a/apps/server/src/core/workspace/entities/workspace.entity.ts +++ b/apps/server/src/core/workspace/entities/workspace.entity.ts @@ -7,12 +7,14 @@ import { ManyToOne, OneToMany, JoinColumn, + OneToOne, } 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'; import { Comment } from '../../comment/entities/comment.entity'; +import { Space } from '../../space/entities/space.entity'; @Entity('workspaces') export class Workspace { @@ -50,6 +52,13 @@ export class Workspace { @JoinColumn({ name: 'creatorId' }) creator: User; + @Column({ nullable: true }) + defaultSpaceId: string; + + @OneToOne(() => Space, { onDelete: 'SET NULL' }) + @JoinColumn({ name: 'defaultSpaceId' }) + defaultSpace: Space; + @CreateDateColumn() createdAt: Date; @@ -70,4 +79,7 @@ export class Workspace { @OneToMany(() => Comment, (comment) => comment.workspace) comments: Comment[]; + + @OneToMany(() => Space, (space) => space.workspace) + spaces: []; } diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts index dc11c28..5891b0c 100644 --- a/apps/server/src/core/workspace/services/workspace.service.ts +++ b/apps/server/src/core/workspace/services/workspace.service.ts @@ -6,19 +6,24 @@ import { import { CreateWorkspaceDto } from '../dto/create-workspace.dto'; import { WorkspaceRepository } from '../repositories/workspace.repository'; import { WorkspaceUserRepository } from '../repositories/workspace-user.repository'; -import { WorkspaceUser } from '../entities/workspace-user.entity'; +import { + WorkspaceUser, + WorkspaceUserRole, +} from '../entities/workspace-user.entity'; import { Workspace } from '../entities/workspace.entity'; import { plainToInstance } from 'class-transformer'; import { v4 as uuid } from 'uuid'; -import { generateHostname } from '../workspace.util'; import { UpdateWorkspaceDto } from '../dto/update-workspace.dto'; import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { SpaceService } from '../../space/space.service'; -import { UserWithRole } from '../../user/entities/user.entity'; import { PaginationOptions } from '../../../helpers/pagination/pagination-options'; import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto'; import { PaginatedResult } from '../../../helpers/pagination/paginated-result'; +import { User } from '../../user/entities/user.entity'; +import { DataSource, EntityManager } from 'typeorm'; +import { transactionWrapper } from '../../../helpers/db.helper'; +import { CreateSpaceDto } from '../../space/dto/create-space.dto'; @Injectable() export class WorkspaceService { @@ -26,6 +31,7 @@ export class WorkspaceService { private workspaceRepository: WorkspaceRepository, private workspaceUserRepository: WorkspaceUserRepository, private spaceService: SpaceService, + private dataSource: DataSource, ) {} async findById(workspaceId: string): Promise { @@ -36,65 +42,107 @@ export class WorkspaceService { return this.workspaceRepository.save(workspace); } - async createOrJoinWorkspace(userId) { - // context: - // only create workspace if it is not a signup to an existing workspace - // OS version is limited to one workspace. - // if there is no existing workspace, create a new workspace - // and make first user owner/admin + async createOrJoinWorkspace( + userId, + createWorkspaceDto?: CreateWorkspaceDto, + manager?: EntityManager, + ) { + await transactionWrapper( + async (manager: EntityManager) => { + const workspaceCount = await manager + .createQueryBuilder(Workspace, 'workspace') + .getCount(); - // check if workspace already exists in the db - // if not create default, if yes add the user to existing workspace if signup is open. - const workspaceCount = await this.workspaceRepository.count(); + if (workspaceCount === 0) { + // create first workspace and add user to workspace as owner + const createdWorkspace = await this.create( + userId, + createWorkspaceDto ?? null, + manager, + ); + await this.addUserToWorkspace( + userId, + createdWorkspace.id, + WorkspaceUserRole.OWNER, + manager, + ); - if (workspaceCount === 0) { - // create first workspace - // add user to workspace as admin + // create default space and add user to it too. + const createdSpace = await this.spaceService.create( + userId, + createdWorkspace.id, + { name: 'General' } as CreateSpaceDto, + manager, + ); - const newWorkspace = await this.create(userId); - await this.addUserToWorkspace(userId, newWorkspace.id, 'owner'); + await this.spaceService.addUserToSpace( + userId, + createdSpace.id, + WorkspaceUserRole.OWNER, + manager, + ); - // maybe create default space and add user to it too. - const newSpace = await this.spaceService.create(userId, newWorkspace.id); - await this.spaceService.addUserToSpace(userId, newSpace.id, 'owner'); - } else { - //TODO: accept role as param - // if no role is passed use default new member role found in workspace settings + createdWorkspace.defaultSpaceId = createdSpace.id; + await manager.save(createdWorkspace); + } else { + // limited to single workspace + // fetch the oldest workspace and add user to it + const firstWorkspace = await manager.find(Workspace, { + order: { + createdAt: 'ASC', + }, + take: 1, + }); - // fetch the oldest workspace and add user to it - const firstWorkspace = await this.workspaceRepository.find({ - order: { - createdAt: 'ASC', - }, - take: 1, - }); + // add user to workspace and default space - await this.addUserToWorkspace(userId, firstWorkspace[0].id, 'member'); - // get workspace - // if there is a default space, we should add new users to it. - } + await this.addUserToWorkspace( + userId, + firstWorkspace[0].id, + WorkspaceUserRole.MEMBER, + manager, + ); + + await this.spaceService.addUserToSpace( + userId, + firstWorkspace[0].defaultSpaceId, + WorkspaceUserRole.MEMBER, + manager, + ); + } + }, + this.dataSource, + manager, + ); } async create( userId: string, createWorkspaceDto?: CreateWorkspaceDto, + manager?: EntityManager, ): Promise { let workspace: Workspace; - if (createWorkspaceDto) { - workspace = plainToInstance(Workspace, createWorkspaceDto); - } else { - workspace = new Workspace(); - } + await transactionWrapper( + async (manager) => { + if (createWorkspaceDto) { + workspace = plainToInstance(Workspace, createWorkspaceDto); + } else { + workspace = new Workspace(); + } - workspace.inviteCode = uuid(); - workspace.creatorId = userId; + workspace.inviteCode = uuid(); + workspace.creatorId = userId; - if (workspace.name && !workspace.hostname?.trim()) { - workspace.hostname = generateHostname(createWorkspaceDto.name); - } + //if (workspace.name && !workspace.hostname?.trim()) { + // workspace.hostname = generateHostname(createWorkspaceDto.name); + // } - workspace = await this.workspaceRepository.save(workspace); + workspace = await manager.save(workspace); + }, + this.dataSource, + manager, + ); return workspace; } @@ -136,41 +184,73 @@ export class WorkspaceService { userId: string, workspaceId: string, role: string, + manager?: EntityManager, ): Promise { - const existingWorkspaceUser = await this.workspaceUserRepository.findOne({ - where: { userId: userId, workspaceId: workspaceId }, - }); + let addedUser; - if (existingWorkspaceUser) { - throw new BadRequestException('User already added to this workspace'); - } + await transactionWrapper( + async (manager) => { + const existingWorkspaceUser = await manager.findOne(WorkspaceUser, { + where: { userId: userId, workspaceId: workspaceId }, + }); - const workspaceUser = new WorkspaceUser(); - workspaceUser.userId = userId; - workspaceUser.workspaceId = workspaceId; - workspaceUser.role = role; + const userExists = await manager.exists(User, { + where: { id: userId }, + }); + if (!userExists) { + throw new NotFoundException('User not found'); + } - return this.workspaceUserRepository.save(workspaceUser); + if (existingWorkspaceUser) { + throw new BadRequestException( + 'User is already a member of this workspace', + ); + } + + const workspaceUser = new WorkspaceUser(); + workspaceUser.userId = userId; + workspaceUser.workspaceId = workspaceId; + workspaceUser.role = role; + + addedUser = await manager.save(workspaceUser); + }, + this.dataSource, + manager, + ); + + return addedUser; } async updateWorkspaceUserRole( + authUser: User, workspaceUserRoleDto: UpdateWorkspaceUserRoleDto, workspaceId: string, ) { - const workspaceUser = await this.workspaceUserRepository.findOne({ - where: { userId: workspaceUserRoleDto.userId, workspaceId: workspaceId }, - }); - - if (!workspaceUser) { - throw new BadRequestException('user is not a member of this workspace'); - } + const workspaceUser = await this.getWorkspaceUser( + workspaceUserRoleDto.userId, + workspaceId, + ); if (workspaceUser.role === workspaceUserRoleDto.role) { return workspaceUser; } + const workspaceOwnerCount = await this.workspaceUserRepository.count({ + where: { + role: WorkspaceUserRole.OWNER, + }, + }); + + if ( + workspaceUser.role === WorkspaceUserRole.OWNER && + workspaceOwnerCount === 1 + ) { + throw new BadRequestException( + 'There must be at least one workspace owner', + ); + } + workspaceUser.role = workspaceUserRoleDto.role; - // TODO: if there is only one workspace owner, prevent the role change return this.workspaceUserRepository.save(workspaceUser); } @@ -179,7 +259,24 @@ export class WorkspaceService { userId: string, workspaceId: string, ): Promise { - await this.validateWorkspaceMember(userId, workspaceId); + await this.getWorkspaceUser(userId, workspaceId); + + const workspaceUser = await this.getWorkspaceUser(userId, workspaceId); + + const workspaceOwnerCount = await this.workspaceUserRepository.count({ + where: { + role: WorkspaceUserRole.OWNER, + }, + }); + + if ( + workspaceUser.role === WorkspaceUserRole.OWNER && + workspaceOwnerCount === 1 + ) { + throw new BadRequestException( + 'There must be at least one workspace owner', + ); + } await this.workspaceUserRepository.delete({ userId, @@ -188,11 +285,12 @@ export class WorkspaceService { } async getUserCurrentWorkspace(userId: string): Promise { - // TODO: use workspaceId and fetch workspace based on the id - // we currently assume the user belongs to one workspace const userWorkspace = await this.workspaceUserRepository.findOne({ where: { userId: userId }, relations: ['workspace'], + order: { + createdAt: 'ASC', + }, }); if (!userWorkspace) { @@ -202,15 +300,25 @@ export class WorkspaceService { return userWorkspace.workspace; } - async getUserWorkspaces(userId: string): Promise { - const workspaces = await this.workspaceUserRepository.find({ - where: { userId: userId }, - relations: ['workspace'], - }); + async getUserWorkspaces( + userId: string, + paginationOptions: PaginationOptions, + ): Promise> { + const [workspaces, count] = await this.workspaceUserRepository.findAndCount( + { + where: { userId: userId }, + relations: ['workspace'], + take: paginationOptions.limit, + skip: paginationOptions.skip, + }, + ); - return workspaces.map( + const userWorkspaces = workspaces.map( (userWorkspace: WorkspaceUser) => userWorkspace.workspace, ); + + const paginationMeta = new PaginationMetaDto({ count, paginationOptions }); + return new PaginatedResult(userWorkspaces, paginationMeta); } async getWorkspaceUsers( @@ -241,16 +349,26 @@ export class WorkspaceService { return new PaginatedResult(users, paginationMeta); } - async validateWorkspaceMember( + async getUserRoleInWorkspace( userId: string, workspaceId: string, - ): Promise { + ): Promise { + const workspaceUser = await this.getWorkspaceUser(userId, workspaceId); + return workspaceUser.role ? workspaceUser.role : null; + } + + async getWorkspaceUser( + userId: string, + workspaceId: string, + ): Promise { const workspaceUser = await this.workspaceUserRepository.findOne({ where: { userId, workspaceId }, }); if (!workspaceUser) { - throw new BadRequestException('User is not a member of this workspace'); + throw new BadRequestException('Workspace member not found'); } + + return workspaceUser; } } diff --git a/apps/server/src/database/migrations/1709215284760-defaultSpace.ts b/apps/server/src/database/migrations/1709215284760-defaultSpace.ts new file mode 100644 index 0000000..c201123 --- /dev/null +++ b/apps/server/src/database/migrations/1709215284760-defaultSpace.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class DefaultSpace1709215284760 implements MigrationInterface { + name = 'DefaultSpace1709215284760' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "workspaces" ADD "defaultSpaceId" uuid`); + await queryRunner.query(`ALTER TABLE "workspaces" ADD CONSTRAINT "UQ_e91d3ec686ece9654aa9f635981" UNIQUE ("defaultSpaceId")`); + await queryRunner.query(`ALTER TABLE "workspaces" ADD CONSTRAINT "FK_e91d3ec686ece9654aa9f635981" FOREIGN KEY ("defaultSpaceId") REFERENCES "spaces"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "workspaces" DROP CONSTRAINT "FK_e91d3ec686ece9654aa9f635981"`); + await queryRunner.query(`ALTER TABLE "workspaces" DROP CONSTRAINT "UQ_e91d3ec686ece9654aa9f635981"`); + await queryRunner.query(`ALTER TABLE "workspaces" DROP COLUMN "defaultSpaceId"`); + } + +} diff --git a/apps/server/src/decorators/current-workspace.decorator.ts b/apps/server/src/decorators/current-workspace.decorator.ts index 4cad4fa..05bedf4 100644 --- a/apps/server/src/decorators/current-workspace.decorator.ts +++ b/apps/server/src/decorators/current-workspace.decorator.ts @@ -9,7 +9,7 @@ export const CurrentWorkspace = createParamDecorator( const request = ctx.switchToHttp().getRequest(); if (!request['user'] || !request['user'].workspace) { - throw new UnauthorizedException(); + throw new UnauthorizedException('Workspace not found'); } return request['user'] ? request['user'].workspace : undefined; diff --git a/apps/server/src/helpers/db.helper.ts b/apps/server/src/helpers/db.helper.ts new file mode 100644 index 0000000..0a269fa --- /dev/null +++ b/apps/server/src/helpers/db.helper.ts @@ -0,0 +1,17 @@ +import { DataSource, EntityManager } from 'typeorm'; + +export async function transactionWrapper( + operation: (...args) => any, + datasource: DataSource, + entityManager: EntityManager, +) { + if (entityManager) { + return await operation(entityManager); + } else { + return await datasource.manager.transaction( + async (manager: EntityManager) => { + return await operation(manager); + }, + ); + } +} diff --git a/apps/server/src/helpers/pagination/pagination-options.ts b/apps/server/src/helpers/pagination/pagination-options.ts index 15d5c5e..f0de192 100644 --- a/apps/server/src/helpers/pagination/pagination-options.ts +++ b/apps/server/src/helpers/pagination/pagination-options.ts @@ -11,7 +11,7 @@ export class PaginationOptions { @IsPositive() @Min(1) @Max(100) - limit = 25; + limit = 20; @IsOptional() @IsNumber() @@ -21,3 +21,8 @@ export class PaginationOptions { return (this.page - 1) * this.limit; } } + +export enum Order { + ASC = 'ASC', + DESC = 'DESC', +}