Files
aurak/server/src/admin/admin.service.ts
T
Developer 0a9588abb7 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
2026-04-23 17:19:11 +08:00

147 lines
4.3 KiB
TypeScript

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 [];
}
}