refactor(server): plugin modules (#5630)

- [x] separates modules into `fundamental`, `core`, `plugins`
- [x] optional modules with `@OptionalModule` decorator to install modules with requirements met(`requires`, `if`)
- [x] `module.contributesTo` defines optional features that will be enabled if module registered
- [x] `AFFiNE.plugins.use('payment', {})` to enable a optional/plugin module
- [x] `PaymentModule` is the first plugin module
- [x] GraphQLSchema will not be generated for non-included modules
- [x] Frontend can use `ServerConfigType` query to detect which features are enabled
- [x] override existing provider globally
This commit is contained in:
liuyi
2024-01-22 07:40:28 +00:00
parent ae8401b6f4
commit e516e0db23
130 changed files with 1297 additions and 974 deletions
@@ -10,13 +10,6 @@ declare global {
// eslint-disable-next-line no-var
var AFFiNE: AFFiNEConfig;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface ProcessEnv {
SERVER_FLAVOR: ServerFlavor | '';
}
}
}
export enum ExternalAccount {
@@ -25,22 +18,28 @@ export enum ExternalAccount {
firebase = 'firebase',
}
export type ServerFlavor = 'allinone' | 'graphql' | 'sync' | 'selfhosted';
type ConfigPaths = LeafPaths<
export type ServerFlavor =
| 'allinone'
| 'main'
// @deprecated
| 'graphql'
| 'sync'
| 'selfhosted';
export type ConfigPaths = LeafPaths<
Omit<
AFFiNEConfig,
| 'ENV_MAP'
| 'version'
| 'baseUrl'
| 'origin'
| 'prod'
| 'dev'
| 'test'
| 'flavor'
| 'env'
| 'affine'
| 'deploy'
| 'node'
| 'baseUrl'
| 'origin'
>,
'',
'....'
'.....'
>;
/**
@@ -52,15 +51,28 @@ export interface AFFiNEConfig {
/**
* Server Identity
*/
readonly serverId: string;
serverId: string;
/**
* Name may show on the UI
*/
serverName: string;
/**
* System version
*/
readonly version: string;
/**
* Server flavor
*/
readonly flavor: ServerFlavor;
get flavor(): {
type: string;
main: boolean;
sync: boolean;
selfhosted: boolean;
};
/**
* Deployment environment
*/
@@ -172,38 +184,6 @@ export interface AFFiNEConfig {
limit: number;
};
/**
* Redis Config
*
* whether to use redis as Socket.IO adapter
*/
redis: {
/**
* if not enabled, use in-memory adapter by default
*/
enabled: boolean;
/**
* url of redis host
*/
host: string;
/**
* port of redis
*/
port: number;
username: string;
password: string;
/**
* redis database index
*
* Rate Limiter scope: database + 1
*
* Session scope: database + 2
*
* @default 0
*/
database: number;
};
/**
* authentication config
*/
@@ -341,15 +321,6 @@ export interface AFFiNEConfig {
metrics: {
enabled: boolean;
};
payment: {
stripe: {
keys: {
APIKey: string;
webhookKey: string;
};
} & import('stripe').Stripe.StripeConfig;
};
}
export * from './storage';
@@ -2,6 +2,7 @@
import { createPrivateKey, createPublicKey } from 'node:crypto';
import { merge } from 'lodash-es';
import parse from 'parse-duration';
import pkg from '../../../package.json' assert { type: 'json' };
@@ -46,11 +47,22 @@ const jwtKeyPair = (function () {
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
let isHttps: boolean | null = null;
const flavor = (process.env.SERVER_FLAVOR ?? 'allinone') as ServerFlavor;
let flavor = (process.env.SERVER_FLAVOR ?? 'allinone') as ServerFlavor;
const defaultConfig = {
serverId: 'affine-nestjs-server',
serverName: flavor === 'selfhosted' ? 'Self-Host Cloud' : 'AFFiNE Cloud',
version: pkg.version,
flavor,
get flavor() {
if (flavor === 'graphql') {
flavor = 'main';
}
return {
type: flavor,
main: flavor === 'main' || flavor === 'allinone',
sync: flavor === 'sync' || flavor === 'allinone',
selfhosted: flavor === 'selfhosted',
};
},
ENV_MAP: {},
affineEnv: 'dev',
get affine() {
@@ -142,15 +154,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
storage: getDefaultAFFiNEStorageConfig(),
rateLimiter: {
ttl: 60,
limit: 60,
},
redis: {
enabled: false,
host: '127.0.0.1',
port: 6379,
username: '',
password: '',
database: 0,
limit: 120,
},
doc: {
manager: {
@@ -162,18 +166,16 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
interval: 1000 * 60 * 10 /* 10 mins */,
},
},
payment: {
stripe: {
keys: {
APIKey: '',
webhookKey: '',
},
apiVersion: '2023-10-16',
},
},
metrics: {
enabled: false,
},
plugins: {
enabled: [],
use(plugin, config) {
this[plugin] = merge(this[plugin], config || {});
this.enabled.push(plugin);
},
},
} satisfies AFFiNEConfig;
return defaultConfig;
@@ -1,7 +1,7 @@
import { DynamicModule, FactoryProvider } from '@nestjs/common';
import { merge } from 'lodash-es';
import { ApplyType, DeepPartial } from '../utils/types';
import { ApplyType } from '../utils/types';
import { AFFiNEConfig } from './def';
/**