Projects view by user

This commit is contained in:
2026-02-02 01:27:15 -06:00
parent 1d278936b1
commit 46aab5fbba
7 changed files with 70 additions and 14 deletions

View File

@@ -17,6 +17,7 @@ export interface User {
description: string; description: string;
permissions: Record<string, Record<string, boolean>>; permissions: Record<string, Record<string, boolean>>;
}; };
project_id: string | null;
is_active: boolean; is_active: boolean;
last_login: string | null; last_login: string | null;
created_at: string; created_at: string;
@@ -28,6 +29,7 @@ export interface CreateUserInput {
password: string; password: string;
name: string; name: string;
role_id: string; role_id: string;
project_id?: string | null;
is_active?: boolean; is_active?: boolean;
} }
@@ -35,6 +37,7 @@ export interface UpdateUserInput {
email?: string; email?: string;
name?: string; name?: string;
role_id?: string; role_id?: string;
project_id?: string | null;
is_active?: boolean; is_active?: boolean;
} }

View File

@@ -16,6 +16,7 @@ interface User {
email: string; email: string;
roleId: string; roleId: string;
roleName: string; roleName: string;
projectId: string | null;
status: "ACTIVE" | "INACTIVE"; status: "ACTIVE" | "INACTIVE";
createdAt: string; createdAt: string;
} }
@@ -65,6 +66,7 @@ export default function UsersPage() {
email: apiUser.email, email: apiUser.email,
roleId: apiUser.role_id, roleId: apiUser.role_id,
roleName: apiUser.role?.name || '', roleName: apiUser.role?.name || '',
projectId: apiUser.project_id || null,
status: apiUser.is_active ? "ACTIVE" : "INACTIVE", status: apiUser.is_active ? "ACTIVE" : "INACTIVE",
createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10) createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10)
})); }));
@@ -126,6 +128,7 @@ export default function UsersPage() {
email: form.email, email: form.email,
name: form.name.trim(), name: form.name.trim(),
role_id: form.roleId, role_id: form.roleId,
project_id: form.projectId || null,
is_active: form.status === "ACTIVE", is_active: form.status === "ACTIVE",
}; };
@@ -136,6 +139,7 @@ export default function UsersPage() {
password: form.password!, password: form.password!,
name: form.name.trim(), name: form.name.trim(),
role_id: form.roleId, role_id: form.roleId,
project_id: form.projectId || null,
is_active: form.status === "ACTIVE", is_active: form.status === "ACTIVE",
}; };
@@ -221,7 +225,7 @@ export default function UsersPage() {
name: user.name, name: user.name,
email: user.email, email: user.email,
roleId: user.roleId, roleId: user.roleId,
projectId: "", projectId: user.projectId || "",
status: user.status, status: user.status,
createdAt: user.createdAt, createdAt: user.createdAt,
password: "" password: ""

View File

@@ -1,4 +1,5 @@
import { Request, Response } from 'express'; import { Response } from 'express';
import { AuthenticatedRequest } from '../types';
import * as concentratorService from '../services/concentrator.service'; import * as concentratorService from '../services/concentrator.service';
import { CreateConcentratorInput, UpdateConcentratorInput } from '../validators/concentrator.validator'; import { CreateConcentratorInput, UpdateConcentratorInput } from '../validators/concentrator.validator';
@@ -7,13 +8,14 @@ import { CreateConcentratorInput, UpdateConcentratorInput } from '../validators/
* Get all concentrators with optional filters and pagination * Get all concentrators with optional filters and pagination
* Query params: project_id, status, page, limit, sortBy, sortOrder * Query params: project_id, status, page, limit, sortBy, sortOrder
*/ */
export async function getAll(req: Request, res: Response): Promise<void> { export async function getAll(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { project_id, status, page, limit, sortBy, sortOrder } = req.query; const { project_id, status, type, page, limit, sortBy, sortOrder } = req.query;
const filters: concentratorService.ConcentratorFilters = {}; const filters: concentratorService.ConcentratorFilters = {};
if (project_id) filters.project_id = project_id as string; if (project_id) filters.project_id = project_id as string;
if (status) filters.status = status as string; if (status) filters.status = status as string;
if (type) filters.type = type as concentratorService.ConcentratorType;
const pagination: concentratorService.PaginationOptions = { const pagination: concentratorService.PaginationOptions = {
page: page ? parseInt(page as string, 10) : 1, page: page ? parseInt(page as string, 10) : 1,
@@ -22,7 +24,13 @@ export async function getAll(req: Request, res: Response): Promise<void> {
sortOrder: sortOrder as 'asc' | 'desc', sortOrder: sortOrder as 'asc' | 'desc',
}; };
const result = await concentratorService.getAll(filters, pagination); // Pass user info for role-based filtering
const requestingUser = req.user ? {
roleName: req.user.roleName,
projectId: req.user.projectId
} : undefined;
const result = await concentratorService.getAll(filters, pagination, requestingUser);
res.status(200).json({ res.status(200).json({
success: true, success: true,

View File

@@ -8,7 +8,7 @@ import * as readingService from '../services/reading.service';
* List all meters with pagination and optional filtering * List all meters with pagination and optional filtering
* Query params: page, pageSize, concentrator_id, project_id, status, type, search * Query params: page, pageSize, concentrator_id, project_id, status, type, search
*/ */
export async function getAll(req: Request, res: Response): Promise<void> { export async function getAll(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const page = parseInt(req.query.page as string, 10) || 1; const page = parseInt(req.query.page as string, 10) || 1;
const pageSize = Math.min(parseInt(req.query.pageSize as string, 10) || 50, 1000); const pageSize = Math.min(parseInt(req.query.pageSize as string, 10) || 50, 1000);
@@ -35,7 +35,13 @@ export async function getAll(req: Request, res: Response): Promise<void> {
filters.search = req.query.search as string; filters.search = req.query.search as string;
} }
const result = await meterService.getAll(filters, { page, pageSize }); // Pass user info for role-based filtering
const requestingUser = req.user ? {
roleName: req.user.roleName,
projectId: req.user.projectId
} : undefined;
const result = await meterService.getAll(filters, { page, pageSize }, requestingUser);
res.status(200).json({ res.status(200).json({
success: true, success: true,

View File

@@ -68,11 +68,13 @@ export interface PaginatedResult<T> {
* Get all concentrators with optional filters and pagination * Get all concentrators with optional filters and pagination
* @param filters - Optional filter criteria * @param filters - Optional filter criteria
* @param pagination - Optional pagination options * @param pagination - Optional pagination options
* @param requestingUser - User making the request (for role-based filtering)
* @returns Paginated list of concentrators * @returns Paginated list of concentrators
*/ */
export async function getAll( export async function getAll(
filters?: ConcentratorFilters, filters?: ConcentratorFilters,
pagination?: PaginationOptions pagination?: PaginationOptions,
requestingUser?: { roleName: string; projectId?: string | null }
): Promise<PaginatedResult<Concentrator>> { ): Promise<PaginatedResult<Concentrator>> {
const page = pagination?.page || 1; const page = pagination?.page || 1;
const limit = pagination?.limit || 10; const limit = pagination?.limit || 10;
@@ -85,7 +87,15 @@ export async function getAll(
const params: unknown[] = []; const params: unknown[] = [];
let paramIndex = 1; let paramIndex = 1;
if (filters?.project_id) { // Role-based filtering: OPERATOR users can only see their assigned project
if (requestingUser && requestingUser.roleName !== 'ADMIN' && requestingUser.projectId) {
conditions.push(`project_id = $${paramIndex}`);
params.push(requestingUser.projectId);
paramIndex++;
}
// Additional filter by project_id (only applies if user is ADMIN or no user context)
if (filters?.project_id && (!requestingUser || requestingUser.roleName === 'ADMIN')) {
conditions.push(`project_id = $${paramIndex}`); conditions.push(`project_id = $${paramIndex}`);
params.push(filters.project_id); params.push(filters.project_id);
paramIndex++; paramIndex++;

View File

@@ -92,10 +92,14 @@ export interface UpdateMeterInput {
/** /**
* Get all meters with optional filtering and pagination * Get all meters with optional filtering and pagination
* @param filters - Optional filter criteria
* @param pagination - Optional pagination options
* @param requestingUser - User making the request (for role-based filtering)
*/ */
export async function getAll( export async function getAll(
filters?: MeterFilters, filters?: MeterFilters,
pagination?: PaginationParams pagination?: PaginationParams,
requestingUser?: { roleName: string; projectId?: string | null }
): Promise<PaginatedResult<MeterWithDetails>> { ): Promise<PaginatedResult<MeterWithDetails>> {
const page = pagination?.page || 1; const page = pagination?.page || 1;
const pageSize = pagination?.pageSize || 50; const pageSize = pagination?.pageSize || 50;
@@ -105,13 +109,21 @@ export async function getAll(
const params: unknown[] = []; const params: unknown[] = [];
let paramIndex = 1; let paramIndex = 1;
// Role-based filtering: OPERATOR users can only see meters from their assigned project
if (requestingUser && requestingUser.roleName !== 'ADMIN' && requestingUser.projectId) {
conditions.push(`c.project_id = $${paramIndex}`);
params.push(requestingUser.projectId);
paramIndex++;
}
if (filters?.concentrator_id) { if (filters?.concentrator_id) {
conditions.push(`m.concentrator_id = $${paramIndex}`); conditions.push(`m.concentrator_id = $${paramIndex}`);
params.push(filters.concentrator_id); params.push(filters.concentrator_id);
paramIndex++; paramIndex++;
} }
if (filters?.project_id) { // Additional filter by project_id (only applies if user is ADMIN or no user context)
if (filters?.project_id && (!requestingUser || requestingUser.roleName === 'ADMIN')) {
conditions.push(`c.project_id = $${paramIndex}`); conditions.push(`c.project_id = $${paramIndex}`);
params.push(filters.project_id); params.push(filters.project_id);
paramIndex++; paramIndex++;

View File

@@ -93,6 +93,7 @@ export async function getAll(
u.role_id, u.role_id,
r.name as role_name, r.name as role_name,
r.description as role_description, r.description as role_description,
u.project_id,
u.is_active, u.is_active,
u.last_login, u.last_login,
u.created_at, u.created_at,
@@ -122,6 +123,7 @@ export async function getAll(
updated_at: row.updated_at, updated_at: row.updated_at,
} }
: undefined, : undefined,
project_id: row.project_id,
is_active: row.is_active, is_active: row.is_active,
last_login: row.last_login, last_login: row.last_login,
created_at: row.created_at, created_at: row.created_at,
@@ -160,6 +162,7 @@ export async function getById(id: string): Promise<UserPublic | null> {
r.name as role_name, r.name as role_name,
r.description as role_description, r.description as role_description,
r.permissions as role_permissions, r.permissions as role_permissions,
u.project_id,
u.is_active, u.is_active,
u.last_login, u.last_login,
u.created_at, u.created_at,
@@ -192,6 +195,7 @@ export async function getById(id: string): Promise<UserPublic | null> {
updated_at: row.updated_at, updated_at: row.updated_at,
} }
: undefined, : undefined,
project_id: row.project_id,
is_active: row.is_active, is_active: row.is_active,
last_login: row.last_login, last_login: row.last_login,
created_at: row.created_at, created_at: row.created_at,
@@ -235,6 +239,7 @@ export async function create(data: {
name: string; name: string;
avatar_url?: string | null; avatar_url?: string | null;
role_id: string; role_id: string;
project_id?: string | null;
is_active?: boolean; is_active?: boolean;
}): Promise<UserPublic> { }): Promise<UserPublic> {
// Check if email already exists // Check if email already exists
@@ -248,9 +253,9 @@ export async function create(data: {
const result = await query( const result = await query(
` `
INSERT INTO users (email, password_hash, name, avatar_url, role_id, is_active) INSERT INTO users (email, password_hash, name, avatar_url, role_id, project_id, is_active)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, email, name, avatar_url, role_id, is_active, last_login, created_at, updated_at RETURNING id, email, name, avatar_url, role_id, project_id, is_active, last_login, created_at, updated_at
`, `,
[ [
data.email.toLowerCase(), data.email.toLowerCase(),
@@ -258,6 +263,7 @@ export async function create(data: {
data.name, data.name,
data.avatar_url ?? null, data.avatar_url ?? null,
data.role_id, data.role_id,
data.project_id ?? null,
data.is_active ?? true, data.is_active ?? true,
] ]
); );
@@ -281,6 +287,7 @@ export async function update(
name?: string; name?: string;
avatar_url?: string | null; avatar_url?: string | null;
role_id?: string; role_id?: string;
project_id?: string | null;
is_active?: boolean; is_active?: boolean;
} }
): Promise<UserPublic | null> { ): Promise<UserPublic | null> {
@@ -327,6 +334,12 @@ export async function update(
paramIndex++; paramIndex++;
} }
if (data.project_id !== undefined) {
updates.push(`project_id = $${paramIndex}`);
params.push(data.project_id);
paramIndex++;
}
if (data.is_active !== undefined) { if (data.is_active !== undefined) {
updates.push(`is_active = $${paramIndex}`); updates.push(`is_active = $${paramIndex}`);
params.push(data.is_active); params.push(data.is_active);