feat: implement QuestionBank CRUD with pagination and template query
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Body,
|
||||
UseGuards,
|
||||
Param,
|
||||
Delete,
|
||||
HttpCode,
|
||||
Patch,
|
||||
ForbiddenException,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { SuperAdminService } from './super-admin.service';
|
||||
import { TenantService } from '../tenant/tenant.service';
|
||||
import { CombinedAuthGuard } from '../auth/combined-auth.guard';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import { Roles } from '../auth/roles.decorator';
|
||||
import { UserRole } from '../user/user-role.enum';
|
||||
import { I18nService } from '../i18n/i18n.service';
|
||||
|
||||
@Controller('v1/tenants')
|
||||
@UseGuards(CombinedAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.SUPER_ADMIN)
|
||||
export class SuperAdminController {
|
||||
constructor(
|
||||
private readonly superAdminService: SuperAdminService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async getTenants(
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.superAdminService.getAllTenants(
|
||||
page ? parseInt(page) : undefined,
|
||||
limit ? parseInt(limit) : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createTenant(
|
||||
@Body()
|
||||
body: {
|
||||
name: string;
|
||||
domain?: string;
|
||||
adminUserId?: string;
|
||||
parentId?: string;
|
||||
},
|
||||
) {
|
||||
return this.superAdminService.createTenant(
|
||||
body.name,
|
||||
body.domain,
|
||||
body.adminUserId,
|
||||
body.parentId,
|
||||
);
|
||||
}
|
||||
|
||||
@Put(':tenantId/admin')
|
||||
async bindTenantAdmin(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Body() body: { userId: string },
|
||||
) {
|
||||
return this.superAdminService.assignUserToTenant(body.userId, tenantId);
|
||||
}
|
||||
|
||||
@Post(':tenantId/admin/new')
|
||||
async createTenantAdmin(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Body() body: { username: string; password?: string },
|
||||
) {
|
||||
return this.superAdminService.createTenantAdmin(
|
||||
tenantId,
|
||||
body.username,
|
||||
body.password,
|
||||
);
|
||||
}
|
||||
|
||||
@Put(':tenantId')
|
||||
async updateTenant(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Body() body: { name?: string; domain?: string; parentId?: string },
|
||||
) {
|
||||
return this.superAdminService.updateTenant(tenantId, body);
|
||||
}
|
||||
|
||||
@Delete(':tenantId')
|
||||
async deleteTenant(@Param('tenantId') tenantId: string) {
|
||||
return this.superAdminService.deleteTenant(tenantId);
|
||||
}
|
||||
|
||||
// --- Member Management ---
|
||||
|
||||
@Get(':tenantId/members')
|
||||
async getMembers(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
const p = page ? parseInt(page) : undefined;
|
||||
const l = limit ? parseInt(limit) : undefined;
|
||||
return this.tenantService.getMembers(tenantId, p, l);
|
||||
}
|
||||
|
||||
@Post(':tenantId/members')
|
||||
async addMember(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Body() body: { userId: string; role?: string },
|
||||
) {
|
||||
return this.tenantService.addMember(tenantId, body.userId, body.role);
|
||||
}
|
||||
|
||||
@Delete(':tenantId/members/:userId')
|
||||
@HttpCode(204)
|
||||
async removeMember(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Param('userId') userId: string,
|
||||
) {
|
||||
await this.tenantService.removeMember(tenantId, userId);
|
||||
}
|
||||
|
||||
@Patch(':tenantId/members/:userId')
|
||||
async updateMemberRole(
|
||||
@Param('tenantId') tenantId: string,
|
||||
@Param('userId') userId: string,
|
||||
@Body() body: { role: string },
|
||||
) {
|
||||
if (body.role !== UserRole.USER && body.role !== UserRole.TENANT_ADMIN) {
|
||||
throw new ForbiddenException(
|
||||
this.i18nService.getErrorMessage('invalidMemberRole'),
|
||||
);
|
||||
}
|
||||
return this.tenantService.updateMemberRole(tenantId, userId, body.role);
|
||||
}
|
||||
|
||||
@Get(':tenantId/members/ids')
|
||||
async getMemberIds(@Param('tenantId') tenantId: string) {
|
||||
return this.tenantService.getMemberIds(tenantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SuperAdminController } from './super-admin.controller';
|
||||
import { SuperAdminService } from './super-admin.service';
|
||||
import { TenantModule } from '../tenant/tenant.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
|
||||
@Module({
|
||||
imports: [TenantModule, UserModule],
|
||||
controllers: [SuperAdminController],
|
||||
providers: [SuperAdminService],
|
||||
})
|
||||
export class SuperAdminModule {}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { TenantService } from '../tenant/tenant.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { User } from '../user/user.entity';
|
||||
import { UserRole } from '../user/user-role.enum';
|
||||
|
||||
@Injectable()
|
||||
export class SuperAdminService {
|
||||
constructor(
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
async getAllTenants(page?: number, limit?: number) {
|
||||
return this.tenantService.findAll(page, limit);
|
||||
}
|
||||
|
||||
async createTenant(
|
||||
name: string,
|
||||
domain?: string,
|
||||
adminUserId?: string,
|
||||
parentId?: string,
|
||||
) {
|
||||
const tenant = await this.tenantService.create(name, domain, parentId);
|
||||
if (adminUserId) {
|
||||
await this.tenantService.addMember(
|
||||
tenant.id,
|
||||
adminUserId,
|
||||
UserRole.TENANT_ADMIN,
|
||||
);
|
||||
}
|
||||
return tenant;
|
||||
}
|
||||
|
||||
async assignUserToTenant(
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
role: UserRole = UserRole.TENANT_ADMIN,
|
||||
) {
|
||||
// Find existing members of this tenant
|
||||
const members = await this.tenantService.getMembers(tenantId);
|
||||
|
||||
// Remove existing admins from this tenant (unlinking them, not changing their role)
|
||||
for (const member of members.data) {
|
||||
if (
|
||||
member.role === UserRole.TENANT_ADMIN ||
|
||||
member.role === UserRole.SUPER_ADMIN
|
||||
) {
|
||||
await this.tenantService.removeMember(tenantId, member.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new admin association for this tenant
|
||||
return this.tenantService.addMember(tenantId, userId, role);
|
||||
}
|
||||
|
||||
async createTenantAdmin(
|
||||
tenantId: string,
|
||||
username: string,
|
||||
password?: string,
|
||||
) {
|
||||
const defaultPassword = password || Math.random().toString(36).slice(-8);
|
||||
const result = await this.userService.createUser(
|
||||
username,
|
||||
defaultPassword,
|
||||
false, // isAdmin
|
||||
tenantId,
|
||||
username, // displayName
|
||||
);
|
||||
return {
|
||||
user: result.user,
|
||||
defaultPassword: defaultPassword,
|
||||
};
|
||||
}
|
||||
|
||||
async updateTenant(
|
||||
tenantId: string,
|
||||
data: { name?: string; domain?: string; parentId?: string },
|
||||
) {
|
||||
return this.tenantService.update(tenantId, data);
|
||||
}
|
||||
|
||||
async deleteTenant(tenantId: string) {
|
||||
return this.tenantService.remove(tenantId);
|
||||
}
|
||||
|
||||
// NOTE: Model Management would be added here depending on ModelService functionality
|
||||
}
|
||||
Reference in New Issue
Block a user