Skip to content
53 changes: 53 additions & 0 deletions drips/user/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsEmail,
IsNotEmpty,
IsString,
MinLength,
MaxLength,
} from 'class-validator';

export class CreateUserDto {
@ApiProperty({
description: 'User email address',
example: 'john.doe@example.com',
uniqueItems: true,
})
@IsEmail({}, { message: 'Please provide a valid email address' })
@IsNotEmpty({ message: 'Email is required' })
email: string;

@ApiProperty({
description: 'User password',
example: 'SecureP@ssw0rd',
minLength: 8,
})
@IsString()
@IsNotEmpty({ message: 'Password is required' })
@MinLength(8, { message: 'Password must be at least 8 characters long' })
password: string;

@ApiProperty({
description: 'User first name',
example: 'John',
minLength: 1,
maxLength: 100,
})
@IsString()
@IsNotEmpty({ message: 'First name is required' })
@MinLength(1, { message: 'First name cannot be empty' })
@MaxLength(100, { message: 'First name cannot exceed 100 characters' })
firstName: string;

@ApiProperty({
description: 'User last name',
example: 'Doe',
minLength: 1,
maxLength: 100,
})
@IsString()
@IsNotEmpty({ message: 'Last name is required' })
@MinLength(1, { message: 'Last name cannot be empty' })
@MaxLength(100, { message: 'Last name cannot exceed 100 characters' })
lastName: string;
}
26 changes: 26 additions & 0 deletions drips/user/paginated-users-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ApiProperty } from '@nestjs/swagger';
import { UserResponseDto } from './user-response.dto';

export class PaginatedUsersResponseDto {
@ApiProperty({
description: 'Array of users',
type: [UserResponseDto],
})
data: UserResponseDto[];

@ApiProperty({
description: 'Pagination metadata',
example: {
total: 100,
page: 1,
limit: 10,
totalPages: 10,
},
})
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
31 changes: 31 additions & 0 deletions drips/user/pagination-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsOptional, IsInt, Min, Max } from 'class-validator';

export class PaginationQueryDto {
@ApiPropertyOptional({
description: 'Page number (starts from 1)',
example: 1,
minimum: 1,
default: 1,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;

@ApiPropertyOptional({
description: 'Number of items per page',
example: 10,
minimum: 1,
maximum: 100,
default: 10,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number = 10;
}
47 changes: 47 additions & 0 deletions drips/user/user-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ApiProperty } from '@nestjs/swagger';
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class UserResponseDto {
@ApiProperty({
description: 'User unique identifier',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@Expose()
id: string;

@ApiProperty({
description: 'User email address',
example: 'john.doe@example.com',
})
@Expose()
email: string;

@ApiProperty({
description: 'User first name',
example: 'John',
})
@Expose()
firstName: string;

@ApiProperty({
description: 'User last name',
example: 'Doe',
})
@Expose()
lastName: string;

@ApiProperty({
description: 'User creation timestamp',
example: '2024-01-24T10:30:00.000Z',
})
@Expose()
createdAt: Date;

@ApiProperty({
description: 'User last update timestamp',
example: '2024-01-24T10:30:00.000Z',
})
@Expose()
updatedAt: Date;
}
37 changes: 37 additions & 0 deletions drips/user/user.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm';
import { Exclude } from 'class-transformer';

@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true, length: 255 })
email: string;

@Column({ name: 'password_hash', length: 255 })
@Exclude()
passwordHash: string;

@Column({ name: 'first_name', length: 100 })
firstName: string;

@Column({ name: 'last_name', length: 100 })
lastName: string;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;

@DeleteDateColumn({ name: 'deleted_at', nullable: true })
deletedAt?: Date;
}
174 changes: 174 additions & 0 deletions drips/user/users.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
HttpCode,
HttpStatus,
UseInterceptors,
ClassSerializerInterceptor,
ParseUUIDPipe,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiParam,
ApiQuery,
ApiBadRequestResponse,
ApiNotFoundResponse,
ApiConflictResponse,
} from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PaginationQueryDto } from './dto/pagination-query.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { PaginatedUsersResponseDto } from './dto/paginated-users-response.dto';

@ApiTags('users')
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiOperation({
summary: 'Create a new user',
description: 'Creates a new user with the provided information',
})
@ApiResponse({
status: HttpStatus.CREATED,
description: 'User successfully created',
type: UserResponseDto,
})
@ApiBadRequestResponse({
description: 'Invalid input data',
})
@ApiConflictResponse({
description: 'User with this email already exists',
})
create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
return this.usersService.create(createUserDto);
}

@Get()
@ApiOperation({
summary: 'Get all users',
description: 'Retrieves a paginated list of all users',
})
@ApiQuery({
name: 'page',
required: false,
type: Number,
description: 'Page number (default: 1)',
example: 1,
})
@ApiQuery({
name: 'limit',
required: false,
type: Number,
description: 'Items per page (default: 10, max: 100)',
example: 10,
})
@ApiResponse({
status: HttpStatus.OK,
description: 'List of users retrieved successfully',
type: PaginatedUsersResponseDto,
})
@ApiBadRequestResponse({
description: 'Invalid pagination parameters',
})
findAll(
@Query() paginationQuery: PaginationQueryDto,
): Promise<PaginatedUsersResponseDto> {
return this.usersService.findAll(paginationQuery);
}

@Get(':id')
@ApiOperation({
summary: 'Get user by ID',
description: 'Retrieves a single user by their unique identifier',
})
@ApiParam({
name: 'id',
description: 'User UUID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'User found',
type: UserResponseDto,
})
@ApiNotFoundResponse({
description: 'User not found',
})
@ApiBadRequestResponse({
description: 'Invalid UUID format',
})
findOne(
@Param('id', new ParseUUIDPipe()) id: string,
): Promise<UserResponseDto> {
return this.usersService.findOne(id);
}

@Patch(':id')
@ApiOperation({
summary: 'Update user',
description: 'Updates an existing user with the provided information',
})
@ApiParam({
name: 'id',
description: 'User UUID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'User successfully updated',
type: UserResponseDto,
})
@ApiNotFoundResponse({
description: 'User not found',
})
@ApiBadRequestResponse({
description: 'Invalid input data or UUID format',
})
@ApiConflictResponse({
description: 'Email already exists',
})
update(
@Param('id', new ParseUUIDPipe()) id: string,
@Body() updateUserDto: UpdateUserDto,
): Promise<UserResponseDto> {
return this.usersService.update(id, updateUserDto);
}

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({
summary: 'Delete user',
description: 'Soft deletes a user (marks as deleted but keeps in database)',
})
@ApiParam({
name: 'id',
description: 'User UUID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@ApiResponse({
status: HttpStatus.NO_CONTENT,
description: 'User successfully deleted',
})
@ApiNotFoundResponse({
description: 'User not found',
})
@ApiBadRequestResponse({
description: 'Invalid UUID format',
})
remove(@Param('id', new ParseUUIDPipe()) id: string): Promise<void> {
return this.usersService.remove(id);
}
}
13 changes: 13 additions & 0 deletions drips/user/users.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';

@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Loading
Loading