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,146 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ForbiddenException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { TenantService } from '../tenant/tenant.service';
|
||||
import { I18nService } from '../i18n/i18n.service';
|
||||
|
||||
interface UserImportRow {
|
||||
Username?: string | number;
|
||||
username?: string | number;
|
||||
DisplayName?: string | number;
|
||||
displayName?: string | number;
|
||||
Name?: string | number;
|
||||
name?: string | number;
|
||||
Password?: string | number;
|
||||
password?: string | number;
|
||||
IsAdmin?: string | number | boolean;
|
||||
isAdmin?: string | number | boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
async getTenantUsers(tenantId?: string, page?: number, limit?: number) {
|
||||
if (!tenantId) {
|
||||
return this.userService.findAll(page, limit);
|
||||
}
|
||||
return this.userService.findByTenantId(tenantId, page, limit);
|
||||
}
|
||||
|
||||
async exportUsers(tenantId?: string): Promise<Buffer> {
|
||||
const { data: users } = tenantId
|
||||
? await this.userService.findByTenantId(tenantId)
|
||||
: await this.userService.findAll();
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(
|
||||
users.map((u) => ({
|
||||
Username: u.username,
|
||||
DisplayName: u.displayName || '',
|
||||
IsAdmin: u.isAdmin ? 'Yes' : 'No',
|
||||
CreatedAt: u.createdAt,
|
||||
Password: '', // Placeholder for new users
|
||||
})),
|
||||
);
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Users');
|
||||
|
||||
return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
||||
}
|
||||
|
||||
async importUsers(tenantId?: string, file?: any) {
|
||||
if (!file)
|
||||
throw new BadRequestException(
|
||||
this.i18nService.getMessage('uploadNoFile'),
|
||||
);
|
||||
|
||||
const workbook = XLSX.read(file.buffer, { type: 'buffer' });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
const data = XLSX.utils.sheet_to_json<UserImportRow>(worksheet);
|
||||
|
||||
const results = {
|
||||
success: 0,
|
||||
failed: 0,
|
||||
errors: [] as string[],
|
||||
};
|
||||
|
||||
for (const row of data) {
|
||||
try {
|
||||
const username = (row.Username || row.username)?.toString();
|
||||
const displayName = (
|
||||
row.DisplayName ||
|
||||
row.displayName ||
|
||||
row.Name ||
|
||||
row.name
|
||||
)?.toString();
|
||||
const password = (row.Password || row.password)?.toString();
|
||||
const isAdminStr = (row.IsAdmin || row.isAdmin || 'No').toString();
|
||||
const isAdmin =
|
||||
isAdminStr.toLowerCase() === 'yes' ||
|
||||
isAdminStr === 'true' ||
|
||||
isAdminStr === '1';
|
||||
|
||||
if (!username) {
|
||||
throw new Error(this.i18nService.getMessage('usernameRequired'));
|
||||
}
|
||||
|
||||
const existingUser = await this.userService.findOneByUsername(username);
|
||||
|
||||
if (existingUser) {
|
||||
await this.userService.updateUser(existingUser.id, {
|
||||
displayName: displayName || existingUser.displayName,
|
||||
password: password || undefined,
|
||||
// We avoid changing isAdmin status via import for security unless explicitly required
|
||||
});
|
||||
} else {
|
||||
if (!password) {
|
||||
throw new Error(
|
||||
this.i18nService.formatMessage('passwordRequiredForNewUser', {
|
||||
username,
|
||||
}),
|
||||
);
|
||||
}
|
||||
await this.userService.createUser(
|
||||
username,
|
||||
password,
|
||||
isAdmin,
|
||||
tenantId,
|
||||
displayName,
|
||||
);
|
||||
}
|
||||
results.success++;
|
||||
} catch (e: any) {
|
||||
results.failed++;
|
||||
results.errors.push(`${row.Username || 'Unknown'}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getTenantSettings(tenantId: string) {
|
||||
return this.tenantService.getSettings(tenantId);
|
||||
}
|
||||
|
||||
async updateTenantSettings(tenantId: string, data: any) {
|
||||
return this.tenantService.updateSettings(tenantId, data);
|
||||
}
|
||||
|
||||
// Notebook sharing approval and model assignments would go here
|
||||
async getPendingShares(tenantId: string) {
|
||||
// Mock implementation for pending shares to satisfy UI.
|
||||
// Needs proper schema/entity support in the future.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user