fix(server): cannot revalidate licenses (#9982)

This commit is contained in:
forehalo
2025-02-06 09:48:02 +00:00
parent 0aa9602d26
commit e9afbbcdc5
2 changed files with 78 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule'; import { Cron, CronExpression } from '@nestjs/schedule';
import { InstalledLicense, PrismaClient } from '@prisma/client'; import { InstalledLicense, PrismaClient } from '@prisma/client';
@@ -23,7 +23,7 @@ interface License {
} }
@Injectable() @Injectable()
export class LicenseService { export class LicenseService implements OnModuleInit {
private readonly logger = new Logger(LicenseService.name); private readonly logger = new Logger(LicenseService.name);
constructor( constructor(
@@ -34,6 +34,55 @@ export class LicenseService {
private readonly models: Models 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) { async getLicense(workspaceId: string) {
return this.db.installedLicense.findUnique({ return this.db.installedLicense.findUnique({
select: { select: {
@@ -208,10 +257,14 @@ export class LicenseService {
@Cron(CronExpression.EVERY_10_MINUTES) @Cron(CronExpression.EVERY_10_MINUTES)
async licensesHealthCheck() { async licensesHealthCheck() {
if (!this.config.isSelfhosted) {
return;
}
const licenses = await this.db.installedLicense.findMany({ const licenses = await this.db.installedLicense.findMany({
where: { where: {
validatedAt: { 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) { private async revalidateLicense(license: InstalledLicense) {
try { try {
const res = await this.fetch<License>( const res = await this.fetch<License>(
`/api/team/licenses/${license.key}/health` `/api/team/licenses/${license.key}/health`,
{
headers: {
'x-validate-key': license.validateKey,
},
}
); );
await this.db.installedLicense.update({ await this.db.installedLicense.update({
@@ -272,18 +330,23 @@ export class LicenseService {
init?: RequestInit init?: RequestInit
): Promise<T & { res: Response }> { ): Promise<T & { res: Response }> {
try { try {
const res = await fetch('https://app.affine.pro' + path, { const res = await fetch(
...init, process.env.AFFINE_PRO_SERVER_ENDPOINT ??
headers: { 'https://app.affine.pro' + path,
'Content-Type': 'application/json', {
}, ...init,
}); headers: {
'Content-Type': 'application/json',
...init?.headers,
},
}
);
if (!res.ok) { if (!res.ok) {
const body = (await res.json()) as UserFriendlyError; const body = (await res.json()) as UserFriendlyError;
throw new UserFriendlyError( throw new UserFriendlyError(
body.type as any, body.type as any,
body.name as any, body.name.toLowerCase() as any,
body.message, body.message,
body.data 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;
}
}
} }

View File

@@ -215,21 +215,21 @@ export const DEFAULT_PRICES = new Map([
// team // team
[ [
`${SubscriptionPlan.Team}_${SubscriptionRecurring.Monthly}`, `${SubscriptionPlan.Team}_${SubscriptionRecurring.Monthly}`,
{ product: 'AFFiNE Team(per seat)', price: 1500 }, { product: 'AFFiNE Team(per seat)', price: 1440 },
], ],
[ [
`${SubscriptionPlan.Team}_${SubscriptionRecurring.Yearly}`, `${SubscriptionPlan.Team}_${SubscriptionRecurring.Yearly}`,
{ product: 'AFFiNE Team(per seat)', price: 14400 }, { product: 'AFFiNE Team(per seat)', price: 12000 },
], ],
// selfhost team // selfhost team
[ [
`${SubscriptionPlan.SelfHostedTeam}_${SubscriptionRecurring.Monthly}`, `${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}`, `${SubscriptionPlan.SelfHostedTeam}_${SubscriptionRecurring.Yearly}`,
{ product: 'AFFiNE Self-hosted Team(per seat)', price: 14400 }, { product: 'AFFiNE Self-hosted Team(per seat)', price: 12000 },
], ],
]); ]);