Fix user schema to match database structure
Updated backend to use single 'name' field instead of 'first_name' and 'last_name' to match the actual database schema where users table has a 'name' column. Changes: - Updated User and UserPublic interfaces to use 'name' and 'avatar_url' - Updated user validators to use 'name' instead of first/last names - Updated all SQL queries in user.service.ts to select u.name - Updated search filters and sort columns - Fixed user creation and update operations This resolves the "column u.first_name does not exist" error.
This commit is contained in:
@@ -128,8 +128,8 @@ export async function createUser(
|
||||
const user = await userService.create({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
name: data.name,
|
||||
avatar_url: data.avatar_url,
|
||||
role_id: data.role_id,
|
||||
is_active: data.is_active,
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ router.get('/:id', userController.getUserById);
|
||||
/**
|
||||
* POST /users
|
||||
* Create a new user (admin only)
|
||||
* Body: { email, password, first_name, last_name, role_id, is_active? }
|
||||
* Body: { email, password, name, avatar_url?, role_id, is_active? }
|
||||
* Response: { success, message, data: User }
|
||||
*/
|
||||
router.post('/', requireRole('ADMIN'), validateCreateUser, userController.createUser);
|
||||
@@ -40,7 +40,7 @@ router.post('/', requireRole('ADMIN'), validateCreateUser, userController.create
|
||||
/**
|
||||
* PUT /users/:id
|
||||
* Update a user (admin can update all, self can update limited fields)
|
||||
* Body: { email?, first_name?, last_name?, role_id?, is_active? }
|
||||
* Body: { email?, name?, avatar_url?, role_id?, is_active? }
|
||||
* Response: { success, message, data: User }
|
||||
*/
|
||||
router.put('/:id', validateUpdateUser, userController.updateUser);
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function getAll(
|
||||
|
||||
if (filters?.search) {
|
||||
conditions.push(
|
||||
`(u.first_name ILIKE $${paramIndex} OR u.last_name ILIKE $${paramIndex} OR u.email ILIKE $${paramIndex})`
|
||||
`(u.name ILIKE $${paramIndex} OR u.email ILIKE $${paramIndex})`
|
||||
);
|
||||
params.push(`%${filters.search}%`);
|
||||
paramIndex++;
|
||||
@@ -70,7 +70,7 @@ export async function getAll(
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
|
||||
// Validate sortBy to prevent SQL injection
|
||||
const allowedSortColumns = ['created_at', 'updated_at', 'email', 'first_name', 'last_name'];
|
||||
const allowedSortColumns = ['created_at', 'updated_at', 'email', 'name'];
|
||||
const safeSortBy = allowedSortColumns.includes(sortBy) ? sortBy : 'created_at';
|
||||
const safeSortOrder = sortOrder === 'asc' ? 'ASC' : 'DESC';
|
||||
|
||||
@@ -88,8 +88,8 @@ export async function getAll(
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
u.name,
|
||||
u.avatar_url,
|
||||
u.role_id,
|
||||
r.name as role_name,
|
||||
r.description as role_description,
|
||||
@@ -109,8 +109,8 @@ export async function getAll(
|
||||
const users: UserPublic[] = usersResult.rows.map((row) => ({
|
||||
id: row.id,
|
||||
email: row.email,
|
||||
first_name: row.first_name,
|
||||
last_name: row.last_name,
|
||||
name: row.name,
|
||||
avatar_url: row.avatar_url,
|
||||
role_id: row.role_id,
|
||||
role: row.role_name
|
||||
? {
|
||||
@@ -154,8 +154,8 @@ export async function getById(id: number): Promise<UserPublic | null> {
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
u.name,
|
||||
u.avatar_url,
|
||||
u.role_id,
|
||||
r.name as role_name,
|
||||
r.description as role_description,
|
||||
@@ -179,8 +179,8 @@ export async function getById(id: number): Promise<UserPublic | null> {
|
||||
return {
|
||||
id: row.id,
|
||||
email: row.email,
|
||||
first_name: row.first_name,
|
||||
last_name: row.last_name,
|
||||
name: row.name,
|
||||
avatar_url: row.avatar_url,
|
||||
role_id: row.role_id,
|
||||
role: row.role_name
|
||||
? {
|
||||
@@ -232,8 +232,8 @@ export async function getByEmail(email: string): Promise<User | null> {
|
||||
export async function create(data: {
|
||||
email: string;
|
||||
password: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
name: string;
|
||||
avatar_url?: string | null;
|
||||
role_id: number;
|
||||
is_active?: boolean;
|
||||
}): Promise<UserPublic> {
|
||||
@@ -248,15 +248,15 @@ export async function create(data: {
|
||||
|
||||
const result = await query(
|
||||
`
|
||||
INSERT INTO users (email, password_hash, first_name, last_name, role_id, is_active)
|
||||
INSERT INTO users (email, password_hash, name, avatar_url, role_id, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, email, first_name, last_name, role_id, is_active, last_login, created_at, updated_at
|
||||
RETURNING id, email, name, avatar_url, role_id, is_active, last_login, created_at, updated_at
|
||||
`,
|
||||
[
|
||||
data.email.toLowerCase(),
|
||||
password_hash,
|
||||
data.first_name,
|
||||
data.last_name,
|
||||
data.name,
|
||||
data.avatar_url ?? null,
|
||||
data.role_id,
|
||||
data.is_active ?? true,
|
||||
]
|
||||
@@ -278,8 +278,8 @@ export async function update(
|
||||
id: number,
|
||||
data: {
|
||||
email?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
name?: string;
|
||||
avatar_url?: string | null;
|
||||
role_id?: number;
|
||||
is_active?: boolean;
|
||||
}
|
||||
@@ -309,15 +309,15 @@ export async function update(
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.first_name !== undefined) {
|
||||
updates.push(`first_name = $${paramIndex}`);
|
||||
params.push(data.first_name);
|
||||
if (data.name !== undefined) {
|
||||
updates.push(`name = $${paramIndex}`);
|
||||
params.push(data.name);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.last_name !== undefined) {
|
||||
updates.push(`last_name = $${paramIndex}`);
|
||||
params.push(data.last_name);
|
||||
if (data.avatar_url !== undefined) {
|
||||
updates.push(`avatar_url = $${paramIndex}`);
|
||||
params.push(data.avatar_url);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
password_hash: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
name: string;
|
||||
avatar_url: string | null;
|
||||
role_id: number;
|
||||
role?: Role;
|
||||
is_active: boolean;
|
||||
@@ -39,8 +39,8 @@ export interface User {
|
||||
export interface UserPublic {
|
||||
id: number;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
name: string;
|
||||
avatar_url: string | null;
|
||||
role_id: number;
|
||||
role?: Role;
|
||||
is_active: boolean;
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Request, Response, NextFunction } from 'express';
|
||||
* Schema for creating a new user
|
||||
* - email: required, must be valid email format
|
||||
* - password: required, minimum 8 characters
|
||||
* - name: required (first_name + last_name combined or separate)
|
||||
* - name: required, full name
|
||||
* - avatar_url: optional, URL to avatar image
|
||||
* - role_id: required, must be valid UUID
|
||||
* - is_active: optional, defaults to true
|
||||
*/
|
||||
@@ -17,14 +18,15 @@ export const createUserSchema = z.object({
|
||||
password: z
|
||||
.string({ required_error: 'Password is required' })
|
||||
.min(8, 'Password must be at least 8 characters'),
|
||||
first_name: z
|
||||
.string({ required_error: 'First name is required' })
|
||||
.min(1, 'First name cannot be empty')
|
||||
.max(100, 'First name cannot exceed 100 characters'),
|
||||
last_name: z
|
||||
.string({ required_error: 'Last name is required' })
|
||||
.min(1, 'Last name cannot be empty')
|
||||
.max(100, 'Last name cannot exceed 100 characters'),
|
||||
name: z
|
||||
.string({ required_error: 'Name is required' })
|
||||
.min(1, 'Name cannot be empty')
|
||||
.max(255, 'Name cannot exceed 255 characters'),
|
||||
avatar_url: z
|
||||
.string()
|
||||
.url('Avatar URL must be a valid URL')
|
||||
.optional()
|
||||
.nullable(),
|
||||
role_id: z
|
||||
.number({ required_error: 'Role ID is required' })
|
||||
.int('Role ID must be an integer')
|
||||
@@ -43,16 +45,16 @@ export const updateUserSchema = z.object({
|
||||
.email('Invalid email format')
|
||||
.transform((val) => val.toLowerCase().trim())
|
||||
.optional(),
|
||||
first_name: z
|
||||
name: z
|
||||
.string()
|
||||
.min(1, 'First name cannot be empty')
|
||||
.max(100, 'First name cannot exceed 100 characters')
|
||||
.min(1, 'Name cannot be empty')
|
||||
.max(255, 'Name cannot exceed 255 characters')
|
||||
.optional(),
|
||||
last_name: z
|
||||
avatar_url: z
|
||||
.string()
|
||||
.min(1, 'Last name cannot be empty')
|
||||
.max(100, 'Last name cannot exceed 100 characters')
|
||||
.optional(),
|
||||
.url('Avatar URL must be a valid URL')
|
||||
.optional()
|
||||
.nullable(),
|
||||
role_id: z
|
||||
.number()
|
||||
.int('Role ID must be an integer')
|
||||
|
||||
Reference in New Issue
Block a user