mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-19 19:13:17 +03:00
refactor(server): standalone runtime module (#9120)
This commit is contained in:
parent
4c23991047
commit
81c68032e1
@ -19,6 +19,7 @@ import { MailModule } from './base/mailer';
|
||||
import { MetricsModule } from './base/metrics';
|
||||
import { MutexModule } from './base/mutex';
|
||||
import { PrismaModule } from './base/prisma';
|
||||
import { RuntimeModule } from './base/runtime';
|
||||
import { StorageProviderModule } from './base/storage';
|
||||
import { RateLimiterModule } from './base/throttler';
|
||||
import { WebSocketModule } from './base/websocket';
|
||||
@ -39,6 +40,7 @@ import { ENABLED_PLUGINS } from './plugins/registry';
|
||||
|
||||
export const FunctionalityModules = [
|
||||
ConfigModule.forRoot(),
|
||||
RuntimeModule,
|
||||
EventModule,
|
||||
CacheModule,
|
||||
MutexModule,
|
||||
@ -74,11 +76,13 @@ function filterOptionalModule(
|
||||
|
||||
if (nonMetRequirements.length) {
|
||||
const name = 'module' in module ? module.module.name : module.name;
|
||||
new Logger(name).warn(
|
||||
`${name} is not enabled because of the required configuration is not satisfied.`,
|
||||
'Unsatisfied configuration:',
|
||||
...nonMetRequirements.map(config => ` AFFiNE.${config}`)
|
||||
);
|
||||
if (!config.node.test) {
|
||||
new Logger(name).warn(
|
||||
`${name} is not enabled because of the required configuration is not satisfied.`,
|
||||
'Unsatisfied configuration:',
|
||||
...nonMetRequirements.map(config => ` AFFiNE.${config}`)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { merge } from 'lodash-es';
|
||||
|
||||
import { AFFiNEConfig } from './def';
|
||||
import { Config } from './provider';
|
||||
import { Runtime } from './runtime/service';
|
||||
|
||||
export * from './def';
|
||||
export * from './default';
|
||||
@ -17,10 +16,10 @@ function createConfigProvider(
|
||||
): FactoryProvider<Config> {
|
||||
return {
|
||||
provide: Config,
|
||||
useFactory: (runtime: Runtime) => {
|
||||
return Object.freeze(merge({}, globalThis.AFFiNE, override, { runtime }));
|
||||
useFactory: () => {
|
||||
return Object.freeze(merge({}, globalThis.AFFiNE, override));
|
||||
},
|
||||
inject: [Runtime],
|
||||
inject: [],
|
||||
};
|
||||
}
|
||||
|
||||
@ -31,10 +30,8 @@ export class ConfigModule {
|
||||
return {
|
||||
global: true,
|
||||
module: ConfigModule,
|
||||
providers: [provider, Runtime],
|
||||
providers: [provider],
|
||||
exports: [provider],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export { Runtime };
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ApplyType } from '../utils/types';
|
||||
import { AFFiNEConfig } from './def';
|
||||
import type { Runtime } from './runtime/service';
|
||||
|
||||
/**
|
||||
* @example
|
||||
@ -14,6 +13,4 @@ import type { Runtime } from './runtime/service';
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export class Config extends ApplyType<AFFiNEConfig>() {
|
||||
runtime!: Runtime;
|
||||
}
|
||||
export class Config extends ApplyType<AFFiNEConfig>() {}
|
||||
|
@ -30,6 +30,7 @@ export {
|
||||
OptionalModule,
|
||||
} from './nestjs';
|
||||
export { type PrismaTransaction } from './prisma';
|
||||
export { Runtime } from './runtime';
|
||||
export * from './storage';
|
||||
export { type StorageProvider, StorageProviderFactory } from './storage';
|
||||
export { CloudThrottlerGuard, SkipThrottle, Throttle } from './throttler';
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { OnEvent } from '../../event';
|
||||
import { Payload } from '../../event/def';
|
||||
import { FlattenedAppRuntimeConfig } from '../types';
|
||||
import { FlattenedAppRuntimeConfig } from '../config/types';
|
||||
import { OnEvent } from '../event';
|
||||
import { Payload } from '../event/def';
|
||||
|
||||
declare module '../../event/def' {
|
||||
declare module '../event/def' {
|
||||
interface EventDefinitions {
|
||||
runtimeConfig: {
|
||||
runtime: {
|
||||
[K in keyof FlattenedAppRuntimeConfig]: {
|
||||
changed: Payload<FlattenedAppRuntimeConfig[K]>;
|
||||
};
|
||||
@ -18,5 +18,5 @@ declare module '../../event/def' {
|
||||
export const OnRuntimeConfigChange_DO_NOT_USE = (
|
||||
nameWithModule: keyof FlattenedAppRuntimeConfig
|
||||
) => {
|
||||
return OnEvent(`runtimeConfig.${nameWithModule}.changed`);
|
||||
return OnEvent(`runtime.${nameWithModule}.changed`);
|
||||
};
|
11
packages/backend/server/src/base/runtime/index.ts
Normal file
11
packages/backend/server/src/base/runtime/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { Runtime } from './service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [Runtime],
|
||||
exports: [Runtime],
|
||||
})
|
||||
export class RuntimeModule {}
|
||||
export { Runtime };
|
@ -8,11 +8,14 @@ import {
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { difference, keyBy } from 'lodash-es';
|
||||
|
||||
import { Cache } from '../../cache';
|
||||
import { InvalidRuntimeConfigType, RuntimeConfigNotFound } from '../../error';
|
||||
import { defer } from '../../utils/promise';
|
||||
import { defaultRuntimeConfig, runtimeConfigType } from '../register';
|
||||
import { AppRuntimeConfigModules, FlattenedAppRuntimeConfig } from '../types';
|
||||
import { Cache } from '../cache';
|
||||
import { defaultRuntimeConfig, runtimeConfigType } from '../config/register';
|
||||
import {
|
||||
AppRuntimeConfigModules,
|
||||
FlattenedAppRuntimeConfig,
|
||||
} from '../config/types';
|
||||
import { InvalidRuntimeConfigType, RuntimeConfigNotFound } from '../error';
|
||||
import { defer } from '../utils/promise';
|
||||
|
||||
function validateConfigType<K extends keyof FlattenedAppRuntimeConfig>(
|
||||
key: K,
|
@ -20,6 +20,7 @@ import {
|
||||
InternalServerError,
|
||||
InvalidEmail,
|
||||
InvalidEmailToken,
|
||||
Runtime,
|
||||
SignUpForbidden,
|
||||
Throttle,
|
||||
URLHelper,
|
||||
@ -57,7 +58,8 @@ export class AuthController {
|
||||
private readonly auth: AuthService,
|
||||
private readonly user: UserService,
|
||||
private readonly token: TokenService,
|
||||
private readonly config: Config
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime
|
||||
) {
|
||||
if (config.node.dev) {
|
||||
// set DNS servers in dev mode
|
||||
@ -159,12 +161,12 @@ export class AuthController {
|
||||
// send email magic link
|
||||
const user = await this.user.findUserByEmail(email);
|
||||
if (!user) {
|
||||
const allowSignup = await this.config.runtime.fetch('auth/allowSignup');
|
||||
const allowSignup = await this.runtime.fetch('auth/allowSignup');
|
||||
if (!allowSignup) {
|
||||
throw new SignUpForbidden();
|
||||
}
|
||||
|
||||
const requireEmailDomainVerification = await this.config.runtime.fetch(
|
||||
const requireEmailDomainVerification = await this.runtime.fetch(
|
||||
'auth/requireEmailDomainVerification'
|
||||
);
|
||||
if (requireEmailDomainVerification) {
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
import { RuntimeConfig, RuntimeConfigType } from '@prisma/client';
|
||||
import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
|
||||
|
||||
import { Config, URLHelper } from '../../base';
|
||||
import { Config, Runtime, URLHelper } from '../../base';
|
||||
import { Public } from '../auth';
|
||||
import { Admin } from '../common';
|
||||
import { FeatureType } from '../features';
|
||||
@ -76,6 +76,7 @@ export class ServerFlagsType implements ServerFlags {
|
||||
export class ServerConfigResolver {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime,
|
||||
private readonly url: URLHelper,
|
||||
private readonly server: ServerService
|
||||
) {}
|
||||
@ -103,7 +104,7 @@ export class ServerConfigResolver {
|
||||
description: 'credentials requirement',
|
||||
})
|
||||
async credentialsRequirement() {
|
||||
const config = await this.config.runtime.fetchAll({
|
||||
const config = await this.runtime.fetchAll({
|
||||
'auth/password.max': true,
|
||||
'auth/password.min': true,
|
||||
});
|
||||
@ -120,7 +121,7 @@ export class ServerConfigResolver {
|
||||
description: 'server flags',
|
||||
})
|
||||
async flags(): Promise<ServerFlagsType> {
|
||||
const records = await this.config.runtime.list('flags');
|
||||
const records = await this.runtime.list('flags');
|
||||
|
||||
return records.reduce((flags, record) => {
|
||||
flags[record.key as keyof ServerFlagsType] = record.value as any;
|
||||
@ -184,13 +185,13 @@ interface ServerDatabaseConfig {
|
||||
@Admin()
|
||||
@Resolver(() => ServerRuntimeConfigType)
|
||||
export class ServerRuntimeConfigResolver {
|
||||
constructor(private readonly config: Config) {}
|
||||
constructor(private readonly runtime: Runtime) {}
|
||||
|
||||
@Query(() => [ServerRuntimeConfigType], {
|
||||
description: 'get all server runtime configurable settings',
|
||||
})
|
||||
serverRuntimeConfig(): Promise<ServerRuntimeConfigType[]> {
|
||||
return this.config.runtime.list();
|
||||
return this.runtime.list();
|
||||
}
|
||||
|
||||
@Mutation(() => ServerRuntimeConfigType, {
|
||||
@ -200,7 +201,7 @@ export class ServerRuntimeConfigResolver {
|
||||
@Args('id') id: string,
|
||||
@Args({ type: () => GraphQLJSON, name: 'value' }) value: any
|
||||
): Promise<ServerRuntimeConfigType> {
|
||||
return await this.config.runtime.set(id as any, value);
|
||||
return await this.runtime.set(id as any, value);
|
||||
}
|
||||
|
||||
@Mutation(() => [ServerRuntimeConfigType], {
|
||||
@ -211,7 +212,7 @@ export class ServerRuntimeConfigResolver {
|
||||
): Promise<ServerRuntimeConfigType[]> {
|
||||
const keys = Object.keys(updates);
|
||||
const results = await Promise.all(
|
||||
keys.map(key => this.config.runtime.set(key as any, updates[key]))
|
||||
keys.map(key => this.runtime.set(key as any, updates[key]))
|
||||
);
|
||||
|
||||
return results;
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
Config,
|
||||
mergeUpdatesInApplyWay as yotcoMergeUpdates,
|
||||
metrics,
|
||||
Runtime,
|
||||
} from '../../base';
|
||||
import { PermissionService } from '../permission';
|
||||
import { QuotaService } from '../quota';
|
||||
@ -35,6 +36,7 @@ export class DocStorageOptions implements IDocStorageOptions {
|
||||
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime,
|
||||
private readonly permission: PermissionService,
|
||||
private readonly quota: QuotaService
|
||||
) {}
|
||||
@ -43,9 +45,7 @@ export class DocStorageOptions implements IDocStorageOptions {
|
||||
const doc = await this.recoverDoc(updates);
|
||||
const yjsResult = Buffer.from(Y.encodeStateAsUpdate(doc));
|
||||
|
||||
const useYocto = await this.config.runtime.fetch(
|
||||
'doc/experimentalMergeWithYOcto'
|
||||
);
|
||||
const useYocto = await this.runtime.fetch('doc/experimentalMergeWithYOcto');
|
||||
|
||||
if (useYocto) {
|
||||
metrics.jwst.counter('codec_merge_counter').add(1);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { Config, type EventPayload, OnEvent } from '../../base';
|
||||
import { type EventPayload, OnEvent, Runtime } from '../../base';
|
||||
import { UserService } from '../user/service';
|
||||
import { FeatureService } from './service';
|
||||
import { FeatureType } from './types';
|
||||
@ -19,7 +19,7 @@ export class FeatureManagementService {
|
||||
constructor(
|
||||
private readonly feature: FeatureService,
|
||||
private readonly user: UserService,
|
||||
private readonly config: Config
|
||||
private readonly runtime: Runtime
|
||||
) {}
|
||||
|
||||
// ======== Admin ========
|
||||
@ -95,7 +95,7 @@ export class FeatureManagementService {
|
||||
email: string,
|
||||
type: EarlyAccessType = EarlyAccessType.App
|
||||
) {
|
||||
const earlyAccessControlEnabled = await this.config.runtime.fetch(
|
||||
const earlyAccessControlEnabled = await this.runtime.fetch(
|
||||
'flags/earlyAccessControl'
|
||||
);
|
||||
|
||||
|
@ -12,11 +12,11 @@ import { Socket } from 'socket.io';
|
||||
import {
|
||||
AlreadyInSpace,
|
||||
CallMetric,
|
||||
Config,
|
||||
DocNotFound,
|
||||
GatewayErrorWrapper,
|
||||
metrics,
|
||||
NotInSpace,
|
||||
Runtime,
|
||||
SpaceAccessDenied,
|
||||
VersionRejected,
|
||||
} from '../../base';
|
||||
@ -139,7 +139,7 @@ export class SpaceSyncGateway
|
||||
private connectionCount = 0;
|
||||
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime,
|
||||
private readonly permissions: PermissionService,
|
||||
private readonly workspace: PgWorkspaceDocStorageAdapter,
|
||||
private readonly userspace: PgUserspaceDocStorageAdapter
|
||||
@ -175,7 +175,7 @@ export class SpaceSyncGateway
|
||||
}
|
||||
|
||||
async assertVersion(client: Socket, version?: string) {
|
||||
const shouldCheckClientVersion = await this.config.runtime.fetch(
|
||||
const shouldCheckClientVersion = await this.runtime.fetch(
|
||||
'flags/syncClientVersionCheck'
|
||||
);
|
||||
if (
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
EventEmitter,
|
||||
type EventPayload,
|
||||
OnEvent,
|
||||
Runtime,
|
||||
WrongSignInCredentials,
|
||||
WrongSignInMethod,
|
||||
} from '../../base';
|
||||
@ -33,6 +34,7 @@ export class UserService {
|
||||
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime,
|
||||
private readonly crypto: CryptoHelper,
|
||||
private readonly prisma: PrismaClient,
|
||||
private readonly emitter: EventEmitter,
|
||||
@ -60,7 +62,7 @@ export class UserService {
|
||||
validators.assertValidEmail(data.email);
|
||||
|
||||
if (data.password) {
|
||||
const config = await this.config.runtime.fetchAll({
|
||||
const config = await this.runtime.fetchAll({
|
||||
'auth/password.max': true,
|
||||
'auth/password.min': true,
|
||||
});
|
||||
@ -242,7 +244,7 @@ export class UserService {
|
||||
select: Prisma.UserSelect = this.defaultUserSelect
|
||||
) {
|
||||
if (data.password) {
|
||||
const config = await this.config.runtime.fetchAll({
|
||||
const config = await this.runtime.fetchAll({
|
||||
'auth/password.max': true,
|
||||
'auth/password.min': true,
|
||||
});
|
||||
|
@ -6,9 +6,9 @@ import type {
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
Config,
|
||||
getRequestResponseFromContext,
|
||||
GuardProvider,
|
||||
Runtime,
|
||||
} from '../../base';
|
||||
import { CaptchaService } from './service';
|
||||
|
||||
@ -21,13 +21,13 @@ export class CaptchaGuardProvider
|
||||
|
||||
constructor(
|
||||
private readonly captcha: CaptchaService,
|
||||
private readonly config: Config
|
||||
private readonly runtime: Runtime
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
if (!(await this.config.runtime.fetch('plugins.captcha/enable'))) {
|
||||
if (!(await this.runtime.fetch('plugins.captcha/enable'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,10 @@ import Stripe from 'stripe';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
Config,
|
||||
EventEmitter,
|
||||
InternalServerError,
|
||||
InvalidCheckoutParameters,
|
||||
Runtime,
|
||||
SubscriptionAlreadyExists,
|
||||
SubscriptionPlanNotFound,
|
||||
URLHelper,
|
||||
@ -56,7 +56,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
constructor(
|
||||
stripe: Stripe,
|
||||
db: PrismaClient,
|
||||
private readonly config: Config,
|
||||
private readonly runtime: Runtime,
|
||||
private readonly feature: FeatureManagementService,
|
||||
private readonly event: EventEmitter,
|
||||
private readonly url: URLHelper
|
||||
@ -617,7 +617,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
{ proEarlyAccess, proSubscribed, onetime }: PriceStrategyStatus
|
||||
) {
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
return this.config.runtime.fetch('plugins.payment/showLifetimePrice');
|
||||
return this.runtime.fetch('plugins.payment/showLifetimePrice');
|
||||
}
|
||||
|
||||
if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
|
@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common';
|
||||
import type { TestFn } from 'ava';
|
||||
import ava from 'ava';
|
||||
|
||||
import { Config, ConfigModule } from '../src/base/config';
|
||||
import { Runtime } from '../src/base';
|
||||
import { AuthService } from '../src/core/auth/service';
|
||||
import {
|
||||
FeatureManagementService,
|
||||
@ -26,15 +26,7 @@ const test = ava as TestFn<{
|
||||
|
||||
test.beforeEach(async t => {
|
||||
const { app } = await createTestingApp({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
server: {
|
||||
host: 'example.org',
|
||||
https: true,
|
||||
},
|
||||
}),
|
||||
FeatureModule,
|
||||
],
|
||||
imports: [FeatureModule],
|
||||
providers: [WorkspaceResolver],
|
||||
tapModule: module => {
|
||||
module
|
||||
@ -43,8 +35,8 @@ test.beforeEach(async t => {
|
||||
},
|
||||
});
|
||||
|
||||
const config = app.get(Config);
|
||||
await config.runtime.set('flags/earlyAccessControl', true);
|
||||
const runtime = app.get(Runtime);
|
||||
await runtime.set('flags/earlyAccessControl', true);
|
||||
t.context.app = app;
|
||||
t.context.auth = app.get(AuthService);
|
||||
t.context.feature = app.get(FeatureService);
|
||||
|
@ -7,8 +7,8 @@ import Sinon from 'sinon';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { AppModule } from '../../src/app.module';
|
||||
import { EventEmitter } from '../../src/base';
|
||||
import { Config, ConfigModule, Runtime } from '../../src/base/config';
|
||||
import { EventEmitter, Runtime } from '../../src/base';
|
||||
import { ConfigModule } from '../../src/base/config';
|
||||
import { CurrentUser } from '../../src/core/auth';
|
||||
import { AuthService } from '../../src/core/auth/service';
|
||||
import {
|
||||
@ -543,8 +543,8 @@ test('should get correct pro plan price for checking out', async t => {
|
||||
// any user, lifetime recurring
|
||||
{
|
||||
feature.isEarlyAccessUser.resolves(false);
|
||||
const config = app.get(Config);
|
||||
await config.runtime.set('plugins.payment/showLifetimePrice', true);
|
||||
const runtime = app.get(Runtime);
|
||||
await runtime.set('plugins.payment/showLifetimePrice', true);
|
||||
|
||||
await service.checkout(
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ import type { Response } from 'supertest';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { AppModule, FunctionalityModules } from '../../src/app.module';
|
||||
import { Config, GlobalExceptionFilter } from '../../src/base';
|
||||
import { GlobalExceptionFilter, Runtime } from '../../src/base';
|
||||
import { GqlModule } from '../../src/base/graphql';
|
||||
import { AuthGuard, AuthModule } from '../../src/core/auth';
|
||||
import { UserFeaturesInit1698652531198 } from '../../src/data/migrations/1698652531198-user-features-init';
|
||||
@ -111,9 +111,9 @@ export async function createTestingModule(
|
||||
if (init) {
|
||||
await m.init();
|
||||
|
||||
const config = m.get(Config);
|
||||
const runtime = m.get(Runtime);
|
||||
// by pass password min length validation
|
||||
await config.runtime.set('auth/password.min', 1);
|
||||
await runtime.set('auth/password.min', 1);
|
||||
}
|
||||
|
||||
return m;
|
||||
@ -145,9 +145,9 @@ export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) {
|
||||
|
||||
await app.init();
|
||||
|
||||
const config = app.get(Config);
|
||||
const runtime = app.get(Runtime);
|
||||
// by pass password min length validation
|
||||
await config.runtime.set('auth/password.min', 1);
|
||||
await runtime.set('auth/password.min', 1);
|
||||
|
||||
return {
|
||||
module: m,
|
||||
|
Loading…
Reference in New Issue
Block a user