Create users controllers#6
Conversation
… to accept users credentials compulsorily
| @Get() | ||
| async findAll(@Query('adminId', ParseIntPipe) adminId: number) { | ||
| const isAdmin = await this.usersService.isAdmin(adminId); | ||
| if (!isAdmin) { |
There was a problem hiding this comment.
This should a role guard
|
|
||
| @Get(':id') | ||
| async findById(@Query('adminId', ParseIntPipe) adminId: number, @Param('id', ParseIntPipe) id: number) { | ||
| const isAdmin = await this.usersService.isAdmin(adminId); |
c1b1b2f to
246a9f2
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds a new Users module (controller/service/repository + DTOs and basic tests) and wires it into the existing NestJS application, alongside a small schema tightening for the users table.
Changes:
- Added
UsersController,UsersService,UsersRepository, andUsersModuleundersrc/users/. - Added
CreateUserDtovalidation for user creation. - Updated DB schema to make several user columns non-null and added
jsonwebtokendependency.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/users/users.service.ts | Implements basic user operations and admin check. |
| src/users/users.service.spec.ts | Adds a minimal UsersService test scaffold. |
| src/users/users.repository.ts | Adds Drizzle repository methods for users CRUD reads/inserts. |
| src/users/users.module.ts | Declares Users module with controller/service and DB import. |
| src/users/users.controller.ts | Exposes /users endpoints including “admin-only” reads. |
| src/users/users.controller.spec.ts | Adds a minimal UsersController test scaffold. |
| src/users/dto/users-dto.ts | Introduces DTO + validation rules for user creation. |
| src/transactions/dto/create-transaction.dto.ts | Removes an unused validator import. |
| src/db/schema.ts | Makes user fields non-null and reformats schema definition. |
| src/app.module.ts | Minor formatting-only change. |
| package.json | Adds jsonwebtoken dependency. |
| package-lock.json | Locks jsonwebtoken and transitive dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Get() | ||
| async findAll(@Query('adminId', ParseIntPipe) adminId: number) { | ||
| const isAdmin = await this.usersService.isAdmin(adminId); | ||
| if (!isAdmin) { | ||
| throw new ForbiddenException('Only admins can access this resource'); | ||
| } | ||
| return this.usersService.findAll(); |
There was a problem hiding this comment.
Authorization is based on a client-supplied adminId query param, which can be trivially spoofed (any user can pass an admin's id). Replace this with server-validated authentication (e.g., derive the caller identity from an auth token / session) and a role check, rather than trusting request parameters.
|
|
||
| @Injectable() | ||
| export class UsersService { | ||
| constructor(private readonly usersRepository: UsersRepository) {} | ||
|
|
||
| async create(createUserDto: CreateUserDto) { | ||
| return this.usersRepository.create(createUserDto); |
There was a problem hiding this comment.
CreateUserDto uses the property name passwordHash, but it is validated as a strong password and is passed straight into the password_hash DB column. This strongly suggests the API is accepting a plaintext password but storing it as-is; rename the field to password, hash it in the service, and only persist the hash.
| @Injectable() | |
| export class UsersService { | |
| constructor(private readonly usersRepository: UsersRepository) {} | |
| async create(createUserDto: CreateUserDto) { | |
| return this.usersRepository.create(createUserDto); | |
| import * as bcrypt from 'bcrypt'; | |
| @Injectable() | |
| export class UsersService { | |
| constructor(private readonly usersRepository: UsersRepository) {} | |
| async create(createUserDto: CreateUserDto) { | |
| const hashedPassword = await bcrypt.hash(createUserDto.passwordHash, 10); | |
| const userToCreate = { ...createUserDto, passwordHash: hashedPassword }; | |
| return this.usersRepository.create(userToCreate); |
| .select() | ||
| .from(users); | ||
| } | ||
|
|
||
| //for admin use only | ||
| async findById(id: number) { | ||
| return this.dbClient.select() |
There was a problem hiding this comment.
findAll() selects and returns all user columns, including passwordHash. Even for admin-only endpoints, returning password hashes is unsafe and unnecessary. Select only non-sensitive fields (e.g., id, name, email, userType) or map results to omit passwordHash before returning.
| .select() | |
| .from(users); | |
| } | |
| //for admin use only | |
| async findById(id: number) { | |
| return this.dbClient.select() | |
| .select({ | |
| id: users.id, | |
| name: users.name, | |
| email: users.email, | |
| userType: users.userType, | |
| }) | |
| .from(users); | |
| } | |
| //for admin use only | |
| async findById(id: number) { | |
| return this.dbClient | |
| .select({ | |
| id: users.id, | |
| name: users.name, | |
| email: users.email, | |
| userType: users.userType, | |
| }) |
| return this.dbClient | ||
| .select() | ||
| .from(users); | ||
| } | ||
|
|
||
| //for admin use only | ||
| async findById(id: number) { | ||
| return this.dbClient.select() | ||
| .from(users) | ||
| .where(eq(users.id, id)); |
There was a problem hiding this comment.
findById() selects and returns all user columns, including passwordHash, which should never be exposed via API responses. Restrict the selected columns or strip passwordHash from the returned object(s).
| return this.dbClient | |
| .select() | |
| .from(users); | |
| } | |
| //for admin use only | |
| async findById(id: number) { | |
| return this.dbClient.select() | |
| .from(users) | |
| .where(eq(users.id, id)); | |
| const results = await this.dbClient | |
| .select() | |
| .from(users); | |
| return results.map(({ passwordHash, ...userWithoutPasswordHash }) => userWithoutPasswordHash); | |
| } | |
| //for admin use only | |
| async findById(id: number) { | |
| const results = await this.dbClient | |
| .select() | |
| .from(users) | |
| .where(eq(users.id, id)); | |
| return results.map(({ passwordHash, ...userWithoutPasswordHash }) => userWithoutPasswordHash); |
| passwordHash: text('password_hash'), | ||
|
|
||
| userType: mysqlEnum('user_type', UserType).notNull() | ||
| .default(UserType.USER), // ADMIN or USER sert to user by default |
There was a problem hiding this comment.
The comment says "sert"; correct the typo to "set" ("... set to user by default").
| .default(UserType.USER), // ADMIN or USER sert to user by default | |
| .default(UserType.USER), // ADMIN or USER set to user by default |
|
|
||
|
|
||
| @Module({ | ||
| providers: [UsersService], |
There was a problem hiding this comment.
UsersService injects UsersRepository, but UsersRepository is not registered in UsersModule providers. This will cause Nest to fail to resolve the dependency at runtime; add UsersRepository to the module's providers (and export it if other modules need it).
| @Module({ | |
| providers: [UsersService], | |
| import { UsersRepository } from './users.repository'; | |
| @Module({ | |
| providers: [UsersService, UsersRepository], |
| const module: TestingModule = await Test.createTestingModule({ | ||
| controllers: [UsersController], | ||
| }).compile(); |
There was a problem hiding this comment.
This controller test registers UsersController without providing its UsersService dependency, so Nest will fail to instantiate the controller. Add UsersService to providers (and mock its dependencies) or override the provider with a mock.
| "class-validator": "^0.14.3", | ||
| "dotenv": "^17.2.3", | ||
| "drizzle-orm": "^0.45.1", | ||
| "jsonwebtoken": "^9.0.3", |
There was a problem hiding this comment.
jsonwebtoken was added as a dependency but isn't used anywhere in the repository. If this PR doesn't introduce JWT usage yet, consider removing it to avoid extra supply-chain surface area and keep dependencies minimal.
| "jsonwebtoken": "^9.0.3", |
|
|
||
| describe('UsersService', () => { | ||
| let service: UsersService; | ||
|
|
||
| beforeEach(async () => { | ||
| const module: TestingModule = await Test.createTestingModule({ | ||
| providers: [UsersService], |
There was a problem hiding this comment.
This unit test module only provides UsersService, but UsersService requires UsersRepository. The test will fail to compile unless you provide UsersRepository (and its DB dependency) or mock UsersRepository in the providers array.
| describe('UsersService', () => { | |
| let service: UsersService; | |
| beforeEach(async () => { | |
| const module: TestingModule = await Test.createTestingModule({ | |
| providers: [UsersService], | |
| import { UsersRepository } from './users.repository'; | |
| describe('UsersService', () => { | |
| let service: UsersService; | |
| beforeEach(async () => { | |
| const module: TestingModule = await Test.createTestingModule({ | |
| providers: [ | |
| UsersService, | |
| { | |
| provide: UsersRepository, | |
| useValue: {}, | |
| }, | |
| ], |
##What I did:
I created the users controllers in the users folder.