diff --git a/src/controllers/report-builder/folder.controller.ts b/src/controllers/report-builder/folder.controller.ts index 8c66203..c8e569a 100644 --- a/src/controllers/report-builder/folder.controller.ts +++ b/src/controllers/report-builder/folder.controller.ts @@ -16,7 +16,7 @@ import { } from '@loopback/rest'; import _ from 'lodash'; import {FolderModel} from 'rb-core-middleware/dist/models'; -import {FolderService} from 'rb-core-middleware/dist/services'; +import {FolderService, ReportService} from 'rb-core-middleware/dist/services'; import {Logger} from 'winston'; export class FolderController { @@ -24,6 +24,7 @@ export class FolderController { @inject(RestBindings.Http.REQUEST) private req: Request, @inject('services.logger') private logger: Logger, @inject('services.FolderService') private folderService: FolderService, + @inject('services.ReportService') private reportService: ReportService, ) {} @post('/folder') @@ -67,12 +68,69 @@ export class FolderController { // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) async find( @param.filter(FolderModel) filter?: Filter, - ): Promise { + @param.query.string('includeSubFolders') includeSubFolders?: string, + ): Promise { const userId = _.get(this.req, 'user.sub', 'anonymous'); this.logger.info( `FolderController - find - Fetching folders for user ${userId}`, ); - return this.folderService.find(userId, filter); + if (Boolean(includeSubFolders)) { + return this.folderService.getAllFoldersWithSubfolders(userId, filter); + } else { + const folders = await this.folderService.find(userId, filter); + const folderById = new Map( + folders.map(f => [f.id, f]), + ); + const pathCache = new Map(); + const buildFolderPath = (folderId: string | undefined): string => { + if (!folderId) return 'My Workspace'; + const cached = pathCache.get(folderId); + if (cached !== undefined) return cached; + const folder = folderById.get(folderId); + if (!folder) return 'My Workspace'; + const parentPath = buildFolderPath(folder.parentId); + const path = + parentPath === 'My Workspace' + ? `My Workspace > ${folder.name}` + : `${parentPath} > ${folder.name}`; + pathCache.set(folderId, path); + return path; + }; + + const foldersWithPath = _.filter(folders, folder => !folder.parentId).map( + folder => { + const locationPath = buildFolderPath(folder.parentId); + return { + ...folder, + locationPath, + }; + }, + ); + return foldersWithPath; + } + } + + @get('/folders-structure') + @response(200, { + description: 'Array of FolderModel instances', + content: { + 'application/json': { + schema: { + type: 'array', + items: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async getFoldersStructure( + @param.filter(FolderModel) filter?: Filter, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - getFoldersStructure - Fetching folder structure for user ${userId}`, + ); + return this.folderService.getFolderTree(userId); } @get('/folder/{id}') @@ -173,4 +231,129 @@ export class FolderController { ); return this.folderService.duplicate(userId, id); } + + @get('/folder/add-asset/{id}') + @response(200, { + description: 'FolderModel instance', + content: { + 'application/json': { + schema: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async addAssetToFolder( + @param.path.string('id') id: string, + @param.query.string('assetId') assetId: string, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - addAssetToFolder - Adding asset ${assetId} to folder ${id} for user ${userId}`, + ); + return this.folderService.addAsset(userId, id, assetId); + } + + @get('/folder/remove-asset/{id}') + @response(200, { + description: 'FolderModel instance', + content: { + 'application/json': { + schema: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async removeAssetFromFolder( + @param.path.string('id') id: string, + @param.query.string('assetId') assetId: string, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - removeAssetFromFolder - Removing asset ${assetId} from folder ${id} for user ${userId}`, + ); + return this.folderService.removeAsset(userId, id, assetId); + } + + @get('/folder/add-report/{id}') + @response(200, { + description: 'FolderModel instance', + content: { + 'application/json': { + schema: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async addReportToFolder( + @param.path.string('id') id: string, + @param.query.string('reportId') reportId: string, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - addReportToFolder - Adding report ${reportId} to folder ${id} for user ${userId}`, + ); + const reportPrevFolder = await this.reportService.findById( + [userId], + reportId, + ); + return this.folderService.addReport( + userId, + id, + reportId, + // @ts-ignore + reportPrevFolder?.folderId ?? undefined, + ); + } + + @get('/folder/remove-report/{id}') + @response(200, { + description: 'FolderModel instance', + content: { + 'application/json': { + schema: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async removeReportFromFolder( + @param.path.string('id') id: string, + @param.query.string('reportId') reportId: string, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - removeReportFromFolder - Removing report ${reportId} from folder ${id} for user ${userId}`, + ); + return this.folderService.removeReport(userId, id, reportId); + } + + @get('/folder/add-folder/{id}') + @response(200, { + description: 'FolderModel instance', + content: { + 'application/json': { + schema: getModelSchemaRef(FolderModel, {includeRelations: true}), + }, + }, + }) + // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) + async addFolderToFolder( + @param.path.string('id') id: string, + @param.query.string('folderId') folderId: string, + ): Promise { + const userId = _.get(this.req, 'user.sub', 'anonymous'); + this.logger.info( + `FolderController - addFolderToFolder - Adding folder ${folderId} to folder ${id} for user ${userId}`, + ); + const folderPrevFolder = await this.folderService.findById( + [userId], + folderId, + ); + return this.folderService.addFolder( + userId, + id, + folderId, + // @ts-ignore + folderPrevFolder?.folderId ?? undefined, + ); + } } diff --git a/src/controllers/report-builder/report.controller.ts b/src/controllers/report-builder/report.controller.ts index 75f0410..aa8fbbb 100644 --- a/src/controllers/report-builder/report.controller.ts +++ b/src/controllers/report-builder/report.controller.ts @@ -18,8 +18,8 @@ import { import axios, {AxiosResponse} from 'axios'; import fs from 'fs/promises'; import _ from 'lodash'; -import {ReportModel} from 'rb-core-middleware/dist/models'; -import {ReportService} from 'rb-core-middleware/dist/services'; +import {FolderModel, ReportModel} from 'rb-core-middleware/dist/models'; +import {FolderService, ReportService} from 'rb-core-middleware/dist/services'; import {Logger} from 'winston'; import {queueReportThumbnailGeneration} from '../../queues/report.queue'; import {handleDataApiError} from '../../utils/dataApiError'; @@ -32,6 +32,7 @@ export class ReportController { @inject(RestBindings.Http.RESPONSE) private response: Response, @inject('services.logger') private logger: Logger, @inject('services.ReportService') private reportService: ReportService, + @inject('services.FolderService') private folderService: FolderService, ) {} @post('/report/render-chart-data') @@ -44,47 +45,6 @@ export class ReportController { } } - @get('/report/dummy') - @response(200) - async dummy() { - this.logger.info('ReportController - dummy - Dummy endpoint called'); - return this.reportService.create('dummy-user', { - name: 'Dummy Report', - nameLower: 'dummy report', - description: 'This is a dummy report', - items: [], - public: false, - baseline: false, - owner: 'dummy-user', - updatedDate: new Date().toISOString(), - createdDate: new Date().toISOString(), - settings: { - width: 800, - height: 600, - paddingLeft: 10, - paddingTop: 10, - paddingRight: 10, - paddingBottom: 10, - stroke: 0, - strokeColor: '#000000', - backgroundColor: '#FFFFFF', - borderRadius: 0, - }, - getId: function () { - return ''; - }, - getIdObject: function () { - return {}; - }, - toJSON: function () { - return {}; - }, - toObject: function () { - return {}; - }, - }); - } - @post('/report') @response(200, { description: 'ReportModel instance', @@ -140,12 +100,98 @@ export class ReportController { // @authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}}) async find( @param.filter(ReportModel) filter?: Filter, - ): Promise { + @param.query.string('onlyRootLevel') onlyRootLevel?: boolean, + @param.filter(FolderModel) folderFilter?: Filter, + @param.query.string('includeFolders') includeFolders?: boolean, + ): Promise< + { + id: string; + name: string; + owner: string; + public: boolean; + description: string; + createdDate: string; + updatedDate: string; + isFolder?: boolean; + assetCount?: number; + reportCount?: number; + locationPath: string; + }[] + > { const userId = _.get(this.req, 'user.sub', 'anonymous'); this.logger.info( `ReportController - find - Fetching reports for user ${userId}`, ); - return this.reportService.find(userId, filter); + const reports = await this.reportService.find(userId, filter); + const allFolders = await this.folderService.find(userId, folderFilter); + + const folderById = new Map( + allFolders.map(f => [f.id, f]), + ); + const pathCache = new Map(); + const buildFolderPath = (folderId: string | undefined): string => { + if (!folderId) return 'My Workspace'; + const cached = pathCache.get(folderId); + if (cached !== undefined) return cached; + const folder = folderById.get(folderId); + if (!folder) return 'My Workspace'; + const parentPath = buildFolderPath(folder.parentId); + const path = + parentPath === 'My Workspace' + ? `My Workspace > ${folder.name}` + : `${parentPath} > ${folder.name}`; + pathCache.set(folderId, path); + return path; + }; + + const reportsWithPath = _.filter( + reports, + report => !onlyRootLevel || !report.folderId, + ).map(report => { + const locationPath = buildFolderPath(report.folderId); + return { + ..._.omit(report, ['folderId']), + locationPath, + }; + }); + + const foldersWithPath = _.filter( + allFolders, + folder => !onlyRootLevel || !folder.parentId, + ).map(folder => { + const locationPath = buildFolderPath(folder.parentId); + return { + ..._.omit(folder, ['parentId']), + locationPath, + }; + }); + + if (includeFolders) { + const orderFilter = _.get(filter, 'order[0]', 'createdDate DESC'); + const [orderByField, orderByDirection] = orderFilter.split(' '); + return _.orderBy( + [ + ...reportsWithPath, + ...foldersWithPath.map(folder => ({ + id: folder.id, + name: folder.name, + public: false, + owner: folder.owner, + createdDate: folder.createdDate, + updatedDate: folder.updatedDate, + description: '', + isFolder: true, + assetCount: folder.assets ? folder.assets.length : 0, + reportCount: folder.reports ? folder.reports.length : 0, + folderCount: folder.children ? folder.children.length : 0, + locationPath: folder.locationPath, + })), + ], + [orderByField], + [orderByDirection.toLowerCase() as 'asc' | 'desc'], + ); + } + return reportsWithPath; } @get('/report/{id}')