mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): add self host team plan (#9569)
This commit is contained in:
@@ -19,6 +19,8 @@ export { EventSourceService } from './services/eventsource';
|
||||
export { FetchService } from './services/fetch';
|
||||
export { GraphQLService } from './services/graphql';
|
||||
export { InvoicesService } from './services/invoices';
|
||||
export { SelfhostGenerateLicenseService } from './services/selfhost-generate-license';
|
||||
export { SelfhostLicenseService } from './services/selfhost-license';
|
||||
export { ServerService } from './services/server';
|
||||
export { ServersService } from './services/servers';
|
||||
export { SubscriptionService } from './services/subscription';
|
||||
@@ -58,6 +60,8 @@ import { EventSourceService } from './services/eventsource';
|
||||
import { FetchService } from './services/fetch';
|
||||
import { GraphQLService } from './services/graphql';
|
||||
import { InvoicesService } from './services/invoices';
|
||||
import { SelfhostGenerateLicenseService } from './services/selfhost-generate-license';
|
||||
import { SelfhostLicenseService } from './services/selfhost-license';
|
||||
import { ServerService } from './services/server';
|
||||
import { ServersService } from './services/servers';
|
||||
import { SubscriptionService } from './services/subscription';
|
||||
@@ -70,6 +74,8 @@ import { WorkspaceSubscriptionService } from './services/workspace-subscription'
|
||||
import { AuthStore } from './stores/auth';
|
||||
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
|
||||
import { InvoicesStore } from './stores/invoices';
|
||||
import { SelfhostGenerateLicenseStore } from './stores/selfhost-generate-license';
|
||||
import { SelfhostLicenseStore } from './stores/selfhost-license';
|
||||
import { ServerConfigStore } from './stores/server-config';
|
||||
import { ServerListStore } from './stores/server-list';
|
||||
import { SubscriptionStore } from './stores/subscription';
|
||||
@@ -128,7 +134,9 @@ export function configureCloudModule(framework: Framework) {
|
||||
.store(UserFeatureStore, [GraphQLService])
|
||||
.service(InvoicesService)
|
||||
.store(InvoicesStore, [GraphQLService])
|
||||
.entity(Invoices, [InvoicesStore]);
|
||||
.entity(Invoices, [InvoicesStore])
|
||||
.service(SelfhostGenerateLicenseService, [SelfhostGenerateLicenseStore])
|
||||
.store(SelfhostGenerateLicenseStore, [GraphQLService]);
|
||||
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
@@ -142,5 +150,7 @@ export function configureCloudModule(framework: Framework) {
|
||||
.service(WorkspaceSubscriptionService, [WorkspaceServerService])
|
||||
.entity(WorkspaceSubscription, [WorkspaceService, WorkspaceServerService])
|
||||
.service(WorkspaceInvoicesService)
|
||||
.entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService]);
|
||||
.entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService])
|
||||
.service(SelfhostLicenseService, [SelfhostLicenseStore, WorkspaceService])
|
||||
.store(SelfhostLicenseStore, [WorkspaceServerService]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { UserFriendlyError } from '@affine/graphql';
|
||||
import {
|
||||
backoffRetry,
|
||||
effect,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
Service,
|
||||
} from '@toeverything/infra';
|
||||
import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
||||
|
||||
import { isBackendError, isNetworkError } from '../error';
|
||||
import type { SelfhostGenerateLicenseStore } from '../stores/selfhost-generate-license';
|
||||
|
||||
export class SelfhostGenerateLicenseService extends Service {
|
||||
constructor(private readonly store: SelfhostGenerateLicenseStore) {
|
||||
super();
|
||||
}
|
||||
licenseKey$ = new LiveData<string | null>(null);
|
||||
isLoading$ = new LiveData(false);
|
||||
error$ = new LiveData<UserFriendlyError | null>(null);
|
||||
|
||||
generateLicenseKey = effect(
|
||||
exhaustMap((sessionId: string) => {
|
||||
return fromPromise(async () => {
|
||||
return await this.store.generateKey(sessionId);
|
||||
}).pipe(
|
||||
backoffRetry({
|
||||
when: isNetworkError,
|
||||
count: Infinity,
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isBackendError,
|
||||
}),
|
||||
mergeMap(key => {
|
||||
this.licenseKey$.next(key);
|
||||
return EMPTY;
|
||||
}),
|
||||
catchError(err => {
|
||||
this.error$.next(UserFriendlyError.fromAnyError(err));
|
||||
console.error(err);
|
||||
return EMPTY;
|
||||
}),
|
||||
onStart(() => {
|
||||
this.isLoading$.next(true);
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isLoading$.next(false);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { License } from '@affine/graphql';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
effect,
|
||||
exhaustMapWithTrailing,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
Service,
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, mergeMap } from 'rxjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { isBackendError, isNetworkError } from '../error';
|
||||
import type { SelfhostLicenseStore } from '../stores/selfhost-license';
|
||||
|
||||
export class SelfhostLicenseService extends Service {
|
||||
constructor(
|
||||
private readonly store: SelfhostLicenseStore,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
license$ = new LiveData<License | null>(null);
|
||||
isRevalidating$ = new LiveData(false);
|
||||
error$ = new LiveData<any | null>(null);
|
||||
|
||||
revalidate = effect(
|
||||
exhaustMapWithTrailing(() => {
|
||||
return fromPromise(async signal => {
|
||||
const currentWorkspaceId = this.workspaceService.workspace.id;
|
||||
if (!currentWorkspaceId) {
|
||||
return undefined; // no subscription if no user
|
||||
}
|
||||
return await this.store.getLicense(currentWorkspaceId, signal);
|
||||
}).pipe(
|
||||
backoffRetry({
|
||||
when: isNetworkError,
|
||||
count: Infinity,
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isBackendError,
|
||||
}),
|
||||
mergeMap(data => {
|
||||
if (data) {
|
||||
this.license$.next(data);
|
||||
}
|
||||
|
||||
return EMPTY;
|
||||
}),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => this.isRevalidating$.next(true)),
|
||||
onComplete(() => this.isRevalidating$.next(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
async activateLicense(workspaceId: string, licenseKey: string) {
|
||||
return await this.store.activate(workspaceId, licenseKey);
|
||||
}
|
||||
|
||||
async deactivateLicense(workspaceId: string) {
|
||||
return await this.store.deactivate(workspaceId);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.license$.next(null);
|
||||
this.error$.next(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { generateLicenseKeyMutation } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
export class SelfhostGenerateLicenseStore extends Store {
|
||||
constructor(private readonly gqlService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async generateKey(sessionId: string, signal?: AbortSignal): Promise<string> {
|
||||
const data = await this.gqlService.gql({
|
||||
query: generateLicenseKeyMutation,
|
||||
variables: {
|
||||
sessionId: sessionId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
|
||||
return data.generateLicenseKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
activateLicenseMutation,
|
||||
deactivateLicenseMutation,
|
||||
getLicenseQuery,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceServerService } from '../services/workspace-server';
|
||||
|
||||
export class SelfhostLicenseStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getLicense(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getLicenseQuery,
|
||||
variables: {
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
|
||||
return data.workspace.license;
|
||||
}
|
||||
|
||||
async activate(workspaceId: string, license: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: activateLicenseMutation,
|
||||
variables: {
|
||||
workspaceId: workspaceId,
|
||||
license: license,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
|
||||
return data.activateLicense;
|
||||
}
|
||||
|
||||
async deactivate(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: deactivateLicenseMutation,
|
||||
variables: {
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
|
||||
return data.deactivateLicense;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,13 @@ import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
exhaustMapWithTrailing,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
||||
import { EMPTY, mergeMap } from 'rxjs';
|
||||
|
||||
import { isBackendError, isNetworkError } from '../../cloud';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
@@ -32,7 +33,7 @@ export class WorkspacePermission extends Entity {
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
exhaustMap(() => {
|
||||
exhaustMapWithTrailing(() => {
|
||||
return fromPromise(async signal => {
|
||||
if (this.workspaceService.workspace.flavour !== 'local') {
|
||||
const info = await this.store.fetchWorkspaceInfo(
|
||||
|
||||
Reference in New Issue
Block a user