WIP - admin users and roles

This commit is contained in:
Francisco Gaona
2025-12-30 09:06:42 +01:00
parent 3086f78d34
commit 3fbc019083
8 changed files with 986 additions and 1 deletions

View File

@@ -3,11 +3,12 @@ import { RbacService } from './rbac.service';
import { AbilityFactory } from './ability.factory';
import { AuthorizationService } from './authorization.service';
import { SetupRolesController } from './setup-roles.controller';
import { SetupUsersController } from './setup-users.controller';
import { TenantModule } from '../tenant/tenant.module';
@Module({
imports: [TenantModule],
controllers: [SetupRolesController],
controllers: [SetupRolesController, SetupUsersController],
providers: [RbacService, AbilityFactory, AuthorizationService],
exports: [RbacService, AbilityFactory, AuthorizationService],
})

View File

@@ -1,6 +1,10 @@
import {
Controller,
Get,
Post,
Delete,
Param,
Body,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@@ -20,4 +24,77 @@ export class SetupRolesController {
return await Role.query(knex).select('*').orderBy('name', 'asc');
}
@Get(':id')
async getRole(
@TenantId() tenantId: string,
@Param('id') id: string,
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
return await Role.query(knex).findById(id).withGraphFetched('users');
}
@Post()
async createRole(
@TenantId() tenantId: string,
@Body() data: { name: string; description?: string; guardName?: string },
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
const role = await Role.query(knex).insert({
name: data.name,
description: data.description,
guardName: data.guardName || 'tenant',
});
return role;
}
@Post(':roleId/users')
async addUserToRole(
@TenantId() tenantId: string,
@Param('roleId') roleId: string,
@Body() data: { userId: string },
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
// Check if assignment already exists
const existing = await knex('user_roles')
.where({ userId: data.userId, roleId })
.first();
if (existing) {
return { success: true, message: 'User already assigned' };
}
await knex('user_roles').insert({
id: knex.raw('(UUID())'),
userId: data.userId,
roleId,
created_at: knex.fn.now(),
updated_at: knex.fn.now(),
});
return { success: true };
}
@Delete(':roleId/users/:userId')
async removeUserFromRole(
@TenantId() tenantId: string,
@Param('roleId') roleId: string,
@Param('userId') userId: string,
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
await knex('user_roles')
.where({ userId, roleId })
.delete();
return { success: true };
}
}

View File

@@ -0,0 +1,104 @@
import {
Controller,
Get,
Post,
Delete,
Param,
Body,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { TenantId } from '../tenant/tenant.decorator';
import { TenantDatabaseService } from '../tenant/tenant-database.service';
import { User } from '../models/user.model';
import * as bcrypt from 'bcrypt';
@Controller('setup/users')
@UseGuards(JwtAuthGuard)
export class SetupUsersController {
constructor(private tenantDbService: TenantDatabaseService) {}
@Get()
async getUsers(@TenantId() tenantId: string) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
return await User.query(knex).withGraphFetched('roles');
}
@Get(':id')
async getUser(
@TenantId() tenantId: string,
@Param('id') id: string,
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
return await User.query(knex).findById(id).withGraphFetched('roles');
}
@Post()
async createUser(
@TenantId() tenantId: string,
@Body() data: { email: string; password: string; firstName?: string; lastName?: string },
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
// Hash password
const hashedPassword = await bcrypt.hash(data.password, 10);
const user = await User.query(knex).insert({
email: data.email,
password: hashedPassword,
firstName: data.firstName,
lastName: data.lastName,
isActive: true,
});
return user;
}
@Post(':userId/roles')
async addRoleToUser(
@TenantId() tenantId: string,
@Param('userId') userId: string,
@Body() data: { roleId: string },
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
// Check if assignment already exists
const existing = await knex('user_roles')
.where({ userId, roleId: data.roleId })
.first();
if (existing) {
return { success: true, message: 'Role already assigned' };
}
await knex('user_roles').insert({
id: knex.raw('(UUID())'),
userId,
roleId: data.roleId,
created_at: knex.fn.now(),
updated_at: knex.fn.now(),
});
return { success: true };
}
@Delete(':userId/roles/:roleId')
async removeRoleFromUser(
@TenantId() tenantId: string,
@Param('userId') userId: string,
@Param('roleId') roleId: string,
) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
await knex('user_roles')
.where({ userId, roleId })
.delete();
return { success: true };
}
}