diff --git a/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx
index 16ad6e1f1..def049621 100644
--- a/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx
+++ b/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx
@@ -59,7 +59,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
content: {
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: {
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": {
title: t("certd.pipelineCount"),
type: "text",
diff --git a/packages/ui/certd-client/src/views/sys/suite/setting/index.vue b/packages/ui/certd-client/src/views/sys/suite/setting/index.vue
index 9b69ad141..3e0577958 100644
--- a/packages/ui/certd-client/src/views/sys/suite/setting/index.vue
+++ b/packages/ui/certd-client/src/views/sys/suite/setting/index.vue
@@ -17,6 +17,7 @@
+
泛域名数量受域名总数量限制:泛域名会同时占用域名总数量和泛域名数量额度。
不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐
diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx
index e370d4be1..3ab6ca276 100644
--- a/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx
+++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx
@@ -223,6 +223,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
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": {
title: t("certd.pipeline_count"),
type: "text",
diff --git a/packages/ui/certd-server/db/migration/v10044__cert_wildcard_domain_count.sql b/packages/ui/certd-server/db/migration/v10044__cert_wildcard_domain_count.sql
new file mode 100644
index 000000000..6b99e4c95
--- /dev/null
+++ b/packages/ui/certd-server/db/migration/v10044__cert_wildcard_domain_count.sql
@@ -0,0 +1 @@
+ALTER TABLE cd_cert_info ADD COLUMN wildcard_domain_count integer NOT NULL DEFAULT 0;
diff --git a/packages/ui/certd-server/src/modules/auto/auto-a-register.ts b/packages/ui/certd-server/src/modules/auto/auto-a-register.ts
index 08488b620..2df4846ac 100644
--- a/packages/ui/certd-server/src/modules/auto/auto-a-register.ts
+++ b/packages/ui/certd-server/src/modules/auto/auto-a-register.ts
@@ -4,7 +4,7 @@ import { AutoLoadPlugins } from "./auto-load-plugins.js";
import { AutoCron } from "./auto-cron.js";
import { AutoMitterRegister } from "./auto-mitter-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";
@Autoload()
diff --git a/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts b/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts
deleted file mode 100644
index a542aea24..000000000
--- a/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts
+++ /dev/null
@@ -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);
- });
-
-});
diff --git a/packages/ui/certd-server/src/modules/auto/fix/auto-fix.test.ts b/packages/ui/certd-server/src/modules/auto/fix/auto-fix.test.ts
new file mode 100644
index 000000000..f2ab27e93
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/auto-fix.test.ts
@@ -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"]);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/auto/fix/auto-fix.ts b/packages/ui/certd-server/src/modules/auto/fix/auto-fix.ts
new file mode 100644
index 000000000..e007a6a0e
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/auto-fix.ts
@@ -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();
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.test.ts b/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.test.ts
new file mode 100644
index 000000000..732b7c25b
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.test.ts
@@ -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,
+ },
+ ]);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.ts b/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.ts
new file mode 100644
index 000000000..46689a093
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/cert-info-wildcard-domain-count-fix.ts
@@ -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);
+ }
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.test.ts b/packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.test.ts
new file mode 100644
index 000000000..88d0f0c3e
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.test.ts
@@ -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"),
+ });
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/auto/auto-fix.ts b/packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.ts
similarity index 51%
rename from packages/ui/certd-server/src/modules/auto/auto-fix.ts
rename to packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.ts
index cd37e550e..24f1a4e22 100644
--- a/packages/ui/certd-server/src/modules/auto/auto-fix.ts
+++ b/packages/ui/certd-server/src/modules/auto/fix/google-common-eab-account-key-fix.ts
@@ -1,11 +1,9 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
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 { PluginConfigService } from "../plugin/service/plugin-config-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";
+import { PluginConfigService } from "../../plugin/service/plugin-config-service.js";
+import { StorageService } from "../../pipeline/service/storage-service.js";
export function parseStorageValue(value?: string) {
if (!value) {
@@ -35,13 +33,9 @@ export function buildLegacyGoogleAccountConfigWhere(email: string) {
};
}
-export function buildOauthBoundType(type: string, subtype?: string) {
- return subtype ? `${type}:${subtype}` : type;
-}
-
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
-export class AutoFix {
+export class GoogleCommonEabAccountKeyFix {
@Inject()
pluginConfigService: PluginConfigService;
@@ -51,67 +45,7 @@ export class AutoFix {
@Inject()
storageService: StorageService;
- @Inject()
- sysSettingsService: SysSettingsService;
-
- @Inject()
- oauthBoundService: OauthBoundService;
-
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()) {
return;
}
@@ -163,7 +97,7 @@ export class AutoFix {
}
return null;
}
-
+
parseStorageValue(value?: string) {
return parseStorageValue(value);
}
diff --git a/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.test.ts b/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.test.ts
new file mode 100644
index 000000000..53feffff5
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.test.ts
@@ -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);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.ts b/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.ts
new file mode 100644
index 000000000..a44ce8d85
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/oauth-subtype-bound-type-fix.ts
@@ -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) });
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.test.ts b/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.test.ts
new file mode 100644
index 000000000..58c8f3f81
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.test.ts
@@ -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);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.ts b/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.ts
new file mode 100644
index 000000000..aeb650a94
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/auto/fix/suite-content-wildcard-domain-count-fix.ts
@@ -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;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts b/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts
index 1f1234ffb..d47d19138 100644
--- a/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts
+++ b/packages/ui/certd-server/src/modules/monitor/entity/cert-info.ts
@@ -18,6 +18,9 @@ export class CertInfoEntity {
@Column({ name: 'domain_count', comment: '域名数量' })
domainCount: number;
+ @Column({ name: 'wildcard_domain_count', comment: '泛域名数量', default: 0 })
+ wildcardDomainCount: number;
+
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
pipelineId: number;
diff --git a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.test.ts b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.test.ts
new file mode 100644
index 000000000..2b2fadd92
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.test.ts
@@ -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);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts
index 190840966..4c7c2852d 100644
--- a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts
+++ b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts
@@ -40,6 +40,22 @@ export class CertInfoService extends BaseService
{
});
}
+ 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) {
const found = await this.repository.findOne({
where: {
@@ -67,10 +83,12 @@ export class CertInfoService extends BaseService {
bean.domain = '';
bean.domains = '';
bean.domainCount = 0;
+ bean.wildcardDomainCount = 0;
} else {
bean.domain = domains[0];
bean.domains = domains.join(',');
bean.domainCount = domains.length;
+ bean.wildcardDomainCount = this.countWildcardDomains(domains);
}
await this.addOrUpdate(bean);
@@ -171,6 +189,7 @@ export class CertInfoService extends BaseService {
bean.domains = domains.join(',');
bean.domain = domains[0];
bean.domainCount = domains.length;
+ bean.wildcardDomainCount = this.countWildcardDomains(domains);
bean.effectiveTime = certReader.effective;
bean.expiresTime = certReader.expires;
bean.certProvider = certReader.detail.issuer.commonName;
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
index c29a7c898..1c6835e3e 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
@@ -53,6 +53,7 @@ import { executorQueue } from "@certd/lib-server";
import parser from "cron-parser";
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js";
+import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
const runningTasks: Map = new Map();
@@ -76,7 +77,6 @@ export class PipelineService extends BaseService {
historyService: HistoryService;
@Inject()
historyLogService: HistoryLogService;
-
@Inject()
pluginConfigGetter: PluginConfigGetter;
@@ -287,10 +287,7 @@ export class PipelineService extends BaseService {
});
}
- if (!isUpdate) {
- //如果是添加,校验数量
- await this.checkMaxPipelineCount(bean, pipeline, domains);
- }
+ await this.checkMaxPipelineCount(bean, pipeline, domains, old);
if (!bean.status) {
bean.status = ResultType.none;
@@ -345,7 +342,7 @@ export class PipelineService extends BaseService {
return bean
}
- private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[]) {
+ private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[], old?: PipelineEntity) {
// if (!isPlus()) {
// const count = await this.repository.count();
// if (count >= freeCount) {
@@ -363,15 +360,31 @@ export class PipelineService extends BaseService {
const suiteSetting = await this.userSuiteService.getSuiteSetting();
if (suiteSetting.enabled) {
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}条流水线,请购买或升级套餐`);
}
- 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}个域名,请购买或升级套餐`);
}
+
+ 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 userIsAdmin = await this.userService.isAdmin(userId);
@@ -1332,7 +1345,6 @@ export class PipelineService extends BaseService {
}
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
-
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.test.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.test.ts
new file mode 100644
index 000000000..a774c471c
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.test.ts
@@ -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);
+ });
+});
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.ts
new file mode 100644
index 000000000..165863e60
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-suite-limit.ts
@@ -0,0 +1,3 @@
+export function calcNextSuiteCountUsed(used: number, oldCount: number, newCount: number) {
+ return (used ?? 0) - (oldCount ?? 0) + (newCount ?? 0);
+}
diff --git a/packages/ui/certd-server/src/modules/suite/service/my-count-service.ts b/packages/ui/certd-server/src/modules/suite/service/my-count-service.ts
index 9faa2f14b..cedef6bf5 100644
--- a/packages/ui/certd-server/src/modules/suite/service/my-count-service.ts
+++ b/packages/ui/certd-server/src/modules/suite/service/my-count-service.ts
@@ -21,10 +21,12 @@ export class MyCountService implements IUsedCountService {
}
const pipelineCountUsed = await this.pipelineService.getUserPipelineCount(userId);
const domainCountUsed = await this.certInfoService.getUserDomainCount(userId);
+ const wildcardDomainCountUsed = await this.certInfoService.getUserWildcardDomainCount(userId);
const monitorCountUsed = await this.siteInfoService.getUserMonitorCount(userId);
return {
pipelineCountUsed,
domainCountUsed,
+ wildcardDomainCountUsed,
monitorCountUsed,
};
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c97bfcb2..9da3358d5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1621,16 +1621,16 @@ importers:
specifier: ^1.3.0
version: 1.3.0(encoding@0.1.13)
'@huaweicloud/huaweicloud-sdk-cdn':
- specifier: ^3.1.185
+ specifier: 3.1.185
version: 3.1.185
'@huaweicloud/huaweicloud-sdk-core':
- specifier: ^3.1.185
+ specifier: 3.1.185
version: 3.1.185
'@huaweicloud/huaweicloud-sdk-elb':
- specifier: ^3.1.185
+ specifier: 3.1.185
version: 3.1.185
'@huaweicloud/huaweicloud-sdk-iam':
- specifier: ^3.1.185
+ specifier: 3.1.185
version: 3.1.185
'@koa/cors':
specifier: ^5.0.0