feat: Implement comprehensive user authentication with JWT, social login, and password management.

This commit is contained in:
pradeepkumar
2025-12-20 19:22:30 +05:30
parent 1812fd3c98
commit 158a617600
26 changed files with 1608 additions and 4 deletions

3
src/users/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './users.module';
export * from './users.service';
export * from './users.controller';

View File

@@ -0,0 +1,117 @@
import {
Controller,
Get,
Param,
Patch,
Body,
Query,
UseGuards,
NotFoundException,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiQuery,
} from '@nestjs/swagger';
import { UsersService } from './users.service';
import { JwtAuthGuard, RolesGuard } from '../auth/guards';
import { Roles } from '../auth/decorators';
import { UserRole, UserStatus } from '@prisma/client';
@ApiTags('Users')
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT-auth')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@Roles(UserRole.ADMIN)
@ApiOperation({ summary: 'Get all users (Admin only)' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiResponse({
status: 200,
description: 'List of users',
schema: {
example: {
success: true,
data: {
users: [
{
id: 'uuid',
email: 'user@example.com',
role: 'USER',
status: 'ACTIVE',
emailVerified: true,
authProvider: 'LOCAL',
createdAt: '2024-01-01T00:00:00.000Z',
lastLoginAt: '2024-01-01T00:00:00.000Z',
profile: {
firstName: 'John',
lastName: 'Doe',
avatar: null,
phone: null,
city: null,
state: null,
country: null,
},
},
],
total: 100,
page: 1,
limit: 10,
totalPages: 10,
},
},
},
})
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 403, description: 'Forbidden - Admin only' })
async findAll(
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
const pageNum = page ? parseInt(page, 10) : 1;
const limitNum = limit ? parseInt(limit, 10) : 10;
return this.usersService.findAll(pageNum, limitNum);
}
@Get(':id')
@Roles(UserRole.ADMIN)
@ApiOperation({ summary: 'Get user by ID (Admin only)' })
@ApiResponse({
status: 200,
description: 'User details',
})
@ApiResponse({ status: 404, description: 'User not found' })
async findOne(@Param('id') id: string) {
const user = await this.usersService.findOne(id);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
}
@Patch(':id/status')
@Roles(UserRole.ADMIN)
@ApiOperation({ summary: 'Update user status (Admin only)' })
@ApiResponse({
status: 200,
description: 'User status updated',
})
async updateStatus(
@Param('id') id: string,
@Body('status') status: UserStatus,
) {
const user = await this.usersService.updateStatus(id, status);
return {
id: user.id,
status: user.status,
message: 'User status updated successfully',
};
}
}

12
src/users/users.module.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,88 @@
import { Injectable } from '@nestjs/common';
import { UserStatus, Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
type UserWithProfile = Prisma.UserGetPayload<{
include: { userProfile: true };
}>;
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async findAll(page = 1, limit = 10) {
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
this.prisma.user.findMany({
skip,
take: limit,
orderBy: { createdAt: 'desc' },
include: {
userProfile: true,
},
}) as Promise<UserWithProfile[]>,
this.prisma.user.count(),
]);
return {
users: users.map((user) => ({
id: user.id,
email: user.email,
role: user.role,
status: user.status,
emailVerified: user.emailVerified,
authProvider: user.authProvider,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
profile: user.userProfile
? {
firstName: user.userProfile.firstName,
lastName: user.userProfile.lastName,
avatar: user.userProfile.avatar,
phone: user.userProfile.phone,
city: user.userProfile.city,
state: user.userProfile.state,
country: user.userProfile.country,
}
: null,
})),
total,
page,
limit,
totalPages: Math.ceil(total / limit),
};
}
async findOne(id: string) {
const user = (await this.prisma.user.findUnique({
where: { id },
include: {
userProfile: true,
},
})) as UserWithProfile | null;
if (!user) {
return null;
}
return {
id: user.id,
email: user.email,
role: user.role,
status: user.status,
emailVerified: user.emailVerified,
authProvider: user.authProvider,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
profile: user.userProfile,
};
}
async updateStatus(id: string, status: UserStatus) {
return this.prisma.user.update({
where: { id },
data: { status },
});
}
}