@rytass/cms-base-nestjs-module
Version:
Rytass Content Management System NestJS Base Module
238 lines (235 loc) • 11.9 kB
JavaScript
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 };