From e9afbbcdc507e0ad5fcbf58c6f76003799bf986e Mon Sep 17 00:00:00 2001 From: forehalo Date: Thu, 6 Feb 2025 09:48:02 +0000 Subject: [PATCH] fix(server): cannot revalidate licenses (#9982) --- .../server/src/plugins/license/service.ts | 123 +++++++++++------- .../server/src/plugins/payment/types.ts | 8 +- 2 files changed, 78 insertions(+), 53 deletions(-) diff --git a/packages/backend/server/src/plugins/license/service.ts b/packages/backend/server/src/plugins/license/service.ts index 93f6a553f7..7621233a24 100644 --- a/packages/backend/server/src/plugins/license/service.ts +++ b/packages/backend/server/src/plugins/license/service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { InstalledLicense, PrismaClient } from '@prisma/client'; @@ -23,7 +23,7 @@ interface License { } @Injectable() -export class LicenseService { +export class LicenseService implements OnModuleInit { private readonly logger = new Logger(LicenseService.name); constructor( @@ -34,6 +34,55 @@ export class LicenseService { private readonly models: Models ) {} + async onModuleInit() { + if (this.config.isSelfhosted) { + this.event.on( + 'workspace.subscription.activated', + this.onWorkspaceSubscriptionUpdated + ); + this.event.on( + 'workspace.subscription.canceled', + this.onWorkspaceSubscriptionCanceled + ); + } + } + + private readonly onWorkspaceSubscriptionUpdated = async ({ + workspaceId, + plan, + recurring, + quantity, + }: Events['workspace.subscription.activated']) => { + switch (plan) { + case SubscriptionPlan.SelfHostedTeam: + await this.models.workspaceFeature.add( + workspaceId, + 'team_plan_v1', + `${recurring} team subscription activated`, + { + memberLimit: quantity, + } + ); + await this.permission.refreshSeatStatus(workspaceId, quantity); + break; + default: + break; + } + }; + + private readonly onWorkspaceSubscriptionCanceled = async ({ + workspaceId, + plan, + }: Events['workspace.subscription.canceled']) => { + switch (plan) { + case SubscriptionPlan.SelfHostedTeam: + await this.models.workspaceFeature.remove(workspaceId, 'team_plan_v1'); + break; + default: + break; + } + }; + async getLicense(workspaceId: string) { return this.db.installedLicense.findUnique({ select: { @@ -208,10 +257,14 @@ export class LicenseService { @Cron(CronExpression.EVERY_10_MINUTES) async licensesHealthCheck() { + if (!this.config.isSelfhosted) { + return; + } + const licenses = await this.db.installedLicense.findMany({ where: { validatedAt: { - lte: new Date(Date.now() - 1000 * 60 * 60), + lte: new Date(Date.now() - 1000 * 60 * 60 /* 1h */), }, }, }); @@ -224,7 +277,12 @@ export class LicenseService { private async revalidateLicense(license: InstalledLicense) { try { const res = await this.fetch( - `/api/team/licenses/${license.key}/health` + `/api/team/licenses/${license.key}/health`, + { + headers: { + 'x-validate-key': license.validateKey, + }, + } ); await this.db.installedLicense.update({ @@ -272,18 +330,23 @@ export class LicenseService { init?: RequestInit ): Promise { try { - const res = await fetch('https://app.affine.pro' + path, { - ...init, - headers: { - 'Content-Type': 'application/json', - }, - }); + const res = await fetch( + process.env.AFFINE_PRO_SERVER_ENDPOINT ?? + 'https://app.affine.pro' + path, + { + ...init, + headers: { + 'Content-Type': 'application/json', + ...init?.headers, + }, + } + ); if (!res.ok) { const body = (await res.json()) as UserFriendlyError; throw new UserFriendlyError( body.type as any, - body.name as any, + body.name.toLowerCase() as any, body.message, body.data ); @@ -306,42 +369,4 @@ export class LicenseService { ); } } - - @OnEvent('workspace.subscription.activated') - async onWorkspaceSubscriptionUpdated({ - workspaceId, - plan, - recurring, - quantity, - }: Events['workspace.subscription.activated']) { - switch (plan) { - case SubscriptionPlan.SelfHostedTeam: - await this.models.workspaceFeature.add( - workspaceId, - 'team_plan_v1', - `${recurring} team subscription activated`, - { - memberLimit: quantity, - } - ); - await this.permission.refreshSeatStatus(workspaceId, quantity); - break; - default: - break; - } - } - - @OnEvent('workspace.subscription.canceled') - async onWorkspaceSubscriptionCanceled({ - workspaceId, - plan, - }: Events['workspace.subscription.canceled']) { - switch (plan) { - case SubscriptionPlan.SelfHostedTeam: - await this.models.workspaceFeature.remove(workspaceId, 'team_plan_v1'); - break; - default: - break; - } - } } diff --git a/packages/backend/server/src/plugins/payment/types.ts b/packages/backend/server/src/plugins/payment/types.ts index b599a2047a..0e754bcca8 100644 --- a/packages/backend/server/src/plugins/payment/types.ts +++ b/packages/backend/server/src/plugins/payment/types.ts @@ -215,21 +215,21 @@ export const DEFAULT_PRICES = new Map([ // team [ `${SubscriptionPlan.Team}_${SubscriptionRecurring.Monthly}`, - { product: 'AFFiNE Team(per seat)', price: 1500 }, + { product: 'AFFiNE Team(per seat)', price: 1440 }, ], [ `${SubscriptionPlan.Team}_${SubscriptionRecurring.Yearly}`, - { product: 'AFFiNE Team(per seat)', price: 14400 }, + { product: 'AFFiNE Team(per seat)', price: 12000 }, ], // selfhost team [ `${SubscriptionPlan.SelfHostedTeam}_${SubscriptionRecurring.Monthly}`, - { product: 'AFFiNE Self-hosted Team(per seat)', price: 1500 }, + { product: 'AFFiNE Self-hosted Team(per seat)', price: 1440 }, ], [ `${SubscriptionPlan.SelfHostedTeam}_${SubscriptionRecurring.Yearly}`, - { product: 'AFFiNE Self-hosted Team(per seat)', price: 14400 }, + { product: 'AFFiNE Self-hosted Team(per seat)', price: 12000 }, ], ]);