mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-19 03:01:10 +10:00
Refactoring
* Refactor workspace membership system * Create setup endpoint * Use Passport.js * Several updates and fixes
This commit is contained in:
@ -1,23 +0,0 @@
|
||||
import {
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsOptional()
|
||||
@MinLength(3)
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@MinLength(8)
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateUserDto } from './create-user.dto';
|
||||
import { CreateUserDto } from '../../auth/dto/create-user.dto';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateUserDto extends PartialType(CreateUserDto) {
|
||||
|
||||
@ -3,19 +3,22 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
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';
|
||||
import { Comment } from '../../comment/entities/comment.entity';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
import { SpaceUser } from '../../space/entities/space-user.entity';
|
||||
import { Group } from '../../group/entities/group.entity';
|
||||
|
||||
@Entity('users')
|
||||
@Unique(['email', 'workspaceId'])
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
@ -23,7 +26,7 @@ export class User {
|
||||
@Column({ length: 255, nullable: true })
|
||||
name: string;
|
||||
|
||||
@Column({ length: 255, unique: true })
|
||||
@Column({ length: 255 })
|
||||
email: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@ -35,6 +38,15 @@ export class User {
|
||||
@Column({ nullable: true })
|
||||
avatarUrl: string;
|
||||
|
||||
@Column({ nullable: true, length: 100 })
|
||||
role: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.users)
|
||||
workspace: Workspace;
|
||||
|
||||
@Column({ length: 100, nullable: true })
|
||||
locale: string;
|
||||
|
||||
@ -56,11 +68,8 @@ export class User {
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
|
||||
@OneToMany(() => Workspace, (workspace) => workspace.creator)
|
||||
workspaces: Workspace[];
|
||||
|
||||
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.user)
|
||||
workspaceUsers: WorkspaceUser[];
|
||||
@OneToMany(() => Group, (group) => group.creator)
|
||||
groups: Group[];
|
||||
|
||||
@OneToMany(() => Page, (page) => page.creator)
|
||||
createdPages: Page[];
|
||||
@ -69,10 +78,10 @@ export class User {
|
||||
comments: Comment[];
|
||||
|
||||
@OneToMany(() => Space, (space) => space.creator)
|
||||
spaces: Space[];
|
||||
createdSpaces: Space[];
|
||||
|
||||
@OneToMany(() => SpaceUser, (spaceUser) => spaceUser.user)
|
||||
spaceUsers: SpaceUser[];
|
||||
spaces: SpaceUser[];
|
||||
|
||||
toJSON() {
|
||||
delete this.password;
|
||||
@ -85,8 +94,3 @@ export class User {
|
||||
this.password = await bcrypt.hash(this.password, saltRounds);
|
||||
}
|
||||
}
|
||||
|
||||
export type UserRole = {
|
||||
role: string;
|
||||
};
|
||||
export type UserWithRole = User & UserRole;
|
||||
|
||||
@ -7,11 +7,29 @@ export class UserRepository extends Repository<User> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(User, dataSource.createEntityManager());
|
||||
}
|
||||
async findByEmail(email: string) {
|
||||
return this.findOneBy({ email: email });
|
||||
async findByEmail(email: string): Promise<User> {
|
||||
const queryBuilder = this.dataSource.createQueryBuilder(User, 'user');
|
||||
return await queryBuilder.where('user.email = :email', { email }).getOne();
|
||||
}
|
||||
|
||||
async findById(userId: string) {
|
||||
return this.findOneBy({ id: userId });
|
||||
async findById(userId: string): Promise<User> {
|
||||
const queryBuilder = this.dataSource.createQueryBuilder(User, 'user');
|
||||
return await queryBuilder.where('user.id = :id', { id: userId }).getOne();
|
||||
}
|
||||
|
||||
async findOneByEmail(email: string, workspaceId: string): Promise<User> {
|
||||
const queryBuilder = this.dataSource.createQueryBuilder(User, 'user');
|
||||
return await queryBuilder
|
||||
.where('user.email = :email', { email })
|
||||
.andWhere('user.workspaceId = :workspaceId', { workspaceId })
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async findOneByIdx(userId: string, workspaceId: string): Promise<User> {
|
||||
const queryBuilder = this.dataSource.createQueryBuilder(User, 'user');
|
||||
return await queryBuilder
|
||||
.where('user.id = :id', { id: userId })
|
||||
.andWhere('user.workspaceId = :workspaceId', { workspaceId })
|
||||
.getOne();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
UseGuards,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
UnauthorizedException,
|
||||
Post,
|
||||
Body,
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { JwtGuard } from '../auth/guards/jwt.guard';
|
||||
import { User } from './entities/user.entity';
|
||||
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { AuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { JwtAuthGuard } from '../../guards/jwt-auth.guard';
|
||||
|
||||
@UseGuards(JwtGuard)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('users')
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
@ -28,16 +27,13 @@ export class UserController {
|
||||
throw new UnauthorizedException('Invalid user');
|
||||
}
|
||||
|
||||
return { user };
|
||||
return user;
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('info')
|
||||
async getUserInfo(@AuthUser() user: User) {
|
||||
const data: { workspace: Workspace; user: User } =
|
||||
await this.userService.getUserInstance(user.id);
|
||||
|
||||
return data;
|
||||
return await this.userService.getUserInstance(user.id);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { UserController } from './user.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from './entities/user.entity';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User]), AuthModule, WorkspaceModule],
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
controllers: [UserController],
|
||||
providers: [UserService, UserRepository],
|
||||
exports: [UserService, UserRepository],
|
||||
|
||||
@ -3,7 +3,7 @@ import { UserService } from './user.service';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { User } from './entities/user.entity';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { CreateUserDto } from '../auth/dto/create-user.dto';
|
||||
|
||||
describe('UserService', () => {
|
||||
let userService: UserService;
|
||||
@ -63,7 +63,7 @@ describe('UserService', () => {
|
||||
lastLoginIp: null,
|
||||
};
|
||||
|
||||
userRepository.findByEmail.mockResolvedValue(undefined);
|
||||
//userRepository.findByEmail.mockResolvedValue(undefined);
|
||||
userRepository.save.mockResolvedValue(savedUser);
|
||||
|
||||
const result = await userService.create(createUserDto);
|
||||
|
||||
@ -3,92 +3,31 @@ import {
|
||||
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';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { WorkspaceService } from '../workspace/services/workspace.service';
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
import { transactionWrapper } from '../../helpers/db.helper';
|
||||
import { CreateWorkspaceDto } from '../workspace/dto/create-workspace.dto';
|
||||
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||
|
||||
export type UserWithWorkspace = {
|
||||
user: User;
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
private userRepository: UserRepository,
|
||||
private workspaceService: WorkspaceService,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
async create(
|
||||
createUserDto: CreateUserDto,
|
||||
manager?: EntityManager,
|
||||
): Promise<User> {
|
||||
let user: User;
|
||||
|
||||
const existingUser: User = await this.findByEmail(createUserDto.email);
|
||||
|
||||
if (existingUser) {
|
||||
throw new BadRequestException('A user with this email already exists');
|
||||
}
|
||||
|
||||
await transactionWrapper(
|
||||
async (manager: EntityManager) => {
|
||||
user = plainToInstance(User, createUserDto);
|
||||
user.locale = 'en';
|
||||
user.lastLoginAt = new Date();
|
||||
user.name = createUserDto.email.split('@')[0];
|
||||
|
||||
user = await manager.save(User, user);
|
||||
|
||||
const createWorkspaceDto: CreateWorkspaceDto = {
|
||||
name: 'My Workspace',
|
||||
};
|
||||
|
||||
await this.workspaceService.createOrJoinWorkspace(
|
||||
user.id,
|
||||
createWorkspaceDto,
|
||||
manager,
|
||||
);
|
||||
},
|
||||
this.dataSource,
|
||||
manager,
|
||||
);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async getUserInstance(userId: string): Promise<UserWithWorkspace> {
|
||||
const user: User = await this.findById(userId);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
let workspace;
|
||||
|
||||
try {
|
||||
workspace = await this.workspaceService.getUserCurrentWorkspace(userId);
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
}
|
||||
|
||||
return { user, workspace };
|
||||
}
|
||||
constructor(private userRepository: UserRepository) {}
|
||||
|
||||
async findById(userId: string) {
|
||||
return this.userRepository.findById(userId);
|
||||
}
|
||||
|
||||
async findByEmail(email: string) {
|
||||
return this.userRepository.findByEmail(email);
|
||||
async getUserInstance(userId: string): Promise<any> {
|
||||
const user: User = await this.userRepository.findOne({
|
||||
relations: ['workspace'],
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async update(userId: string, updateUserDto: UpdateUserDto) {
|
||||
@ -101,6 +40,7 @@ export class UserService {
|
||||
user.name = updateUserDto.name;
|
||||
}
|
||||
|
||||
// todo need workspace scoping
|
||||
if (updateUserDto.email && user.email != updateUserDto.email) {
|
||||
if (await this.userRepository.findByEmail(updateUserDto.email)) {
|
||||
throw new BadRequestException('A user with this email already exists');
|
||||
@ -114,11 +54,4 @@ export class UserService {
|
||||
|
||||
return this.userRepository.save(user);
|
||||
}
|
||||
|
||||
async compareHash(
|
||||
plainPassword: string,
|
||||
passwordHash: string,
|
||||
): Promise<boolean> {
|
||||
return await bcrypt.compare(plainPassword, passwordHash);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user