Implement Phase 2 features: roles, assessment groups, budget import, Kanban
- Add hierarchical roles: SuperUser Admin (is_superadmin flag), Tenant Admin, Tenant User with separate /admin route and admin panel - Add Assessment Groups module for property type-based assessment rates (SFHs, Condos, Estate Lots with different regular/special rates) - Enhance Chart of Accounts: initial balance on create (with journal entry), archive/restore accounts, edit all fields including account number & fund type - Add Budget CSV import with downloadable template and account mapping - Add Capital Projects Kanban board with drag-and-drop between year columns, table/kanban view toggle, and PDF export via browser print - Update seed data with assessment groups, second test user, superadmin flag - Create repeatable reseed.sh script for clean database population - Fix AgingReportPage Mantine v7 Table prop compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
45
backend/src/modules/auth/admin.controller.ts
Normal file
45
backend/src/modules/auth/admin.controller.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Controller, Get, Post, Body, Param, UseGuards, Req, ForbiddenException } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||
import { UsersService } from '../users/users.service';
|
||||
|
||||
@ApiTags('admin')
|
||||
@Controller('admin')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class AdminController {
|
||||
constructor(private usersService: UsersService) {}
|
||||
|
||||
private async requireSuperadmin(req: any) {
|
||||
const user = await this.usersService.findById(req.user.userId || req.user.sub);
|
||||
if (!user?.isSuperadmin) {
|
||||
throw new ForbiddenException('SuperUser Admin access required');
|
||||
}
|
||||
}
|
||||
|
||||
@Get('users')
|
||||
async listUsers(@Req() req: any) {
|
||||
await this.requireSuperadmin(req);
|
||||
const users = await this.usersService.findAllUsers();
|
||||
return users.map(u => ({
|
||||
id: u.id, email: u.email, firstName: u.firstName, lastName: u.lastName,
|
||||
isSuperadmin: u.isSuperadmin, lastLoginAt: u.lastLoginAt, createdAt: u.createdAt,
|
||||
organizations: u.userOrganizations?.map(uo => ({
|
||||
id: uo.organizationId, name: uo.organization?.name, role: uo.role,
|
||||
})) || [],
|
||||
}));
|
||||
}
|
||||
|
||||
@Get('organizations')
|
||||
async listOrganizations(@Req() req: any) {
|
||||
await this.requireSuperadmin(req);
|
||||
return this.usersService.findAllOrganizations();
|
||||
}
|
||||
|
||||
@Post('users/:id/superadmin')
|
||||
async toggleSuperadmin(@Req() req: any, @Param('id') id: string, @Body() body: { isSuperadmin: boolean }) {
|
||||
await this.requireSuperadmin(req);
|
||||
await this.usersService.setSuperadmin(id, body.isSuperadmin);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AdminController } from './admin.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { LocalStrategy } from './strategies/local.strategy';
|
||||
@@ -21,7 +22,7 @@ import { UsersModule } from '../users/users.module';
|
||||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
controllers: [AuthController, AdminController],
|
||||
providers: [AuthService, JwtStrategy, LocalStrategy],
|
||||
exports: [AuthService],
|
||||
})
|
||||
|
||||
@@ -109,6 +109,7 @@ export class AuthService {
|
||||
const payload: Record<string, any> = {
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
isSuperadmin: user.isSuperadmin || false,
|
||||
};
|
||||
|
||||
if (defaultOrg) {
|
||||
@@ -124,6 +125,7 @@ export class AuthService {
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
isSuperadmin: user.isSuperadmin || false,
|
||||
},
|
||||
organizations: orgs.map((uo) => ({
|
||||
id: uo.organizationId,
|
||||
|
||||
@@ -20,6 +20,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
orgId: payload.orgId,
|
||||
orgSchema: payload.orgSchema,
|
||||
role: payload.role,
|
||||
isSuperadmin: payload.isSuperadmin || false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user