import { Request as exRequest } from "express"; import { getRepository, getManager, getConnection } from "typeorm"; import { Tags, Route, Post, Security, Request, Body, Controller, Get, Path, Put, Delete, Query, Patch } from "tsoa"; import { Service } from '../../entity/manager/Service'; import ApplicationError from "../../ApplicationError"; import { Meta, MetaStatus } from '../../entity/manager/Meta'; import BullManager from '../../util/BullManager'; import { MetaColumn, AcceptableType } from "../../entity/manager/MetaColumn"; import DbmsParams from "../../interfaces/requestParams/DbmsParams"; import FileParams from "../../interfaces/requestParams/FileParams"; import { Stage } from "../../entity/manager/Stage"; import ServiceParams from '../../interfaces/requestParams/ServiceParams'; import Pagination from "../../util/Pagination"; import { ERROR_CODE } from '../../util/ErrorCodes'; import { SwaggerBuilder } from "../../util/SwaggerBuilder"; import { MetaParamParams } from "../../interfaces/requestParams/MetaParamParams"; import { MetaParam } from "../../entity/manager/MetaParam"; @Route("/api/metas") @Tags("Meta") export class ApiMetaController extends Controller { /** * meta의 목록 * @param request * @param page * @param perPage */ @Get("/") @Security("jwt") public async getIndex( @Request() request: exRequest, @Query('page') page?: number, @Query('perPage') perPage?: number ):Promise<Pagination<Meta>>{ const pagination = new Pagination(Meta, getConnection()); const relations = ["stage", "stage.application"]; await pagination.findBySearchParams(relations, page, perPage, request.user.id); return Promise.resolve(pagination); } /** * meta의 id를 사용하여 meta의 상세 정보를 불러 올 수 있습니다. * meta의 Columns 정보를 포함합니다. * @param metaId * @param request */ @Get("/{metaId}") @Security("jwt") public async get( @Path() metaId: number, @Request() request: exRequest ): Promise<Meta> { const metaRepo = getRepository(Meta); const meta = await metaRepo.findOne({ relations: ["columns", "service"], where: { id: metaId, userId: request.user.id } }) if(!meta) { throw new ApplicationError(404, ERROR_CODE.META.META_NOT_FOUND); } return Promise.resolve(meta); } /** * meta의 id를 사용하여 meta의 상세 정보를 불러 올 수 있습니다. * meta의 Columns 정보를 포함합니다. * @param metaId * @param request */ @Get("/{metaId}/api-docs") public async getApiDoc( @Path() metaId: number, @Request() request: exRequest ): Promise<String> { const metaRepo = getRepository(Meta); const meta = await metaRepo.findOne({ relations: ["columns", "service", "stage", "stage.application", "columns.params"], where: { id: metaId } }) if(!meta) { throw new ApplicationError(404, ERROR_CODE.META.META_NOT_FOUND); } const doc = SwaggerBuilder.buildApplicationDoc(meta.stage, meta); return Promise.resolve(doc); } /** * database 연결 정보를 이용하여 DB 데이터를 불러오기 위한 Meta 를 등록합니다. * * @param request * @param dbmsParams Meta title과 DB 연결 정보, stageId</br> 지원 dbms: "mysql"|"cubrid" */ @Post("/dbms") @Security("jwt") public async postDbms( @Request() request: exRequest, @Body() dbmsParams: DbmsParams ): Promise<Meta> { const metaRepo = getRepository(Meta); const stageRepo = getRepository(Stage); const { title, dbms, host, port, database, user, password, table, stageId } = dbmsParams; if(title.length == 0 || dbms.length == 0 || host.length == 0 || port.length == 0 || database.length == 0 || user.length == 0 || table.length == 0) { throw new ApplicationError(400, ERROR_CODE.META.NEED_ALL_PARAM); } const stage = await stageRepo.findOne({ where: { id: stageId, userId: request.user.id } }); if(!stage) {throw new ApplicationError(404, ERROR_CODE.STAGE.STAGE_NOT_FOUND)} const queryRunner = await getConnection().createQueryRunner() const meta: Meta = new Meta(); meta.dataType = 'dbms'; meta.dbms = dbms; meta.dbUser = user; meta.pwd = password; meta.host = host; meta.port = port; meta.db = database; meta.table = table; meta.title = title; meta.stageId = stage.id; meta.userId = request.user.id; meta.status = MetaStatus.METALOAD_SCHEDULED; await queryRunner.startTransaction(); try { await queryRunner.manager.save(meta); BullManager.Instance.setMetaLoaderSchedule(meta.id); await queryRunner.commitTransaction(); } catch(err) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } this.setStatus(201); return Promise.resolve(meta); } /** * File 정보를 이용하여 Meta를 등록합니다.</br></br> * dataType이 'file'인 경우 Meta가 바로 등록이 됩니다.</br> * dataType이 'file-url'인 경우 meta-download-scheduled 상태와 함께 등록이 되고, 이후 Scheduler가 파일 다운로드를 완료한 이후 상태값이 변경됩니다. * @param request * @param fileParam dataType은 'file' 또는 'file-url'이 허용됩니다. */ @Post("/file") @Security("jwt") public async postFile( @Request() request: exRequest, @Body() params: FileParams ): Promise<Meta> { const queryRunner = await getConnection().createQueryRunner() switch(params.dataType) { case 'file': const meta: Meta = new Meta(); meta.dataType = 'file'; meta.title = params.title; meta.skip = params.skip; meta.sheet = params.sheet; meta.filePath = params.filePath; meta.originalFileName = params.originalFileName; meta.extension = params.ext; meta.stageId = params.stageId; meta.userId = request.user.id; meta.status = MetaStatus.METALOAD_SCHEDULED; await queryRunner.startTransaction(); try { await queryRunner.manager.save(meta); BullManager.Instance.setMetaLoaderSchedule(meta.id); await queryRunner.commitTransaction(); } catch(err) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } this.setStatus(201); return Promise.resolve(meta); case 'file-url': /** * JobScheduler에 등록을 실패 하는 경우에도 Rollback */ const newMeta = new Meta(); newMeta.dataType = 'file-url'; newMeta.remoteFilePath = params.url; newMeta.dataType = params.dataType; newMeta.extension = params.ext; newMeta.title = params.title || "empty title"; newMeta.stageId = params.stageId; newMeta.userId = request.user.id; newMeta.status = MetaStatus.DOWNLOAD_SCHEDULED; const fileName = `${request.user.id}-${Date.now()}.${params.ext}` await queryRunner.startTransaction(); try { await queryRunner.manager.save(newMeta); BullManager.Instance.setDownloadSchedule(newMeta.id, params.url, fileName); await queryRunner.commitTransaction(); } catch(err) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } this.setStatus(201); return Promise.resolve(newMeta); default: throw new ApplicationError(400, ERROR_CODE.META.UNACCEPTABLE_FILE_TYPE) } } /** * Meta의 Columns 정보를 수정 할 수 있습니다. * * @param request * @param metaColumnsParam 변경된 Meta.columns를 { columns: [] } 형식 * @param metaId Columns의 Parent meta id */ @Put('/{metaId}/columns') @Security('jwt') public async putColumns( @Request() request: exRequest, @Body() metaColumnsParam: MetaColumnsParam, @Path('metaId') metaId: number, ) { const metaColumnRepo = getRepository(MetaColumn); await metaColumnRepo.save(metaColumnsParam.columns) const meta = await getRepository(Meta).findOne({ relations: ["columns", "columns.params"], where: { id: metaId } }); this.setStatus(201); return Promise.resolve(meta); } /** * Meta의 Columns 정보를 수정 할 수 있습니다.\n * 해당 entity의 id가 있는 경우 update, 없는 경우 Insert를 수행합니다. * * @param request * @param updateMetaParam 변경된 Meta.columns를 { columns: [] } 형식 * @param metaId Columns의 Parent meta id */ @Put('/{metaId}') @Security('jwt') public async putMeta( @Request() request: exRequest, @Body() updateMetaParam: UpdateMetaParam, @Path('metaId') metaId: number, ) { const meta = await getRepository(Meta).findOne({ relations: ["columns", "service"], where: { id: metaId, userId: request.user.id } }); if(!meta) { throw new ApplicationError(404, ERROR_CODE.META.META_NOT_FOUND); } let service:Service; if(updateMetaParam.service) { if(!meta.service) { service = new Service(); service.meta = meta; } else { service = meta.service } service.entityName = updateMetaParam.service.entityName; service.description = updateMetaParam.service.description; service.method = updateMetaParam.service.method; } await getManager().transaction("SERIALIZABLE", async transactionalEntityManager => { if(updateMetaParam.columns) { await transactionalEntityManager.save(MetaColumn, updateMetaParam.columns) }; if(updateMetaParam.service) { await transactionalEntityManager.save(service) }; for(let column of updateMetaParam.columns) { if(column.params) { await transactionalEntityManager.save(MetaParam, column.params); } } }); this.setStatus(201); return Promise.resolve(await getRepository(Meta).findOne({ relations: ["columns", "columns.params", "service"], where: { id: meta.id } })); } @Delete('/{metaId}/service') @Security('jwt') public async delete( @Request() request: exRequest, @Path('metaId') metaId: number, ) { const meta = await getRepository(Meta).findOne({ relations: ["service"], where: { id: metaId, userId: request.user.id } }); if(!meta || !meta.service) { throw new ApplicationError(404, ERROR_CODE.META.META_NOT_FOUND) } await getRepository(Service).delete(meta.service.id); this.setStatus(201); return Promise.resolve(); } } interface UpdateMetaParam { columns?: MetaColumnParam[], service?: ServiceParams } interface MetaColumnsParam { columns: MetaColumnParam[] } interface MetaColumnParam { id: number, columnName: string, type: AcceptableType, size: string, isSearchable: boolean, isNullable: boolean, isHidden: boolean, dateFormat?: string params?: MetaParamParams[] }