Compare commits
1 Commits
main
...
versions/b
| Author | SHA1 | Date | |
|---|---|---|---|
| b48e59b073 |
2344
package-lock.json
generated
2344
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,12 +21,18 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/mapped-types": "^2.1.0",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
|
"@types/pg": "^8.15.5",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1",
|
||||||
|
"typeorm": "^0.3.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
|||||||
@@ -1,10 +1,33 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { AuthenticationModule } from './authentication/authentication.module';
|
import { AuthenticationModule } from './authentication/authentication.module';
|
||||||
|
import { UsersModule } from './users/users.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthenticationModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
envFilePath: '.env',
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
type: 'postgres',
|
||||||
|
url: configService.get('DATABASE_URL') || 'postgresql://postgres.zgmimnyowgdnlwccjubz:renolation29@aws-1-ap-southeast-1.pooler.supabase.com:5432/postgres',
|
||||||
|
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||||
|
synchronize: true,
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
AuthenticationModule,
|
||||||
|
UsersModule,
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthenticationController } from './authentication.controller';
|
import { AuthenticationController } from './authentication.controller';
|
||||||
import { AuthenticationService } from './authentication.service';
|
import { AuthenticationService } from './authentication.service';
|
||||||
|
import { UsersModule } from '../users/users.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication module for handling user authentication
|
* Authentication module for handling user authentication
|
||||||
*/
|
*/
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [UsersModule],
|
||||||
controllers: [AuthenticationController],
|
controllers: [AuthenticationController],
|
||||||
providers: [AuthenticationService],
|
providers: [AuthenticationService],
|
||||||
exports: [AuthenticationService],
|
exports: [AuthenticationService],
|
||||||
|
|||||||
@@ -24,22 +24,19 @@ describe('AuthenticationService', () => {
|
|||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'password123',
|
password: 'password123',
|
||||||
};
|
};
|
||||||
const expectedResponse = {
|
|
||||||
message: 'Login successful',
|
|
||||||
user: {
|
|
||||||
email: 'test@example.com',
|
|
||||||
},
|
|
||||||
token: 'placeholder-token',
|
|
||||||
};
|
|
||||||
|
|
||||||
const actualResponse = await service.login(inputLoginDto);
|
const actualResponse = await service.login(inputLoginDto);
|
||||||
|
|
||||||
expect(actualResponse).toEqual(expectedResponse);
|
expect(actualResponse).toHaveProperty('message', 'Login successful');
|
||||||
|
expect(actualResponse).toHaveProperty('user');
|
||||||
|
expect(actualResponse.user).toHaveProperty('email', 'test@example.com');
|
||||||
|
expect(actualResponse).toHaveProperty('token');
|
||||||
|
expect(actualResponse.token).toMatch(/^auth_token_\d+_[a-z0-9]+$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw UnauthorizedException with invalid email', async () => {
|
it('should throw UnauthorizedException with empty email', async () => {
|
||||||
const inputLoginDto: LoginDto = {
|
const inputLoginDto: LoginDto = {
|
||||||
email: 'invalid@example.com',
|
email: '',
|
||||||
password: 'password123',
|
password: 'password123',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,10 +45,10 @@ describe('AuthenticationService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw UnauthorizedException with invalid password', async () => {
|
it('should throw UnauthorizedException with empty password', async () => {
|
||||||
const inputLoginDto: LoginDto = {
|
const inputLoginDto: LoginDto = {
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'wrongpassword',
|
password: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(service.login(inputLoginDto)).rejects.toThrow(
|
await expect(service.login(inputLoginDto)).rejects.toThrow(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
|
import { UserService } from '../users/user.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response interface for successful login
|
* Response interface for successful login
|
||||||
@@ -7,9 +8,11 @@ import { LoginDto } from './dto/login.dto';
|
|||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
message: string;
|
message: string;
|
||||||
user: {
|
user: {
|
||||||
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
token?: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +20,8 @@ export interface LoginResponse {
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthenticationService {
|
export class AuthenticationService {
|
||||||
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a user with email and password
|
* Authenticates a user with email and password
|
||||||
* @param loginDto - Login credentials containing email and password
|
* @param loginDto - Login credentials containing email and password
|
||||||
@@ -26,33 +31,33 @@ export class AuthenticationService {
|
|||||||
async login(loginDto: LoginDto): Promise<LoginResponse> {
|
async login(loginDto: LoginDto): Promise<LoginResponse> {
|
||||||
const { email, password } = loginDto;
|
const { email, password } = loginDto;
|
||||||
|
|
||||||
// TODO: Implement actual user validation logic
|
if (!email || !password) {
|
||||||
// This is a placeholder implementation
|
throw new UnauthorizedException('Email and password are required');
|
||||||
const isValidUser = await this.validateUser(email, password);
|
}
|
||||||
|
|
||||||
if (!isValidUser) {
|
// Find user by email
|
||||||
|
const user = await this.userService.findUserByEmail(email);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
throw new UnauthorizedException('Invalid email or password');
|
throw new UnauthorizedException('Invalid email or password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple password validation (in production, use bcrypt)
|
||||||
|
if (user.password !== password) {
|
||||||
|
throw new UnauthorizedException('Invalid email or password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a simple token (in production, use JWT)
|
||||||
|
const token = `auth_token_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Login successful',
|
message: 'Login successful',
|
||||||
user: {
|
user: {
|
||||||
email,
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
},
|
},
|
||||||
// TODO: Generate JWT token
|
token,
|
||||||
token: 'placeholder-token',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates user credentials
|
|
||||||
* @param email - User email
|
|
||||||
* @param password - User password
|
|
||||||
* @returns Promise<boolean> - Whether credentials are valid
|
|
||||||
*/
|
|
||||||
private async validateUser(email: string, password: string): Promise<boolean> {
|
|
||||||
// TODO: Implement actual user validation against database
|
|
||||||
// For now, using placeholder validation
|
|
||||||
return email === 'test@example.com' && password === 'password123';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { Request } from 'express';
|
|
||||||
import {UsersService} from "../users/user.service";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtRefreshTokenStrategy extends PassportStrategy(
|
|
||||||
Strategy,
|
|
||||||
'jwt-refresh-token'
|
|
||||||
) {
|
|
||||||
constructor(
|
|
||||||
private readonly configService: ConfigService,
|
|
||||||
private readonly userService: UsersService,
|
|
||||||
) {
|
|
||||||
super({
|
|
||||||
jwtFromRequest: ExtractJwt.fromExtractors([(request: Request) => {
|
|
||||||
return request?.cookies?.Refresh;
|
|
||||||
}]),
|
|
||||||
secretOrKey: configService.get('JWT_REFRESH_TOKEN_SECRET'),
|
|
||||||
passReqToCallback: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(request: Request, payload: TokenPayload) {
|
|
||||||
const refreshToken = request.cookies?.Refresh;
|
|
||||||
return this.userService.getUserIfRefreshTokenMatches(refreshToken, payload.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export default class JwtRefreshGuard extends AuthGuard('jwt-refresh-token') {}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import {
|
|
||||||
ConnectedSocket,
|
|
||||||
MessageBody, OnGatewayConnection,
|
|
||||||
SubscribeMessage,
|
|
||||||
WebSocketGateway,
|
|
||||||
WebSocketServer,
|
|
||||||
} from '@nestjs/websockets';
|
|
||||||
import {Server, Socket} from 'socket.io';
|
|
||||||
import {ChatService} from "./chat.service";
|
|
||||||
|
|
||||||
@WebSocketGateway()
|
|
||||||
export class ChatGateway implements OnGatewayConnection {
|
|
||||||
@WebSocketServer()
|
|
||||||
server: Server;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly chatService: ChatService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleConnection(socket: Socket) {
|
|
||||||
await this.chatService.getUserFromSocket(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeMessage('send_message')
|
|
||||||
async listenForMessages(@MessageBody() content: string, @ConnectedSocket() socket: Socket,) {
|
|
||||||
const author = await this.chatService.getUserFromSocket(socket);
|
|
||||||
const message = await this.chatService.saveMessage(content, author);
|
|
||||||
this.server.sockets.emit('receive_message', message);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeMessage('request_all_messages')
|
|
||||||
async requestAllMessages(
|
|
||||||
@ConnectedSocket() socket: Socket,
|
|
||||||
) {
|
|
||||||
await this.chatService.getUserFromSocket(socket);
|
|
||||||
const messages = await this.chatService.getAllMessages();
|
|
||||||
|
|
||||||
socket.emit('send_all_messages', messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AuthenticationService } from '../authentication/authentication.service';
|
|
||||||
import { Socket } from 'socket.io';
|
|
||||||
import { parse } from 'cookie';
|
|
||||||
import { WsException } from '@nestjs/websockets';
|
|
||||||
import UserEntity from "../users/entities/user.entity";
|
|
||||||
import {InjectRepository} from "@nestjs/typeorm";
|
|
||||||
import Message from "./mesage.entity";
|
|
||||||
import {Repository} from "typeorm";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ChatService {
|
|
||||||
constructor(
|
|
||||||
private readonly authenticationService: AuthenticationService,
|
|
||||||
@InjectRepository(Message)
|
|
||||||
private messagesRepository: Repository<Message>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveMessage(content: string, author: UserEntity) {
|
|
||||||
const newMessage = this.messagesRepository.create({
|
|
||||||
content,
|
|
||||||
author
|
|
||||||
});
|
|
||||||
await this.messagesRepository.save(newMessage);
|
|
||||||
return newMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllMessages() {
|
|
||||||
return this.messagesRepository.find({
|
|
||||||
relations: {
|
|
||||||
author: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserFromSocket(socket: Socket) {
|
|
||||||
const cookie = socket.handshake.headers.cookie;
|
|
||||||
const { Authentication: authenticationToken } = parse(cookie);
|
|
||||||
const user = await this.authenticationService.getUserFromAuthenticationToken(authenticationToken);
|
|
||||||
if (!user) {
|
|
||||||
throw new WsException('Invalid credentials.');
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|
||||||
import User from '../users/entities/user.entity';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
class Message {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
public id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
public content: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => User)
|
|
||||||
public author: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Message;
|
|
||||||
@@ -3,6 +3,6 @@ import { AppModule } from './app.module';
|
|||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(4003);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { CACHE_KEY_METADATA, CacheInterceptor, } from '@nestjs/cache-manager';
|
|
||||||
import {ExecutionContext, Injectable} from "@nestjs/common";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HttpCacheInterceptor extends CacheInterceptor {
|
|
||||||
trackBy(context: ExecutionContext): string | undefined {
|
|
||||||
const cacheKey = this.reflector.get(
|
|
||||||
CACHE_KEY_METADATA,
|
|
||||||
context.getHandler(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cacheKey) {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
return `${cacheKey}-${request._parsedUrl.query}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.trackBy(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const GET_POSTS_CACHE_KEY = 'GET_POSTS_CACHE';
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import {Injectable} from '@nestjs/common';
|
|
||||||
import {ElasticsearchService} from '@nestjs/elasticsearch';
|
|
||||||
import Post from "./entities/post.entity";
|
|
||||||
import {PostSearchResult} from "./types/postSearchResult.interface";
|
|
||||||
import {PostSearchBody} from "./types/postSearchBody.interface";
|
|
||||||
import PostCountResult from "./types/postCountBody.interface";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export default class PostsSearchService {
|
|
||||||
index = 'posts';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly elasticsearchService: ElasticsearchService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async indexPost(post: Post) {
|
|
||||||
return this.elasticsearchService.index<PostSearchBody>({
|
|
||||||
index: this.index,
|
|
||||||
document: {
|
|
||||||
id: post.id,
|
|
||||||
title: post.title,
|
|
||||||
content: post.content,
|
|
||||||
authorId: post.author.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async count(query: string, fields: string[]) {
|
|
||||||
const result = await this.elasticsearchService.count({
|
|
||||||
index: this.index,
|
|
||||||
query: {
|
|
||||||
multi_match: {
|
|
||||||
query,
|
|
||||||
fields,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async search(text: string) {
|
|
||||||
const result = await this.elasticsearchService.search<PostSearchResult>({
|
|
||||||
index: this.index,
|
|
||||||
query: {
|
|
||||||
multi_match: {
|
|
||||||
query: text,
|
|
||||||
fields: ['title', 'content'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const hits = result.hits.hits;
|
|
||||||
return hits.map((item) => item._source);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async remove(postId: number) {
|
|
||||||
await this.elasticsearchService.deleteByQuery({
|
|
||||||
index: this.index,
|
|
||||||
query: {
|
|
||||||
match: {
|
|
||||||
id: postId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(post: Post) {
|
|
||||||
const newBody: PostSearchBody = {
|
|
||||||
id: post.id,
|
|
||||||
title: post.title,
|
|
||||||
content: post.content,
|
|
||||||
authorId: post.author.id
|
|
||||||
};
|
|
||||||
|
|
||||||
const script = Object.entries(newBody).reduce((result, [key, value]) => {
|
|
||||||
return `${result} ctx._source.${key}='${value}';`;
|
|
||||||
}, '');
|
|
||||||
|
|
||||||
await this.elasticsearchService.updateByQuery({
|
|
||||||
index: this.index,
|
|
||||||
query: {
|
|
||||||
match: {
|
|
||||||
id: post.id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
script: {
|
|
||||||
source: script
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
interface PostCountResult {
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostCountResult;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export interface PostSearchBody {
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
content: string,
|
|
||||||
authorId: number
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import {PostSearchBody} from "./postSearchBody.interface";
|
|
||||||
|
|
||||||
export interface PostSearchResult {
|
|
||||||
hits: {
|
|
||||||
total: number;
|
|
||||||
hits: Array<{
|
|
||||||
_source: PostSearchBody;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
|
||||||
import Product from '../products/product.entity';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
class ProductCategory {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
public id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
public name: string;
|
|
||||||
|
|
||||||
@OneToMany(
|
|
||||||
() => Product,
|
|
||||||
(product: Product) => product.category,
|
|
||||||
)
|
|
||||||
public products: Product[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductCategory;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
|
|
||||||
import ProductCategory from '../productCategories/productCategory.entity';
|
|
||||||
import { CarProperties } from './types/carProperties.interface';
|
|
||||||
import { BookProperties } from './types/bookProperties.interface';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
class Product {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
public id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
public name: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductCategory, (category: ProductCategory) => category.products)
|
|
||||||
public category: ProductCategory;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
type: 'jsonb'
|
|
||||||
})
|
|
||||||
public properties: CarProperties | BookProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Product;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface BookProperties {
|
|
||||||
authors: string[];
|
|
||||||
publicationYear: string;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export interface CarProperties {
|
|
||||||
brand: string;
|
|
||||||
engine: {
|
|
||||||
fuel: string;
|
|
||||||
numberOfCylinders: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
||||||
import { ElasticsearchModule } from '@nestjs/elasticsearch';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
ConfigModule,
|
|
||||||
ElasticsearchModule.registerAsync({
|
|
||||||
imports: [ConfigModule],
|
|
||||||
useFactory: async (configService: ConfigService) => ({
|
|
||||||
node: configService.get<string>('ELASTICSEARCH_NODE'),
|
|
||||||
auth: {
|
|
||||||
username: configService.get<string>('ELASTICSEARCH_USERNAME'),
|
|
||||||
password: configService.get<string>('ELASTICSEARCH_PASSWORD'),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
inject: [ConfigService],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
exports: [ElasticsearchModule]
|
|
||||||
})
|
|
||||||
export class SearchModule {}
|
|
||||||
20
src/users/dto/create-user.dto.ts
Normal file
20
src/users/dto/create-user.dto.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUserDto {
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MinLength(6, { message: 'Password must be at least 6 characters long' })
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsUrl()
|
||||||
|
avatarUrl?: string;
|
||||||
|
}
|
||||||
4
src/users/dto/update-user.dto.ts
Normal file
4
src/users/dto/update-user.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateUserDto } from './create-user.dto';
|
||||||
|
|
||||||
|
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
||||||
31
src/users/user.entity.ts
Normal file
31
src/users/user.entity.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('users')
|
||||||
|
export class User {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
avatarUrl?: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', nullable: true })
|
||||||
|
tokenExpiry?: Date;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
133
src/users/user.service.spec.ts
Normal file
133
src/users/user.service.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { NotFoundException } from '@nestjs/common';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
|
||||||
|
describe('UserService', () => {
|
||||||
|
let service: UserService;
|
||||||
|
let repository: Repository<User>;
|
||||||
|
|
||||||
|
const mockRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
UserService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(User),
|
||||||
|
useValue: mockRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<UserService>(UserService);
|
||||||
|
repository = module.get<Repository<User>>(getRepositoryToken(User));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createUser', () => {
|
||||||
|
it('should create a user with token and tokenExpiry', async () => {
|
||||||
|
const inputCreateUserDto: CreateUserDto = {
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedUser = {
|
||||||
|
...inputCreateUserDto,
|
||||||
|
id: 'uuid',
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockRepository.create.mockReturnValue(expectedUser);
|
||||||
|
mockRepository.save.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await service.createUser(inputCreateUserDto);
|
||||||
|
|
||||||
|
expect(mockRepository.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
email: inputCreateUserDto.email,
|
||||||
|
name: inputCreateUserDto.name,
|
||||||
|
avatarUrl: inputCreateUserDto.avatarUrl,
|
||||||
|
token: expect.stringMatching(/^mock_jwt_token_\d+$/),
|
||||||
|
tokenExpiry: expect.any(Date),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(mockRepository.save).toHaveBeenCalledWith(expectedUser);
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findUserById', () => {
|
||||||
|
it('should return a user when found', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
const expectedUser = {
|
||||||
|
id: inputId,
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockRepository.findOne.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await service.findUserById(inputId);
|
||||||
|
|
||||||
|
expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: inputId } });
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException when user not found', async () => {
|
||||||
|
const inputId = 'non-existent-id';
|
||||||
|
mockRepository.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.findUserById(inputId)).rejects.toThrow(NotFoundException);
|
||||||
|
expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: inputId } });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refreshToken', () => {
|
||||||
|
it('should refresh user token and tokenExpiry', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
const mockUser = {
|
||||||
|
id: inputId,
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
token: 'old_token',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockRepository.findOne.mockResolvedValue(mockUser);
|
||||||
|
mockRepository.save.mockResolvedValue(mockUser);
|
||||||
|
|
||||||
|
const actualUser = await service.refreshToken(inputId);
|
||||||
|
|
||||||
|
expect(mockUser.token).toMatch(/^mock_jwt_token_\d+$/);
|
||||||
|
expect(mockUser.tokenExpiry).toBeInstanceOf(Date);
|
||||||
|
expect(mockRepository.save).toHaveBeenCalledWith(mockUser);
|
||||||
|
expect(actualUser).toEqual(mockUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
75
src/users/user.service.ts
Normal file
75
src/users/user.service.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(User)
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createUser(createUserDto: CreateUserDto): Promise<User> {
|
||||||
|
const token = `mock_jwt_token_${Date.now()}`;
|
||||||
|
const tokenExpiry = new Date();
|
||||||
|
tokenExpiry.setDate(tokenExpiry.getDate() + 365);
|
||||||
|
|
||||||
|
const user = this.userRepository.create({
|
||||||
|
...createUserDto,
|
||||||
|
token,
|
||||||
|
tokenExpiry,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllUsers(): Promise<User[]> {
|
||||||
|
return this.userRepository.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
async findUserById(id: string): Promise<User> {
|
||||||
|
const user = await this.userRepository.findOne({ where: { id } });
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException(`User with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findUserByEmail(email: string): Promise<User | null> {
|
||||||
|
return this.userRepository.findOne({ where: { email } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateUser(id: string, updateUserDto: UpdateUserDto): Promise<User> {
|
||||||
|
const user = await this.findUserById(id);
|
||||||
|
Object.assign(user, updateUserDto);
|
||||||
|
return this.userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(id: string): Promise<void> {
|
||||||
|
const user = await this.findUserById(id);
|
||||||
|
await this.userRepository.remove(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshToken(id: string): Promise<User> {
|
||||||
|
const user = await this.findUserById(id);
|
||||||
|
const token = `mock_jwt_token_${Date.now()}`;
|
||||||
|
const tokenExpiry = new Date();
|
||||||
|
tokenExpiry.setDate(tokenExpiry.getDate() + 365);
|
||||||
|
|
||||||
|
user.token = token;
|
||||||
|
user.tokenExpiry = tokenExpiry;
|
||||||
|
|
||||||
|
return this.userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserIfRefreshTokenMatches(refreshToken: string, userId: string): Promise<User | null> {
|
||||||
|
const user = await this.findUserById(userId);
|
||||||
|
if (user && user.token === refreshToken) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/users/users.controller.spec.ts
Normal file
192
src/users/users.controller.spec.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { UsersController } from './users.controller';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
|
describe('UsersController', () => {
|
||||||
|
let controller: UsersController;
|
||||||
|
let service: UserService;
|
||||||
|
|
||||||
|
const mockUserService = {
|
||||||
|
createUser: jest.fn(),
|
||||||
|
findAllUsers: jest.fn(),
|
||||||
|
findUserById: jest.fn(),
|
||||||
|
updateUser: jest.fn(),
|
||||||
|
deleteUser: jest.fn(),
|
||||||
|
refreshToken: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [UsersController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useValue: mockUserService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<UsersController>(UsersController);
|
||||||
|
service = module.get<UserService>(UserService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createUser', () => {
|
||||||
|
it('should create a user', async () => {
|
||||||
|
const inputCreateUserDto: CreateUserDto = {
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedUser = {
|
||||||
|
id: 'uuid',
|
||||||
|
...inputCreateUserDto,
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserService.createUser.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await controller.createUser(inputCreateUserDto);
|
||||||
|
|
||||||
|
expect(mockUserService.createUser).toHaveBeenCalledWith(inputCreateUserDto);
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAllUsers', () => {
|
||||||
|
it('should return all users', async () => {
|
||||||
|
const expectedUsers = [
|
||||||
|
{
|
||||||
|
id: 'uuid1',
|
||||||
|
email: 'test1@example.com',
|
||||||
|
name: 'Test User 1',
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uuid2',
|
||||||
|
email: 'test2@example.com',
|
||||||
|
name: 'Test User 2',
|
||||||
|
token: 'mock_jwt_token_1234567891',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockUserService.findAllUsers.mockResolvedValue(expectedUsers);
|
||||||
|
|
||||||
|
const actualUsers = await controller.findAllUsers();
|
||||||
|
|
||||||
|
expect(mockUserService.findAllUsers).toHaveBeenCalled();
|
||||||
|
expect(actualUsers).toEqual(expectedUsers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findUserById', () => {
|
||||||
|
it('should return a user by id', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
const expectedUser = {
|
||||||
|
id: inputId,
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserService.findUserById.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await controller.findUserById(inputId);
|
||||||
|
|
||||||
|
expect(mockUserService.findUserById).toHaveBeenCalledWith(inputId);
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateUser', () => {
|
||||||
|
it('should update a user', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
const inputUpdateUserDto: UpdateUserDto = {
|
||||||
|
name: 'Updated Name',
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedUser = {
|
||||||
|
id: inputId,
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Updated Name',
|
||||||
|
token: 'mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserService.updateUser.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await controller.updateUser(inputId, inputUpdateUserDto);
|
||||||
|
|
||||||
|
expect(mockUserService.updateUser).toHaveBeenCalledWith(inputId, inputUpdateUserDto);
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteUser', () => {
|
||||||
|
it('should delete a user', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
|
||||||
|
mockUserService.deleteUser.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await controller.deleteUser(inputId);
|
||||||
|
|
||||||
|
expect(mockUserService.deleteUser).toHaveBeenCalledWith(inputId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refreshToken', () => {
|
||||||
|
it('should refresh user token', async () => {
|
||||||
|
const inputId = 'test-id';
|
||||||
|
const expectedUser = {
|
||||||
|
id: inputId,
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: 'Test User',
|
||||||
|
token: 'new_mock_jwt_token_1234567890',
|
||||||
|
tokenExpiry: new Date(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserService.refreshToken.mockResolvedValue(expectedUser);
|
||||||
|
|
||||||
|
const actualUser = await controller.refreshToken(inputId);
|
||||||
|
|
||||||
|
expect(mockUserService.refreshToken).toHaveBeenCalledWith(inputId);
|
||||||
|
expect(actualUser).toEqual(expectedUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testEndpoint', () => {
|
||||||
|
it('should return test message', async () => {
|
||||||
|
const expectedMessage = { message: 'User module is working correctly' };
|
||||||
|
|
||||||
|
const actualMessage = await controller.testEndpoint();
|
||||||
|
|
||||||
|
expect(actualMessage).toEqual(expectedMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
57
src/users/users.controller.ts
Normal file
57
src/users/users.controller.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
|
@Controller('users')
|
||||||
|
export class UsersController {
|
||||||
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@HttpCode(HttpStatus.CREATED)
|
||||||
|
async createUser(@Body() createUserDto: CreateUserDto) {
|
||||||
|
console.log('createUserDto', createUserDto);
|
||||||
|
return this.userService.createUser(createUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAllUsers() {
|
||||||
|
return this.userService.findAllUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findUserById(@Param('id') id: string) {
|
||||||
|
return this.userService.findUserById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
|
||||||
|
return this.userService.updateUser(id, updateUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
async deleteUser(@Param('id') id: string) {
|
||||||
|
await this.userService.deleteUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/refresh-token')
|
||||||
|
async refreshToken(@Param('id') id: string) {
|
||||||
|
return this.userService.refreshToken(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('admin/test')
|
||||||
|
async testEndpoint() {
|
||||||
|
return { message: 'User module is working correctly' };
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/users/users.module.ts
Normal file
13
src/users/users.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { UsersController } from './users.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([User])],
|
||||||
|
controllers: [UsersController],
|
||||||
|
providers: [UserService],
|
||||||
|
exports: [UserService],
|
||||||
|
})
|
||||||
|
export class UsersModule {}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { IsNumber, Min, IsOptional } from 'class-validator';
|
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
|
|
||||||
export class PaginationParams {
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
startId?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
offset?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import * as request from 'supertest';
|
import request from 'supertest';
|
||||||
import { App } from 'supertest/types';
|
|
||||||
import { AppModule } from './../src/app.module';
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
describe('AppController (e2e)', () => {
|
||||||
let app: INestApplication<App>;
|
let app: INestApplication;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
|||||||
Reference in New Issue
Block a user