* fixes and cleanups

* db transactions
* add default space to workspace
This commit is contained in:
Philipinho
2024-03-01 01:07:30 +00:00
parent 41ecf2d082
commit 3d90fc01ad
13 changed files with 374 additions and 121 deletions

View File

@ -35,13 +35,19 @@ export class JwtGuard implements CanActivate {
throw new UnauthorizedException('Invalid jwt token'); throw new UnauthorizedException('Invalid jwt token');
} }
try { let payload;
const payload = await this.tokenService.verifyJwt(token);
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 //fetch user and current workspace data from db
request['user'] = await this.userService.getUserInstance(payload.sub); request['user'] = await this.userService.getUserInstance(payload.sub);
} catch (error) { } catch (error) {
throw new UnauthorizedException('Could not verify jwt token'); throw new UnauthorizedException(error.message);
} }
return true; return true;

View File

@ -11,6 +11,7 @@ import {
import { User } from '../../user/entities/user.entity'; import { User } from '../../user/entities/user.entity';
import { Workspace } from '../../workspace/entities/workspace.entity'; import { Workspace } from '../../workspace/entities/workspace.entity';
import { SpaceUser } from './space-user.entity'; import { SpaceUser } from './space-user.entity';
import { Page } from '../../page/entities/page.entity';
@Entity('spaces') @Entity('spaces')
export class Space { export class Space {
@ -39,13 +40,18 @@ export class Space {
@Column() @Column()
workspaceId: string; workspaceId: string;
@ManyToOne(() => Workspace, { onDelete: 'CASCADE' }) @ManyToOne(() => Workspace, (workspace) => workspace.spaces, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'workspaceId' }) @JoinColumn({ name: 'workspaceId' })
workspace: Workspace; workspace: Workspace;
@OneToMany(() => SpaceUser, (workspaceUser) => workspaceUser.space) @OneToMany(() => SpaceUser, (workspaceUser) => workspaceUser.space)
spaceUsers: SpaceUser[]; spaceUsers: SpaceUser[];
@OneToMany(() => Page, (page) => page.space)
pages: Page[];
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;

View File

@ -5,30 +5,45 @@ import { plainToInstance } from 'class-transformer';
import { SpaceRepository } from './repositories/space.repository'; import { SpaceRepository } from './repositories/space.repository';
import { SpaceUserRepository } from './repositories/space-user.repository'; import { SpaceUserRepository } from './repositories/space-user.repository';
import { SpaceUser } from './entities/space-user.entity'; import { SpaceUser } from './entities/space-user.entity';
import { transactionWrapper } from '../../helpers/db.helper';
import { DataSource, EntityManager } from 'typeorm';
@Injectable() @Injectable()
export class SpaceService { export class SpaceService {
constructor( constructor(
private spaceRepository: SpaceRepository, private spaceRepository: SpaceRepository,
private spaceUserRepository: SpaceUserRepository, 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; let space: Space;
if (createSpaceDto) { await transactionWrapper(
space = plainToInstance(Space, createSpaceDto); async (manager: EntityManager) => {
} else { if (createSpaceDto) {
space = new Space(); space = plainToInstance(Space, createSpaceDto);
} } else {
space = new Space();
}
space.creatorId = userId; space.creatorId = userId;
space.workspaceId = workspaceId; space.workspaceId = workspaceId;
space.name = createSpaceDto?.name ?? 'untitled space'; space.name = createSpaceDto?.name ?? 'untitled space';
space.description = createSpaceDto?.description ?? null; space.description = createSpaceDto?.description ?? null;
space = await manager.save(space);
},
this.dataSource,
manager,
);
space = await this.spaceRepository.save(space);
return space; return space;
} }
@ -36,21 +51,32 @@ export class SpaceService {
userId: string, userId: string,
spaceId: string, spaceId: string,
role: string, role: string,
manager?: EntityManager,
): Promise<SpaceUser> { ): Promise<SpaceUser> {
const existingSpaceUser = await this.spaceUserRepository.findOne({ let addedUser: SpaceUser;
where: { userId: userId, spaceId: spaceId },
});
if (existingSpaceUser) { await transactionWrapper(
throw new BadRequestException('User already added to this space'); async (manager: EntityManager) => {
} const existingSpaceUser = await manager.findOne(SpaceUser, {
where: { userId: userId, spaceId: spaceId },
});
const spaceUser = new SpaceUser(); if (existingSpaceUser) {
spaceUser.userId = userId; throw new BadRequestException('User already added to this space');
spaceUser.spaceId = spaceId; }
spaceUser.role = role;
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) { async getUserSpacesInWorkspace(userId: string, workspaceId: string) {

View File

@ -85,3 +85,8 @@ export class User {
this.password = await bcrypt.hash(this.password, saltRounds); this.password = await bcrypt.hash(this.password, saltRounds);
} }
} }
export type UserRole = {
role: string;
};
export type UserWithRole = User & UserRole;

View File

@ -11,27 +11,51 @@ import { plainToInstance } from 'class-transformer';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { WorkspaceService } from '../workspace/services/workspace.service'; import { WorkspaceService } from '../workspace/services/workspace.service';
import { Workspace } from '../workspace/entities/workspace.entity'; 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() @Injectable()
export class UserService { export class UserService {
constructor( constructor(
private userRepository: UserRepository, private userRepository: UserRepository,
private workspaceService: WorkspaceService, private workspaceService: WorkspaceService,
private dataSource: DataSource,
) {} ) {}
async create(createUserDto: CreateUserDto): Promise<User> { async create(
createUserDto: CreateUserDto,
manager?: EntityManager,
): Promise<User> {
let user: User;
const existingUser: User = await this.findByEmail(createUserDto.email); const existingUser: User = await this.findByEmail(createUserDto.email);
if (existingUser) { if (existingUser) {
throw new BadRequestException('A user with this email already exists'); throw new BadRequestException('A user with this email already exists');
} }
let user: User = plainToInstance(User, createUserDto); await transactionWrapper(
user.locale = 'en'; async (manager: EntityManager) => {
user.lastLoginAt = new Date(); 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; return user;
} }
@ -43,12 +67,14 @@ export class UserService {
throw new NotFoundException('User not found'); throw new NotFoundException('User not found');
} }
const workspace: Workspace = let workspace;
await this.workspaceService.getUserCurrentWorkspace(userId);
if (!workspace) { try {
throw new NotFoundException('Workspace not found'); workspace = await this.workspaceService.getUserCurrentWorkspace(userId);
} catch (error) {
//console.log(error);
} }
return { user, workspace }; return { user, workspace };
} }

View File

@ -1,20 +1,14 @@
import { import {
Body, Body,
Controller, Controller,
DefaultValuePipe,
Delete,
Get,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
ParseIntPipe,
Post, Post,
Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { WorkspaceService } from '../services/workspace.service'; import { WorkspaceService } from '../services/workspace.service';
import { JwtGuard } from '../../auth/guards/jwt.guard'; import { JwtGuard } from '../../auth/guards/jwt.guard';
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto'; import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto'; import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto'; import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto';
@ -30,6 +24,17 @@ import { PaginationOptions } from '../../../helpers/pagination/pagination-option
export class WorkspaceController { export class WorkspaceController {
constructor(private readonly workspaceService: WorkspaceService) {} 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) @HttpCode(HttpStatus.OK)
@Post('create') @Post('create')
async createWorkspace( async createWorkspace(
@ -38,6 +43,7 @@ export class WorkspaceController {
) { ) {
return this.workspaceService.create(user.id, createWorkspaceDto); return this.workspaceService.create(user.id, createWorkspaceDto);
} }
*/
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post('update') @Post('update')
@ -78,7 +84,7 @@ export class WorkspaceController {
} }
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Delete('members/delete') @Post('members/remove')
async removeWorkspaceMember( async removeWorkspaceMember(
@Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto, @Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto,
@CurrentWorkspace() workspace: Workspace, @CurrentWorkspace() workspace: Workspace,
@ -93,9 +99,11 @@ export class WorkspaceController {
@Post('members/role') @Post('members/role')
async updateWorkspaceMemberRole( async updateWorkspaceMemberRole(
@Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto, @Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto,
@AuthUser() authUser: User,
@CurrentWorkspace() workspace: Workspace, @CurrentWorkspace() workspace: Workspace,
) { ) {
return this.workspaceService.updateWorkspaceUserRole( return this.workspaceService.updateWorkspaceUserRole(
authUser,
workspaceUserRoleDto, workspaceUserRoleDto,
workspace.id, workspace.id,
); );

View File

@ -44,3 +44,9 @@ export class WorkspaceUser {
@UpdateDateColumn() @UpdateDateColumn()
updatedAt: Date; updatedAt: Date;
} }
export enum WorkspaceUserRole {
OWNER = 'owner',
ADMIN = 'admin',
MEMBER = 'member',
}

View File

@ -7,12 +7,14 @@ import {
ManyToOne, ManyToOne,
OneToMany, OneToMany,
JoinColumn, JoinColumn,
OneToOne,
} from 'typeorm'; } from 'typeorm';
import { User } from '../../user/entities/user.entity'; import { User } from '../../user/entities/user.entity';
import { WorkspaceUser } from './workspace-user.entity'; import { WorkspaceUser } from './workspace-user.entity';
import { Page } from '../../page/entities/page.entity'; import { Page } from '../../page/entities/page.entity';
import { WorkspaceInvitation } from './workspace-invitation.entity'; import { WorkspaceInvitation } from './workspace-invitation.entity';
import { Comment } from '../../comment/entities/comment.entity'; import { Comment } from '../../comment/entities/comment.entity';
import { Space } from '../../space/entities/space.entity';
@Entity('workspaces') @Entity('workspaces')
export class Workspace { export class Workspace {
@ -50,6 +52,13 @@ export class Workspace {
@JoinColumn({ name: 'creatorId' }) @JoinColumn({ name: 'creatorId' })
creator: User; creator: User;
@Column({ nullable: true })
defaultSpaceId: string;
@OneToOne(() => Space, { onDelete: 'SET NULL' })
@JoinColumn({ name: 'defaultSpaceId' })
defaultSpace: Space;
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;
@ -70,4 +79,7 @@ export class Workspace {
@OneToMany(() => Comment, (comment) => comment.workspace) @OneToMany(() => Comment, (comment) => comment.workspace)
comments: Comment[]; comments: Comment[];
@OneToMany(() => Space, (space) => space.workspace)
spaces: [];
} }

View File

@ -6,19 +6,24 @@ import {
import { CreateWorkspaceDto } from '../dto/create-workspace.dto'; import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
import { WorkspaceRepository } from '../repositories/workspace.repository'; import { WorkspaceRepository } from '../repositories/workspace.repository';
import { WorkspaceUserRepository } from '../repositories/workspace-user.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 { Workspace } from '../entities/workspace.entity';
import { plainToInstance } from 'class-transformer'; import { plainToInstance } from 'class-transformer';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { generateHostname } from '../workspace.util';
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto'; import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto'; import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
import { SpaceService } from '../../space/space.service'; import { SpaceService } from '../../space/space.service';
import { UserWithRole } from '../../user/entities/user.entity';
import { PaginationOptions } from '../../../helpers/pagination/pagination-options'; import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto'; import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
import { PaginatedResult } from '../../../helpers/pagination/paginated-result'; 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() @Injectable()
export class WorkspaceService { export class WorkspaceService {
@ -26,6 +31,7 @@ export class WorkspaceService {
private workspaceRepository: WorkspaceRepository, private workspaceRepository: WorkspaceRepository,
private workspaceUserRepository: WorkspaceUserRepository, private workspaceUserRepository: WorkspaceUserRepository,
private spaceService: SpaceService, private spaceService: SpaceService,
private dataSource: DataSource,
) {} ) {}
async findById(workspaceId: string): Promise<Workspace> { async findById(workspaceId: string): Promise<Workspace> {
@ -36,65 +42,107 @@ export class WorkspaceService {
return this.workspaceRepository.save(workspace); return this.workspaceRepository.save(workspace);
} }
async createOrJoinWorkspace(userId) { async createOrJoinWorkspace(
// context: userId,
// only create workspace if it is not a signup to an existing workspace createWorkspaceDto?: CreateWorkspaceDto,
// OS version is limited to one workspace. manager?: EntityManager,
// if there is no existing workspace, create a new workspace ) {
// and make first user owner/admin await transactionWrapper(
async (manager: EntityManager) => {
const workspaceCount = await manager
.createQueryBuilder(Workspace, 'workspace')
.getCount();
// check if workspace already exists in the db if (workspaceCount === 0) {
// if not create default, if yes add the user to existing workspace if signup is open. // create first workspace and add user to workspace as owner
const workspaceCount = await this.workspaceRepository.count(); const createdWorkspace = await this.create(
userId,
createWorkspaceDto ?? null,
manager,
);
await this.addUserToWorkspace(
userId,
createdWorkspace.id,
WorkspaceUserRole.OWNER,
manager,
);
if (workspaceCount === 0) { // create default space and add user to it too.
// create first workspace const createdSpace = await this.spaceService.create(
// add user to workspace as admin userId,
createdWorkspace.id,
{ name: 'General' } as CreateSpaceDto,
manager,
);
const newWorkspace = await this.create(userId); await this.spaceService.addUserToSpace(
await this.addUserToWorkspace(userId, newWorkspace.id, 'owner'); userId,
createdSpace.id,
WorkspaceUserRole.OWNER,
manager,
);
// maybe create default space and add user to it too. createdWorkspace.defaultSpaceId = createdSpace.id;
const newSpace = await this.spaceService.create(userId, newWorkspace.id); await manager.save(createdWorkspace);
await this.spaceService.addUserToSpace(userId, newSpace.id, 'owner'); } else {
} else { // limited to single workspace
//TODO: accept role as param // fetch the oldest workspace and add user to it
// if no role is passed use default new member role found in workspace settings const firstWorkspace = await manager.find(Workspace, {
order: {
createdAt: 'ASC',
},
take: 1,
});
// fetch the oldest workspace and add user to it // add user to workspace and default space
const firstWorkspace = await this.workspaceRepository.find({
order: {
createdAt: 'ASC',
},
take: 1,
});
await this.addUserToWorkspace(userId, firstWorkspace[0].id, 'member'); await this.addUserToWorkspace(
// get workspace userId,
// if there is a default space, we should add new users to it. firstWorkspace[0].id,
} WorkspaceUserRole.MEMBER,
manager,
);
await this.spaceService.addUserToSpace(
userId,
firstWorkspace[0].defaultSpaceId,
WorkspaceUserRole.MEMBER,
manager,
);
}
},
this.dataSource,
manager,
);
} }
async create( async create(
userId: string, userId: string,
createWorkspaceDto?: CreateWorkspaceDto, createWorkspaceDto?: CreateWorkspaceDto,
manager?: EntityManager,
): Promise<Workspace> { ): Promise<Workspace> {
let workspace: Workspace; let workspace: Workspace;
if (createWorkspaceDto) { await transactionWrapper(
workspace = plainToInstance(Workspace, createWorkspaceDto); async (manager) => {
} else { if (createWorkspaceDto) {
workspace = new Workspace(); workspace = plainToInstance(Workspace, createWorkspaceDto);
} } else {
workspace = new Workspace();
}
workspace.inviteCode = uuid(); workspace.inviteCode = uuid();
workspace.creatorId = userId; workspace.creatorId = userId;
if (workspace.name && !workspace.hostname?.trim()) { //if (workspace.name && !workspace.hostname?.trim()) {
workspace.hostname = generateHostname(createWorkspaceDto.name); // workspace.hostname = generateHostname(createWorkspaceDto.name);
} // }
workspace = await this.workspaceRepository.save(workspace); workspace = await manager.save(workspace);
},
this.dataSource,
manager,
);
return workspace; return workspace;
} }
@ -136,41 +184,73 @@ export class WorkspaceService {
userId: string, userId: string,
workspaceId: string, workspaceId: string,
role: string, role: string,
manager?: EntityManager,
): Promise<WorkspaceUser> { ): Promise<WorkspaceUser> {
const existingWorkspaceUser = await this.workspaceUserRepository.findOne({ let addedUser;
where: { userId: userId, workspaceId: workspaceId },
});
if (existingWorkspaceUser) { await transactionWrapper(
throw new BadRequestException('User already added to this workspace'); async (manager) => {
} const existingWorkspaceUser = await manager.findOne(WorkspaceUser, {
where: { userId: userId, workspaceId: workspaceId },
});
const workspaceUser = new WorkspaceUser(); const userExists = await manager.exists(User, {
workspaceUser.userId = userId; where: { id: userId },
workspaceUser.workspaceId = workspaceId; });
workspaceUser.role = role; 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( async updateWorkspaceUserRole(
authUser: User,
workspaceUserRoleDto: UpdateWorkspaceUserRoleDto, workspaceUserRoleDto: UpdateWorkspaceUserRoleDto,
workspaceId: string, workspaceId: string,
) { ) {
const workspaceUser = await this.workspaceUserRepository.findOne({ const workspaceUser = await this.getWorkspaceUser(
where: { userId: workspaceUserRoleDto.userId, workspaceId: workspaceId }, workspaceUserRoleDto.userId,
}); workspaceId,
);
if (!workspaceUser) {
throw new BadRequestException('user is not a member of this workspace');
}
if (workspaceUser.role === workspaceUserRoleDto.role) { if (workspaceUser.role === workspaceUserRoleDto.role) {
return workspaceUser; 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; workspaceUser.role = workspaceUserRoleDto.role;
// TODO: if there is only one workspace owner, prevent the role change
return this.workspaceUserRepository.save(workspaceUser); return this.workspaceUserRepository.save(workspaceUser);
} }
@ -179,7 +259,24 @@ export class WorkspaceService {
userId: string, userId: string,
workspaceId: string, workspaceId: string,
): Promise<void> { ): Promise<void> {
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({ await this.workspaceUserRepository.delete({
userId, userId,
@ -188,11 +285,12 @@ export class WorkspaceService {
} }
async getUserCurrentWorkspace(userId: string): Promise<Workspace> { async getUserCurrentWorkspace(userId: string): Promise<Workspace> {
// 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({ const userWorkspace = await this.workspaceUserRepository.findOne({
where: { userId: userId }, where: { userId: userId },
relations: ['workspace'], relations: ['workspace'],
order: {
createdAt: 'ASC',
},
}); });
if (!userWorkspace) { if (!userWorkspace) {
@ -202,15 +300,25 @@ export class WorkspaceService {
return userWorkspace.workspace; return userWorkspace.workspace;
} }
async getUserWorkspaces(userId: string): Promise<Workspace[]> { async getUserWorkspaces(
const workspaces = await this.workspaceUserRepository.find({ userId: string,
where: { userId: userId }, paginationOptions: PaginationOptions,
relations: ['workspace'], ): Promise<PaginatedResult<Workspace>> {
}); 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, (userWorkspace: WorkspaceUser) => userWorkspace.workspace,
); );
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
return new PaginatedResult(userWorkspaces, paginationMeta);
} }
async getWorkspaceUsers( async getWorkspaceUsers(
@ -241,16 +349,26 @@ export class WorkspaceService {
return new PaginatedResult(users, paginationMeta); return new PaginatedResult(users, paginationMeta);
} }
async validateWorkspaceMember( async getUserRoleInWorkspace(
userId: string, userId: string,
workspaceId: string, workspaceId: string,
): Promise<void> { ): Promise<string> {
const workspaceUser = await this.getWorkspaceUser(userId, workspaceId);
return workspaceUser.role ? workspaceUser.role : null;
}
async getWorkspaceUser(
userId: string,
workspaceId: string,
): Promise<WorkspaceUser> {
const workspaceUser = await this.workspaceUserRepository.findOne({ const workspaceUser = await this.workspaceUserRepository.findOne({
where: { userId, workspaceId }, where: { userId, workspaceId },
}); });
if (!workspaceUser) { if (!workspaceUser) {
throw new BadRequestException('User is not a member of this workspace'); throw new BadRequestException('Workspace member not found');
} }
return workspaceUser;
} }
} }

View File

@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DefaultSpace1709215284760 implements MigrationInterface {
name = 'DefaultSpace1709215284760'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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"`);
}
}

View File

@ -9,7 +9,7 @@ export const CurrentWorkspace = createParamDecorator(
const request = ctx.switchToHttp().getRequest(); const request = ctx.switchToHttp().getRequest();
if (!request['user'] || !request['user'].workspace) { if (!request['user'] || !request['user'].workspace) {
throw new UnauthorizedException(); throw new UnauthorizedException('Workspace not found');
} }
return request['user'] ? request['user'].workspace : undefined; return request['user'] ? request['user'].workspace : undefined;

View File

@ -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);
},
);
}
}

View File

@ -11,7 +11,7 @@ export class PaginationOptions {
@IsPositive() @IsPositive()
@Min(1) @Min(1)
@Max(100) @Max(100)
limit = 25; limit = 20;
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@ -21,3 +21,8 @@ export class PaginationOptions {
return (this.page - 1) * this.limit; return (this.page - 1) * this.limit;
} }
} }
export enum Order {
ASC = 'ASC',
DESC = 'DESC',
}