Convert user and role IDs from number to UUID string
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.
This commit is contained in:
@@ -8,8 +8,8 @@ import { apiClient } from './client';
|
|||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
first_name: string;
|
name: string;
|
||||||
last_name: string;
|
avatar_url: string | null;
|
||||||
role_id: string;
|
role_id: string;
|
||||||
role?: {
|
role?: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -26,17 +26,15 @@ export interface User {
|
|||||||
export interface CreateUserInput {
|
export interface CreateUserInput {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
first_name: string;
|
name: string;
|
||||||
last_name: string;
|
role_id: string;
|
||||||
role_id: number;
|
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUserInput {
|
export interface UpdateUserInput {
|
||||||
email?: string;
|
email?: string;
|
||||||
first_name?: string;
|
name?: string;
|
||||||
last_name?: string;
|
role_id?: string;
|
||||||
role_id?: number;
|
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react";
|
import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react";
|
||||||
import MaterialTable from "@material-table/core";
|
import MaterialTable from "@material-table/core";
|
||||||
|
import { createUser, updateUser, deleteUser, getAllUsers, CreateUserInput, UpdateUserInput, User as ApiUser } from "../api/users";
|
||||||
import { getAllRoles, Role as ApiRole } from "../api/roles";
|
import { getAllRoles, Role as ApiRole } from "../api/roles";
|
||||||
import { createUser, getAllUsers, CreateUserInput, User as ApiUser } from "../api/users";
|
|
||||||
|
interface RoleOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -15,8 +20,7 @@ interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UserForm {
|
interface UserForm {
|
||||||
firstName: string;
|
name: string;
|
||||||
lastName: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
roleId: string;
|
roleId: string;
|
||||||
@@ -28,31 +32,22 @@ export default function UsersPage() {
|
|||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
const [activeUser, setActiveUser] = useState<User | null>(null);
|
const [activeUser, setActiveUser] = useState<User | null>(null);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [selectedRoleFilter, setSelectedRoleFilter] = useState<string>(""); // Filter state
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const [roles, setRoles] = useState<ApiRole[]>([]);
|
const [roles, setRoles] = useState<RoleOption[]>([]);
|
||||||
const [loadingRoles, setLoadingRoles] = useState(true);
|
const [modalRoles, setModalRoles] = useState<ApiRole[]>([]);
|
||||||
const [loadingUsers, setLoadingUsers] = useState(true);
|
const [loadingUsers, setLoadingUsers] = useState(true);
|
||||||
|
const [loadingModalRoles, setLoadingModalRoles] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const emptyUser: UserForm = { firstName: "", lastName: "", email: "", roleId: "", password: "", status: "ACTIVE", createdAt: new Date().toISOString().slice(0,10) };
|
const emptyUser: UserForm = { name: "", email: "", roleId: "", password: "", status: "ACTIVE", createdAt: new Date().toISOString().slice(0,10) };
|
||||||
const [form, setForm] = useState<UserForm>(emptyUser);
|
const [form, setForm] = useState<UserForm>(emptyUser);
|
||||||
|
|
||||||
// Fetch roles and users from API on component mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRoles = async () => {
|
fetchUsers();
|
||||||
try {
|
}, []);
|
||||||
setLoadingRoles(true);
|
|
||||||
const rolesData = await getAllRoles();
|
|
||||||
console.log('Roles fetched:', rolesData);
|
|
||||||
setRoles(rolesData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch roles:', error);
|
|
||||||
} finally {
|
|
||||||
setLoadingRoles(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -60,10 +55,9 @@ export default function UsersPage() {
|
|||||||
const usersResponse = await getAllUsers();
|
const usersResponse = await getAllUsers();
|
||||||
console.log('Users API response:', usersResponse);
|
console.log('Users API response:', usersResponse);
|
||||||
|
|
||||||
// Map API users to UI format
|
|
||||||
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
|
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
|
||||||
id: apiUser.id,
|
id: apiUser.id,
|
||||||
name: `${apiUser.first_name} ${apiUser.last_name}`,
|
name: apiUser.name,
|
||||||
email: apiUser.email,
|
email: apiUser.email,
|
||||||
roleId: apiUser.role_id,
|
roleId: apiUser.role_id,
|
||||||
roleName: apiUser.role?.name || '',
|
roleName: apiUser.role?.name || '',
|
||||||
@@ -71,26 +65,33 @@ export default function UsersPage() {
|
|||||||
createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10)
|
createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('Mapped users:', mappedUsers);
|
|
||||||
setUsers(mappedUsers);
|
setUsers(mappedUsers);
|
||||||
|
|
||||||
|
const uniqueRolesMap = new Map<string, RoleOption>();
|
||||||
|
usersResponse.data.forEach((apiUser: ApiUser) => {
|
||||||
|
if (apiUser.role) {
|
||||||
|
uniqueRolesMap.set(apiUser.role.id, {
|
||||||
|
id: apiUser.role.id,
|
||||||
|
name: apiUser.role.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const uniqueRoles = Array.from(uniqueRolesMap.values());
|
||||||
|
console.log('Unique roles extracted:', uniqueRoles);
|
||||||
|
setRoles(uniqueRoles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch users:', error);
|
console.error('Failed to fetch users:', error);
|
||||||
// Keep empty array on error
|
|
||||||
setUsers([]);
|
setUsers([]);
|
||||||
|
setRoles([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingUsers(false);
|
setLoadingUsers(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchRoles();
|
|
||||||
fetchUsers();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Validation
|
if (!form.name || !form.email || !form.roleId) {
|
||||||
if (!form.firstName || !form.lastName || !form.email || !form.roleId) {
|
|
||||||
setError("Please fill in all required fields");
|
setError("Please fill in all required fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,52 +110,28 @@ export default function UsersPage() {
|
|||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
// TODO: Implement update user
|
const updateData: UpdateUserInput = {
|
||||||
const roleName = roles.find(r => r.id === form.roleId)?.name || "";
|
|
||||||
const fullName = `${form.firstName} ${form.lastName}`;
|
|
||||||
setUsers(prev => prev.map(u => u.id === editingId ? {
|
|
||||||
id: editingId,
|
|
||||||
name: fullName,
|
|
||||||
email: form.email,
|
email: form.email,
|
||||||
roleId: form.roleId,
|
name: form.name.trim(),
|
||||||
roleName,
|
role_id: form.roleId,
|
||||||
status: form.status,
|
|
||||||
createdAt: form.createdAt
|
|
||||||
} : u));
|
|
||||||
} else {
|
|
||||||
// Create new user
|
|
||||||
const roleIdNum = parseInt(form.roleId, 10);
|
|
||||||
|
|
||||||
if (isNaN(roleIdNum)) {
|
|
||||||
setError("Please select a valid role");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createData: CreateUserInput = {
|
|
||||||
email: form.email,
|
|
||||||
password: form.password!,
|
|
||||||
first_name: form.firstName,
|
|
||||||
last_name: form.lastName,
|
|
||||||
role_id: roleIdNum,
|
|
||||||
is_active: form.status === "ACTIVE",
|
is_active: form.status === "ACTIVE",
|
||||||
};
|
};
|
||||||
|
|
||||||
const newUser = await createUser(createData);
|
await updateUser(editingId, updateData);
|
||||||
const roleName = roles.find(r => r.id === form.roleId)?.name || "";
|
} else {
|
||||||
|
const createData: CreateUserInput = {
|
||||||
|
email: form.email,
|
||||||
|
password: form.password!,
|
||||||
|
name: form.name.trim(),
|
||||||
|
role_id: form.roleId,
|
||||||
|
is_active: form.status === "ACTIVE",
|
||||||
|
};
|
||||||
|
|
||||||
// Add the new user to the list
|
await createUser(createData);
|
||||||
const apiUser: ApiUser = newUser;
|
|
||||||
setUsers(prev => [...prev, {
|
|
||||||
id: apiUser.id,
|
|
||||||
name: `${apiUser.first_name} ${apiUser.last_name}`,
|
|
||||||
email: apiUser.email,
|
|
||||||
roleId: apiUser.role_id,
|
|
||||||
roleName: roleName,
|
|
||||||
status: apiUser.is_active ? "ACTIVE" : "INACTIVE",
|
|
||||||
createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10)
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await handleRefresh();
|
||||||
|
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
setForm(emptyUser);
|
setForm(emptyUser);
|
||||||
@@ -167,44 +144,100 @@ export default function UsersPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
|
await fetchUsers();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (!activeUser) return;
|
||||||
|
|
||||||
|
if (!window.confirm(`Are you sure you want to delete user "${activeUser.name}"?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoadingUsers(true);
|
setSaving(true);
|
||||||
const usersResponse = await getAllUsers();
|
await deleteUser(activeUser.id);
|
||||||
const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({
|
|
||||||
id: apiUser.id,
|
await handleRefresh();
|
||||||
name: `${apiUser.first_name} ${apiUser.last_name}`,
|
setActiveUser(null);
|
||||||
email: apiUser.email,
|
|
||||||
roleId: apiUser.role_id,
|
|
||||||
roleName: apiUser.role?.name || '',
|
|
||||||
status: apiUser.is_active ? "ACTIVE" : "INACTIVE",
|
|
||||||
createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10)
|
|
||||||
}));
|
|
||||||
setUsers(mappedUsers);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh users:', error);
|
console.error('Failed to delete user:', error);
|
||||||
|
alert('Failed to delete user. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingUsers(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const fetchModalRoles = async () => {
|
||||||
if (!activeUser) return;
|
try {
|
||||||
setUsers(prev => prev.filter(u => u.id !== activeUser.id));
|
setLoadingModalRoles(true);
|
||||||
setActiveUser(null);
|
const rolesData = await getAllRoles();
|
||||||
|
console.log('Modal roles fetched:', rolesData);
|
||||||
|
setModalRoles(rolesData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch modal roles:', error);
|
||||||
|
} finally {
|
||||||
|
setLoadingModalRoles(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filtered = users.filter(u => u.name.toLowerCase().includes(search.toLowerCase()) || u.email.toLowerCase().includes(search.toLowerCase()));
|
const handleOpenAddModal = () => {
|
||||||
|
setForm(emptyUser);
|
||||||
|
setEditingId(null);
|
||||||
|
setError(null);
|
||||||
|
setShowModal(true);
|
||||||
|
fetchModalRoles();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenEditModal = (user: User) => {
|
||||||
|
setEditingId(user.id);
|
||||||
|
setForm({
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
roleId: user.roleId,
|
||||||
|
status: user.status,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
password: ""
|
||||||
|
});
|
||||||
|
setError(null);
|
||||||
|
setShowModal(true);
|
||||||
|
fetchModalRoles();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter users by search and selected role
|
||||||
|
const filtered = users.filter(u => {
|
||||||
|
const matchesSearch = u.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
u.email.toLowerCase().includes(search.toLowerCase());
|
||||||
|
const matchesRole = !selectedRoleFilter || u.roleId === selectedRoleFilter;
|
||||||
|
return matchesSearch && matchesRole;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-6 p-6 w-full bg-gray-100">
|
<div className="flex gap-6 p-6 w-full bg-gray-100">
|
||||||
{/* LEFT INFO SIDEBAR */}
|
{/* LEFT INFO SIDEBAR */}
|
||||||
<div className="w-72 bg-white rounded-xl shadow p-4">
|
<div className="w-72 bg-white rounded-xl shadow p-4">
|
||||||
<h3 className="text-xs font-semibold text-gray-500 mb-3">Project Information</h3>
|
<h3 className="text-xs font-semibold text-gray-500 mb-3">Filter Options</h3>
|
||||||
<p className="text-sm text-gray-700">Usuarios disponibles y sus roles.</p>
|
<p className="text-sm text-gray-700 mb-4">Filter users by role</p>
|
||||||
<select value={form.roleId} onChange={e => setForm({...form, roleId: e.target.value})} className="w-full border px-3 py-2 rounded mt-2" disabled={loadingRoles}>
|
|
||||||
<option value="">{loadingRoles ? "Loading roles..." : "Select Role"}</option>
|
<label className="text-xs font-semibold text-gray-500 mb-2 block">Role</label>
|
||||||
|
<select
|
||||||
|
value={selectedRoleFilter}
|
||||||
|
onChange={e => setSelectedRoleFilter(e.target.value)}
|
||||||
|
className="w-full border px-3 py-2 rounded"
|
||||||
|
disabled={loadingUsers}
|
||||||
|
>
|
||||||
|
<option value="">All Roles</option>
|
||||||
{roles.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
|
{roles.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
{selectedRoleFilter && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedRoleFilter("")}
|
||||||
|
className="mt-2 text-xs text-blue-600 hover:text-blue-800"
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MAIN */}
|
{/* MAIN */}
|
||||||
@@ -217,23 +250,12 @@ export default function UsersPage() {
|
|||||||
<p className="text-sm text-blue-100">Usuarios registrados</p>
|
<p className="text-sm text-blue-100">Usuarios registrados</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button onClick={() => { setForm(emptyUser); setEditingId(null); setShowModal(true); }} className="flex items-center gap-2 px-4 py-2 bg-white text-[#4c5f9e] rounded-lg"><Plus size={16} /> Add</button>
|
<button onClick={handleOpenAddModal} className="flex items-center gap-2 px-4 py-2 bg-white text-[#4c5f9e] rounded-lg"><Plus size={16} /> Add</button>
|
||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
if(!activeUser) return;
|
if(!activeUser) return;
|
||||||
const [firstName = "", lastName = ""] = activeUser.name.split(" ");
|
handleOpenEditModal(activeUser);
|
||||||
setEditingId(activeUser.id);
|
|
||||||
setForm({
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email: activeUser.email,
|
|
||||||
roleId: activeUser.roleId,
|
|
||||||
status: activeUser.status,
|
|
||||||
createdAt: activeUser.createdAt,
|
|
||||||
password: ""
|
|
||||||
});
|
|
||||||
setShowModal(true);
|
|
||||||
}} disabled={!activeUser} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"><Pencil size={16}/> Edit</button>
|
}} disabled={!activeUser} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"><Pencil size={16}/> Edit</button>
|
||||||
<button onClick={handleDelete} disabled={!activeUser} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"><Trash2 size={16}/> Delete</button>
|
<button onClick={handleDelete} disabled={!activeUser || saving} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"><Trash2 size={16}/> Delete</button>
|
||||||
<button onClick={handleRefresh} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg" disabled={loadingUsers}><RefreshCcw size={16}/> Refresh</button>
|
<button onClick={handleRefresh} className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg" disabled={loadingUsers}><RefreshCcw size={16}/> Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -277,17 +299,9 @@ export default function UsersPage() {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
placeholder="First Name *"
|
placeholder="Full Name *"
|
||||||
value={form.firstName}
|
value={form.name}
|
||||||
onChange={e => setForm({...form, firstName: e.target.value})}
|
onChange={e => setForm({...form, name: e.target.value})}
|
||||||
disabled={saving}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Last Name *"
|
|
||||||
value={form.lastName}
|
|
||||||
onChange={e => setForm({...form, lastName: e.target.value})}
|
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -315,10 +329,10 @@ export default function UsersPage() {
|
|||||||
value={form.roleId}
|
value={form.roleId}
|
||||||
onChange={e => setForm({...form, roleId: e.target.value})}
|
onChange={e => setForm({...form, roleId: e.target.value})}
|
||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
disabled={loadingRoles || saving}
|
disabled={loadingModalRoles || saving}
|
||||||
>
|
>
|
||||||
<option value="">Select Role *</option>
|
<option value="">{loadingModalRoles ? "Loading roles..." : "Select Role *"}</option>
|
||||||
{roles.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
|
{modalRoles.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -340,7 +354,7 @@ export default function UsersPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="bg-[#4c5f9e] text-white px-4 py-2 rounded disabled:opacity-50"
|
className="bg-[#4c5f9e] text-white px-4 py-2 rounded disabled:opacity-50"
|
||||||
disabled={saving || loadingRoles}
|
disabled={saving || loadingModalRoles}
|
||||||
>
|
>
|
||||||
{saving ? "Saving..." : "Save"}
|
{saving ? "Saving..." : "Save"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ export async function getUserById(
|
|||||||
res: Response
|
res: Response
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const userId = parseInt(req.params.id, 10);
|
const userId = req.params.id;
|
||||||
|
|
||||||
if (isNaN(userId)) {
|
if (!userId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Invalid user ID',
|
error: 'Invalid user ID',
|
||||||
@@ -80,7 +80,7 @@ export async function getUserById(
|
|||||||
// Check if user is admin or requesting their own data
|
// Check if user is admin or requesting their own data
|
||||||
const requestingUser = req.user;
|
const requestingUser = req.user;
|
||||||
const isAdmin = requestingUser?.role === 'ADMIN';
|
const isAdmin = requestingUser?.role === 'ADMIN';
|
||||||
const isSelf = requestingUser?.id === userId.toString();
|
const isSelf = requestingUser?.id === userId;
|
||||||
|
|
||||||
if (!isAdmin && !isSelf) {
|
if (!isAdmin && !isSelf) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
@@ -166,9 +166,9 @@ export async function updateUser(
|
|||||||
res: Response
|
res: Response
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const userId = parseInt(req.params.id, 10);
|
const userId = req.params.id;
|
||||||
|
|
||||||
if (isNaN(userId)) {
|
if (!userId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Invalid user ID',
|
error: 'Invalid user ID',
|
||||||
@@ -178,7 +178,7 @@ export async function updateUser(
|
|||||||
|
|
||||||
const requestingUser = req.user;
|
const requestingUser = req.user;
|
||||||
const isAdmin = requestingUser?.role === 'ADMIN';
|
const isAdmin = requestingUser?.role === 'ADMIN';
|
||||||
const isSelf = requestingUser?.id === userId.toString();
|
const isSelf = requestingUser?.id === userId;
|
||||||
|
|
||||||
if (!isAdmin && !isSelf) {
|
if (!isAdmin && !isSelf) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
@@ -243,9 +243,9 @@ export async function deleteUser(
|
|||||||
res: Response
|
res: Response
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const userId = parseInt(req.params.id, 10);
|
const userId = req.params.id;
|
||||||
|
|
||||||
if (isNaN(userId)) {
|
if (!userId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Invalid user ID',
|
error: 'Invalid user ID',
|
||||||
@@ -254,7 +254,7 @@ export async function deleteUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent admin from deleting themselves
|
// Prevent admin from deleting themselves
|
||||||
if (req.user?.id === userId.toString()) {
|
if (req.user?.id === userId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Cannot deactivate your own account',
|
error: 'Cannot deactivate your own account',
|
||||||
@@ -294,9 +294,9 @@ export async function changePassword(
|
|||||||
res: Response
|
res: Response
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const userId = parseInt(req.params.id, 10);
|
const userId = req.params.id;
|
||||||
|
|
||||||
if (isNaN(userId)) {
|
if (!userId) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Invalid user ID',
|
error: 'Invalid user ID',
|
||||||
@@ -305,7 +305,7 @@ export async function changePassword(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only allow users to change their own password
|
// Only allow users to change their own password
|
||||||
if (req.user?.id !== userId.toString()) {
|
if (req.user?.id !== userId) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'You can only change your own password',
|
error: 'You can only change your own password',
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export async function getAll(
|
|||||||
* @param id - User ID
|
* @param id - User ID
|
||||||
* @returns User without password_hash or null if not found
|
* @returns User without password_hash or null if not found
|
||||||
*/
|
*/
|
||||||
export async function getById(id: number): Promise<UserPublic | null> {
|
export async function getById(id: string): Promise<UserPublic | null> {
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -234,7 +234,7 @@ export async function create(data: {
|
|||||||
password: string;
|
password: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar_url?: string | null;
|
avatar_url?: string | null;
|
||||||
role_id: number;
|
role_id: string;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
}): Promise<UserPublic> {
|
}): Promise<UserPublic> {
|
||||||
// Check if email already exists
|
// Check if email already exists
|
||||||
@@ -275,12 +275,12 @@ export async function create(data: {
|
|||||||
* @returns Updated user without password_hash
|
* @returns Updated user without password_hash
|
||||||
*/
|
*/
|
||||||
export async function update(
|
export async function update(
|
||||||
id: number,
|
id: string,
|
||||||
data: {
|
data: {
|
||||||
email?: string;
|
email?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar_url?: string | null;
|
avatar_url?: string | null;
|
||||||
role_id?: number;
|
role_id?: string;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
): Promise<UserPublic | null> {
|
): Promise<UserPublic | null> {
|
||||||
@@ -357,7 +357,7 @@ export async function update(
|
|||||||
* @param id - User ID
|
* @param id - User ID
|
||||||
* @returns True if deleted, false if user not found
|
* @returns True if deleted, false if user not found
|
||||||
*/
|
*/
|
||||||
export async function deleteUser(id: number): Promise<boolean> {
|
export async function deleteUser(id: string): Promise<boolean> {
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`
|
`
|
||||||
UPDATE users
|
UPDATE users
|
||||||
@@ -379,7 +379,7 @@ export async function deleteUser(id: number): Promise<boolean> {
|
|||||||
* @returns True if password changed, throws error if verification fails
|
* @returns True if password changed, throws error if verification fails
|
||||||
*/
|
*/
|
||||||
export async function changePassword(
|
export async function changePassword(
|
||||||
id: number,
|
id: string,
|
||||||
currentPassword: string,
|
currentPassword: string,
|
||||||
newPassword: string
|
newPassword: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
@@ -421,7 +421,7 @@ export async function changePassword(
|
|||||||
* @param id - User ID
|
* @param id - User ID
|
||||||
* @returns True if updated, false if user not found
|
* @returns True if updated, false if user not found
|
||||||
*/
|
*/
|
||||||
export async function updateLastLogin(id: number): Promise<boolean> {
|
export async function updateLastLogin(id: string): Promise<boolean> {
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`
|
`
|
||||||
UPDATE users
|
UPDATE users
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ export interface Role {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
password_hash: string;
|
password_hash: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
role_id: number;
|
role_id: string;
|
||||||
role?: Role;
|
role?: Role;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
last_login: Date | null;
|
last_login: Date | null;
|
||||||
@@ -37,11 +37,11 @@ export interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserPublic {
|
export interface UserPublic {
|
||||||
id: number;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
role_id: number;
|
role_id: string;
|
||||||
role?: Role;
|
role?: Role;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
last_login: Date | null;
|
last_login: Date | null;
|
||||||
@@ -50,9 +50,9 @@ export interface UserPublic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
userId: number;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
roleId: number;
|
roleId: string;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
iat?: number;
|
iat?: number;
|
||||||
exp?: number;
|
exp?: number;
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ export const createUserSchema = z.object({
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
role_id: z
|
role_id: z
|
||||||
.number({ required_error: 'Role ID is required' })
|
.string({ required_error: 'Role ID is required' })
|
||||||
.int('Role ID must be an integer')
|
.uuid('Role ID must be a valid UUID'),
|
||||||
.positive('Role ID must be a positive number'),
|
|
||||||
is_active: z.boolean().default(true),
|
is_active: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,9 +55,8 @@ export const updateUserSchema = z.object({
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
role_id: z
|
role_id: z
|
||||||
.number()
|
.string()
|
||||||
.int('Role ID must be an integer')
|
.uuid('Role ID must be a valid UUID')
|
||||||
.positive('Role ID must be a positive number')
|
|
||||||
.optional(),
|
.optional(),
|
||||||
is_active: z.boolean().optional(),
|
is_active: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user