mirror of
https://github.com/certd/certd.git
synced 2026-05-17 13:57:31 +08:00
perf: 商业版支持限制泛域名数量
This commit is contained in:
@@ -6,6 +6,9 @@ export default {
|
|||||||
specifications: "Specifications",
|
specifications: "Specifications",
|
||||||
pipeline: "Pipeline",
|
pipeline: "Pipeline",
|
||||||
domain: "Domain",
|
domain: "Domain",
|
||||||
|
totalDomain: "Total Domain Count",
|
||||||
|
wildcardDomain: "Wildcard Domain",
|
||||||
|
includedWildcardDomain: "Included Wildcard Domain Count",
|
||||||
deployTimes: "Deployments",
|
deployTimes: "Deployments",
|
||||||
monitorCount: "DomainMonitors",
|
monitorCount: "DomainMonitors",
|
||||||
duration: "Duration",
|
duration: "Duration",
|
||||||
@@ -24,7 +27,8 @@ export default {
|
|||||||
please_select_package: "Please select a package",
|
please_select_package: "Please select a package",
|
||||||
package: "Package",
|
package: "Package",
|
||||||
addon_package: "Addon Package",
|
addon_package: "Addon Package",
|
||||||
domain_count: "Domain Count",
|
domain_count: "Total Domain Count",
|
||||||
|
wildcard_domain_count: "Wildcard Domain Count",
|
||||||
unit_count: "pcs",
|
unit_count: "pcs",
|
||||||
pipeline_count: "Pipeline Count",
|
pipeline_count: "Pipeline Count",
|
||||||
unit_item: "items",
|
unit_item: "items",
|
||||||
@@ -42,6 +46,7 @@ export default {
|
|||||||
intro: "Introduction",
|
intro: "Introduction",
|
||||||
packageContent: "Package Content",
|
packageContent: "Package Content",
|
||||||
maxDomainCount: "Max Domain Count",
|
maxDomainCount: "Max Domain Count",
|
||||||
|
maxWildcardDomainCount: "Max Wildcard Domain Count",
|
||||||
maxPipelineCount: "Max Pipeline Count",
|
maxPipelineCount: "Max Pipeline Count",
|
||||||
maxDeployCount: "Max Deploy Count",
|
maxDeployCount: "Max Deploy Count",
|
||||||
maxMonitorCount: "Max Monitor Count",
|
maxMonitorCount: "Max Monitor Count",
|
||||||
@@ -51,6 +56,10 @@ export default {
|
|||||||
addon: "Addon",
|
addon: "Addon",
|
||||||
typeHelper: "Suite: Only the most recently purchased one is active at a time\nAddon: Multiple can be purchased, effective immediately without affecting the suite\nThe quantities of suite and addon can be accumulated",
|
typeHelper: "Suite: Only the most recently purchased one is active at a time\nAddon: Multiple can be purchased, effective immediately without affecting the suite\nThe quantities of suite and addon can be accumulated",
|
||||||
pipelineCount: "Pipeline Count",
|
pipelineCount: "Pipeline Count",
|
||||||
|
wildcardDomainCount: "Wildcard Domain Count",
|
||||||
|
wildcardDomainCountPart: "Included Wildcard Domain Count",
|
||||||
|
wildcardDomainCountSub: "- Wildcard Domain Count",
|
||||||
|
wildcardDomainCountHelper: "Wildcard domains are also limited by the total domain count: each wildcard domain consumes both domain count and wildcard domain count quota.",
|
||||||
unitPipeline: "pipelines",
|
unitPipeline: "pipelines",
|
||||||
deployCount: "Deployment Count",
|
deployCount: "Deployment Count",
|
||||||
unitDeploy: "times",
|
unitDeploy: "times",
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default {
|
|||||||
specifications: "规格",
|
specifications: "规格",
|
||||||
pipeline: "流水线",
|
pipeline: "流水线",
|
||||||
domain: "域名",
|
domain: "域名",
|
||||||
|
totalDomain: "域名总数量",
|
||||||
|
wildcardDomain: "泛域名",
|
||||||
|
includedWildcardDomain: "其中泛域名数量",
|
||||||
deployTimes: "部署次数",
|
deployTimes: "部署次数",
|
||||||
monitorCount: "域名监控数",
|
monitorCount: "域名监控数",
|
||||||
duration: "时长",
|
duration: "时长",
|
||||||
@@ -24,7 +27,8 @@ export default {
|
|||||||
please_select_package: "请选择套餐",
|
please_select_package: "请选择套餐",
|
||||||
package: "套餐",
|
package: "套餐",
|
||||||
addon_package: "加量包",
|
addon_package: "加量包",
|
||||||
domain_count: "域名数量",
|
domain_count: "域名总数量",
|
||||||
|
wildcard_domain_count: "泛域名数量",
|
||||||
unit_count: "个",
|
unit_count: "个",
|
||||||
pipeline_count: "流水线数量",
|
pipeline_count: "流水线数量",
|
||||||
unit_item: "条",
|
unit_item: "条",
|
||||||
@@ -42,6 +46,7 @@ export default {
|
|||||||
intro: "介绍",
|
intro: "介绍",
|
||||||
packageContent: "套餐内容",
|
packageContent: "套餐内容",
|
||||||
maxDomainCount: "最大域名数",
|
maxDomainCount: "最大域名数",
|
||||||
|
maxWildcardDomainCount: "最大泛域名数",
|
||||||
maxPipelineCount: "最大流水线数",
|
maxPipelineCount: "最大流水线数",
|
||||||
maxDeployCount: "最大部署数",
|
maxDeployCount: "最大部署数",
|
||||||
maxMonitorCount: "最大监控数",
|
maxMonitorCount: "最大监控数",
|
||||||
@@ -51,6 +56,10 @@ export default {
|
|||||||
addon: "加量包",
|
addon: "加量包",
|
||||||
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加",
|
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加",
|
||||||
pipelineCount: "流水线数量",
|
pipelineCount: "流水线数量",
|
||||||
|
wildcardDomainCount: "泛域名数量",
|
||||||
|
wildcardDomainCountPart: "其中泛域名数量",
|
||||||
|
wildcardDomainCountSub: "- 泛域名数量",
|
||||||
|
wildcardDomainCountHelper: "泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度;注意:如果域名总数有限制,泛域名数量不要设置为无限制。",
|
||||||
unitPipeline: "条",
|
unitPipeline: "条",
|
||||||
deployCount: "部署次数",
|
deployCount: "部署次数",
|
||||||
unitDeploy: "次",
|
unitDeploy: "次",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type SuiteDetail = {
|
|||||||
expiresTime?: number;
|
expiresTime?: number;
|
||||||
pipelineCount?: SuiteValue;
|
pipelineCount?: SuiteValue;
|
||||||
domainCount?: SuiteValue;
|
domainCount?: SuiteValue;
|
||||||
|
wildcardDomainCount?: SuiteValue;
|
||||||
deployCount?: SuiteValue;
|
deployCount?: SuiteValue;
|
||||||
monitorCount?: SuiteValue;
|
monitorCount?: SuiteValue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"content.maxDomainCount": {
|
"content.maxDomainCount": {
|
||||||
title: "域名数量",
|
title: "域名总数量",
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
key: ["content", "maxDomainCount"],
|
key: ["content", "maxDomainCount"],
|
||||||
@@ -168,6 +168,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
align: "center",
|
align: "center",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: "其中泛域名数量",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: "个",
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: "个",
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: "流水线数量",
|
title: "流水线数量",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
<span class="label">{{ $t("certd.order.specifications") }}:</span>
|
<span class="label">{{ $t("certd.order.specifications") }}:</span>
|
||||||
<span class="flex-o flex-wrap">
|
<span class="flex-o flex-wrap">
|
||||||
<span class="flex-o"> {{ $t("certd.order.pipeline") }}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.pipeline") }}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />; </span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.domain") }}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.totalDomain") }}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />; </span>
|
||||||
|
<span class="flex-o" style="padding-left: 2em">
|
||||||
|
- {{ $t("certd.order.includedWildcardDomain") }}<suite-value class="ml-5" :model-value="product.content.maxWildcardDomainCount" :unit="$t('certd.order.unit.count')" />;
|
||||||
|
</span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.deployTimes") }}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.deployTimes") }}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||||
<span class="flex-o"> {{ $t("certd.order.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />; </span>
|
<span class="flex-o"> {{ $t("certd.order.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="product.title" class="product-card">
|
<a-card :title="product.title" class="product-card">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<fs-values-format v-model="product.type" :dict="productTypeDictRef"></fs-values-format>
|
<fs-values-format :model-value="product.type" :dict="productTypeDictRef"></fs-values-format>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
|
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
|
||||||
@@ -12,9 +12,13 @@
|
|||||||
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名数量:</div>
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名总数量:</div>
|
||||||
<suite-value :model-value="product.content.maxDomainCount" unit="个" />
|
<suite-value :model-value="product.content.maxDomainCount" unit="个" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o" style="padding-left: 2em">- 其中泛域名数量:</div>
|
||||||
|
<suite-value :model-value="product.content.maxWildcardDomainCount" unit="个" />
|
||||||
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
|
|||||||
@@ -20,10 +20,14 @@
|
|||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
域名数量:
|
域名总数量:
|
||||||
</div>
|
</div>
|
||||||
<suite-value :model-value="detail.domainCount.max" :used="detail.domainCount.used" unit="个" />
|
<suite-value :model-value="detail.domainCount.max" :used="detail.domainCount.used" unit="个" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o ml-20">- 其中泛域名数量:</div>
|
||||||
|
<suite-value :model-value="detail.wildcardDomainCount.max" :used="detail.wildcardDomainCount.used" unit="个" />
|
||||||
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
header: t("certd.packageContent"),
|
header: t("certd.packageContent"),
|
||||||
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"],
|
columns: ["content.maxDomainCount", "content.maxWildcardDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"],
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
header: t("certd.price"),
|
header: t("certd.price"),
|
||||||
@@ -154,6 +154,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: t("certd.wildcardDomainCountPart"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
helper: t("certd.wildcardDomainCountHelper"),
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unitCount"),
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: t("certd.requiredField") }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unitCount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: t("certd.pipelineCount"),
|
title: t("certd.pipelineCount"),
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<div style="height: 400px">
|
<div style="height: 400px">
|
||||||
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="helper">泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度。</div>
|
||||||
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
align: "center",
|
align: "center",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"content.maxWildcardDomainCount": {
|
||||||
|
title: t("certd.wildcardDomainCountPart"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
key: ["content", "maxWildcardDomainCount"],
|
||||||
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unit_count"),
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: t("certd.field_required") }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: {
|
||||||
|
name: SuiteValue,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: t("certd.unit_count"),
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
},
|
||||||
"content.maxPipelineCount": {
|
"content.maxPipelineCount": {
|
||||||
title: t("certd.pipeline_count"),
|
title: t("certd.pipeline_count"),
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE cd_cert_info ADD COLUMN wildcard_domain_count integer NOT NULL DEFAULT 0;
|
||||||
@@ -4,7 +4,7 @@ import { AutoLoadPlugins } from "./auto-load-plugins.js";
|
|||||||
import { AutoCron } from "./auto-cron.js";
|
import { AutoCron } from "./auto-cron.js";
|
||||||
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
||||||
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
||||||
import { AutoFix } from "./auto-fix.js";
|
import { AutoFix } from "./fix/auto-fix.js";
|
||||||
import { AutoPrint } from "./auto-print.js";
|
import { AutoPrint } from "./auto-print.js";
|
||||||
|
|
||||||
@Autoload()
|
@Autoload()
|
||||||
|
|||||||
@@ -1,301 +0,0 @@
|
|||||||
import assert from "assert";
|
|
||||||
import esmock from "esmock";
|
|
||||||
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, buildOauthBoundType, parseStorageValue } from "./auto-fix.js";
|
|
||||||
|
|
||||||
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any; sysSettingsService?: any; oauthBoundService?: any }) {
|
|
||||||
const autoFix = new AutoFix();
|
|
||||||
autoFix.pluginConfigService = options.pluginConfigService;
|
|
||||||
autoFix.accessService = options.accessService;
|
|
||||||
autoFix.storageService = options.storageService;
|
|
||||||
autoFix.sysSettingsService = options.sysSettingsService;
|
|
||||||
autoFix.oauthBoundService = options.oauthBoundService;
|
|
||||||
return autoFix;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("AutoFix", () => {
|
|
||||||
it("parses legacy storage values", () => {
|
|
||||||
const config = parseStorageValue(
|
|
||||||
JSON.stringify({
|
|
||||||
value: {
|
|
||||||
key: "legacy-private-key",
|
|
||||||
accountUrl: "https://example.com/acct/1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(config.key, "legacy-private-key");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds the EAB account key payload", () => {
|
|
||||||
const payload = JSON.parse(buildEabAccountKeyValue("kid-1", "private-key"));
|
|
||||||
|
|
||||||
assert.deepEqual(payload, {
|
|
||||||
kid: "kid-1",
|
|
||||||
privateKey: "private-key",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds legacy Google account config query by exact email key only", () => {
|
|
||||||
assert.deepEqual(buildLegacyGoogleAccountConfigWhere("user@example.com"), {
|
|
||||||
userId: 1,
|
|
||||||
scope: "user",
|
|
||||||
namespace: "1",
|
|
||||||
key: "acme.config.google.user@example.com",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds OAuth subtype bound type", () => {
|
|
||||||
assert.equal(buildOauthBoundType("clogin", "alipay"), "clogin:alipay");
|
|
||||||
assert.equal(buildOauthBoundType("github"), "github");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("finds legacy Google account config by exact email key only", async () => {
|
|
||||||
let findOneWhere: any;
|
|
||||||
let findCalled = false;
|
|
||||||
const autoFix = createAutoFix({
|
|
||||||
pluginConfigService: null as any,
|
|
||||||
accessService: null as any,
|
|
||||||
storageService: {
|
|
||||||
getRepository() {
|
|
||||||
return {
|
|
||||||
async findOne(options: any) {
|
|
||||||
findOneWhere = options.where;
|
|
||||||
return {
|
|
||||||
value: JSON.stringify({
|
|
||||||
value: {
|
|
||||||
privateKey: "legacy-private-key",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async find() {
|
|
||||||
findCalled = true;
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
const config = await autoFix.getLegacyGoogleAccountConfig("user@example.com");
|
|
||||||
|
|
||||||
assert.equal(config.privateKey, "legacy-private-key");
|
|
||||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
|
||||||
assert.equal(findCalled, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not query legacy Google account config without email", async () => {
|
|
||||||
let repositoryCalled = false;
|
|
||||||
const autoFix = createAutoFix({
|
|
||||||
pluginConfigService: null as any,
|
|
||||||
accessService: null as any,
|
|
||||||
storageService: {
|
|
||||||
getRepository() {
|
|
||||||
repositoryCalled = true;
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
const config = await autoFix.getLegacyGoogleAccountConfig();
|
|
||||||
|
|
||||||
assert.equal(config, null);
|
|
||||||
assert.equal(repositoryCalled, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips Google common EAB account key fix outside commercial edition", async () => {
|
|
||||||
let pluginConfigCalled = false;
|
|
||||||
const autoFix = createAutoFix({
|
|
||||||
pluginConfigService: {
|
|
||||||
async getPluginConfig() {
|
|
||||||
pluginConfigCalled = true;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
accessService: null as any,
|
|
||||||
storageService: null as any,
|
|
||||||
sysSettingsService: {
|
|
||||||
async getPublicSettings() {
|
|
||||||
return {
|
|
||||||
oauthProviders: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
oauthBoundService: {
|
|
||||||
async transaction(callback: any) {
|
|
||||||
return await callback({
|
|
||||||
async findOne() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async update() {
|
|
||||||
return { affected: 0 };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await autoFix.init();
|
|
||||||
|
|
||||||
assert.equal(pluginConfigCalled, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fixes Google common EAB account key in commercial edition", async () => {
|
|
||||||
const { AutoFix: MockedAutoFix } = await esmock("./auto-fix.js", {
|
|
||||||
"@certd/plus-core": {
|
|
||||||
isComm: () => true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let getAccessByIdArgs: any[] = [];
|
|
||||||
let findOneWhere: any;
|
|
||||||
let updateAccessParam: any;
|
|
||||||
const autoFix = new MockedAutoFix();
|
|
||||||
autoFix.pluginConfigService = {
|
|
||||||
async getPluginConfig(options: any) {
|
|
||||||
assert.deepEqual(options, {
|
|
||||||
name: "CertApply",
|
|
||||||
type: "builtIn",
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
sysSetting: {
|
|
||||||
input: {
|
|
||||||
googleCommonEabAccessId: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
autoFix.accessService = {
|
|
||||||
async getAccessById(...args: any[]) {
|
|
||||||
getAccessByIdArgs = args;
|
|
||||||
return {
|
|
||||||
kid: "kid-1",
|
|
||||||
email: "user@example.com",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async updateAccess(param: any) {
|
|
||||||
updateAccessParam = param;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
autoFix.storageService = {
|
|
||||||
getRepository() {
|
|
||||||
return {
|
|
||||||
async findOne(options: any) {
|
|
||||||
findOneWhere = options.where;
|
|
||||||
return {
|
|
||||||
value: JSON.stringify({
|
|
||||||
value: {
|
|
||||||
privateKey: "legacy-private-key",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await autoFix.fixGoogleCommonEabAccountKey();
|
|
||||||
|
|
||||||
assert.deepEqual(getAccessByIdArgs, [12, false]);
|
|
||||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
|
||||||
assert.deepEqual(updateAccessParam, {
|
|
||||||
id: 12,
|
|
||||||
eabType: "google",
|
|
||||||
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fixes legacy OAuth bound type from string addon loginType and converts loginType to array", async () => {
|
|
||||||
const updates: any[] = [];
|
|
||||||
const autoFix = createAutoFix({
|
|
||||||
pluginConfigService: null as any,
|
|
||||||
accessService: null as any,
|
|
||||||
storageService: null as any,
|
|
||||||
sysSettingsService: {
|
|
||||||
async getPublicSettings() {
|
|
||||||
return {
|
|
||||||
oauthProviders: {
|
|
||||||
clogin: {
|
|
||||||
addonId: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
oauthBoundService: {
|
|
||||||
async transaction(callback: any) {
|
|
||||||
return await callback({
|
|
||||||
async findOne(entity: any, options: any) {
|
|
||||||
assert.equal(entity.name, "AddonEntity");
|
|
||||||
assert.deepEqual(options, { where: { id: 1 } });
|
|
||||||
return {
|
|
||||||
id: 1,
|
|
||||||
setting: JSON.stringify({
|
|
||||||
loginType: "alipay",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async update(entity: any, where: any, value: any) {
|
|
||||||
updates.push({ entity: entity.name, where, value });
|
|
||||||
return { affected: entity.name === "OauthBoundEntity" ? 1 : 0 };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await autoFix.fixOauthSubtypeBoundType();
|
|
||||||
|
|
||||||
assert.deepEqual(updates[0], {
|
|
||||||
entity: "OauthBoundEntity",
|
|
||||||
where: { type: "clogin" },
|
|
||||||
value: { type: "clogin:alipay" },
|
|
||||||
});
|
|
||||||
assert.equal(updates[1].entity, "AddonEntity");
|
|
||||||
assert.deepEqual(updates[1].where, { id: 1 });
|
|
||||||
assert.deepEqual(JSON.parse(updates[1].value.setting).loginType, ["alipay"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips OAuth subtype fix when addon loginType is already not legacy string", async () => {
|
|
||||||
let updateCalled = false;
|
|
||||||
const autoFix = createAutoFix({
|
|
||||||
pluginConfigService: null as any,
|
|
||||||
accessService: null as any,
|
|
||||||
storageService: null as any,
|
|
||||||
sysSettingsService: {
|
|
||||||
async getPublicSettings() {
|
|
||||||
return {
|
|
||||||
oauthProviders: {
|
|
||||||
clogin: {
|
|
||||||
addonId: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
oauthBoundService: {
|
|
||||||
async transaction(callback: any) {
|
|
||||||
return await callback({
|
|
||||||
async findOne() {
|
|
||||||
return {
|
|
||||||
id: 1,
|
|
||||||
setting: JSON.stringify({
|
|
||||||
loginType: ["alipay", "github"],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async update() {
|
|
||||||
updateCalled = true;
|
|
||||||
return { affected: 0 };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await autoFix.fixOauthSubtypeBoundType();
|
|
||||||
|
|
||||||
assert.equal(updateCalled, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { AutoFix } from "./auto-fix.js";
|
||||||
|
|
||||||
|
describe("AutoFix", () => {
|
||||||
|
it("runs fix tasks in order", async () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const autoFix = new AutoFix();
|
||||||
|
autoFix.googleCommonEabAccountKeyFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("google");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.oauthSubtypeBoundTypeFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("oauth");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.certInfoWildcardDomainCountFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("cert");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
autoFix.suiteContentWildcardDomainCountFix = {
|
||||||
|
async init() {
|
||||||
|
calls.push("suite");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await autoFix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(calls, ["google", "oauth", "cert", "suite"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { GoogleCommonEabAccountKeyFix } from "./google-common-eab-account-key-fix.js";
|
||||||
|
import { OauthSubtypeBoundTypeFix } from "./oauth-subtype-bound-type-fix.js";
|
||||||
|
import { CertInfoWildcardDomainCountFix } from "./cert-info-wildcard-domain-count-fix.js";
|
||||||
|
import { SuiteContentWildcardDomainCountFix } from "./suite-content-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class AutoFix {
|
||||||
|
@Inject()
|
||||||
|
googleCommonEabAccountKeyFix: GoogleCommonEabAccountKeyFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthSubtypeBoundTypeFix: OauthSubtypeBoundTypeFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
certInfoWildcardDomainCountFix: CertInfoWildcardDomainCountFix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
suiteContentWildcardDomainCountFix: SuiteContentWildcardDomainCountFix;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.googleCommonEabAccountKeyFix.init();
|
||||||
|
await this.oauthSubtypeBoundTypeFix.init();
|
||||||
|
await this.certInfoWildcardDomainCountFix.init();
|
||||||
|
await this.suiteContentWildcardDomainCountFix.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { CertInfoWildcardDomainCountFix } from "./cert-info-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
describe("CertInfoWildcardDomainCountFix", () => {
|
||||||
|
it("fixes cert info wildcard domain count only when value changed", async () => {
|
||||||
|
const updated: any[] = [];
|
||||||
|
const rows = [
|
||||||
|
{ id: 1, domains: "*.a.com,a.com, *.b.com ", wildcardDomainCount: 0 },
|
||||||
|
{ id: 2, domains: "c.com", wildcardDomainCount: 0 },
|
||||||
|
{ id: 3, domains: "*.d.com", wildcardDomainCount: 1 },
|
||||||
|
];
|
||||||
|
const fix = new CertInfoWildcardDomainCountFix();
|
||||||
|
fix.certInfoService = {
|
||||||
|
countWildcardDomains(domains: string[]) {
|
||||||
|
return domains.filter(item => item.trim().toLowerCase().startsWith("*.")).length;
|
||||||
|
},
|
||||||
|
async find() {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
async update(value: any) {
|
||||||
|
updated.push(value);
|
||||||
|
const row = rows.find(item => item.id === value.id);
|
||||||
|
Object.assign(row, value);
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(updated, [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
wildcardDomainCount: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class CertInfoWildcardDomainCountFix {
|
||||||
|
@Inject()
|
||||||
|
certInfoService: CertInfoService;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.certInfoService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const list = await this.certInfoService.find({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
domains: true,
|
||||||
|
wildcardDomainCount: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let fixedCount = 0;
|
||||||
|
for (const item of list) {
|
||||||
|
if (!item.domains) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const wildcardDomainCount = this.certInfoService.countWildcardDomains(item.domains.split(","));
|
||||||
|
if ((item.wildcardDomainCount ?? 0) === wildcardDomainCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await this.certInfoService.update({
|
||||||
|
id: item.id,
|
||||||
|
wildcardDomainCount,
|
||||||
|
});
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
logger.info(`已修复证书泛域名数量历史数据,数量=${fixedCount}`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复证书泛域名数量历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import esmock from "esmock";
|
||||||
|
import { buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, GoogleCommonEabAccountKeyFix, parseStorageValue } from "./google-common-eab-account-key-fix.js";
|
||||||
|
|
||||||
|
describe("GoogleCommonEabAccountKeyFix", () => {
|
||||||
|
it("parses legacy storage values", () => {
|
||||||
|
const config = parseStorageValue(
|
||||||
|
JSON.stringify({
|
||||||
|
value: {
|
||||||
|
key: "legacy-private-key",
|
||||||
|
accountUrl: "https://example.com/acct/1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(config.key, "legacy-private-key");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds the EAB account key payload", () => {
|
||||||
|
const payload = JSON.parse(buildEabAccountKeyValue("kid-1", "private-key"));
|
||||||
|
|
||||||
|
assert.deepEqual(payload, {
|
||||||
|
kid: "kid-1",
|
||||||
|
privateKey: "private-key",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds legacy Google account config query by exact email key only", () => {
|
||||||
|
assert.deepEqual(buildLegacyGoogleAccountConfigWhere("user@example.com"), {
|
||||||
|
userId: 1,
|
||||||
|
scope: "user",
|
||||||
|
namespace: "1",
|
||||||
|
key: "acme.config.google.user@example.com",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds legacy Google account config by exact email key only", async () => {
|
||||||
|
let findOneWhere: any;
|
||||||
|
let findCalled = false;
|
||||||
|
const fix = new GoogleCommonEabAccountKeyFix();
|
||||||
|
fix.storageService = {
|
||||||
|
getRepository() {
|
||||||
|
return {
|
||||||
|
async findOne(options: any) {
|
||||||
|
findOneWhere = options.where;
|
||||||
|
return {
|
||||||
|
value: JSON.stringify({
|
||||||
|
value: {
|
||||||
|
privateKey: "legacy-private-key",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async find() {
|
||||||
|
findCalled = true;
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const config = await fix.getLegacyGoogleAccountConfig("user@example.com");
|
||||||
|
|
||||||
|
assert.equal(config.privateKey, "legacy-private-key");
|
||||||
|
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||||
|
assert.equal(findCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not query legacy Google account config without email", async () => {
|
||||||
|
let repositoryCalled = false;
|
||||||
|
const fix = new GoogleCommonEabAccountKeyFix();
|
||||||
|
fix.storageService = {
|
||||||
|
getRepository() {
|
||||||
|
repositoryCalled = true;
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const config = await fix.getLegacyGoogleAccountConfig();
|
||||||
|
|
||||||
|
assert.equal(config, null);
|
||||||
|
assert.equal(repositoryCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips Google common EAB account key fix outside commercial edition", async () => {
|
||||||
|
const { GoogleCommonEabAccountKeyFix: MockedFix } = await esmock("./google-common-eab-account-key-fix.js", {
|
||||||
|
"@certd/plus-core": {
|
||||||
|
isComm: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let pluginConfigCalled = false;
|
||||||
|
const fix = new MockedFix();
|
||||||
|
fix.pluginConfigService = {
|
||||||
|
async getPluginConfig() {
|
||||||
|
pluginConfigCalled = true;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.equal(pluginConfigCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fixes Google common EAB account key in commercial edition", async () => {
|
||||||
|
const { GoogleCommonEabAccountKeyFix: MockedFix } = await esmock("./google-common-eab-account-key-fix.js", {
|
||||||
|
"@certd/plus-core": {
|
||||||
|
isComm: () => true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let getAccessByIdArgs: any[] = [];
|
||||||
|
let findOneWhere: any;
|
||||||
|
let updateAccessParam: any;
|
||||||
|
const fix = new MockedFix();
|
||||||
|
fix.pluginConfigService = {
|
||||||
|
async getPluginConfig(options: any) {
|
||||||
|
assert.deepEqual(options, {
|
||||||
|
name: "CertApply",
|
||||||
|
type: "builtIn",
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
sysSetting: {
|
||||||
|
input: {
|
||||||
|
googleCommonEabAccessId: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fix.accessService = {
|
||||||
|
async getAccessById(...args: any[]) {
|
||||||
|
getAccessByIdArgs = args;
|
||||||
|
return {
|
||||||
|
kid: "kid-1",
|
||||||
|
email: "user@example.com",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async updateAccess(param: any) {
|
||||||
|
updateAccessParam = param;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fix.storageService = {
|
||||||
|
getRepository() {
|
||||||
|
return {
|
||||||
|
async findOne(options: any) {
|
||||||
|
findOneWhere = options.where;
|
||||||
|
return {
|
||||||
|
value: JSON.stringify({
|
||||||
|
value: {
|
||||||
|
privateKey: "legacy-private-key",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(getAccessByIdArgs, [12, false]);
|
||||||
|
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||||
|
assert.deepEqual(updateAccessParam, {
|
||||||
|
id: 12,
|
||||||
|
eabType: "google",
|
||||||
|
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+5
-71
@@ -1,11 +1,9 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { logger } from "@certd/basic";
|
import { logger } from "@certd/basic";
|
||||||
import { AccessService, AddonEntity, SysSettingsService } from "@certd/lib-server";
|
import { AccessService } from "@certd/lib-server";
|
||||||
import { isComm } from "@certd/plus-core";
|
import { isComm } from "@certd/plus-core";
|
||||||
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
|
import { PluginConfigService } from "../../plugin/service/plugin-config-service.js";
|
||||||
import { StorageService } from "../pipeline/service/storage-service.js";
|
import { StorageService } from "../../pipeline/service/storage-service.js";
|
||||||
import { OauthBoundService } from "../login/service/oauth-bound-service.js";
|
|
||||||
import { OauthBoundEntity } from "../login/entity/oauth-bound.js";
|
|
||||||
|
|
||||||
export function parseStorageValue(value?: string) {
|
export function parseStorageValue(value?: string) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -35,13 +33,9 @@ export function buildLegacyGoogleAccountConfigWhere(email: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildOauthBoundType(type: string, subtype?: string) {
|
|
||||||
return subtype ? `${type}:${subtype}` : type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class AutoFix {
|
export class GoogleCommonEabAccountKeyFix {
|
||||||
@Inject()
|
@Inject()
|
||||||
pluginConfigService: PluginConfigService;
|
pluginConfigService: PluginConfigService;
|
||||||
|
|
||||||
@@ -51,67 +45,7 @@ export class AutoFix {
|
|||||||
@Inject()
|
@Inject()
|
||||||
storageService: StorageService;
|
storageService: StorageService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
sysSettingsService: SysSettingsService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
oauthBoundService: OauthBoundService;
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.fixGoogleCommonEabAccountKey();
|
|
||||||
await this.fixOauthSubtypeBoundType();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fixOauthSubtypeBoundType() {
|
|
||||||
try {
|
|
||||||
const publicSettings = await this.sysSettingsService.getPublicSettings();
|
|
||||||
const oauthProviders = publicSettings.oauthProviders || {};
|
|
||||||
await this.oauthBoundService.transaction(async manager => {
|
|
||||||
for (const [type, provider] of Object.entries(oauthProviders)) {
|
|
||||||
if (!provider.addonId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addonEntity = await manager.findOne(AddonEntity, { where: { id: provider.addonId } });
|
|
||||||
const legacyLoginType = this.getLegacyAddonLoginType(addonEntity?.setting);
|
|
||||||
if (!legacyLoginType) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newType = buildOauthBoundType(type, legacyLoginType);
|
|
||||||
const res = await manager.update(OauthBoundEntity, { type }, { type: newType });
|
|
||||||
if (res.affected) {
|
|
||||||
logger.info(`已修复OAuth绑定历史数据,${type} -> ${newType},数量=${res.affected}`);
|
|
||||||
}
|
|
||||||
await this.convertLegacyAddonLoginTypeToArray(addonEntity, legacyLoginType, manager);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
logger.error("修复OAuth subtype绑定历史数据失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLegacyAddonLoginType(settingValue?: string) {
|
|
||||||
if (!settingValue) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const setting = JSON.parse(settingValue);
|
|
||||||
return typeof setting.loginType === "string" && setting.loginType ? setting.loginType : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async convertLegacyAddonLoginTypeToArray(addonEntity: AddonEntity | null, loginType: string, manager: any) {
|
|
||||||
if (!addonEntity?.setting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const setting = JSON.parse(addonEntity.setting);
|
|
||||||
if (typeof setting.loginType !== "string") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setting.loginType = [loginType];
|
|
||||||
await manager.update(AddonEntity, { id: addonEntity.id }, { setting: JSON.stringify(setting) });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fixGoogleCommonEabAccountKey() {
|
|
||||||
if (!isComm()) {
|
if (!isComm()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -163,7 +97,7 @@ export class AutoFix {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseStorageValue(value?: string) {
|
parseStorageValue(value?: string) {
|
||||||
return parseStorageValue(value);
|
return parseStorageValue(value);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { buildOauthBoundType, OauthSubtypeBoundTypeFix } from "./oauth-subtype-bound-type-fix.js";
|
||||||
|
|
||||||
|
describe("OauthSubtypeBoundTypeFix", () => {
|
||||||
|
it("builds OAuth subtype bound type", () => {
|
||||||
|
assert.equal(buildOauthBoundType("clogin", "alipay"), "clogin:alipay");
|
||||||
|
assert.equal(buildOauthBoundType("github"), "github");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fixes legacy OAuth bound type from string addon loginType and converts loginType to array", async () => {
|
||||||
|
const updates: any[] = [];
|
||||||
|
const fix = new OauthSubtypeBoundTypeFix();
|
||||||
|
fix.sysSettingsService = {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
fix.oauthBoundService = {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne(entity: any, options: any) {
|
||||||
|
assert.equal(entity.name, "AddonEntity");
|
||||||
|
assert.deepEqual(options, { where: { id: 1 } });
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: "alipay",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update(entity: any, where: any, value: any) {
|
||||||
|
updates.push({ entity: entity.name, where, value });
|
||||||
|
return { affected: entity.name === "OauthBoundEntity" ? 1 : 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.deepEqual(updates[0], {
|
||||||
|
entity: "OauthBoundEntity",
|
||||||
|
where: { type: "clogin" },
|
||||||
|
value: { type: "clogin:alipay" },
|
||||||
|
});
|
||||||
|
assert.equal(updates[1].entity, "AddonEntity");
|
||||||
|
assert.deepEqual(updates[1].where, { id: 1 });
|
||||||
|
assert.deepEqual(JSON.parse(updates[1].value.setting).loginType, ["alipay"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips OAuth subtype fix when addon loginType is already not legacy string", async () => {
|
||||||
|
let updateCalled = false;
|
||||||
|
const fix = new OauthSubtypeBoundTypeFix();
|
||||||
|
fix.sysSettingsService = {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
fix.oauthBoundService = {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne() {
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: ["alipay", "github"],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
updateCalled = true;
|
||||||
|
return { affected: 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.equal(updateCalled, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { AddonEntity, SysSettingsService } from "@certd/lib-server";
|
||||||
|
import { OauthBoundService } from "../../login/service/oauth-bound-service.js";
|
||||||
|
import { OauthBoundEntity } from "../../login/entity/oauth-bound.js";
|
||||||
|
|
||||||
|
export function buildOauthBoundType(type: string, subtype?: string) {
|
||||||
|
return subtype ? `${type}:${subtype}` : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class OauthSubtypeBoundTypeFix {
|
||||||
|
@Inject()
|
||||||
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthBoundService: OauthBoundService;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const publicSettings = await this.sysSettingsService.getPublicSettings();
|
||||||
|
const oauthProviders = publicSettings.oauthProviders || {};
|
||||||
|
await this.oauthBoundService.transaction(async manager => {
|
||||||
|
for (const [type, provider] of Object.entries(oauthProviders)) {
|
||||||
|
if (!provider.addonId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addonEntity = await manager.findOne(AddonEntity, { where: { id: provider.addonId } });
|
||||||
|
const legacyLoginType = this.getLegacyAddonLoginType(addonEntity?.setting);
|
||||||
|
if (!legacyLoginType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newType = buildOauthBoundType(type, legacyLoginType);
|
||||||
|
const res = await manager.update(OauthBoundEntity, { type }, { type: newType });
|
||||||
|
if (res.affected) {
|
||||||
|
logger.info(`已修复OAuth绑定历史数据,${type} -> ${newType},数量=${res.affected}`);
|
||||||
|
}
|
||||||
|
await this.convertLegacyAddonLoginTypeToArray(addonEntity, legacyLoginType, manager);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复OAuth subtype绑定历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLegacyAddonLoginType(settingValue?: string) {
|
||||||
|
if (!settingValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(settingValue);
|
||||||
|
return typeof setting.loginType === "string" && setting.loginType ? setting.loginType : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async convertLegacyAddonLoginTypeToArray(addonEntity: AddonEntity | null, loginType: string, manager: any) {
|
||||||
|
if (!addonEntity?.setting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(addonEntity.setting);
|
||||||
|
if (typeof setting.loginType !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setting.loginType = [loginType];
|
||||||
|
await manager.update(AddonEntity, { id: addonEntity.id }, { setting: JSON.stringify(setting) });
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { fixSuiteContentWildcardDomainCount, SuiteContentWildcardDomainCountFix } from "./suite-content-wildcard-domain-count-fix.js";
|
||||||
|
|
||||||
|
describe("SuiteContentWildcardDomainCountFix", () => {
|
||||||
|
it("fills missing suite wildcard domain count from total domain count", () => {
|
||||||
|
const fixed = fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: 10,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(JSON.parse(fixed).maxWildcardDomainCount, 10);
|
||||||
|
assert.equal(
|
||||||
|
JSON.parse(
|
||||||
|
fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: -1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).maxWildcardDomainCount,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
fixSuiteContentWildcardDomainCount(
|
||||||
|
JSON.stringify({
|
||||||
|
maxDomainCount: 10,
|
||||||
|
maxWildcardDomainCount: 3,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fixes suite content wildcard domain count in product and user suite tables", async () => {
|
||||||
|
const rows = {
|
||||||
|
cd_product: [
|
||||||
|
{ id: 1, content: JSON.stringify({ maxDomainCount: 1 }) },
|
||||||
|
{ id: 2, content: JSON.stringify({ maxDomainCount: 1, maxWildcardDomainCount: 0 }) },
|
||||||
|
],
|
||||||
|
cd_user_suite: [{ id: 3, content: JSON.stringify({ maxDomainCount: 2 }) }],
|
||||||
|
};
|
||||||
|
const updates: any[] = [];
|
||||||
|
const entityManager = {
|
||||||
|
async query(sql: string) {
|
||||||
|
const table = sql.includes("cd_product") ? "cd_product" : "cd_user_suite";
|
||||||
|
return rows[table];
|
||||||
|
},
|
||||||
|
async update(tableName: string, where: any, value: any) {
|
||||||
|
updates.push({ tableName, where, value });
|
||||||
|
const row = rows[tableName].find((item: any) => item.id === where.id);
|
||||||
|
Object.assign(row, value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const fix = new SuiteContentWildcardDomainCountFix();
|
||||||
|
fix.dataSourceManager = {
|
||||||
|
getDataSource() {
|
||||||
|
return {
|
||||||
|
manager: entityManager,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fix.init();
|
||||||
|
await fix.init();
|
||||||
|
|
||||||
|
assert.equal(updates.length, 2);
|
||||||
|
assert.equal(updates[0].tableName, "cd_product");
|
||||||
|
assert.equal(JSON.parse(updates[0].value.content).maxWildcardDomainCount, 1);
|
||||||
|
assert.equal(updates[1].tableName, "cd_user_suite");
|
||||||
|
assert.equal(JSON.parse(updates[1].value.content).maxWildcardDomainCount, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||||
|
|
||||||
|
export function fixSuiteContentWildcardDomainCount(contentValue?: string) {
|
||||||
|
if (!contentValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const content = JSON.parse(contentValue);
|
||||||
|
if (content.maxWildcardDomainCount != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
content.maxWildcardDomainCount = content.maxDomainCount == null || content.maxDomainCount === -1 ? -1 : content.maxDomainCount;
|
||||||
|
return JSON.stringify(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class SuiteContentWildcardDomainCountFix {
|
||||||
|
@Inject()
|
||||||
|
dataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.dataSourceManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const entityManager = this.dataSourceManager.getDataSource("default").manager;
|
||||||
|
let fixedCount = 0;
|
||||||
|
fixedCount += await this.fixSuiteContentWildcardDomainCountByTable(entityManager, "cd_product");
|
||||||
|
fixedCount += await this.fixSuiteContentWildcardDomainCountByTable(entityManager, "cd_user_suite");
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
logger.info(`已修复套餐最大泛域名数量历史数据,数量=${fixedCount}`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复套餐最大泛域名数量历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fixSuiteContentWildcardDomainCountByTable(entityManager: any, tableName: string) {
|
||||||
|
const list = await entityManager.query(`select id, content from ${tableName}`);
|
||||||
|
let fixedCount = 0;
|
||||||
|
for (const item of list) {
|
||||||
|
const content = fixSuiteContentWildcardDomainCount(item.content);
|
||||||
|
if (!content) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await entityManager.update(tableName, { id: item.id }, { content });
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
return fixedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@ export class CertInfoEntity {
|
|||||||
@Column({ name: 'domain_count', comment: '域名数量' })
|
@Column({ name: 'domain_count', comment: '域名数量' })
|
||||||
domainCount: number;
|
domainCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'wildcard_domain_count', comment: '泛域名数量', default: 0 })
|
||||||
|
wildcardDomainCount: number;
|
||||||
|
|
||||||
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
||||||
pipelineId: number;
|
pipelineId: number;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { CertInfoService } from "./cert-info-service.js";
|
||||||
|
|
||||||
|
describe("CertInfoService", () => {
|
||||||
|
it("counts wildcard domains by normalized prefix", () => {
|
||||||
|
const service = new CertInfoService();
|
||||||
|
|
||||||
|
assert.equal(service.countWildcardDomains(["*.a.com", "a.com", " *.B.com "]), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("saves wildcard domain count when updating pipeline domains", async () => {
|
||||||
|
const service = new CertInfoService();
|
||||||
|
let saved: any;
|
||||||
|
service.repository = {
|
||||||
|
async findOne() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
service.addOrUpdate = async (bean: any) => {
|
||||||
|
saved = bean;
|
||||||
|
return bean;
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.updateDomains(1, 2, null, ["*.a.com", "a.com", "*.b.com"], "pipeline");
|
||||||
|
|
||||||
|
assert.equal(saved.domainCount, 3);
|
||||||
|
assert.equal(saved.wildcardDomainCount, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -40,6 +40,22 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserWildcardDomainCount(userId: number) {
|
||||||
|
if (userId == null) {
|
||||||
|
throw new Error('userId is required');
|
||||||
|
}
|
||||||
|
return await this.repository.sum('wildcardDomainCount', {
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
countWildcardDomains(domains?: string[]) {
|
||||||
|
if (!domains) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return domains.filter(domain => domain?.trim().toLowerCase().startsWith("*.")).length;
|
||||||
|
}
|
||||||
|
|
||||||
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
|
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
|
||||||
const found = await this.repository.findOne({
|
const found = await this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -67,10 +83,12 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
bean.domain = '';
|
bean.domain = '';
|
||||||
bean.domains = '';
|
bean.domains = '';
|
||||||
bean.domainCount = 0;
|
bean.domainCount = 0;
|
||||||
|
bean.wildcardDomainCount = 0;
|
||||||
} else {
|
} else {
|
||||||
bean.domain = domains[0];
|
bean.domain = domains[0];
|
||||||
bean.domains = domains.join(',');
|
bean.domains = domains.join(',');
|
||||||
bean.domainCount = domains.length;
|
bean.domainCount = domains.length;
|
||||||
|
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.addOrUpdate(bean);
|
await this.addOrUpdate(bean);
|
||||||
@@ -171,6 +189,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
|||||||
bean.domains = domains.join(',');
|
bean.domains = domains.join(',');
|
||||||
bean.domain = domains[0];
|
bean.domain = domains[0];
|
||||||
bean.domainCount = domains.length;
|
bean.domainCount = domains.length;
|
||||||
|
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||||
bean.effectiveTime = certReader.effective;
|
bean.effectiveTime = certReader.effective;
|
||||||
bean.expiresTime = certReader.expires;
|
bean.expiresTime = certReader.expires;
|
||||||
bean.certProvider = certReader.detail.issuer.commonName;
|
bean.certProvider = certReader.detail.issuer.commonName;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import { executorQueue } from "@certd/lib-server";
|
|||||||
import parser from "cron-parser";
|
import parser from "cron-parser";
|
||||||
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
|
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
|
||||||
import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js";
|
import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js";
|
||||||
|
import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
|
||||||
const runningTasks: Map<string | number, Executor> = new Map();
|
const runningTasks: Map<string | number, Executor> = new Map();
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +77,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
historyService: HistoryService;
|
historyService: HistoryService;
|
||||||
@Inject()
|
@Inject()
|
||||||
historyLogService: HistoryLogService;
|
historyLogService: HistoryLogService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
pluginConfigGetter: PluginConfigGetter;
|
pluginConfigGetter: PluginConfigGetter;
|
||||||
|
|
||||||
@@ -287,10 +287,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUpdate) {
|
await this.checkMaxPipelineCount(bean, pipeline, domains, old);
|
||||||
//如果是添加,校验数量
|
|
||||||
await this.checkMaxPipelineCount(bean, pipeline, domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bean.status) {
|
if (!bean.status) {
|
||||||
bean.status = ResultType.none;
|
bean.status = ResultType.none;
|
||||||
@@ -345,7 +342,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
return bean
|
return bean
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[]) {
|
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[], old?: PipelineEntity) {
|
||||||
// if (!isPlus()) {
|
// if (!isPlus()) {
|
||||||
// const count = await this.repository.count();
|
// const count = await this.repository.count();
|
||||||
// if (count >= freeCount) {
|
// if (count >= freeCount) {
|
||||||
@@ -363,15 +360,31 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
const suiteSetting = await this.userSuiteService.getSuiteSetting();
|
const suiteSetting = await this.userSuiteService.getSuiteSetting();
|
||||||
if (suiteSetting.enabled) {
|
if (suiteSetting.enabled) {
|
||||||
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
||||||
if (userSuite?.pipelineCount.max != -1 && userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
if (!old && userSuite?.pipelineCount.max != -1 && userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
||||||
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userSuite.domainCount.max != -1 && userSuite.domainCount.used + domains.length > userSuite.domainCount.max) {
|
let oldDomainCount = 0;
|
||||||
|
let oldWildcardDomainCount = 0;
|
||||||
|
if (old?.id) {
|
||||||
|
const oldCertInfo = await this.certInfoService.getByPipelineId(old.id);
|
||||||
|
oldDomainCount = oldCertInfo?.domainCount ?? 0;
|
||||||
|
oldWildcardDomainCount = oldCertInfo?.wildcardDomainCount ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextDomainCountUsed = calcNextSuiteCountUsed(userSuite.domainCount.used, oldDomainCount, domains.length);
|
||||||
|
if (userSuite.domainCount.max != -1 && nextDomainCountUsed > userSuite.domainCount.max) {
|
||||||
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suiteWildcardDomainCount = userSuite.wildcardDomainCount;
|
||||||
|
const wildcardDomainCount = this.certInfoService.countWildcardDomains(domains);
|
||||||
|
const nextWildcardDomainCountUsed = calcNextSuiteCountUsed(suiteWildcardDomainCount.used, oldWildcardDomainCount, wildcardDomainCount);
|
||||||
|
if (suiteWildcardDomainCount.max != -1 && nextWildcardDomainCountUsed > suiteWildcardDomainCount.max) {
|
||||||
|
throw new NeedSuiteException(`对不起,您最多只能添加${suiteWildcardDomainCount.max}个泛域名,请购买或升级套餐`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!old) {
|
||||||
//非商业版校验用户最大流水线数量
|
//非商业版校验用户最大流水线数量
|
||||||
const userId = bean.userId;
|
const userId = bean.userId;
|
||||||
const userIsAdmin = await this.userService.isAdmin(userId);
|
const userIsAdmin = await this.userService.isAdmin(userId);
|
||||||
@@ -1332,7 +1345,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
|
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
|
||||||
|
|
||||||
const randomHour = Math.floor(Math.random() * 6);
|
const randomHour = Math.floor(Math.random() * 6);
|
||||||
const randomMin = Math.floor(Math.random() * 60);
|
const randomMin = Math.floor(Math.random() * 60);
|
||||||
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
|
||||||
|
|
||||||
|
describe("Pipeline suite limits", () => {
|
||||||
|
it("calculates next usage by subtracting current pipeline usage on update", () => {
|
||||||
|
assert.equal(calcNextSuiteCountUsed(2, 1, 1), 2);
|
||||||
|
assert.equal(calcNextSuiteCountUsed(2, 1, 2), 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function calcNextSuiteCountUsed(used: number, oldCount: number, newCount: number) {
|
||||||
|
return (used ?? 0) - (oldCount ?? 0) + (newCount ?? 0);
|
||||||
|
}
|
||||||
@@ -21,10 +21,12 @@ export class MyCountService implements IUsedCountService {
|
|||||||
}
|
}
|
||||||
const pipelineCountUsed = await this.pipelineService.getUserPipelineCount(userId);
|
const pipelineCountUsed = await this.pipelineService.getUserPipelineCount(userId);
|
||||||
const domainCountUsed = await this.certInfoService.getUserDomainCount(userId);
|
const domainCountUsed = await this.certInfoService.getUserDomainCount(userId);
|
||||||
|
const wildcardDomainCountUsed = await this.certInfoService.getUserWildcardDomainCount(userId);
|
||||||
const monitorCountUsed = await this.siteInfoService.getUserMonitorCount(userId);
|
const monitorCountUsed = await this.siteInfoService.getUserMonitorCount(userId);
|
||||||
return {
|
return {
|
||||||
pipelineCountUsed,
|
pipelineCountUsed,
|
||||||
domainCountUsed,
|
domainCountUsed,
|
||||||
|
wildcardDomainCountUsed,
|
||||||
monitorCountUsed,
|
monitorCountUsed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+4
-4
@@ -1621,16 +1621,16 @@ importers:
|
|||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0(encoding@0.1.13)
|
version: 1.3.0(encoding@0.1.13)
|
||||||
'@huaweicloud/huaweicloud-sdk-cdn':
|
'@huaweicloud/huaweicloud-sdk-cdn':
|
||||||
specifier: ^3.1.185
|
specifier: 3.1.185
|
||||||
version: 3.1.185
|
version: 3.1.185
|
||||||
'@huaweicloud/huaweicloud-sdk-core':
|
'@huaweicloud/huaweicloud-sdk-core':
|
||||||
specifier: ^3.1.185
|
specifier: 3.1.185
|
||||||
version: 3.1.185
|
version: 3.1.185
|
||||||
'@huaweicloud/huaweicloud-sdk-elb':
|
'@huaweicloud/huaweicloud-sdk-elb':
|
||||||
specifier: ^3.1.185
|
specifier: 3.1.185
|
||||||
version: 3.1.185
|
version: 3.1.185
|
||||||
'@huaweicloud/huaweicloud-sdk-iam':
|
'@huaweicloud/huaweicloud-sdk-iam':
|
||||||
specifier: ^3.1.185
|
specifier: 3.1.185
|
||||||
version: 3.1.185
|
version: 3.1.185
|
||||||
'@koa/cors':
|
'@koa/cors':
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user