add user-project relation and role-based filtering
This commit is contained in:
27
water-api/sql/add_user_project_relation.sql
Normal file
27
water-api/sql/add_user_project_relation.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- Add project_id to users table
|
||||||
|
-- This allows assigning a specific project to OPERATOR users
|
||||||
|
-- ADMIN users don't need a project assignment (can see all projects)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Add project_id column to users table
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
|
-- Add index for better query performance
|
||||||
|
CREATE INDEX idx_users_project_id ON users(project_id);
|
||||||
|
|
||||||
|
-- Add comment
|
||||||
|
COMMENT ON COLUMN users.project_id IS 'Assigned project for OPERATOR users. NULL for ADMIN users who can access all projects.';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Verify the change
|
||||||
|
-- ============================================================================
|
||||||
|
SELECT
|
||||||
|
column_name,
|
||||||
|
data_type,
|
||||||
|
is_nullable,
|
||||||
|
column_default
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'users'
|
||||||
|
AND column_name = 'project_id';
|
||||||
@@ -41,6 +41,7 @@ export function authenticateToken(
|
|||||||
email: (decoded as any).email,
|
email: (decoded as any).email,
|
||||||
roleId: (decoded as any).roleId || (decoded as any).role,
|
roleId: (decoded as any).roleId || (decoded as any).role,
|
||||||
roleName: (decoded as any).roleName || (decoded as any).role,
|
roleName: (decoded as any).roleName || (decoded as any).role,
|
||||||
|
projectId: (decoded as any).projectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -55,9 +55,10 @@ export async function login(
|
|||||||
password_hash: string;
|
password_hash: string;
|
||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
role_name: string;
|
role_name: string;
|
||||||
|
project_id: string | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
}>(
|
}>(
|
||||||
`SELECT u.id, u.email, u.name, u.password_hash, u.avatar_url, r.name as role_name, u.created_at
|
`SELECT u.id, u.email, u.name, u.password_hash, u.avatar_url, r.name as role_name, u.project_id, u.created_at
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN roles r ON u.role_id = r.id
|
JOIN roles r ON u.role_id = r.id
|
||||||
WHERE LOWER(u.email) = LOWER($1) AND u.is_active = true
|
WHERE LOWER(u.email) = LOWER($1) AND u.is_active = true
|
||||||
@@ -83,6 +84,7 @@ export async function login(
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
roleId: user.id,
|
roleId: user.id,
|
||||||
roleName: user.role_name,
|
roleName: user.role_name,
|
||||||
|
projectId: user.project_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshToken = generateRefreshToken({
|
const refreshToken = generateRefreshToken({
|
||||||
@@ -170,8 +172,9 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
|||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
role_name: string;
|
role_name: string;
|
||||||
|
project_id: string | null;
|
||||||
}>(
|
}>(
|
||||||
`SELECT u.id, u.email, r.name as role_name
|
`SELECT u.id, u.email, r.name as role_name, u.project_id
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN roles r ON u.role_id = r.id
|
JOIN roles r ON u.role_id = r.id
|
||||||
WHERE u.id = $1 AND u.is_active = true
|
WHERE u.id = $1 AND u.is_active = true
|
||||||
@@ -190,6 +193,7 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
roleId: user.id,
|
roleId: user.id,
|
||||||
roleName: user.role_name,
|
roleName: user.role_name,
|
||||||
|
projectId: user.project_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { accessToken };
|
return { accessToken };
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface User {
|
|||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
role_id: string;
|
role_id: string;
|
||||||
role?: Role;
|
role?: Role;
|
||||||
|
project_id: string | null;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
last_login: Date | null;
|
last_login: Date | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
@@ -43,6 +44,7 @@ export interface UserPublic {
|
|||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
role_id: string;
|
role_id: string;
|
||||||
role?: Role;
|
role?: Role;
|
||||||
|
project_id: string | null;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
last_login: Date | null;
|
last_login: Date | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
@@ -54,6 +56,7 @@ export interface JwtPayload {
|
|||||||
email: string;
|
email: string;
|
||||||
roleId: string;
|
roleId: string;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
|
projectId?: string | null;
|
||||||
iat?: number;
|
iat?: number;
|
||||||
exp?: number;
|
exp?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ export const createUserSchema = z.object({
|
|||||||
role_id: z
|
role_id: z
|
||||||
.string({ required_error: 'Role ID is required' })
|
.string({ required_error: 'Role ID is required' })
|
||||||
.uuid('Role ID must be a valid UUID'),
|
.uuid('Role ID must be a valid UUID'),
|
||||||
|
project_id: z
|
||||||
|
.string()
|
||||||
|
.uuid('Project ID must be a valid UUID')
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
is_active: z.boolean().default(true),
|
is_active: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,6 +63,11 @@ export const updateUserSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.uuid('Role ID must be a valid UUID')
|
.uuid('Role ID must be a valid UUID')
|
||||||
.optional(),
|
.optional(),
|
||||||
|
project_id: z
|
||||||
|
.string()
|
||||||
|
.uuid('Project ID must be a valid UUID')
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
is_active: z.boolean().optional(),
|
is_active: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user