| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- import { Injectable, Logger, BadRequestException } from '@nestjs/common';
- import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
- import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
- import * as fs from 'fs';
- import * as path from 'path';
- @Injectable()
- export class UploadService {
- private readonly logger = new Logger(UploadService.name);
- constructor(
- private kbService: KnowledgeBaseService,
- private groupService: KnowledgeGroupService,
- ) {}
- async processUploadedFile(file: Express.Multer.File) {
- // Add more business logic here. Example:
- // - Save file info to database
- // - Call other services to process file (Tika text extraction, ES indexing etc.)
- // - Validate file format or analyze content
- // Currently only return basic file info
- return {
- filename: file.filename,
- originalname: file.originalname,
- size: file.size,
- mimetype: file.mimetype,
- path: file.path, // After Multer saves file, full path is in file.path
- };
- }
- async importLocalFolder(
- sourcePath: string,
- userId: string,
- tenantId: string,
- config: any,
- ) {
- if (!fs.existsSync(sourcePath)) {
- throw new BadRequestException(`Directory not found: ${sourcePath}`);
- }
- const stat = fs.statSync(sourcePath);
- if (!stat.isDirectory()) {
- throw new BadRequestException(`Path is not a directory: ${sourcePath}`);
- }
- // Determine root group for hierarchy or single group
- let rootGroupId: string | null = null;
- if (config.groupIds && config.groupIds.length > 0) {
- rootGroupId = config.groupIds[0];
- }
- this.logger.log(
- `Starting local folder import: ${sourcePath} for user ${userId}, tenant ${tenantId}`,
- );
- // Trigger scanning and processing asynchronously to not block the request
- this.executeLocalImport(
- sourcePath,
- userId,
- tenantId,
- config,
- rootGroupId,
- ).catch((err) => {
- this.logger.error(`Local folder import failed for ${sourcePath}`, err);
- });
- return {
- sourcePath,
- status: 'PROCESSING',
- };
- }
- private async executeLocalImport(
- sourcePath: string,
- userId: string,
- tenantId: string,
- config: any,
- rootGroupId: string | null,
- ) {
- const files = this.scanDir(sourcePath);
- this.logger.log(`Found ${files.length} files in ${sourcePath}`);
- const dirToGroupId = new Map<string, string>();
- if (rootGroupId) {
- dirToGroupId.set('.', rootGroupId);
- } else {
- // Create a root group based on folder name if none provided
- const rootName = path.basename(sourcePath);
- const rootGroup = await this.groupService.create(userId, tenantId, {
- name: rootName,
- description: `Imported from local path: ${sourcePath}`,
- });
- rootGroupId = rootGroup.id;
- dirToGroupId.set('.', rootGroupId);
- }
- const uploadBaseDir = process.env.UPLOAD_FILE_PATH || './uploads';
- for (const filePath of files) {
- try {
- const relativeDir = path.relative(sourcePath, path.dirname(filePath));
- const normalizedDir = relativeDir || '.';
- let targetGroupId = rootGroupId;
- if (config.useHierarchy) {
- targetGroupId = await this.ensureHierarchy(
- userId,
- tenantId,
- normalizedDir,
- dirToGroupId,
- rootGroupId,
- );
- }
- const filename = path.basename(filePath);
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
- const storedFilename = `local-${uniqueSuffix}-${filename}`;
- // Ensure tenant directory exists
- const tenantDir = path.join(uploadBaseDir, tenantId);
- if (!fs.existsSync(tenantDir)) {
- fs.mkdirSync(tenantDir, { recursive: true });
- }
- const targetPath = path.join(tenantDir, storedFilename);
- fs.copyFileSync(filePath, targetPath);
- const stats = fs.statSync(targetPath);
- const fileInfo = {
- filename: storedFilename,
- originalname: filename,
- path: targetPath,
- size: stats.size,
- mimetype: this.getMimeType(filename),
- };
- await this.kbService.createAndIndex(fileInfo, userId, tenantId, {
- ...config,
- groupIds: [targetGroupId],
- });
- } catch (err) {
- this.logger.error(`Failed to process local file: ${filePath}`, err);
- }
- }
- this.logger.log(`Local folder import completed: ${sourcePath}`);
- }
- private async ensureHierarchy(
- userId: string,
- tenantId: string,
- relativeDir: string,
- dirToGroupId: Map<string, string>,
- rootGroupId: string,
- ): Promise<string> {
- if (dirToGroupId.has(relativeDir)) {
- return dirToGroupId.get(relativeDir)!;
- }
- const segments = relativeDir.split(path.sep);
- let currentPath = '';
- let parentId = rootGroupId;
- for (const segment of segments) {
- if (!segment || segment === '.') continue;
- currentPath = currentPath ? path.join(currentPath, segment) : segment;
- if (dirToGroupId.has(currentPath)) {
- parentId = dirToGroupId.get(currentPath)!;
- continue;
- }
- const group = await this.groupService.findOrCreate(
- userId,
- tenantId,
- segment,
- parentId,
- `Sub-folder from local import: ${currentPath}`,
- );
- dirToGroupId.set(currentPath, group.id);
- parentId = group.id;
- }
- return parentId;
- }
- private scanDir(directory: string): string[] {
- let results: string[] = [];
- if (!fs.existsSync(directory)) return results;
- const items = fs.readdirSync(directory);
- for (const item of items) {
- const fullPath = path.join(directory, item);
- const stat = fs.statSync(fullPath);
- if (stat.isDirectory()) {
- results = results.concat(this.scanDir(fullPath));
- } else {
- // Only include supported document and code extensions
- const ext = path.extname(item).toLowerCase().slice(1);
- if (
- [
- 'pdf',
- 'doc',
- 'docx',
- 'xls',
- 'xlsx',
- 'ppt',
- 'pptx',
- 'rtf',
- 'csv',
- 'txt',
- 'md',
- 'html',
- 'json',
- 'xml',
- 'js',
- 'ts',
- 'py',
- 'java',
- 'sql',
- ].includes(ext)
- ) {
- results.push(fullPath);
- }
- }
- }
- return results;
- }
- private getMimeType(filename: string): string {
- const ext = path.extname(filename).toLowerCase();
- const mimeMap: Record<string, string> = {
- '.pdf': 'application/pdf',
- '.doc': 'application/msword',
- '.docx':
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- '.md': 'text/markdown',
- '.txt': 'text/plain',
- '.json': 'application/json',
- '.html': 'text/html',
- '.csv': 'text/csv',
- };
- return mimeMap[ext] || 'application/octet-stream';
- }
- }
|