mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 08:11:13 +10:00
Add new user and workspace endpoints
* add account update endpoint to user module * add membership management endpoints to workspace module
This commit is contained in:
@ -3,6 +3,8 @@ import {
|
|||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
JoinTable,
|
||||||
|
ManyToMany,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
@ -57,10 +59,10 @@ export class User {
|
|||||||
workspaces: Workspace[];
|
workspaces: Workspace[];
|
||||||
|
|
||||||
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user)
|
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user)
|
||||||
workspaceUser: WorkspaceUser[];
|
workspaceUsers: WorkspaceUser[];
|
||||||
|
|
||||||
@OneToMany(() => Page, (page) => page.creator)
|
@OneToMany(() => Page, (page) => page.creator)
|
||||||
createdPages;
|
createdPages: Page[];
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
delete this.password;
|
delete this.password;
|
||||||
|
|||||||
@ -6,12 +6,15 @@ import {
|
|||||||
HttpStatus,
|
HttpStatus,
|
||||||
Req,
|
Req,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { JwtGuard } from '../auth/guards/JwtGuard';
|
import { JwtGuard } from '../auth/guards/JwtGuard';
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
import { Workspace } from '../workspace/entities/workspace.entity';
|
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
@ -41,4 +44,15 @@ export class UserController {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('update')
|
||||||
|
async updateUser(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() updateUserDto: UpdateUserDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
|
||||||
|
return this.userService.update(jwtPayload.sub, updateUserDto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { CreateUserDto } from './dto/create-user.dto';
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
@ -6,8 +10,7 @@ import { UserRepository } from './repositories/user.repository';
|
|||||||
import { plainToInstance } from 'class-transformer';
|
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 { CreateWorkspaceDto } from '../workspace/dto/create-workspace.dto';
|
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||||
import { Workspace } from "../workspace/entities/workspace.entity";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@ -49,8 +52,24 @@ export class UserService {
|
|||||||
return this.userRepository.findByEmail(email);
|
return this.userRepository.findByEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: number, updateUserDto: UpdateUserDto) {
|
async update(userId: string, updateUserDto: UpdateUserDto) {
|
||||||
return `This action updates a #${id} user`;
|
const user = await this.userRepository.findById(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateUserDto.name) {
|
||||||
|
user.name = updateUserDto.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateUserDto.email && user.email != updateUserDto.email) {
|
||||||
|
if (await this.userRepository.findByEmail(updateUserDto.email)) {
|
||||||
|
throw new BadRequestException('A user with this email already exists');
|
||||||
|
}
|
||||||
|
user.email = updateUserDto.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async compareHash(
|
async compareHash(
|
||||||
|
|||||||
@ -1,7 +1,121 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { WorkspaceService } from '../services/workspace.service';
|
import { WorkspaceService } from '../services/workspace.service';
|
||||||
|
import { FastifyRequest } from 'fastify';
|
||||||
|
import { JwtGuard } from '../../auth/guards/JwtGuard';
|
||||||
|
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';
|
||||||
|
import { AddWorkspaceUserDto } from '../dto/add-workspace-user.dto';
|
||||||
|
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
@Controller('workspace')
|
@Controller('workspace')
|
||||||
export class WorkspaceController {
|
export class WorkspaceController {
|
||||||
constructor(private readonly workspaceService: WorkspaceService) {}
|
constructor(private readonly workspaceService: WorkspaceService) {}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('create')
|
||||||
|
async createWorkspace(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() createWorkspaceDto: CreateWorkspaceDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const userId = jwtPayload.sub;
|
||||||
|
return this.workspaceService.create(userId, createWorkspaceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('update')
|
||||||
|
async updateWorkspace(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() updateWorkspaceDto: UpdateWorkspaceDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const workspaceId = (
|
||||||
|
await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub)
|
||||||
|
).id;
|
||||||
|
|
||||||
|
return this.workspaceService.update(workspaceId, updateWorkspaceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('delete')
|
||||||
|
async deleteWorkspace(@Body() deleteWorkspaceDto: DeleteWorkspaceDto,
|
||||||
|
) {
|
||||||
|
return this.workspaceService.delete(deleteWorkspaceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Get('members')
|
||||||
|
async getWorkspaceMembers(@Req() req: FastifyRequest) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const workspaceId = (
|
||||||
|
await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub)
|
||||||
|
).id;
|
||||||
|
|
||||||
|
return this.workspaceService.getWorkspaceUsers(workspaceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('member')
|
||||||
|
async addWorkspaceMember(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() addWorkspaceUserDto: AddWorkspaceUserDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const workspaceId = (
|
||||||
|
await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub)
|
||||||
|
).id;
|
||||||
|
|
||||||
|
return this.workspaceService.addUserToWorkspace(
|
||||||
|
addWorkspaceUserDto.userId,
|
||||||
|
workspaceId,
|
||||||
|
addWorkspaceUserDto.role,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Delete('member')
|
||||||
|
async removeWorkspaceMember(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const workspaceId = (
|
||||||
|
await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub)
|
||||||
|
).id;
|
||||||
|
|
||||||
|
return this.workspaceService.removeUserFromWorkspace(
|
||||||
|
removeWorkspaceUserDto.userId,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('member/role')
|
||||||
|
async updateWorkspaceMemberRole(
|
||||||
|
@Req() req: FastifyRequest,
|
||||||
|
@Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto,
|
||||||
|
) {
|
||||||
|
const jwtPayload = req['user'];
|
||||||
|
const workspaceId = (
|
||||||
|
await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub)
|
||||||
|
).id;
|
||||||
|
|
||||||
|
return this.workspaceService.updateWorkspaceUserRole(
|
||||||
|
workspaceUserRoleDto,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
server/src/core/workspace/dto/add-workspace-user.dto.ts
Normal file
11
server/src/core/workspace/dto/add-workspace-user.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class AddWorkspaceUserDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
6
server/src/core/workspace/dto/delete-workspace.dto.ts
Normal file
6
server/src/core/workspace/dto/delete-workspace.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class DeleteWorkspaceDto {
|
||||||
|
@IsString()
|
||||||
|
workspaceId: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { IsNotEmpty, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class RemoveWorkspaceUserDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateWorkspaceUserRoleDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ export class WorkspaceUser {
|
|||||||
@Column()
|
@Column()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ManyToOne(() => User, (user) => user.workspaceUser, {
|
@ManyToOne(() => User, (user) => user.workspaceUsers, {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
})
|
})
|
||||||
@JoinColumn({ name: 'userId' })
|
@JoinColumn({ name: 'userId' })
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
ManyToOne,
|
ManyToOne,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
ManyToMany,
|
||||||
} 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';
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
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';
|
||||||
@ -7,6 +11,9 @@ 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 { 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';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
@ -40,6 +47,33 @@ export class WorkspaceService {
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
workspaceId: string,
|
||||||
|
updateWorkspaceDto: UpdateWorkspaceDto,
|
||||||
|
): Promise<Workspace> {
|
||||||
|
const workspace = await this.workspaceRepository.findById(workspaceId);
|
||||||
|
if (!workspace) {
|
||||||
|
throw new NotFoundException('Workspace not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateWorkspaceDto.name) {
|
||||||
|
workspace.name = updateWorkspaceDto.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.workspaceRepository.save(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(deleteWorkspaceDto: DeleteWorkspaceDto) {
|
||||||
|
const workspace = await this.workspaceRepository.findById(
|
||||||
|
deleteWorkspaceDto.workspaceId,
|
||||||
|
);
|
||||||
|
if (!workspace) {
|
||||||
|
throw new NotFoundException('Workspace not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
async addUserToWorkspace(
|
async addUserToWorkspace(
|
||||||
userId: string,
|
userId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
@ -53,14 +87,51 @@ export class WorkspaceService {
|
|||||||
return this.workspaceUserRepository.save(workspaceUser);
|
return this.workspaceUserRepository.save(workspaceUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateWorkspaceUserRole(
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceUser.role === workspaceUserRoleDto.role) {
|
||||||
|
return workspaceUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceUser.role = workspaceUserRoleDto.role;
|
||||||
|
// if there is only one workspace owner, prevent the role change
|
||||||
|
|
||||||
|
return this.workspaceUserRepository.save(workspaceUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeUserFromWorkspace(
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const workspaceUser = await this.workspaceUserRepository.findOne({
|
||||||
|
where: { userId, workspaceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspaceUser) {
|
||||||
|
throw new BadRequestException('User is not a member of this workspace');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspaceUserRepository.delete({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async findById(workspaceId: string): Promise<Workspace> {
|
async findById(workspaceId: string): Promise<Workspace> {
|
||||||
return await this.workspaceRepository.findById(workspaceId);
|
return await this.workspaceRepository.findById(workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserCurrentWorkspace(
|
async getUserCurrentWorkspace(userId: string): Promise<Workspace> {
|
||||||
userId: string,
|
|
||||||
workspaceId?: string,
|
|
||||||
): Promise<Workspace> {
|
|
||||||
// TODO: use workspaceId and fetch workspace based on the id
|
// TODO: use workspaceId and fetch workspace based on the id
|
||||||
// we currently assume the user belongs to one workspace
|
// we currently assume the user belongs to one workspace
|
||||||
const userWorkspace = await this.workspaceUserRepository.findOne({
|
const userWorkspace = await this.workspaceUserRepository.findOne({
|
||||||
@ -71,7 +142,7 @@ export class WorkspaceService {
|
|||||||
return userWorkspace.workspace;
|
return userWorkspace.workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
async userWorkspaces(userId: string): Promise<Workspace[]> {
|
async getUserWorkspaces(userId: string): Promise<Workspace[]> {
|
||||||
const workspaces = await this.workspaceUserRepository.find({
|
const workspaces = await this.workspaceUserRepository.find({
|
||||||
where: { userId: userId },
|
where: { userId: userId },
|
||||||
relations: ['workspace'],
|
relations: ['workspace'],
|
||||||
@ -81,4 +152,25 @@ export class WorkspaceService {
|
|||||||
(userWorkspace: WorkspaceUser) => userWorkspace.workspace,
|
(userWorkspace: WorkspaceUser) => userWorkspace.workspace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getWorkspaceUsers(workspaceId: string) {
|
||||||
|
const workspace = await this.workspaceRepository.findOne({
|
||||||
|
where: { id: workspaceId },
|
||||||
|
relations: ['workspaceUsers', 'workspaceUsers.user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspace) {
|
||||||
|
throw new BadRequestException('Invalid workspace');
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = workspace.workspaceUsers.map((workspaceUser) => {
|
||||||
|
workspaceUser.user.password = '';
|
||||||
|
return {
|
||||||
|
...workspaceUser.user,
|
||||||
|
workspaceRole: workspaceUser.role,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { users };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,12 @@ import { Workspace } from './entities/workspace.entity';
|
|||||||
import { WorkspaceUser } from './entities/workspace-user.entity';
|
import { WorkspaceUser } from './entities/workspace-user.entity';
|
||||||
import { WorkspaceInvitation } from './entities/workspace-invitation.entity';
|
import { WorkspaceInvitation } from './entities/workspace-invitation.entity';
|
||||||
import { WorkspaceUserRepository } from './repositories/workspace-user.repository';
|
import { WorkspaceUserRepository } from './repositories/workspace-user.repository';
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Workspace, WorkspaceUser, WorkspaceInvitation]),
|
TypeOrmModule.forFeature([Workspace, WorkspaceUser, WorkspaceInvitation]),
|
||||||
|
AuthModule,
|
||||||
],
|
],
|
||||||
controllers: [WorkspaceController],
|
controllers: [WorkspaceController],
|
||||||
providers: [WorkspaceService, WorkspaceRepository, WorkspaceUserRepository],
|
providers: [WorkspaceService, WorkspaceRepository, WorkspaceUserRepository],
|
||||||
|
|||||||
Reference in New Issue
Block a user