chore: add utils

This commit is contained in:
DarkSky
2026-05-09 02:32:10 +08:00
parent 5813e7dd77
commit 32a94d68dc
9 changed files with 464 additions and 38 deletions
@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { FeatureModule } from '../../core/features';
import { PermissionModule } from '../../core/permission';
import { QuotaModule } from '../../core/quota';
import { WorkspaceModule } from '../../core/workspaces';
import { LicenseResolver } from './resolver';
import { AdminLicenseResolver, LicenseResolver } from './resolver';
import { LicenseService } from './service';
@Module({
imports: [QuotaModule, PermissionModule, WorkspaceModule],
providers: [LicenseService, LicenseResolver],
imports: [FeatureModule, QuotaModule, PermissionModule, WorkspaceModule],
providers: [LicenseService, LicenseResolver, AdminLicenseResolver],
})
export class LicenseModule {}
@@ -14,9 +14,14 @@ import GraphQLUpload, {
import { toBuffer, UseNamedGuard } from '../../base';
import { CurrentUser } from '../../core/auth';
import { Admin } from '../../core/common';
import { AccessController } from '../../core/permission';
import { WorkspaceType } from '../../core/workspaces';
import { SubscriptionRecurring, SubscriptionVariant } from '../payment/types';
import {
SubscriptionPlan,
SubscriptionRecurring,
SubscriptionVariant,
} from '../payment/types';
import { LicenseService } from './service';
@ObjectType()
@@ -40,6 +45,42 @@ export class License {
expiredAt!: Date | null;
}
@ObjectType()
export class AdminLicensePreview {
@Field()
id!: string;
@Field()
workspaceId!: string;
@Field(() => SubscriptionPlan)
plan!: string;
@Field(() => SubscriptionRecurring)
recurring!: string;
@Field(() => Int)
quantity!: number;
@Field(() => Date)
issuedAt!: Date;
@Field(() => Date)
expiresAt!: Date;
@Field(() => Date)
endAt!: Date;
@Field()
entity!: string;
@Field()
issuer!: string;
@Field()
valid!: boolean;
}
@UseNamedGuard('selfhost')
@Resolver(() => WorkspaceType)
export class LicenseResolver {
@@ -124,3 +165,18 @@ export class LicenseResolver {
return license;
}
}
@Admin()
@Resolver(() => AdminLicensePreview)
export class AdminLicenseResolver {
constructor(private readonly service: LicenseService) {}
@Mutation(() => AdminLicensePreview)
async previewLicense(
@Args('license', { type: () => GraphQLUpload }) licenseFile: FileUpload
) {
const buffer = await toBuffer(licenseFile.createReadStream());
return this.service.previewLicense(buffer);
}
}
@@ -31,6 +31,20 @@ interface License {
endAt: number;
}
export interface LicensePreview {
id: string;
workspaceId: string;
plan: SubscriptionPlan.SelfHostedTeam;
recurring: SubscriptionRecurring;
quantity: number;
issuedAt: Date;
expiresAt: Date;
endAt: Date;
entity: string;
issuer: string;
valid: boolean;
}
const BaseLicenseSchema = z.object({
entity: z.string().nonempty(),
issuer: z.string().nonempty(),
@@ -167,6 +181,34 @@ export class LicenseService {
return installed;
}
previewLicense(license: Buffer): LicensePreview {
const payload = this.decryptWorkspaceTeamLicensePayload(license);
const data = payload.data;
const now = new Date();
const expiresAt = new Date(payload.expiresAt);
const endAt = new Date(data.endAt);
if (expiresAt < now || endAt < now) {
throw new InvalidLicenseToActivate({
reason: 'Invalid license.',
});
}
return {
id: data.id,
workspaceId: data.workspaceId,
plan: data.plan,
recurring: data.recurring,
quantity: data.quantity,
issuedAt: new Date(payload.issuedAt),
expiresAt,
endAt,
entity: payload.entity,
issuer: payload.issuer,
valid: true,
};
}
async activateTeamLicense(workspaceId: string, licenseKey: string) {
const installedLicense = await this.getLicense(workspaceId);
@@ -480,6 +522,25 @@ export class LicenseService {
}
private decryptWorkspaceTeamLicense(workspaceId: string, buf: Buffer) {
const payload = this.decryptWorkspaceTeamLicensePayload(buf);
if (new Date(payload.expiresAt) < new Date()) {
throw new InvalidLicenseToActivate({
reason:
'License file has expired. Please contact with Affine support to fetch a latest one.',
});
}
if (payload.data.workspaceId !== workspaceId) {
throw new InvalidLicenseToActivate({
reason: 'Workspace mismatched with license.',
});
}
return payload;
}
private decryptWorkspaceTeamLicensePayload(buf: Buffer) {
if (!this.crypto.AFFiNEProPublicKey) {
throw new InternalServerError(
'License public key is not loaded. Please contact with Affine support.'
@@ -514,19 +575,6 @@ export class LicenseService {
});
}
if (new Date(parseResult.data.expiresAt) < new Date()) {
throw new InvalidLicenseToActivate({
reason:
'License file has expired. Please contact with Affine support to fetch a latest one.',
});
}
if (parseResult.data.data.workspaceId !== workspaceId) {
throw new InvalidLicenseToActivate({
reason: 'Workspace mismatched with license.',
});
}
return parseResult.data;
}
+15
View File
@@ -91,6 +91,20 @@ type AdminDashboardValueDayPoint {
value: SafeInt!
}
type AdminLicensePreview {
endAt: DateTime!
entity: String!
expiresAt: DateTime!
id: String!
issuedAt: DateTime!
issuer: String!
plan: SubscriptionPlan!
quantity: Int!
recurring: SubscriptionRecurring!
valid: Boolean!
workspaceId: String!
}
type AdminSharedLinkTopItem {
docId: String!
guestViews: SafeInt!
@@ -1632,6 +1646,7 @@ type Mutation {
"""mention user in a doc"""
mentionUser(input: MentionInput!): ID!
previewLicense(license: Upload!): AdminLicensePreview!
publishDoc(docId: String!, mode: PublicDocMode = Page, workspaceId: String!): DocType!
"""queue workspace doc embedding"""