mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 03:31:15 +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,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
@ -57,10 +59,10 @@ export class User {
|
||||
workspaces: Workspace[];
|
||||
|
||||
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user)
|
||||
workspaceUser: WorkspaceUser[];
|
||||
workspaceUsers: WorkspaceUser[];
|
||||
|
||||
@OneToMany(() => Page, (page) => page.creator)
|
||||
createdPages;
|
||||
createdPages: Page[];
|
||||
|
||||
toJSON() {
|
||||
delete this.password;
|
||||
|
||||
@ -6,12 +6,15 @@ import {
|
||||
HttpStatus,
|
||||
Req,
|
||||
UnauthorizedException,
|
||||
Post,
|
||||
Body,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { JwtGuard } from '../auth/guards/JwtGuard';
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { User } from './entities/user.entity';
|
||||
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
|
||||
@UseGuards(JwtGuard)
|
||||
@Controller('user')
|
||||
@ -41,4 +44,15 @@ export class UserController {
|
||||
|
||||
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 { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { User } from './entities/user.entity';
|
||||
@ -6,8 +10,7 @@ import { UserRepository } from './repositories/user.repository';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
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()
|
||||
export class UserService {
|
||||
@ -49,8 +52,24 @@ export class UserService {
|
||||
return this.userRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
async update(id: number, updateUserDto: UpdateUserDto) {
|
||||
return `This action updates a #${id} user`;
|
||||
async update(userId: string, updateUserDto: UpdateUserDto) {
|
||||
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(
|
||||
|
||||
@ -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 { 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')
|
||||
export class WorkspaceController {
|
||||
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()
|
||||
userId: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.workspaceUser, {
|
||||
@ManyToOne(() => User, (user) => user.workspaceUsers, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'userId' })
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
ManyToMany,
|
||||
} from 'typeorm';
|
||||
import { User } from '../../user/entities/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 { WorkspaceRepository } from '../repositories/workspace.repository';
|
||||
import { WorkspaceUserRepository } from '../repositories/workspace-user.repository';
|
||||
@ -7,6 +11,9 @@ 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';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
@ -40,6 +47,33 @@ export class WorkspaceService {
|
||||
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(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
@ -53,14 +87,51 @@ export class WorkspaceService {
|
||||
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> {
|
||||
return await this.workspaceRepository.findById(workspaceId);
|
||||
}
|
||||
|
||||
async getUserCurrentWorkspace(
|
||||
userId: string,
|
||||
workspaceId?: 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({
|
||||
@ -71,7 +142,7 @@ export class WorkspaceService {
|
||||
return userWorkspace.workspace;
|
||||
}
|
||||
|
||||
async userWorkspaces(userId: string): Promise<Workspace[]> {
|
||||
async getUserWorkspaces(userId: string): Promise<Workspace[]> {
|
||||
const workspaces = await this.workspaceUserRepository.find({
|
||||
where: { userId: userId },
|
||||
relations: ['workspace'],
|
||||
@ -81,4 +152,25 @@ export class WorkspaceService {
|
||||
(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 { WorkspaceInvitation } from './entities/workspace-invitation.entity';
|
||||
import { WorkspaceUserRepository } from './repositories/workspace-user.repository';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace, WorkspaceUser, WorkspaceInvitation]),
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [WorkspaceController],
|
||||
providers: [WorkspaceService, WorkspaceRepository, WorkspaceUserRepository],
|
||||
|
||||
Reference in New Issue
Block a user