mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 09:06:19 +08:00
chore: add utils
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user