From 91eb293b792a8423a16a123a59a27b2ec7fef325 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:26:14 -0300 Subject: [PATCH 01/10] feat: added users module, controller and service to app module --- src/app.module.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 67cdcde..1d6a1ad 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,21 +5,14 @@ import { PostsService } from './modules/posts/posts.service'; import { PrismaService } from './prisma.service'; import { PostsModule } from './modules/posts/posts.module'; import { AuthModule } from './modules/auth/auth.module'; +import { UsersModule } from './modules/users/users.module'; +import { UsersController } from './modules/users/users.controller'; +import { UsersService } from './modules/users/users.service'; @Module({ - imports: [ - // GraphQLModule.forRoot({ - // driver: ApolloDriver, - // autoSchemaFile: join(process.cwd(), 'src/schema.gql'), - // definitions: { - // path: join(process.cwd(), 'src/graphql.ts'), - // }, - // }), - PostsModule, - AuthModule - ], - controllers: [AppController], - providers: [AppService, PrismaService, PostsService], + imports: [PostsModule, AuthModule, UsersModule], + controllers: [AppController, UsersController], + providers: [AppService, PrismaService, PostsService, UsersService], exports: [PrismaService], }) export class AppModule {} From e9536b53e76a1a901aa2994a1a8203cfc48a9732 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:27:57 -0300 Subject: [PATCH 02/10] feat: created UsersService to handle user related logic --- src/modules/users/users.service.spec.ts | 18 +++++++++++ src/modules/users/users.service.ts | 40 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/modules/users/users.service.spec.ts create mode 100644 src/modules/users/users.service.ts diff --git a/src/modules/users/users.service.spec.ts b/src/modules/users/users.service.spec.ts new file mode 100644 index 0000000..62815ba --- /dev/null +++ b/src/modules/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts new file mode 100644 index 0000000..e7ddbe3 --- /dev/null +++ b/src/modules/users/users.service.ts @@ -0,0 +1,40 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from 'src/prisma.service'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; + +@Injectable() +export class UsersService { + constructor(private prisma: PrismaService) {} + + async create(data: CreateUserDto) { + return this.prisma.user.create({ data }); + } + + async findAll() { + return this.prisma.user.findMany({ where: { deletedAt: null } }); + } + + async findOne(id: number) { + const user = await this.prisma.user.findUnique({ where: { id } }); + + if (!user) { + throw new NotFoundException('User not found'); + } + + return user; + } + + async update(id: number, data: UpdateUserDto) { + await this.findOne(id); + return this.prisma.user.update({ where: { id }, data }); + } + + async remove(id: number) { + await this.findOne(id); + + await this.prisma.user.delete({ where: { id } }); + + return { message: 'User successfully removed', id }; + } +} From b2aa9615d8e62893e6ca6f5f3a1947574b2ea356 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:29:12 -0300 Subject: [PATCH 03/10] feat: created UsersController with basic routes --- src/modules/users/users.controller.spec.ts | 18 +++++++++ src/modules/users/users.controller.ts | 43 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/modules/users/users.controller.spec.ts create mode 100644 src/modules/users/users.controller.ts diff --git a/src/modules/users/users.controller.spec.ts b/src/modules/users/users.controller.spec.ts new file mode 100644 index 0000000..3e27c39 --- /dev/null +++ b/src/modules/users/users.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; + +describe('UsersController', () => { + let controller: UsersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + }).compile(); + + controller = module.get(UsersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts new file mode 100644 index 0000000..63b2f1d --- /dev/null +++ b/src/modules/users/users.controller.ts @@ -0,0 +1,43 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, +} from '@nestjs/common'; +import { UsersService } from './users.service'; +import { CreateUserDto } from './dto/create-user.dto'; +import { ParseIdPipe } from './pipes/parse-id.pipe'; +import { UpdateUserDto } from './dto/update-user.dto'; + +@Controller('users') +export class UsersController { + constructor(private userService: UsersService) {} + + @Post() + create(@Body() dto: CreateUserDto) { + return this.userService.create(dto); + } + + @Get() + findAll() { + return this.userService.findAll(); + } + + @Get(':id') + findOne(@Param('id', ParseIdPipe) id: number) { + return this.userService.findOne(id); + } + + @Patch(':id') + update(@Param('id', ParseIdPipe) id: number, @Body() dto: UpdateUserDto) { + return this.userService.update(id, dto); + } + + @Delete(':id') + remove(@Param('id', ParseIdPipe) id: number) { + return this.userService.remove(id); + } +} From 3dbb1e1a7510ce57d8396f229a014f838ad7d49e Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:30:11 -0300 Subject: [PATCH 04/10] feat: created CreateUserDto for data validation and typing --- src/modules/users/dto/create-user.dto.ts | 37 ++++++++++++++++++++++++ src/modules/users/dto/update-user.dto.ts | 4 +++ 2 files changed, 41 insertions(+) create mode 100644 src/modules/users/dto/create-user.dto.ts create mode 100644 src/modules/users/dto/update-user.dto.ts diff --git a/src/modules/users/dto/create-user.dto.ts b/src/modules/users/dto/create-user.dto.ts new file mode 100644 index 0000000..599b671 --- /dev/null +++ b/src/modules/users/dto/create-user.dto.ts @@ -0,0 +1,37 @@ +import { + IsEmail, + IsNotEmpty, + IsOptional, + IsString, + IsUrl, +} from 'class-validator'; + +export class CreateUserDto { + @IsEmail() + @IsNotEmpty() + email: string; + + @IsString() + @IsNotEmpty() + name: string; + + @IsOptional() + @IsUrl() + avatarLink?: string; + + @IsOptional() + @IsString() + passwordHash?: string; + + @IsString() + @IsNotEmpty() + function: string; + + @IsOptional() + @IsString() + role?: string; + + @IsOptional() + @IsString() + description?: string; +} diff --git a/src/modules/users/dto/update-user.dto.ts b/src/modules/users/dto/update-user.dto.ts new file mode 100644 index 0000000..dfd37fb --- /dev/null +++ b/src/modules/users/dto/update-user.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateUserDto } from './create-user.dto'; + +export class UpdateUserDto extends PartialType(CreateUserDto) {} From 2fb687f699e9090539e8ccf0a2db667d81a98d1f Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:32:56 -0300 Subject: [PATCH 05/10] feat: created ParseIdPipe for validating id params --- src/modules/users/pipes/parse-id.pipe.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/modules/users/pipes/parse-id.pipe.ts diff --git a/src/modules/users/pipes/parse-id.pipe.ts b/src/modules/users/pipes/parse-id.pipe.ts new file mode 100644 index 0000000..db358dc --- /dev/null +++ b/src/modules/users/pipes/parse-id.pipe.ts @@ -0,0 +1,13 @@ +import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; +import { isNumber } from 'class-validator'; + +@Injectable() +export class ParseIdPipe implements PipeTransform { + transform(value: number) { + if (!isNumber(value)) { + throw new BadRequestException('ID invalid!'); + } + + return value; + } +} From df4cdf152f0ab5585c8da3fcd7357cc0209f83e0 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:33:53 -0300 Subject: [PATCH 06/10] chore: removed unused comments from auth.module.ts file --- src/modules/auth/auth.module.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 6bbc2ed..bf3368b 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -8,14 +8,14 @@ import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ - PassportModule.register({ defaultStrategy: 'jwt' }), // ✅ registers 'jwt' + PassportModule.register({ defaultStrategy: 'jwt' }), // ✅ registers 'jwt' JwtModule.register({ - secret: 'secretKey', // replace with process.env.JWT_SECRET in production - signOptions: { expiresIn: '1h' }, // token expiry time + secret: 'secretKey', // replace with process.env.JWT_SECRET in production + signOptions: { expiresIn: '1h' }, // token expiry time }), ], controllers: [AuthController], - providers: [AuthService, PrismaService, JwtStrategy], // ✅ provide JwtStrategy + providers: [AuthService, PrismaService, JwtStrategy], // ✅ provide JwtStrategy exports: [AuthService], }) export class AuthModule {} From 77da2131e4e19d2b0283b9683fb4c07edb2f3cc8 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:35:18 -0300 Subject: [PATCH 07/10] chore: removed unused comments from auth.module.ts file --- src/modules/auth/auth.module.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index bf3368b..4848661 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -8,14 +8,14 @@ import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ - PassportModule.register({ defaultStrategy: 'jwt' }), // ✅ registers 'jwt' + PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.register({ - secret: 'secretKey', // replace with process.env.JWT_SECRET in production - signOptions: { expiresIn: '1h' }, // token expiry time + secret: 'secretKey', + signOptions: { expiresIn: '1h' }, }), ], controllers: [AuthController], - providers: [AuthService, PrismaService, JwtStrategy], // ✅ provide JwtStrategy + providers: [AuthService, PrismaService, JwtStrategy], exports: [AuthService], }) export class AuthModule {} From 42b5bafe13b4ef4ef9a4c742e792bf5cd8f06fcf Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:37:51 -0300 Subject: [PATCH 08/10] feat: Global ValidationPipe applied with transformation enabled --- src/main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.ts b/src/main.ts index 648d27d..afb76f9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as dotenv from 'dotenv'; +import { ValidationPipe } from '@nestjs/common'; dotenv.config(); async function bootstrap() { @@ -9,6 +10,8 @@ async function bootstrap() { origin: '*', credentials: true, }); + + app.useGlobalPipes(new ValidationPipe({ transform: true })); await app.listen(3000); } bootstrap(); From ccdf0c23101e2b26027a47f89d23132f2dd676a0 Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Tue, 29 Jul 2025 21:39:01 -0300 Subject: [PATCH 09/10] feat: user module created --- src/modules/users/users.module.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/modules/users/users.module.ts diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts new file mode 100644 index 0000000..f442fd0 --- /dev/null +++ b/src/modules/users/users.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { PrismaService } from 'src/prisma.service'; + +@Module({ + controllers: [UsersController], + providers: [UsersService, PrismaService], +}) +export class UsersModule {} From efcaf804c3413a6829120decb7c4a52fdf41ff8a Mon Sep 17 00:00:00 2001 From: Leticia Dias Date: Wed, 30 Jul 2025 10:42:55 -0300 Subject: [PATCH 10/10] feat: added return only specific fields in findAll() and findOne() functions --- src/modules/users/users.service.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index e7ddbe3..12a7e54 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -12,11 +12,24 @@ export class UsersService { } async findAll() { - return this.prisma.user.findMany({ where: { deletedAt: null } }); + return this.prisma.user.findMany({ + where: { deletedAt: null }, + select: { + name: true, + function: true, + }, + }); } async findOne(id: number) { - const user = await this.prisma.user.findUnique({ where: { id } }); + const user = await this.prisma.user.findUnique({ + where: { id }, + select: { + name: true, + function: true, + posts: { where: { isPublished: true }, select: { title: true } }, + }, + }); if (!user) { throw new NotFoundException('User not found'); @@ -26,12 +39,21 @@ export class UsersService { } async update(id: number, data: UpdateUserDto) { - await this.findOne(id); + const findUser = await this.findOne(id); + + if (!findUser) { + throw new NotFoundException('User not found'); + } + return this.prisma.user.update({ where: { id }, data }); } async remove(id: number) { - await this.findOne(id); + const findUser = await this.findOne(id); + + if (!findUser) { + throw new NotFoundException('User not found'); + } await this.prisma.user.delete({ where: { id } });