Updated backend and frontend to use UUID strings instead of integers for user and role IDs to match PostgreSQL database schema (UUID type). Backend changes: - Updated User and UserPublic interfaces: id and role_id now string (UUID) - Updated JwtPayload: userId and roleId now string - Updated user validators: role_id validation changed from number to UUID string - Removed parseInt() calls in user controller (getUserById, updateUser, deleteUser, changePassword) - Updated all user service function signatures to accept string IDs - Updated create() and update() functions to accept role_id as string Frontend changes: - Updated User interface in users API: role_id is string - Updated CreateUserInput and UpdateUserInput: role_id is string - Added role filter in UsersPage sidebar - Removed number conversion logic (parseInt) This fixes the "invalid input syntax for type uuid" error when creating/updating users.
353 lines
7.8 KiB
TypeScript
353 lines
7.8 KiB
TypeScript
import { Response } from 'express';
|
|
import { AuthenticatedRequest } from '../middleware/auth.middleware';
|
|
import * as userService from '../services/user.service';
|
|
import {
|
|
CreateUserInput,
|
|
UpdateUserInput,
|
|
ChangePasswordInput,
|
|
} from '../validators/user.validator';
|
|
|
|
/**
|
|
* GET /users
|
|
* List all users (admin only)
|
|
* Supports filtering by role_id, is_active, and search
|
|
* Supports pagination with page, limit, sortBy, sortOrder
|
|
*/
|
|
export async function getAllUsers(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
// Parse query parameters for filters
|
|
const filters: userService.UserFilter = {};
|
|
|
|
if (req.query.role_id) {
|
|
filters.role_id = parseInt(req.query.role_id as string, 10);
|
|
}
|
|
|
|
if (req.query.is_active !== undefined) {
|
|
filters.is_active = req.query.is_active === 'true';
|
|
}
|
|
|
|
if (req.query.search) {
|
|
filters.search = req.query.search as string;
|
|
}
|
|
|
|
// Parse pagination parameters
|
|
const pagination = {
|
|
page: parseInt(req.query.page as string, 10) || 1,
|
|
limit: parseInt(req.query.limit as string, 10) || 10,
|
|
sortBy: (req.query.sortBy as string) || 'created_at',
|
|
sortOrder: (req.query.sortOrder as 'asc' | 'desc') || 'desc',
|
|
};
|
|
|
|
const result = await userService.getAll(filters, pagination);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Users retrieved successfully',
|
|
data: result.users,
|
|
pagination: result.pagination,
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to retrieve users';
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /users/:id
|
|
* Get a single user by ID (admin or self)
|
|
*/
|
|
export async function getUserById(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid user ID',
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if user is admin or requesting their own data
|
|
const requestingUser = req.user;
|
|
const isAdmin = requestingUser?.role === 'ADMIN';
|
|
const isSelf = requestingUser?.id === userId;
|
|
|
|
if (!isAdmin && !isSelf) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'Insufficient permissions',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const user = await userService.getById(userId);
|
|
|
|
if (!user) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'User not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'User retrieved successfully',
|
|
data: user,
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to retrieve user';
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /users
|
|
* Create a new user (admin only)
|
|
*/
|
|
export async function createUser(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const data = req.body as CreateUserInput;
|
|
|
|
const user = await userService.create({
|
|
email: data.email,
|
|
password: data.password,
|
|
name: data.name,
|
|
avatar_url: data.avatar_url,
|
|
role_id: data.role_id,
|
|
is_active: data.is_active,
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User created successfully',
|
|
data: user,
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to create user';
|
|
|
|
if (message === 'Email already in use') {
|
|
res.status(409).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /users/:id
|
|
* Update a user (admin can update all fields, regular users can only update limited fields on self)
|
|
*/
|
|
export async function updateUser(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid user ID',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const requestingUser = req.user;
|
|
const isAdmin = requestingUser?.role === 'ADMIN';
|
|
const isSelf = requestingUser?.id === userId;
|
|
|
|
if (!isAdmin && !isSelf) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'Insufficient permissions',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const data = req.body as UpdateUserInput;
|
|
|
|
// Non-admin users can only update their own profile fields (not role_id or is_active)
|
|
if (!isAdmin) {
|
|
if (data.role_id !== undefined || data.is_active !== undefined) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'You can only update your profile information',
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const user = await userService.update(userId, data);
|
|
|
|
if (!user) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'User not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'User updated successfully',
|
|
data: user,
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to update user';
|
|
|
|
if (message === 'Email already in use') {
|
|
res.status(409).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /users/:id
|
|
* Deactivate a user (soft delete, admin only)
|
|
*/
|
|
export async function deleteUser(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid user ID',
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Prevent admin from deleting themselves
|
|
if (req.user?.id === userId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Cannot deactivate your own account',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const deleted = await userService.deleteUser(userId);
|
|
|
|
if (!deleted) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'User not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'User deactivated successfully',
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to deactivate user';
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /users/:id/password
|
|
* Change user password (self only)
|
|
*/
|
|
export async function changePassword(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid user ID',
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Only allow users to change their own password
|
|
if (req.user?.id !== userId) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'You can only change your own password',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const data = req.body as ChangePasswordInput;
|
|
|
|
await userService.changePassword(
|
|
userId,
|
|
data.current_password,
|
|
data.new_password
|
|
);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Password changed successfully',
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to change password';
|
|
|
|
if (message === 'Current password is incorrect') {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message === 'User not found') {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: message,
|
|
});
|
|
}
|
|
}
|