UNPKG

@rytass/cms-base-nestjs-module

Version:

Rytass Content Management System NestJS Base Module

238 lines (235 loc) 11.9 kB
import { Injectable, Inject, BadRequestException } from '@nestjs/common'; import { RESOLVED_ARTICLE_VERSION_REPO, ENABLE_SIGNATURE_MODE, SIGNATURE_LEVELS, RESOLVED_SIGNATURE_LEVEL_REPO, DRAFT_MODE, AUTO_RELEASE_AFTER_APPROVED } from '../typings/cms-base-providers.js'; import { BaseSignatureLevelEntity } from '../models/base-signature-level.entity.js'; import { IsNull } from 'typeorm'; import { InjectDataSource } from '@nestjs/typeorm'; import { ArticleSignatureRepo, ArticleSignatureEntity } from '../models/base-article-signature.entity.js'; import { ArticleSignatureResult } from '../typings/article-signature-result.enum.js'; function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function _ts_param(paramIndex, decorator) { return function(target, key) { decorator(target, key, paramIndex); }; } class ArticleSignatureService { articleVersionRepo; signatureMode; signatureLevels; signatureLevelRepo; articleSignatureRepo; draftMode; autoReleaseAfterApproved; dataSource; constructor(articleVersionRepo, signatureMode, signatureLevels, signatureLevelRepo, articleSignatureRepo, draftMode, autoReleaseAfterApproved, dataSource){ this.articleVersionRepo = articleVersionRepo; this.signatureMode = signatureMode; this.signatureLevels = signatureLevels; this.signatureLevelRepo = signatureLevelRepo; this.articleSignatureRepo = articleSignatureRepo; this.draftMode = draftMode; this.autoReleaseAfterApproved = autoReleaseAfterApproved; this.dataSource = dataSource; this.signatureLevelsCache = []; } signatureLevelsCache; get finalSignatureLevel() { return this.signatureLevelsCache[this.signatureLevelsCache.length - 1] ?? null; } rejectVersion(articleVersion, signatureInfo) { return this.signature(ArticleSignatureResult.REJECTED, articleVersion, signatureInfo); } approveVersion(articleVersion, signatureInfo) { return this.signature(ArticleSignatureResult.APPROVED, articleVersion, signatureInfo); } async signature(result, articleVersion, signatureInfo) { if (!await this.articleVersionRepo.exists({ where: { articleId: articleVersion.id, version: articleVersion.version } })) { throw new BadRequestException('Invalid article version'); } if (this.signatureLevelsCache.length && !signatureInfo?.signatureLevel) { throw new BadRequestException('Signature level is required'); } const targetLevelIndex = signatureInfo?.signatureLevel ? this.signatureLevelsCache.findIndex((level)=>signatureInfo.signatureLevel instanceof BaseSignatureLevelEntity ? level.id === signatureInfo.signatureLevel.id : level.name === signatureInfo.signatureLevel) : Number.NaN; if (signatureInfo?.signatureLevel && (targetLevelIndex === -1 || Number.isNaN(targetLevelIndex))) { throw new Error('Invalid signature level'); } const runner = this.dataSource.createQueryRunner(); await runner.connect(); await runner.startTransaction(); try { const qb = runner.manager.createQueryBuilder(ArticleSignatureEntity, 'signatures'); qb.andWhere('signatures.articleId = :articleId', { articleId: articleVersion.id }); qb.andWhere('signatures.version = :version', { version: articleVersion.version }); qb.setLock('pessimistic_write'); const signatures = await qb.getMany(); if (!Number.isNaN(targetLevelIndex)) { const signatureMap = new Map(signatures.map((signature)=>[ signature.signatureLevelId, signature ])); const needSignTargets = this.signatureLevelsCache.slice(0, targetLevelIndex + 1).map((level)=>signatureMap.get(level.id) ?? null); const targetSignature = needSignTargets[targetLevelIndex]; if (targetSignature) { if (targetSignature.result === ArticleSignatureResult.REJECTED) { await runner.manager.softDelete(ArticleSignatureEntity, { id: targetSignature.id }); } else { throw new BadRequestException('Already signed'); } } if (targetLevelIndex > 0) { const previousSignature = needSignTargets[targetLevelIndex - 1]; const previousRequiredSignatureLevels = this.signatureLevelsCache.slice(0, targetLevelIndex).filter((level)=>level.required); const latestRequireSignatureLevel = previousRequiredSignatureLevels[previousRequiredSignatureLevels.length - 1]; if (previousSignature && previousSignature.result !== ArticleSignatureResult.APPROVED) { throw new BadRequestException('Previous valid signature not found'); } const latestRequireSignature = signatureMap.get(latestRequireSignatureLevel.id); if (!latestRequireSignature) { throw new BadRequestException('Previous valid signature not found'); } } const signature = this.articleSignatureRepo.create({ articleId: articleVersion.id, version: articleVersion.version, signatureLevelId: this.signatureLevelsCache[targetLevelIndex].id, result, signerId: signatureInfo?.signerId ?? null, rejectReason: result === ArticleSignatureResult.REJECTED ? signatureInfo?.reason ?? null : null }); if (this.draftMode && this.autoReleaseAfterApproved && this.signatureLevelsCache[targetLevelIndex].id === this.finalSignatureLevel?.id) { await runner.manager.update(this.articleVersionRepo.target, { id: articleVersion.id, version: articleVersion.version, releasedAt: IsNull() }, { releasedAt: new Date() }); } await runner.manager.save(signature); await runner.commitTransaction(); return signature; } else if (signatures.length) { throw new BadRequestException('Already signed'); } const signature = this.articleSignatureRepo.create({ articleId: articleVersion.id, version: articleVersion.version, result, signerId: signatureInfo?.signerId ?? null, rejectReason: result === ArticleSignatureResult.REJECTED ? signatureInfo?.reason ?? null : null }); if (this.draftMode && this.autoReleaseAfterApproved) { await runner.manager.update(this.articleVersionRepo.target, { id: articleVersion.id, version: articleVersion.version, releasedAt: IsNull() }, { releasedAt: new Date() }); } await runner.manager.save(signature); await runner.commitTransaction(); return signature; } catch (ex) { await runner.rollbackTransaction(); throw ex; } finally{ await runner.release(); } } async refreshSignatureLevelsCache() { this.signatureLevelsCache = await this.signatureLevelRepo.find({ order: { sequence: 'ASC' } }); } async onApplicationBootstrap() { if (this.signatureMode) { const signatureLevels = await this.signatureLevelRepo.find(); const existedMap = new Map(signatureLevels.map((level)=>[ level.name, level ])); const usedSet = new Set(); const targetLevelNames = new Set(this.signatureLevels.map((level)=>level instanceof BaseSignatureLevelEntity ? level.name : level)); const runner = this.dataSource.createQueryRunner(); await runner.connect(); await runner.startTransaction(); try { await signatureLevels.filter((level)=>!targetLevelNames.has(level.name)).map((level)=>async ()=>{ await runner.manager.delete(ArticleSignatureEntity, { signatureLevelId: level.id }); await runner.manager.softDelete(BaseSignatureLevelEntity, { id: level.id }); }).reduce((prev, next)=>prev.then(next), Promise.resolve()); this.signatureLevelsCache = await this.signatureLevels.map((level, index)=>async (levels)=>{ if (level instanceof BaseSignatureLevelEntity) { level.sequence = index; level.required = true; await runner.manager.save(level); usedSet.add(level); return [ ...levels, level ]; } if (existedMap.has(level)) { const existedLevel = existedMap.get(level); existedLevel.sequence = index; existedLevel.required = true; await runner.manager.save(existedLevel); usedSet.add(existedLevel); return [ ...levels, existedLevel ]; } const newLevel = this.signatureLevelRepo.create({ name: level, required: true, sequence: index }); await runner.manager.save(newLevel); usedSet.add(newLevel); return levels; }).reduce((prev, next)=>prev.then(next), Promise.resolve([])); await runner.commitTransaction(); } catch (ex) { await runner.rollbackTransaction(); throw ex; } finally{ await runner.release(); } } } } ArticleSignatureService = _ts_decorate([ Injectable(), _ts_param(0, Inject(RESOLVED_ARTICLE_VERSION_REPO)), _ts_param(1, Inject(ENABLE_SIGNATURE_MODE)), _ts_param(2, Inject(SIGNATURE_LEVELS)), _ts_param(3, Inject(RESOLVED_SIGNATURE_LEVEL_REPO)), _ts_param(4, Inject(ArticleSignatureRepo)), _ts_param(5, Inject(DRAFT_MODE)), _ts_param(6, Inject(AUTO_RELEASE_AFTER_APPROVED)), _ts_param(7, InjectDataSource()) ], ArticleSignatureService); export { ArticleSignatureService };