完成后端登录dto验证

This commit is contained in:
2025-05-06 22:52:51 +08:00
parent c2868b5128
commit eef23909f4
30 changed files with 7027 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@@ -0,0 +1,33 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { VerificationModule } from './verification/verification.module';
import { NotificationModule } from './notification/notification.module';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
autoLoadEntities: true,
entities: [],
synchronize: process.env.NODE_ENV !== 'production', // Set to false in production
}),
UserModule,
AuthModule,
VerificationModule,
NotificationModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,25 @@
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
) { }
@Post('login')
async login(@Body() loginDto: LoginDto) {
switch (loginDto.type) {
case 'password':
return this.authService.loginWithPassword(loginDto);
case 'phone':
return this.authService.loginWithPhone(loginDto);
case 'email':
return this.authService.loginWithEmail(loginDto);
default:
throw new BadRequestException('Invalid login type');
}
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
controllers: [AuthController],
providers: [AuthService]
})
export class AuthModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,32 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
@Injectable()
export class AuthService {
async loginWithPassword(loginDto: LoginDto) {
const { account, password } = loginDto;
if (!account || !password) {
throw new BadRequestException('account and password are required');
}
return { message: 'Logged in with password', data: loginDto };
}
async loginWithPhone(loginDto: LoginDto) {
const { phone, code } = loginDto;
if (!phone || !code) {
throw new BadRequestException('Phone number and code are required');
}
return { message: 'Logged in with phone', data: loginDto };
}
async loginWithEmail(loginDto: LoginDto) {
const { email, code } = loginDto;
if (!email || !code) {
throw new BadRequestException('Email and code are required');
}
return { message: 'Logged in with email', data: loginDto };
}
}

View File

@@ -0,0 +1,31 @@
import { IsEnum, IsString, Length, ValidateIf } from 'class-validator';
export class LoginDto {
@IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' })
type: 'password' | 'phone' | 'email';
@ValidateIf(o => o.type === 'password')
@IsString({ message: '账户必须输入' })
@Length(1, 254, { message: '账户长度错误' })// 用户名、邮箱、手机号
account?: string;
@ValidateIf(o => o.type === 'password')
@IsString({ message: '密码必须输入' })
@Length(6, 32, { message: '密码长度错误' })// 6-32位
password?: string;
@ValidateIf(o => o.type === 'phone')
@IsString({ message: '手机号必须输入' })
@Length(11, 11, { message: '手机号长度错误' })// 中国大陆11位数字
phone?: string;
@ValidateIf(o => o.type === 'email')
@IsString({ message: '邮箱必须输入' })
@Length(6, 254, { message: '邮箱长度错误' })// RFC 5321
email?: string;
@ValidateIf(o => o.type === 'phone' || o.type === 'email')
@IsString({ message: '验证码必须输入' })
@Length(6, 6, { message: '验证码长度错误' })// 6位数字
code?: string;
}

View File

@@ -0,0 +1,10 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
await app.listen(process.env.PORT ?? 3001);
}
bootstrap();

View File

@@ -0,0 +1,4 @@
import { Module } from '@nestjs/common';
@Module({})
export class NotificationModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
describe('UserController', () => {
let controller: UserController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
}).compile();
controller = module.get<UserController>(UserController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';
@Controller('user')
export class UserController {}

View File

@@ -0,0 +1,43 @@
import { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column('uuid', { unique: true, default: () => 'gen_random_uuid()' })
userId: string;
@Column({ length: 32 })
@Index({ unique: true })
username: string;
@Column({ length: 30 })
nickname: string;
@Column({ nullable: true, type: 'char', length: 32 })
salt: string;
@Column({ nullable: true, type: 'char', length: 64 })
hashed_password: string;
@Column({ nullable: true, length: 254 })// RFC 5321
@Index({ unique: true })
email: string;
@Column({ nullable: true, length: 20 })// China Mainland
@Index({ unique: true })
phone: string;
@Column({ nullable: true })
avatar: string;
@CreateDateColumn({ precision: 3 })
created_at: Date;
@UpdateDateColumn({ precision: 3 })
updated_at: Date;
@DeleteDateColumn({ nullable: true, precision: 3 })
deleted_at: Date;
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) { }
async findOne(options: Partial<Pick<User, 'userId' | 'username' | 'phone' | 'email'>>): Promise<User | null> {
if (Object.keys(options).length === 0) {
return null;
}
return this.userRepository.findOne({ where: options });
}
async create(user: Partial<User>): Promise<User> {
const newUser = this.userRepository.create(user);
return this.userRepository.save(newUser);
}
}

View File

@@ -0,0 +1,4 @@
import { Module } from '@nestjs/common';
@Module({})
export class VerificationModule {}