Convert formatUserResponse to async method to support presigned URL generation for avatar_url field. Add getAvatarPresignedUrl helper method to generate 1-hour expiry presigned URLs for user avatars. Update listUsers and getUserById to await formatUserResponse calls.
216 lines
7.4 KiB
TypeScript
216 lines
7.4 KiB
TypeScript
import { prisma } from '../config/database';
|
|
import { Prisma } from '@prisma/client';
|
|
import { config } from '../config';
|
|
import { logger } from '../config/logger';
|
|
import jwt from 'jsonwebtoken';
|
|
import {
|
|
ListUsersResponse,
|
|
GetUserResponse,
|
|
UpdateUser,
|
|
UpdateRoleResponse,
|
|
DeactivateAccountResponse,
|
|
ActivateAccountResponse,
|
|
} from '../types/usersmanagement.types';
|
|
import { UserResponse } from '../types/user.types';
|
|
import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
|
|
import { getPresignedUrl } from '../config/minio';
|
|
|
|
export class UserManagementService {
|
|
async listUsers(): Promise<ListUsersResponse> {
|
|
try {
|
|
const users = await prisma.user.findMany({
|
|
where: {
|
|
is_deactivated: false
|
|
},
|
|
include: {
|
|
profile: true,
|
|
role: true,
|
|
|
|
}
|
|
});
|
|
|
|
return {
|
|
code: 200,
|
|
message: 'Users fetched successfully',
|
|
total: users.length,
|
|
data: await Promise.all(users.map(user => this.formatUserResponse(user)))
|
|
};
|
|
} catch (error) {
|
|
logger.error('Failed to fetch users', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
|
|
async getUserById(id: number): Promise<GetUserResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
include: {
|
|
role: true,
|
|
profile: true
|
|
}
|
|
});
|
|
if (!user) throw new UnauthorizedError('User not found');
|
|
return {
|
|
code: 200,
|
|
message: 'User fetched successfully',
|
|
data: await this.formatUserResponse(user)
|
|
};
|
|
} catch (error) {
|
|
logger.error('Failed to fetch user by ID', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateUserRole(id: number, role_id: number): Promise<UpdateRoleResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({ where: { id } });
|
|
if (!user) throw new UnauthorizedError('User not found');
|
|
|
|
const role = await prisma.role.findUnique({ where: { id: role_id } });
|
|
if (!role) throw new UnauthorizedError('Role not found');
|
|
|
|
await prisma.user.update({
|
|
where: { id },
|
|
data: { role_id }
|
|
});
|
|
|
|
return {
|
|
code: 200,
|
|
message: 'User role updated successfully'
|
|
};
|
|
} catch (error) {
|
|
logger.error('Failed to update user role', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async deleteUser(id: number): Promise<DeactivateAccountResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({ where: { id } });
|
|
if (!user) throw new UnauthorizedError('User not found');
|
|
|
|
await prisma.user.update({
|
|
where: { id },
|
|
data: { is_deactivated: true }
|
|
});
|
|
return {
|
|
code: 200,
|
|
message: 'User deactivated successfully'
|
|
};
|
|
} catch (error) {
|
|
logger.error('Failed to deactivate user', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async deactivateAccount(id: number): Promise<DeactivateAccountResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({ where: { id } });
|
|
if (!user) throw new UnauthorizedError('User not found');
|
|
|
|
if (user.is_deactivated) {
|
|
logger.warn('Deactivate attempt with deactivated account', { userId: user.id });
|
|
throw new ForbiddenError('This account has already been deactivated');
|
|
}
|
|
|
|
// Deactivate account
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { is_deactivated: true }
|
|
});
|
|
|
|
logger.info('Account deactivated successfully', { userId: user.id });
|
|
return {
|
|
code: 200,
|
|
message: 'Account deactivated successfully'
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof jwt.JsonWebTokenError) {
|
|
logger.error('Invalid JWT token:', error);
|
|
throw new UnauthorizedError('Invalid token');
|
|
}
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
logger.error('JWT token expired:', error);
|
|
throw new UnauthorizedError('Token expired');
|
|
}
|
|
logger.error('Failed to deactivate account', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async activateAccount(id: number): Promise<ActivateAccountResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({ where: { id } });
|
|
if (!user) throw new UnauthorizedError('User not found');
|
|
|
|
// Check if account is already activated
|
|
if (!user.is_deactivated) {
|
|
logger.warn('Activate attempt with activated account', { userId: user.id });
|
|
throw new ForbiddenError('This account has already been activated');
|
|
}
|
|
|
|
// Activate account
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { is_deactivated: false }
|
|
});
|
|
|
|
logger.info('Account activated successfully', { userId: user.id });
|
|
return {
|
|
code: 200,
|
|
message: 'Account activated successfully'
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof jwt.JsonWebTokenError) {
|
|
logger.error('Invalid JWT token:', error);
|
|
throw new UnauthorizedError('Invalid token');
|
|
}
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
logger.error('JWT token expired:', error);
|
|
throw new UnauthorizedError('Token expired');
|
|
}
|
|
logger.error('Failed to activate account', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format user response
|
|
*/
|
|
private async formatUserResponse(user: any): Promise<UserResponse> {
|
|
return {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
email_verified_at: user.email_verified_at,
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at,
|
|
role: {
|
|
code: user.role.code,
|
|
name: user.role.name as { th: string; en: string }
|
|
},
|
|
profile: user.profile ? {
|
|
prefix: user.profile.prefix as { th?: string; en?: string } | undefined,
|
|
first_name: user.profile.first_name,
|
|
last_name: user.profile.last_name,
|
|
avatar_url: user.profile.avatar_url ? await this.getAvatarPresignedUrl(user.profile.avatar_url) : null,
|
|
birth_date: user.profile.birth_date,
|
|
phone: user.profile.phone
|
|
} : undefined
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get presigned URL for avatar
|
|
*/
|
|
private async getAvatarPresignedUrl(avatarPath: string): Promise<string> {
|
|
try {
|
|
return await getPresignedUrl(avatarPath, 3600); // 1 hour expiry
|
|
} catch (error) {
|
|
logger.warn(`Failed to generate presigned URL for avatar: ${error}`);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|