feat(API-ADMIN-010): add enrichWithNames to admin list controllers
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 50s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 50s
- New shared helper: src/common/helpers/name-resolver.ts - Applied to: reviews, users/members, files, knowledge-bases, imports - Learning service refactored to use shared helper Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ff4135b7c6
commit
0ea36a7073
39
src/common/helpers/name-resolver.ts
Normal file
39
src/common/helpers/name-resolver.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enrich result items with human-readable names resolved from IDs.
|
||||||
|
* Adds userName, kbName, materialName fields where applicable.
|
||||||
|
*/
|
||||||
|
export async function enrichWithNames<T extends Record<string, any>>(
|
||||||
|
prisma: PrismaService,
|
||||||
|
items: T[],
|
||||||
|
): Promise<(T & { userName?: string; kbName?: string; materialName?: string })[]> {
|
||||||
|
if (items.length === 0) return items;
|
||||||
|
|
||||||
|
const userIds = [...new Set(items.map(i => i.userId).filter(Boolean))];
|
||||||
|
const kbIds = [...new Set(items.map(i => i.knowledgeBaseId).filter(Boolean))];
|
||||||
|
const materialIds = [...new Set(items.map(i => i.materialId).filter(Boolean))];
|
||||||
|
|
||||||
|
const [users, kbs, materials] = await Promise.all([
|
||||||
|
userIds.length > 0
|
||||||
|
? prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, nickname: true } })
|
||||||
|
: [],
|
||||||
|
kbIds.length > 0
|
||||||
|
? prisma.knowledgeBase.findMany({ where: { id: { in: kbIds } }, select: { id: true, title: true } })
|
||||||
|
: [],
|
||||||
|
materialIds.length > 0
|
||||||
|
? prisma.knowledgeSource.findMany({ where: { id: { in: materialIds } }, select: { id: true, originalFilename: true, title: true } })
|
||||||
|
: [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const userMap = new Map<string, string>(users.map(u => [u.id, u.nickname || u.id] as [string, string]));
|
||||||
|
const kbMap = new Map<string, string>(kbs.map(k => [k.id, k.title] as [string, string]));
|
||||||
|
const matMap = new Map<string, string>(materials.map(m => [m.id, m.originalFilename || m.title || m.id] as [string, string]));
|
||||||
|
|
||||||
|
return items.map(item => ({
|
||||||
|
...item,
|
||||||
|
userName: userMap.get(item.userId),
|
||||||
|
kbName: kbMap.get(item.knowledgeBaseId),
|
||||||
|
materialName: matMap.get(item.materialId),
|
||||||
|
}));
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { Controller, Get, Param, Delete, Query, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Param, Delete, Query, UseGuards } from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ export class AdminKnowledgeController {
|
|||||||
}),
|
}),
|
||||||
this.prisma.knowledgeBase.count({ where: { deletedAt: null } }),
|
this.prisma.knowledgeBase.count({ where: { deletedAt: null } }),
|
||||||
]);
|
]);
|
||||||
return { items, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
const enriched = await enrichWithNames(this.prisma, items); return { items: enriched, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Controller, Get, Post, Param, Query, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Post, Param, Query, UseGuards } from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
import { AdminRoles } from '../../common/decorators/admin-roles.decorator';
|
import { AdminRoles } from '../../common/decorators/admin-roles.decorator';
|
||||||
@ -23,7 +24,7 @@ export class AdminImportsController {
|
|||||||
this.prisma.documentImport.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (p - 1) * l, take: l }),
|
this.prisma.documentImport.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (p - 1) * l, take: l }),
|
||||||
this.prisma.documentImport.count({ where }),
|
this.prisma.documentImport.count({ where }),
|
||||||
]);
|
]);
|
||||||
return { items, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
const enriched = await enrichWithNames(this.prisma, items); return { items: enriched, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Controller, Get, Delete, Param, Query, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Delete, Param, Query, UseGuards } from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
import { QueueService, QUEUE_FILE_CLEANUP } from '../../infrastructure/queue/queue.service';
|
import { QueueService, QUEUE_FILE_CLEANUP } from '../../infrastructure/queue/queue.service';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
@ -28,7 +29,7 @@ export class AdminFilesController {
|
|||||||
}),
|
}),
|
||||||
this.prisma.uploadedFile.count({ where: {} }),
|
this.prisma.uploadedFile.count({ where: {} }),
|
||||||
]);
|
]);
|
||||||
return { items, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
const enriched = await enrichWithNames(this.prisma, items); return { items: enriched, total, page: p, limit: l, totalPages: Math.ceil(total / l) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
import { SnapshotBuilderService } from '../ai-runtime/snapshot-builder.service';
|
import { SnapshotBuilderService } from '../ai-runtime/snapshot-builder.service';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminLearningService {
|
export class AdminLearningService {
|
||||||
@ -11,35 +12,8 @@ export class AdminLearningService {
|
|||||||
|
|
||||||
// ══ Name resolution ══
|
// ══ Name resolution ══
|
||||||
|
|
||||||
private async enrichWithNames<T extends Record<string, any>>(items: T[]): Promise<(T & { userName?: string; kbName?: string; materialName?: string })[]> {
|
private async enrich<T extends Record<string, any>>(items: T[]) {
|
||||||
if (items.length === 0) return items;
|
return enrichWithNames(this.prisma, items);
|
||||||
|
|
||||||
const userIds = [...new Set(items.map(i => i.userId).filter(Boolean))];
|
|
||||||
const kbIds = [...new Set(items.map(i => i.knowledgeBaseId).filter(Boolean))];
|
|
||||||
const materialIds = [...new Set(items.map(i => i.materialId).filter(Boolean))];
|
|
||||||
|
|
||||||
const [users, kbs, materials] = await Promise.all([
|
|
||||||
userIds.length > 0
|
|
||||||
? this.prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, nickname: true } })
|
|
||||||
: [],
|
|
||||||
kbIds.length > 0
|
|
||||||
? this.prisma.knowledgeBase.findMany({ where: { id: { in: kbIds } }, select: { id: true, title: true } })
|
|
||||||
: [],
|
|
||||||
materialIds.length > 0
|
|
||||||
? this.prisma.knowledgeSource.findMany({ where: { id: { in: materialIds } }, select: { id: true, originalFilename: true, title: true } })
|
|
||||||
: [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const userMap = new Map<string, string>(users.map(u => [u.id, u.nickname || u.id] as [string, string]));
|
|
||||||
const kbMap = new Map<string, string>(kbs.map(k => [k.id, k.title] as [string, string]));
|
|
||||||
const matMap = new Map<string, string>(materials.map(m => [m.id, m.originalFilename || m.title || m.id] as [string, string]));
|
|
||||||
|
|
||||||
return items.map(item => ({
|
|
||||||
...item,
|
|
||||||
userName: userMap.get(item.userId),
|
|
||||||
kbName: kbMap.get(item.knowledgeBaseId),
|
|
||||||
materialName: matMap.get(item.materialId),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══ Knowledge Bases (for filter dropdown) ══
|
// ══ Knowledge Bases (for filter dropdown) ══
|
||||||
@ -145,7 +119,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.readingEvent.findMany({ where, orderBy, skip: (page - 1) * limit, take: limit }),
|
this.prisma.readingEvent.findMany({ where, orderBy, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.readingEvent.count({ where }),
|
this.prisma.readingEvent.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReadingEvent(id: string) {
|
async getReadingEvent(id: string) {
|
||||||
@ -171,7 +145,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.learningSession.findMany({ where, orderBy: { startedAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
this.prisma.learningSession.findMany({ where, orderBy: { startedAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.learningSession.count({ where }),
|
this.prisma.learningSession.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSession(id: string) {
|
async getSession(id: string) {
|
||||||
@ -194,7 +168,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.materialReadingProgress.findMany({ where, orderBy: { lastReadAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
this.prisma.materialReadingProgress.findMany({ where, orderBy: { lastReadAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.materialReadingProgress.count({ where }),
|
this.prisma.materialReadingProgress.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProgressDetail(id: string) {
|
async getProgressDetail(id: string) {
|
||||||
@ -217,7 +191,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.dailyLearningActivity.findMany({ where, orderBy: { activityDate: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
this.prisma.dailyLearningActivity.findMany({ where, orderBy: { activityDate: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.dailyLearningActivity.count({ where }),
|
this.prisma.dailyLearningActivity.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══ Records ══
|
// ══ Records ══
|
||||||
@ -235,7 +209,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.learningRecord.findMany({ where, orderBy: { occurredAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
this.prisma.learningRecord.findMany({ where, orderBy: { occurredAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.learningRecord.count({ where }),
|
this.prisma.learningRecord.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecord(id: string) {
|
async getRecord(id: string) {
|
||||||
@ -258,7 +232,7 @@ export class AdminLearningService {
|
|||||||
}),
|
}),
|
||||||
this.prisma.aiAnalysisResult.count({ where }),
|
this.prisma.aiAnalysisResult.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══ AI Usage ══
|
// ══ AI Usage ══
|
||||||
@ -276,7 +250,7 @@ export class AdminLearningService {
|
|||||||
}),
|
}),
|
||||||
this.prisma.aiUsageLog.count({ where }),
|
this.prisma.aiUsageLog.count({ where }),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══ Timeline ══
|
// ══ Timeline ══
|
||||||
@ -343,7 +317,7 @@ export class AdminLearningService {
|
|||||||
this.prisma.temporaryReadingMaterial.findMany({ orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
this.prisma.temporaryReadingMaterial.findMany({ orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }),
|
||||||
this.prisma.temporaryReadingMaterial.count(),
|
this.prisma.temporaryReadingMaterial.count(),
|
||||||
]);
|
]);
|
||||||
const enriched = await this.enrichWithNames(items); return { items: enriched, total, page, limit };
|
const enriched = await this.enrich(items); return { items: enriched, total, page, limit };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══ Operations ══
|
// ══ Operations ══
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
|||||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
|
|
||||||
@ApiTags('admin-review')
|
@ApiTags('admin-review')
|
||||||
@ -44,6 +45,7 @@ export class AdminReviewController {
|
|||||||
this.prisma.reviewCard.count({ where }),
|
this.prisma.reviewCard.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { items, total };
|
const enriched = await enrichWithNames(this.prisma, items);
|
||||||
|
return { items: enriched, total };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Controller, Get, Post, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Post, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
import { PrismaService } from '../../infrastructure/database/prisma.service';
|
||||||
|
import { enrichWithNames } from '../../common/helpers/name-resolver';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
import { AdminRoles } from '../../common/decorators/admin-roles.decorator';
|
import { AdminRoles } from '../../common/decorators/admin-roles.decorator';
|
||||||
@ -27,7 +28,7 @@ export class AdminUsersMgmtController {
|
|||||||
this.prisma.user.findMany({ where, orderBy: { createdAt: 'desc' }, take, skip, select: { id: true, email: true, nickname: true, role: true, status: true, lastLoginAt: true, createdAt: true } }),
|
this.prisma.user.findMany({ where, orderBy: { createdAt: 'desc' }, take, skip, select: { id: true, email: true, nickname: true, role: true, status: true, lastLoginAt: true, createdAt: true } }),
|
||||||
this.prisma.user.count({ where }),
|
this.prisma.user.count({ where }),
|
||||||
]);
|
]);
|
||||||
return { items, total };
|
const enriched = await enrichWithNames(this.prisma, items); return { items: enriched, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Membership ──
|
// ── Membership ──
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user