mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-09-11 19:47:35 +03:00
refactor(server): folder structure (#5573)
This commit is contained in:
parent
d6f65ea414
commit
89b5c96d25
@ -1,12 +0,0 @@
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { SERVER_FLAVOR } from './config/default';
|
||||
|
||||
if (SERVER_FLAVOR === 'selfhosted') {
|
||||
config({ path: join(homedir(), '.affine', '.env') });
|
||||
} else {
|
||||
config();
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import { getDefaultAFFiNEConfig } from './config/default';
|
||||
|
||||
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
@ -1,13 +1,16 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
import { Config } from './fundamentals/config';
|
||||
|
||||
@Controller('/')
|
||||
export class AppController {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
@Get()
|
||||
info() {
|
||||
const version = AFFiNE.version;
|
||||
return {
|
||||
compatibility: version,
|
||||
message: `AFFiNE ${version} Server`,
|
||||
compatibility: this.config.version,
|
||||
message: `AFFiNE ${this.config.version} Server`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,16 @@ import { DynamicModule, Module, Type } from '@nestjs/common';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
|
||||
import { AppController } from './app.controller';
|
||||
import { CacheInterceptor, CacheModule } from './cache';
|
||||
import { RedisModule } from './cache/redis';
|
||||
import { ConfigModule, SERVER_FLAVOR } from './config';
|
||||
import { EventModule } from './event';
|
||||
import { MetricsModule } from './metrics';
|
||||
import { CacheInterceptor, CacheModule } from './fundamentals/cache';
|
||||
import { ConfigModule } from './fundamentals/config';
|
||||
import { EventModule } from './fundamentals/event';
|
||||
import { MailModule } from './fundamentals/mailer';
|
||||
import { MetricsModule } from './fundamentals/metrics';
|
||||
import { PrismaModule } from './fundamentals/prisma';
|
||||
import { SessionModule } from './fundamentals/session';
|
||||
import { RateLimiterModule } from './fundamentals/throttler';
|
||||
import { BusinessModules } from './modules';
|
||||
import { AuthModule } from './modules/auth';
|
||||
import { PrismaModule } from './prisma';
|
||||
import { SessionModule } from './session';
|
||||
import { RateLimiterModule } from './throttler';
|
||||
|
||||
export const FunctionalityModules: Array<Type | DynamicModule> = [
|
||||
ConfigModule.forRoot(),
|
||||
@ -22,13 +22,9 @@ export const FunctionalityModules: Array<Type | DynamicModule> = [
|
||||
SessionModule,
|
||||
RateLimiterModule,
|
||||
AuthModule,
|
||||
MailModule,
|
||||
];
|
||||
|
||||
// better module registration logic
|
||||
if (AFFiNE.redis.enabled) {
|
||||
FunctionalityModules.push(RedisModule);
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
@ -37,6 +33,6 @@ if (AFFiNE.redis.enabled) {
|
||||
},
|
||||
],
|
||||
imports: [...FunctionalityModules, ...BusinessModules],
|
||||
controllers: SERVER_FLAVOR === 'selfhosted' ? [] : [AppController],
|
||||
controllers: [AppController],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
41
packages/backend/server/src/config/affine.env.ts
Normal file
41
packages/backend/server/src/config/affine.env.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// Convenient way to map environment variables to config values.
|
||||
AFFiNE.ENV_MAP = {
|
||||
AFFINE_SERVER_PORT: ['port', 'int'],
|
||||
AFFINE_SERVER_HOST: 'host',
|
||||
AFFINE_SERVER_SUB_PATH: 'path',
|
||||
AFFIHE_SERVER_HTTPS: ['https', 'boolean'],
|
||||
AFFINE_ENV: 'affineEnv',
|
||||
DATABASE_URL: 'db.url',
|
||||
ENABLE_CAPTCHA: ['auth.captcha.enable', 'boolean'],
|
||||
CAPTCHA_TURNSTILE_SECRET: ['auth.captcha.turnstile.secret', 'string'],
|
||||
OAUTH_GOOGLE_ENABLED: ['auth.oauthProviders.google.enabled', 'boolean'],
|
||||
OAUTH_GOOGLE_CLIENT_ID: 'auth.oauthProviders.google.clientId',
|
||||
OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret',
|
||||
OAUTH_GITHUB_ENABLED: ['auth.oauthProviders.github.enabled', 'boolean'],
|
||||
OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId',
|
||||
OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret',
|
||||
OAUTH_EMAIL_LOGIN: 'auth.email.login',
|
||||
OAUTH_EMAIL_SENDER: 'auth.email.sender',
|
||||
OAUTH_EMAIL_SERVER: 'auth.email.server',
|
||||
OAUTH_EMAIL_PORT: ['auth.email.port', 'int'],
|
||||
OAUTH_EMAIL_PASSWORD: 'auth.email.password',
|
||||
THROTTLE_TTL: ['rateLimiter.ttl', 'int'],
|
||||
THROTTLE_LIMIT: ['rateLimiter.limit', 'int'],
|
||||
REDIS_SERVER_ENABLED: ['redis.enabled', 'boolean'],
|
||||
REDIS_SERVER_HOST: 'redis.host',
|
||||
REDIS_SERVER_PORT: ['redis.port', 'int'],
|
||||
REDIS_SERVER_USER: 'redis.username',
|
||||
REDIS_SERVER_PASSWORD: 'redis.password',
|
||||
REDIS_SERVER_DATABASE: ['redis.database', 'int'],
|
||||
DOC_MERGE_INTERVAL: ['doc.manager.updatePollInterval', 'int'],
|
||||
DOC_MERGE_USE_JWST_CODEC: [
|
||||
'doc.manager.experimentalMergeWithJwstCodec',
|
||||
'boolean',
|
||||
],
|
||||
ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'],
|
||||
STRIPE_API_KEY: 'payment.stripe.keys.APIKey',
|
||||
STRIPE_WEBHOOK_KEY: 'payment.stripe.keys.webhookKey',
|
||||
FEATURES_EARLY_ACCESS_PREVIEW: ['featureFlags.earlyAccessPreview', 'boolean'],
|
||||
};
|
||||
|
||||
export default AFFiNE;
|
@ -1,11 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// Custom configurations
|
||||
|
||||
const env = process.env;
|
||||
const node = AFFiNE.node;
|
||||
|
||||
// TODO: may be separate config overring in `affine.[env].config`?
|
||||
// TODO(@forehalo): detail explained
|
||||
AFFiNE.host = 'localhost';
|
||||
AFFiNE.port = 3010;
|
||||
|
||||
if (node.prod) {
|
||||
AFFiNE.host = '0.0.0.0';
|
||||
// Storage
|
||||
if (env.R2_OBJECT_STORAGE_ACCOUNT_ID) {
|
||||
AFFiNE.storage.providers.r2 = {
|
||||
@ -29,3 +32,5 @@ if (node.prod) {
|
||||
// Metrics
|
||||
AFFiNE.metrics.enabled = true;
|
||||
}
|
||||
|
||||
export default AFFiNE;
|
@ -1,17 +0,0 @@
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
import { type AFFiNEConfig, parseEnvValue } from './def';
|
||||
|
||||
export function applyEnvToConfig(rawConfig: AFFiNEConfig) {
|
||||
for (const env in rawConfig.ENV_MAP) {
|
||||
const config = rawConfig.ENV_MAP[env];
|
||||
const [path, value] =
|
||||
typeof config === 'string'
|
||||
? [config, process.env[env]]
|
||||
: [config[0], parseEnvValue(process.env[env], config[1])];
|
||||
|
||||
if (value !== undefined) {
|
||||
set(rawConfig, path, value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const OPERATION_NAME = 'x-operation-name';
|
||||
|
||||
export const REQUEST_ID = 'x-request-id';
|
@ -4,7 +4,7 @@ import { Logger, Module } from '@nestjs/common';
|
||||
import { CommandFactory } from 'nest-commander';
|
||||
|
||||
import { AppModule as BusinessAppModule } from '../app';
|
||||
import { ConfigModule } from '../config';
|
||||
import { ConfigModule } from '../fundamentals/config';
|
||||
import { CreateCommand, NameQuestion } from './commands/create';
|
||||
import { RevertCommand, RunCommand } from './commands/run';
|
||||
|
||||
@ -16,9 +16,6 @@ import { RevertCommand, RunCommand } from './commands/run';
|
||||
enableUpdateAutoMerging: false,
|
||||
},
|
||||
},
|
||||
metrics: {
|
||||
enabled: false,
|
||||
},
|
||||
}),
|
||||
BusinessAppModule,
|
||||
],
|
||||
|
@ -4,15 +4,14 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
|
||||
interface Migration {
|
||||
file: string;
|
||||
name: string;
|
||||
up: (db: PrismaService, injector: ModuleRef) => Promise<void>;
|
||||
down: (db: PrismaService, injector: ModuleRef) => Promise<void>;
|
||||
up: (db: PrismaClient, injector: ModuleRef) => Promise<void>;
|
||||
down: (db: PrismaClient, injector: ModuleRef) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function collectMigrations(): Promise<Migration[]> {
|
||||
@ -48,7 +47,7 @@ export async function collectMigrations(): Promise<Migration[]> {
|
||||
export class RunCommand extends CommandRunner {
|
||||
logger = new Logger(RunCommand.name);
|
||||
constructor(
|
||||
private readonly db: PrismaService,
|
||||
private readonly db: PrismaClient,
|
||||
private readonly injector: ModuleRef
|
||||
) {
|
||||
super();
|
||||
@ -139,7 +138,7 @@ export class RevertCommand extends CommandRunner {
|
||||
logger = new Logger(RevertCommand.name);
|
||||
|
||||
constructor(
|
||||
private readonly db: PrismaService,
|
||||
private readonly db: PrismaClient,
|
||||
private readonly injector: ModuleRef
|
||||
) {
|
||||
super();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { DocID } from '../../modules/utils/doc';
|
||||
|
||||
export class Guid1698398506533 {
|
||||
// do the migration
|
||||
|
@ -4,7 +4,7 @@ import { Redis } from 'ioredis';
|
||||
import { SessionCache, ThrottlerCache } from './instances';
|
||||
import { LocalCache } from './providers/cache';
|
||||
import { RedisCache } from './providers/redis';
|
||||
import { CacheRedis, SessionRedis, ThrottlerRedis } from './redis';
|
||||
import { CacheRedis, RedisModule, SessionRedis, ThrottlerRedis } from './redis';
|
||||
|
||||
function makeCacheProvider(CacheToken: Type, RedisToken: Type): Provider {
|
||||
return {
|
||||
@ -25,6 +25,7 @@ const ThrottlerCacheProvider = makeCacheProvider(
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: AFFiNE.redis.enabled ? [RedisModule] : [],
|
||||
providers: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider],
|
||||
exports: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider],
|
||||
})
|
@ -1,6 +1,7 @@
|
||||
import type { ApolloDriverConfig } from '@nestjs/apollo';
|
||||
|
||||
import type { LeafPaths } from '../utils/types';
|
||||
import { EnvConfigType } from './env';
|
||||
import type { AFFiNEStorageConfig } from './storage';
|
||||
|
||||
declare global {
|
||||
@ -18,8 +19,6 @@ export enum ExternalAccount {
|
||||
}
|
||||
|
||||
export type ServerFlavor = 'allinone' | 'graphql' | 'sync' | 'selfhosted';
|
||||
|
||||
type EnvConfigType = 'string' | 'int' | 'float' | 'boolean';
|
||||
type ConfigPaths = LeafPaths<
|
||||
Omit<
|
||||
AFFiNEConfig,
|
||||
@ -31,40 +30,11 @@ type ConfigPaths = LeafPaths<
|
||||
| 'dev'
|
||||
| 'test'
|
||||
| 'deploy'
|
||||
| 'node'
|
||||
>,
|
||||
'',
|
||||
'....'
|
||||
>;
|
||||
/**
|
||||
* parse number value from environment variables
|
||||
*/
|
||||
function int(value: string) {
|
||||
const n = parseInt(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function float(value: string) {
|
||||
const n = parseFloat(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function boolean(value: string) {
|
||||
return value === '1' || value.toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
export function parseEnvValue(value: string | undefined, type?: EnvConfigType) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return type === 'int'
|
||||
? int(value)
|
||||
: type === 'float'
|
||||
? float(value)
|
||||
: type === 'boolean'
|
||||
? boolean(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
/**
|
||||
* All Configurations that would control AFFiNE server behaviors
|
||||
@ -80,6 +50,10 @@ export interface AFFiNEConfig {
|
||||
* System version
|
||||
*/
|
||||
readonly version: string;
|
||||
/**
|
||||
* Server flavor
|
||||
*/
|
||||
readonly flavor: ServerFlavor;
|
||||
/**
|
||||
* Deployment environment
|
||||
*/
|
||||
@ -370,3 +344,5 @@ export interface AFFiNEConfig {
|
||||
} & import('stripe').Stripe.StripeConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export * from './storage';
|
@ -1,19 +1,15 @@
|
||||
/// <reference types="../global.d.ts" />
|
||||
/// <reference types="../../global.d.ts" />
|
||||
|
||||
import { createPrivateKey, createPublicKey } from 'node:crypto';
|
||||
|
||||
import parse from 'parse-duration';
|
||||
|
||||
import pkg from '../../package.json' assert { type: 'json' };
|
||||
import pkg from '../../../package.json' assert { type: 'json' };
|
||||
import type { AFFiNEConfig, ServerFlavor } from './def';
|
||||
import { applyEnvToConfig } from './env';
|
||||
import { getDefaultAFFiNEStorageConfig } from './storage';
|
||||
|
||||
export const SERVER_FLAVOR = (process.env.SERVER_FLAVOR ??
|
||||
'allinone') as ServerFlavor;
|
||||
|
||||
// Don't use this in production
|
||||
export const examplePrivateKey = `-----BEGIN EC PRIVATE KEY-----
|
||||
const examplePrivateKey = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIEtyAJLIULkphVhqXqxk4Nr8Ggty3XLwUJWBxzAWCWTMoAoGCCqGSM49
|
||||
AwEHoUQDQgAEF3U/0wIeJ3jRKXeFKqQyBKlr9F7xaAUScRrAuSP33rajm3cdfihI
|
||||
3JvMxVNsS2lE8PSGQrvDrJZaDo0L+Lq9Gg==
|
||||
@ -50,50 +46,12 @@ const jwtKeyPair = (function () {
|
||||
|
||||
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
let isHttps: boolean | null = null;
|
||||
const flavor = (process.env.SERVER_FLAVOR ?? 'allinone') as ServerFlavor;
|
||||
const defaultConfig = {
|
||||
serverId: 'affine-nestjs-server',
|
||||
version: pkg.version,
|
||||
ENV_MAP: {
|
||||
AFFINE_SERVER_PORT: ['port', 'int'],
|
||||
AFFINE_SERVER_HOST: 'host',
|
||||
AFFINE_SERVER_SUB_PATH: 'path',
|
||||
AFFIHE_SERVER_HTTPS: 'https',
|
||||
AFFINE_ENV: 'affineEnv',
|
||||
DATABASE_URL: 'db.url',
|
||||
ENABLE_CAPTCHA: ['auth.captcha.enable', 'boolean'],
|
||||
CAPTCHA_TURNSTILE_SECRET: ['auth.captcha.turnstile.secret', 'string'],
|
||||
OAUTH_GOOGLE_ENABLED: ['auth.oauthProviders.google.enabled', 'boolean'],
|
||||
OAUTH_GOOGLE_CLIENT_ID: 'auth.oauthProviders.google.clientId',
|
||||
OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret',
|
||||
OAUTH_GITHUB_ENABLED: ['auth.oauthProviders.github.enabled', 'boolean'],
|
||||
OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId',
|
||||
OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret',
|
||||
OAUTH_EMAIL_LOGIN: 'auth.email.login',
|
||||
OAUTH_EMAIL_SENDER: 'auth.email.sender',
|
||||
OAUTH_EMAIL_SERVER: 'auth.email.server',
|
||||
OAUTH_EMAIL_PORT: ['auth.email.port', 'int'],
|
||||
OAUTH_EMAIL_PASSWORD: 'auth.email.password',
|
||||
THROTTLE_TTL: ['rateLimiter.ttl', 'int'],
|
||||
THROTTLE_LIMIT: ['rateLimiter.limit', 'int'],
|
||||
REDIS_SERVER_ENABLED: ['redis.enabled', 'boolean'],
|
||||
REDIS_SERVER_HOST: 'redis.host',
|
||||
REDIS_SERVER_PORT: ['redis.port', 'int'],
|
||||
REDIS_SERVER_USER: 'redis.username',
|
||||
REDIS_SERVER_PASSWORD: 'redis.password',
|
||||
REDIS_SERVER_DATABASE: ['redis.database', 'int'],
|
||||
DOC_MERGE_INTERVAL: ['doc.manager.updatePollInterval', 'int'],
|
||||
DOC_MERGE_USE_JWST_CODEC: [
|
||||
'doc.manager.experimentalMergeWithJwstCodec',
|
||||
'boolean',
|
||||
],
|
||||
ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'],
|
||||
STRIPE_API_KEY: 'payment.stripe.keys.APIKey',
|
||||
STRIPE_WEBHOOK_KEY: 'payment.stripe.keys.webhookKey',
|
||||
FEATURES_EARLY_ACCESS_PREVIEW: [
|
||||
'featureFlags.earlyAccessPreview',
|
||||
'boolean',
|
||||
],
|
||||
} satisfies AFFiNEConfig['ENV_MAP'],
|
||||
flavor,
|
||||
ENV_MAP: {},
|
||||
affineEnv: 'dev',
|
||||
get affine() {
|
||||
const env = this.affineEnv;
|
||||
@ -194,7 +152,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
},
|
||||
doc: {
|
||||
manager: {
|
||||
enableUpdateAutoMerging: SERVER_FLAVOR !== 'sync',
|
||||
enableUpdateAutoMerging: flavor !== 'sync',
|
||||
updatePollInterval: 3000,
|
||||
experimentalMergeWithJwstCodec: false,
|
||||
},
|
||||
@ -216,7 +174,5 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
},
|
||||
} satisfies AFFiNEConfig;
|
||||
|
||||
applyEnvToConfig(defaultConfig);
|
||||
|
||||
return defaultConfig;
|
||||
};
|
50
packages/backend/server/src/fundamentals/config/env.ts
Normal file
50
packages/backend/server/src/fundamentals/config/env.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
import type { AFFiNEConfig } from './def';
|
||||
|
||||
export type EnvConfigType = 'string' | 'int' | 'float' | 'boolean';
|
||||
/**
|
||||
* parse number value from environment variables
|
||||
*/
|
||||
function int(value: string) {
|
||||
const n = parseInt(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function float(value: string) {
|
||||
const n = parseFloat(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function boolean(value: string) {
|
||||
return value === '1' || value.toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
const envParsers: Record<EnvConfigType, (value: string) => unknown> = {
|
||||
int,
|
||||
float,
|
||||
boolean,
|
||||
string: value => value,
|
||||
};
|
||||
|
||||
export function parseEnvValue(value: string | undefined, type: EnvConfigType) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return envParsers[type](value);
|
||||
}
|
||||
|
||||
export function applyEnvToConfig(rawConfig: AFFiNEConfig) {
|
||||
for (const env in rawConfig.ENV_MAP) {
|
||||
const config = rawConfig.ENV_MAP[env];
|
||||
const [path, value] =
|
||||
typeof config === 'string'
|
||||
? [config, parseEnvValue(process.env[env], 'string')]
|
||||
: [config[0], parseEnvValue(process.env[env], config[1] ?? 'string')];
|
||||
|
||||
if (value !== undefined) {
|
||||
set(rawConfig, path, value);
|
||||
}
|
||||
}
|
||||
}
|
6
packages/backend/server/src/fundamentals/config/index.ts
Normal file
6
packages/backend/server/src/fundamentals/config/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// eslint-disable-next-line simple-import-sort/imports
|
||||
|
||||
export * from './def';
|
||||
export * from './default';
|
||||
export { applyEnvToConfig, parseEnvValue } from './env';
|
||||
export * from './module';
|
@ -1,24 +1,12 @@
|
||||
// eslint-disable-next-line simple-import-sort/imports
|
||||
import type { DynamicModule, FactoryProvider } from '@nestjs/common';
|
||||
import { DynamicModule, FactoryProvider } from '@nestjs/common';
|
||||
import { merge } from 'lodash-es';
|
||||
|
||||
import type { DeepPartial } from '../utils/types';
|
||||
import type { AFFiNEConfig } from './def';
|
||||
|
||||
type ConstructorOf<T> = {
|
||||
new (): T;
|
||||
};
|
||||
|
||||
function ApplyType<T>(): ConstructorOf<T> {
|
||||
// @ts-expect-error used to fake the type of config
|
||||
return class Inner implements T {
|
||||
constructor() {}
|
||||
};
|
||||
}
|
||||
import { ApplyType, DeepPartial } from '../utils/types';
|
||||
import { AFFiNEConfig } from './def';
|
||||
|
||||
/**
|
||||
* usage:
|
||||
* ```
|
||||
* @example
|
||||
*
|
||||
* import { Config } from '@affine/server'
|
||||
*
|
||||
* class TestConfig {
|
||||
@ -27,7 +15,6 @@ function ApplyType<T>(): ConstructorOf<T> {
|
||||
* return this.config.env
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class Config extends ApplyType<AFFiNEConfig>() {}
|
||||
|
||||
@ -69,7 +56,3 @@ export class ConfigModule {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type { AFFiNEConfig } from './def';
|
||||
export { SERVER_FLAVOR } from './default';
|
||||
export * from './storage';
|
@ -6,8 +6,8 @@ import { Request, Response } from 'express';
|
||||
import { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { Config } from './config';
|
||||
import { GQLLoggerPlugin } from './graphql/logger-plugin';
|
||||
import { Config } from '../config';
|
||||
import { GQLLoggerPlugin } from './logger-plugin';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@ -21,12 +21,12 @@ import { GQLLoggerPlugin } from './graphql/logger-plugin';
|
||||
csrfPrevention: {
|
||||
requestHeaders: ['content-type'],
|
||||
},
|
||||
autoSchemaFile: config.node.test
|
||||
? join(
|
||||
fileURLToPath(import.meta.url),
|
||||
'../../node_modules/.cache/schema.gql'
|
||||
)
|
||||
: join(fileURLToPath(import.meta.url), '..', 'schema.gql'),
|
||||
autoSchemaFile: join(
|
||||
fileURLToPath(import.meta.url),
|
||||
config.node.test
|
||||
? '../../../../node_modules/.cache/schema.gql'
|
||||
: '../../../schema.gql'
|
||||
),
|
||||
context: ({ req, res }: { req: Request; res: Response }) => ({
|
||||
req,
|
||||
res,
|
@ -8,17 +8,22 @@ import { Logger } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
import { metrics } from '../metrics/metrics';
|
||||
import { ReqContext } from '../types';
|
||||
|
||||
export interface RequestContext {
|
||||
req: Express.Request & {
|
||||
res: Express.Response;
|
||||
};
|
||||
}
|
||||
|
||||
@Plugin()
|
||||
export class GQLLoggerPlugin implements ApolloServerPlugin {
|
||||
protected logger = new Logger(GQLLoggerPlugin.name);
|
||||
|
||||
requestDidStart(
|
||||
reqContext: GraphQLRequestContext<ReqContext>
|
||||
): Promise<GraphQLRequestListener<GraphQLRequestContext<ReqContext>>> {
|
||||
const res = reqContext.contextValue.req.res as Response;
|
||||
const operation = reqContext.request.operationName;
|
||||
ctx: GraphQLRequestContext<RequestContext>
|
||||
): Promise<GraphQLRequestListener<GraphQLRequestContext<RequestContext>>> {
|
||||
const res = ctx.contextValue.req.res as Response;
|
||||
const operation = ctx.request.operationName;
|
||||
|
||||
metrics.gql.counter('query_counter').add(1, { operation });
|
||||
const start = Date.now();
|
20
packages/backend/server/src/fundamentals/index.ts
Normal file
20
packages/backend/server/src/fundamentals/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export { Cache, CacheInterceptor, MakeCache, PreventCache } from './cache';
|
||||
export {
|
||||
applyEnvToConfig,
|
||||
Config,
|
||||
getDefaultAFFiNEStorageConfig,
|
||||
} from './config';
|
||||
export { EventEmitter, type EventPayload, OnEvent } from './event';
|
||||
export { MailService } from './mailer';
|
||||
export { CallCounter, CallTimer, metrics } from './metrics';
|
||||
export { PrismaService } from './prisma';
|
||||
export { SessionService } from './session';
|
||||
export * from './storage';
|
||||
export { AuthThrottlerGuard, CloudThrottlerGuard, Throttle } from './throttler';
|
||||
export {
|
||||
getRequestFromHost,
|
||||
getRequestResponseFromContext,
|
||||
getRequestResponseFromHost,
|
||||
} from './utils/request';
|
||||
export type * from './utils/types';
|
||||
export { RedisIoAdapter } from './websocket';
|
12
packages/backend/server/src/fundamentals/mailer/index.ts
Normal file
12
packages/backend/server/src/fundamentals/mailer/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { MailService } from './mail.service';
|
||||
import { MAILER } from './mailer';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [MAILER, MailService],
|
||||
exports: [MailService],
|
||||
})
|
||||
export class MailModule {}
|
||||
export { MailService };
|
@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Config } from '../../../config';
|
||||
import { Config } from '../config';
|
||||
import {
|
||||
MAILER_SERVICE,
|
||||
type MailerService,
|
@ -2,7 +2,7 @@ import { FactoryProvider } from '@nestjs/common';
|
||||
import { createTransport, Transporter } from 'nodemailer';
|
||||
import SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
|
||||
import { Config } from '../../../config';
|
||||
import { Config } from '../config';
|
||||
|
||||
export const MAILER_SERVICE = Symbol('MAILER_SERVICE');
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Global, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
|
||||
import { Config } from '../config';
|
||||
import { parseEnvValue } from '../config/def';
|
||||
import { Config, parseEnvValue } from '../config';
|
||||
import { createSDK, registerCustomMetrics } from './opentelemetry';
|
||||
|
||||
@Global()
|
@ -1,6 +1,6 @@
|
||||
import { Global, Injectable, Module } from '@nestjs/common';
|
||||
|
||||
import { SessionCache } from './cache/instances';
|
||||
import { SessionCache } from '../cache';
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
@ -9,10 +9,10 @@ try {
|
||||
const require = createRequire(import.meta.url);
|
||||
storageModule =
|
||||
process.arch === 'arm64'
|
||||
? require('../../storage.arm64.node')
|
||||
? require('../.././storage.arm64.node')
|
||||
: process.arch === 'arm'
|
||||
? require('../../storage.armv7.node')
|
||||
: require('../../storage.node');
|
||||
? require('../../../storage.armv7.node')
|
||||
: require('../../../storage.node');
|
||||
}
|
||||
|
||||
export { storageModule as OctoBaseStorageModule };
|
||||
@ -32,3 +32,14 @@ export const mintChallengeResponse = async (resource: string, bits: number) => {
|
||||
if (!resource) return null;
|
||||
return storageModule.mintChallengeResponse(resource, bits);
|
||||
};
|
||||
|
||||
export type {
|
||||
BlobInputType,
|
||||
BlobOutputType,
|
||||
GetObjectMetadata,
|
||||
ListObjectsMetadata,
|
||||
PutObjectMetadata,
|
||||
StorageProvider,
|
||||
} from './providers';
|
||||
export { createStorageProvider } from './providers';
|
||||
export { toBuffer } from './providers/utils';
|
@ -15,7 +15,7 @@ import { join, parse, resolve } from 'node:path';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { FsStorageConfig } from '../../../config/storage';
|
||||
import { FsStorageConfig } from '../../config/storage';
|
||||
import {
|
||||
BlobInputType,
|
||||
GetObjectMetadata,
|
@ -1,4 +1,4 @@
|
||||
import { AFFiNEStorageConfig, Storages } from '../../../config/storage';
|
||||
import { AFFiNEStorageConfig, Storages } from '../../config/storage';
|
||||
import { FsStorageProvider } from './fs';
|
||||
import type { StorageProvider } from './provider';
|
||||
import { R2StorageProvider } from './r2';
|
@ -1,6 +1,6 @@
|
||||
import type { Readable } from 'node:stream';
|
||||
|
||||
import { StorageProviderType } from '../../../config';
|
||||
import { StorageProviderType } from '../../config';
|
||||
|
||||
export interface GetObjectMetadata {
|
||||
/**
|
@ -1,6 +1,6 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { R2StorageConfig } from '../../../config/storage';
|
||||
import { R2StorageConfig } from '../../config/storage';
|
||||
import { S3StorageProvider } from './s3';
|
||||
|
||||
export class R2StorageProvider extends S3StorageProvider {
|
@ -11,7 +11,7 @@ import {
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { S3StorageConfig } from '../../../config/storage';
|
||||
import { S3StorageConfig } from '../../config/storage';
|
||||
import {
|
||||
BlobInputType,
|
||||
GetObjectMetadata,
|
@ -9,9 +9,9 @@ import {
|
||||
} from '@nestjs/throttler';
|
||||
import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis';
|
||||
|
||||
import { ThrottlerCache } from './cache';
|
||||
import { Config } from './config';
|
||||
import { getRequestResponseFromContext } from './utils/nestjs';
|
||||
import { ThrottlerCache } from '../cache';
|
||||
import { Config } from '../config';
|
||||
import { getRequestResponseFromContext } from '../utils/request';
|
||||
|
||||
@Injectable()
|
||||
class CustomOptionsFactory implements ThrottlerOptionsFactory {
|
@ -1,3 +1,16 @@
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
export type ConstructorOf<T> = {
|
||||
new (): T;
|
||||
};
|
||||
|
||||
export function ApplyType<T>(): ConstructorOf<T> {
|
||||
// @ts-expect-error used to fake the type of config
|
||||
return class Inner implements T {
|
||||
constructor() {}
|
||||
};
|
||||
}
|
||||
|
||||
export type DeepPartial<T> = T extends Array<infer U>
|
||||
? DeepPartial<U>[]
|
||||
: T extends ReadonlyArray<infer U>
|
||||
@ -40,3 +53,10 @@ export type LeafPaths<
|
||||
: never;
|
||||
}[keyof T]
|
||||
: never;
|
||||
|
||||
export interface FileUpload {
|
||||
filename: string;
|
||||
mimetype: string;
|
||||
encoding: string;
|
||||
createReadStream: () => Readable;
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { RedisIoAdapter } from './redis-adapter';
|
@ -9,21 +9,16 @@ import cookieParser from 'cookie-parser';
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
|
||||
import { AppModule } from './app';
|
||||
import { Config } from './config';
|
||||
import { RedisIoAdapter } from './fundamentals';
|
||||
import { CacheRedis } from './fundamentals/cache/redis';
|
||||
import { ExceptionLogger } from './middleware/exception-logger';
|
||||
import { serverTimingAndCache } from './middleware/timing';
|
||||
import { RedisIoAdapter } from './modules/sync/redis-adapter';
|
||||
import { CacheRedis } from './cache/redis';
|
||||
|
||||
const { NODE_ENV, AFFINE_ENV } = process.env;
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
cors: true,
|
||||
rawBody: true,
|
||||
bodyParser: true,
|
||||
logger:
|
||||
NODE_ENV !== 'production' || AFFINE_ENV !== 'production'
|
||||
? ['verbose']
|
||||
: ['log'],
|
||||
logger: AFFiNE.affine.stable ? ['log'] : ['verbose'],
|
||||
});
|
||||
|
||||
app.use(serverTimingAndCache);
|
||||
@ -39,18 +34,16 @@ app.use(
|
||||
app.useGlobalFilters(new ExceptionLogger());
|
||||
app.use(cookieParser());
|
||||
|
||||
const config = app.get(Config);
|
||||
|
||||
const host = config.node.prod ? '0.0.0.0' : 'localhost';
|
||||
const port = config.port ?? 3010;
|
||||
|
||||
if (!config.node.test && config.redis.enabled) {
|
||||
if (AFFiNE.redis.enabled) {
|
||||
const redis = app.get(CacheRedis, { strict: false });
|
||||
const redisIoAdapter = new RedisIoAdapter(app);
|
||||
await redisIoAdapter.connectToRedis(redis);
|
||||
app.useWebSocketAdapter(redisIoAdapter);
|
||||
}
|
||||
|
||||
await app.listen(port, host);
|
||||
await app.listen(AFFiNE.port, AFFiNE.host);
|
||||
|
||||
console.log(`Listening on http://${host}:${port}`);
|
||||
console.log(
|
||||
`AFFiNE Server has been started on http://${AFFiNE.host}:${AFFiNE.port}.`
|
||||
);
|
||||
console.log(`And the public server should be recognized as ${AFFiNE.baseUrl}`);
|
||||
|
@ -9,9 +9,10 @@ import {
|
||||
import { GqlContextType } from '@nestjs/graphql';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { REQUEST_ID } from '../constants';
|
||||
const TrivialExceptions = [NotFoundException];
|
||||
|
||||
export const REQUEST_ID_HEADER = 'x-request-id';
|
||||
|
||||
@Catch()
|
||||
export class ExceptionLogger implements ExceptionFilter {
|
||||
private readonly logger = new Logger('ExceptionLogger');
|
||||
@ -21,7 +22,7 @@ export class ExceptionLogger implements ExceptionFilter {
|
||||
const ctx = host.switchToHttp();
|
||||
|
||||
const request = ctx.getRequest<Request>();
|
||||
const requestId = request?.header(REQUEST_ID);
|
||||
const requestId = request?.header(REQUEST_ID_HEADER);
|
||||
|
||||
const shouldVerboseLog = !TrivialExceptions.some(
|
||||
e => exception instanceof e
|
||||
|
@ -10,8 +10,10 @@ import { Reflector } from '@nestjs/core';
|
||||
import type { NextAuthOptions } from 'next-auth';
|
||||
import { AuthHandler } from 'next-auth/core';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { getRequestResponseFromContext } from '../../utils/nestjs';
|
||||
import {
|
||||
getRequestResponseFromContext,
|
||||
PrismaService,
|
||||
} from '../../fundamentals';
|
||||
import { NextAuthOptionsProvide } from './next-auth-options';
|
||||
import { AuthService } from './service';
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { SessionModule } from '../../session';
|
||||
import { MAILER, MailService } from './mailer';
|
||||
import { NextAuthController } from './next-auth.controller';
|
||||
import { NextAuthOptionsProvider } from './next-auth-options';
|
||||
import { AuthResolver } from './resolver';
|
||||
@ -9,18 +7,12 @@ import { AuthService } from './service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [SessionModule],
|
||||
providers: [
|
||||
AuthService,
|
||||
AuthResolver,
|
||||
NextAuthOptionsProvider,
|
||||
MAILER,
|
||||
MailService,
|
||||
],
|
||||
exports: [AuthService, NextAuthOptionsProvider, MailService],
|
||||
providers: [AuthService, AuthResolver, NextAuthOptionsProvider],
|
||||
exports: [AuthService, NextAuthOptionsProvider],
|
||||
controllers: [NextAuthController],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
export * from './guard';
|
||||
export { TokenType } from './resolver';
|
||||
export { AuthService };
|
||||
|
@ -1,2 +0,0 @@
|
||||
export { MailService } from './mail.service';
|
||||
export { MAILER } from './mailer';
|
@ -8,12 +8,14 @@ import Email from 'next-auth/providers/email';
|
||||
import Github from 'next-auth/providers/github';
|
||||
import Google from 'next-auth/providers/google';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { SessionService } from '../../session';
|
||||
import {
|
||||
Config,
|
||||
MailService,
|
||||
PrismaService,
|
||||
SessionService,
|
||||
} from '../../fundamentals';
|
||||
import { FeatureType } from '../features';
|
||||
import { Quota_FreePlanV1 } from '../quota';
|
||||
import { MailService } from './mailer';
|
||||
import {
|
||||
decode,
|
||||
encode,
|
||||
|
@ -22,11 +22,14 @@ import { nanoid } from 'nanoid';
|
||||
import type { AuthAction, CookieOption, NextAuthOptions } from 'next-auth';
|
||||
import { AuthHandler } from 'next-auth/core';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { metrics } from '../../metrics';
|
||||
import { PrismaService } from '../../prisma/service';
|
||||
import { SessionService } from '../../session';
|
||||
import { AuthThrottlerGuard, Throttle } from '../../throttler';
|
||||
import {
|
||||
AuthThrottlerGuard,
|
||||
Config,
|
||||
metrics,
|
||||
PrismaService,
|
||||
SessionService,
|
||||
Throttle,
|
||||
} from '../../fundamentals';
|
||||
import { NextAuthOptionsProvide } from './next-auth-options';
|
||||
import { AuthService } from './service';
|
||||
|
||||
|
@ -16,9 +16,12 @@ import {
|
||||
import type { Request } from 'express';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { SessionService } from '../../session';
|
||||
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
||||
import {
|
||||
CloudThrottlerGuard,
|
||||
Config,
|
||||
SessionService,
|
||||
Throttle,
|
||||
} from '../../fundamentals';
|
||||
import { UserType } from '../users';
|
||||
import { Auth, CurrentUser } from './guard';
|
||||
import { AuthService } from './service';
|
||||
|
@ -11,11 +11,13 @@ import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken';
|
||||
import type { User } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { verifyChallengeResponse } from '../../storage';
|
||||
import {
|
||||
Config,
|
||||
MailService,
|
||||
PrismaService,
|
||||
verifyChallengeResponse,
|
||||
} from '../../fundamentals';
|
||||
import { Quota_FreePlanV1 } from '../quota';
|
||||
import { MailService } from './mailer';
|
||||
|
||||
export type UserClaim = Pick<
|
||||
User,
|
||||
@ -192,6 +194,7 @@ export class AuthService {
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
// TODO(@forehalo): handle in event system
|
||||
features: {
|
||||
create: {
|
||||
reason: 'created by api sign up',
|
||||
|
@ -4,8 +4,7 @@ import { BadRequestException } from '@nestjs/common';
|
||||
import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken';
|
||||
import { JWT } from 'next-auth/jwt';
|
||||
|
||||
import { Config } from '../../../config';
|
||||
import { PrismaService } from '../../../prisma';
|
||||
import { Config, PrismaService } from '../../../fundamentals';
|
||||
import { getUtcTimestamp, UserClaim } from '../service';
|
||||
|
||||
export const jwtEncode = async (
|
||||
|
@ -2,9 +2,7 @@ import { Logger } from '@nestjs/common';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type { SendVerificationRequestParams } from 'next-auth/providers/email';
|
||||
|
||||
import { Config } from '../../../config';
|
||||
import { SessionService } from '../../../session';
|
||||
import { MailService } from '../mailer';
|
||||
import { Config, MailService, SessionService } from '../../../fundamentals';
|
||||
|
||||
export async function sendVerificationRequest(
|
||||
config: Config,
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Field, ObjectType, Query } from '@nestjs/graphql';
|
||||
|
||||
import { SERVER_FLAVOR } from '../config';
|
||||
|
||||
@ObjectType()
|
||||
export class ServerConfigType {
|
||||
@Field({ description: 'server version' })
|
||||
@ -22,7 +20,7 @@ export class ServerConfigResolver {
|
||||
serverConfig(): ServerConfigType {
|
||||
return {
|
||||
version: AFFiNE.version,
|
||||
flavor: SERVER_FLAVOR,
|
||||
flavor: AFFiNE.flavor,
|
||||
baseUrl: AFFiNE.baseUrl,
|
||||
};
|
||||
}
|
||||
|
@ -3,10 +3,13 @@ import { isDeepStrictEqual } from 'node:util';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { type EventPayload, OnEvent } from '../../event';
|
||||
import { metrics } from '../../metrics';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import {
|
||||
Config,
|
||||
type EventPayload,
|
||||
metrics,
|
||||
OnEvent,
|
||||
PrismaService,
|
||||
} from '../../fundamentals';
|
||||
import { QuotaService } from '../quota';
|
||||
import { Permission } from '../workspaces/types';
|
||||
import { isEmptyBuffer } from './manager';
|
||||
|
@ -16,12 +16,16 @@ import {
|
||||
transact,
|
||||
} from 'yjs';
|
||||
|
||||
import { Cache } from '../../cache';
|
||||
import { Config } from '../../config';
|
||||
import { EventEmitter, type EventPayload, OnEvent } from '../../event';
|
||||
import { metrics } from '../../metrics/metrics';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { mergeUpdatesInApplyWay as jwstMergeUpdates } from '../../storage';
|
||||
import {
|
||||
Cache,
|
||||
Config,
|
||||
EventEmitter,
|
||||
type EventPayload,
|
||||
mergeUpdatesInApplyWay as jwstMergeUpdates,
|
||||
metrics,
|
||||
OnEvent,
|
||||
PrismaService,
|
||||
} from '../../fundamentals';
|
||||
|
||||
function compare(yBinary: Buffer, jwstBinary: Buffer, strict = false): boolean {
|
||||
if (yBinary.equals(jwstBinary)) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { Feature, FeatureSchema, FeatureType } from './types';
|
||||
|
||||
class FeatureConfig {
|
||||
@ -66,7 +67,7 @@ export type FeatureConfigType<F extends FeatureType> = InstanceType<
|
||||
|
||||
const FeatureCache = new Map<number, FeatureConfigType<FeatureType>>();
|
||||
|
||||
export async function getFeature(prisma: PrismaService, featureId: number) {
|
||||
export async function getFeature(prisma: PrismaClient, featureId: number) {
|
||||
const cachedQuota = FeatureCache.get(featureId);
|
||||
|
||||
if (cachedQuota) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { FeatureManagementService } from './management';
|
||||
import { FeatureService } from './service';
|
||||
|
||||
@ -18,4 +17,4 @@ export class FeatureModule {}
|
||||
|
||||
export { type CommonFeature, commonFeatureSchema } from './types';
|
||||
export { FeatureKind, Features, FeatureType } from './types';
|
||||
export { FeatureManagementService, FeatureService, PrismaService };
|
||||
export { FeatureManagementService, FeatureService };
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { Config, PrismaService } from '../../fundamentals';
|
||||
import { FeatureService } from './service';
|
||||
import { FeatureType } from './types';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaService } from '../../fundamentals';
|
||||
import { UserType } from '../users/types';
|
||||
import { WorkspaceType } from '../workspaces/types';
|
||||
import { FeatureConfigType, getFeature } from './feature';
|
||||
|
@ -4,8 +4,7 @@ import { DynamicModule, Type } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
|
||||
import { SERVER_FLAVOR } from '../config';
|
||||
import { GqlModule } from '../graphql.module';
|
||||
import { GqlModule } from '../fundamentals/graphql';
|
||||
import { ServerConfigModule } from './config';
|
||||
import { DocModule } from './doc';
|
||||
import { PaymentModule } from './payment';
|
||||
@ -18,7 +17,7 @@ import { WorkspaceModule } from './workspaces';
|
||||
|
||||
const BusinessModules: (Type | DynamicModule)[] = [];
|
||||
|
||||
switch (SERVER_FLAVOR) {
|
||||
switch (AFFiNE.flavor) {
|
||||
case 'sync':
|
||||
BusinessModules.push(SyncModule, DocModule);
|
||||
break;
|
||||
@ -68,4 +67,4 @@ switch (SERVER_FLAVOR) {
|
||||
break;
|
||||
}
|
||||
|
||||
export { BusinessModules, SERVER_FLAVOR };
|
||||
export { BusinessModules };
|
||||
|
@ -16,8 +16,7 @@ import type { User, UserInvoice, UserSubscription } from '@prisma/client';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { groupBy } from 'lodash-es';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { Config, PrismaService } from '../../fundamentals';
|
||||
import { Auth, CurrentUser, Public } from '../auth';
|
||||
import { UserType } from '../users';
|
||||
import {
|
||||
|
@ -9,8 +9,7 @@ import type {
|
||||
} from '@prisma/client';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { Config, PrismaService } from '../../fundamentals';
|
||||
import { FeatureManagementService } from '../features';
|
||||
import { QuotaService, QuotaType } from '../quota';
|
||||
import { ScheduleManager } from './schedule';
|
||||
|
@ -2,7 +2,7 @@ import { FactoryProvider } from '@nestjs/common';
|
||||
import { omit } from 'lodash-es';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { Config } from '../../fundamentals';
|
||||
|
||||
export const StripeProvider: FactoryProvider = {
|
||||
provide: Stripe,
|
||||
|
@ -10,7 +10,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import type { Request } from 'express';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { Config } from '../../config';
|
||||
import { Config } from '../../fundamentals';
|
||||
|
||||
@Controller('/api/stripe')
|
||||
export class StripeWebhook {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaService } from '../../fundamentals';
|
||||
import { formatDate, formatSize, Quota, QuotaSchema } from './types';
|
||||
|
||||
const QuotaCache = new Map<number, QuotaConfig>();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaService } from '../../fundamentals';
|
||||
import { FeatureKind } from '../features';
|
||||
import { QuotaConfig } from './quota';
|
||||
import { QuotaType } from './types';
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { AFFiNEStorageConfig, Config } from '../../../config';
|
||||
import { type EventPayload, OnEvent } from '../../../event';
|
||||
import {
|
||||
import type {
|
||||
BlobInputType,
|
||||
createStorageProvider,
|
||||
EventPayload,
|
||||
PutObjectMetadata,
|
||||
StorageProvider,
|
||||
} from '../providers';
|
||||
} from '../../../fundamentals';
|
||||
import { Config, createStorageProvider, OnEvent } from '../../../fundamentals';
|
||||
|
||||
@Injectable()
|
||||
export class AvatarStorage {
|
||||
public readonly provider: StorageProvider;
|
||||
private readonly storageConfig: AFFiNEStorageConfig['storages']['avatar'];
|
||||
private readonly storageConfig: Config['storage']['storages']['avatar'];
|
||||
|
||||
constructor(private readonly config: Config) {
|
||||
this.provider = createStorageProvider(this.config.storage, 'avatar');
|
||||
|
@ -3,15 +3,19 @@ import { Readable } from 'node:stream';
|
||||
import type { Storage } from '@affine/storage';
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
import { Config } from '../../../config';
|
||||
import { EventEmitter, type EventPayload, OnEvent } from '../../../event';
|
||||
import { OctoBaseStorageModule } from '../../../storage';
|
||||
import {
|
||||
import type {
|
||||
BlobInputType,
|
||||
createStorageProvider,
|
||||
EventPayload,
|
||||
StorageProvider,
|
||||
} from '../providers';
|
||||
import { toBuffer } from '../providers/utils';
|
||||
} from '../../../fundamentals';
|
||||
import {
|
||||
Config,
|
||||
createStorageProvider,
|
||||
EventEmitter,
|
||||
OctoBaseStorageModule,
|
||||
OnEvent,
|
||||
toBuffer,
|
||||
} from '../../../fundamentals';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceBlobStorage implements OnModuleInit {
|
||||
|
@ -11,12 +11,11 @@ import {
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { encodeStateAsUpdate, encodeStateVector } from 'yjs';
|
||||
|
||||
import { metrics } from '../../../metrics';
|
||||
import { CallTimer } from '../../../metrics/utils';
|
||||
import { DocID } from '../../../utils/doc';
|
||||
import { CallTimer, metrics } from '../../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../../auth';
|
||||
import { DocManager } from '../../doc';
|
||||
import { UserType } from '../../users';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { PermissionService } from '../../workspaces/permission';
|
||||
import { Permission } from '../../workspaces/types';
|
||||
import {
|
||||
|
@ -1,11 +0,0 @@
|
||||
export function assertExists<T>(
|
||||
val: T | null | undefined,
|
||||
message: string | Error = 'val does not exist'
|
||||
): asserts val is T {
|
||||
if (val === null || val === undefined) {
|
||||
if (message instanceof Error) {
|
||||
throw message;
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { Args, Context, Int, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
||||
import { CloudThrottlerGuard, Throttle } from '../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../auth/guard';
|
||||
import { AuthService } from '../auth/service';
|
||||
import { FeatureManagementService } from '../features';
|
||||
|
@ -11,10 +11,13 @@ import type { User } from '@prisma/client';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
|
||||
import { EventEmitter } from '../../event';
|
||||
import { PrismaService } from '../../prisma/service';
|
||||
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
||||
import type { FileUpload } from '../../types';
|
||||
import {
|
||||
CloudThrottlerGuard,
|
||||
EventEmitter,
|
||||
type FileUpload,
|
||||
PrismaService,
|
||||
Throttle,
|
||||
} from '../../fundamentals';
|
||||
import { Auth, CurrentUser, Public, Publicable } from '../auth/guard';
|
||||
import { FeatureManagementService } from '../features';
|
||||
import { QuotaService } from '../quota';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaService } from '../../fundamentals';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
|
@ -9,13 +9,12 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
|
||||
import { CallTimer } from '../../metrics';
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { CallTimer, PrismaService } from '../../fundamentals';
|
||||
import { Auth, CurrentUser, Publicable } from '../auth';
|
||||
import { DocHistoryManager, DocManager } from '../doc';
|
||||
import { WorkspaceBlobStorage } from '../storage';
|
||||
import { UserType } from '../users';
|
||||
import { DocID } from '../utils/doc';
|
||||
import { PermissionService, PublicPageMode } from './permission';
|
||||
import { Permission } from './types';
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
||||
import { CloudThrottlerGuard, Throttle } from '../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../auth';
|
||||
import { FeatureManagementService, FeatureType } from '../features';
|
||||
import { UserType } from '../users';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ForbiddenException, Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from '../../prisma';
|
||||
import { PrismaService } from '../../fundamentals';
|
||||
import { Permission } from './types';
|
||||
|
||||
export enum PublicPageMode {
|
||||
|
@ -12,9 +12,12 @@ import {
|
||||
import { GraphQLError } from 'graphql';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
|
||||
import { MakeCache, PreventCache } from '../../../cache';
|
||||
import { CloudThrottlerGuard } from '../../../throttler';
|
||||
import type { FileUpload } from '../../../types';
|
||||
import {
|
||||
CloudThrottlerGuard,
|
||||
type FileUpload,
|
||||
MakeCache,
|
||||
PreventCache,
|
||||
} from '../../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../../auth';
|
||||
import { FeatureManagementService, FeatureType } from '../../features';
|
||||
import { QuotaManagementService } from '../../quota';
|
||||
|
@ -12,11 +12,11 @@ import {
|
||||
} from '@nestjs/graphql';
|
||||
import type { SnapshotHistory } from '@prisma/client';
|
||||
|
||||
import { CloudThrottlerGuard } from '../../../throttler';
|
||||
import { DocID } from '../../../utils/doc';
|
||||
import { CloudThrottlerGuard } from '../../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../../auth';
|
||||
import { DocHistoryManager } from '../../doc/history';
|
||||
import { DocHistoryManager } from '../../doc';
|
||||
import { UserType } from '../../users';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { PermissionService } from '../permission';
|
||||
import { Permission, WorkspaceType } from '../types';
|
||||
|
||||
|
@ -11,11 +11,10 @@ import {
|
||||
} from '@nestjs/graphql';
|
||||
import type { WorkspacePage as PrismaWorkspacePage } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from '../../../prisma';
|
||||
import { CloudThrottlerGuard } from '../../../throttler';
|
||||
import { DocID } from '../../../utils/doc';
|
||||
import { CloudThrottlerGuard, PrismaService } from '../../../fundamentals';
|
||||
import { Auth, CurrentUser } from '../../auth';
|
||||
import { UserType } from '../../users';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { PermissionService, PublicPageMode } from '../permission';
|
||||
import { Permission, WorkspaceType } from '../types';
|
||||
|
||||
|
@ -20,12 +20,15 @@ import { GraphQLError } from 'graphql';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import { applyUpdate, Doc } from 'yjs';
|
||||
|
||||
import { EventEmitter } from '../../../event';
|
||||
import { PrismaService } from '../../../prisma';
|
||||
import { CloudThrottlerGuard, Throttle } from '../../../throttler';
|
||||
import type { FileUpload } from '../../../types';
|
||||
import {
|
||||
CloudThrottlerGuard,
|
||||
EventEmitter,
|
||||
type FileUpload,
|
||||
MailService,
|
||||
PrismaService,
|
||||
Throttle,
|
||||
} from '../../../fundamentals';
|
||||
import { Auth, CurrentUser, Public } from '../../auth';
|
||||
import { MailService } from '../../auth/mailer';
|
||||
import { AuthService } from '../../auth/service';
|
||||
import { FeatureManagementService, FeatureType } from '../../features';
|
||||
import { QuotaManagementService } from '../../quota';
|
||||
|
@ -1,8 +1,43 @@
|
||||
import 'reflect-metadata';
|
||||
import './affine.env';
|
||||
import './affine';
|
||||
import './affine.config';
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('AFFiNE Config:', globalThis.AFFiNE);
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import {
|
||||
applyEnvToConfig,
|
||||
getDefaultAFFiNEConfig,
|
||||
} from './fundamentals/config';
|
||||
|
||||
async function load() {
|
||||
// Initializing AFFiNE config
|
||||
//
|
||||
// 1. load dotenv file to `process.env`
|
||||
// load `.env` under pwd
|
||||
config();
|
||||
// load `.env` under user config folder
|
||||
config({
|
||||
path: join(fileURLToPath(import.meta.url), '../config/.env'),
|
||||
});
|
||||
|
||||
// 2. generate AFFiNE default config and assign to `globalThis.AFFiNE`
|
||||
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
||||
|
||||
// TODO(@forehalo):
|
||||
// Modules may contribute to ENV_MAP, figure out a good way to involve them instead of hardcoding in `./config/affine.env`
|
||||
// 3. load env => config map to `globalThis.AFFiNE.ENV_MAP
|
||||
await import('./config/affine.env');
|
||||
|
||||
// 4. apply `process.env` map overriding to `globalThis.AFFiNE`
|
||||
applyEnvToConfig(globalThis.AFFiNE);
|
||||
|
||||
// 5. load `./config/affine` to patch custom configs
|
||||
await import('./config/affine');
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('AFFiNE Config:', JSON.stringify(globalThis.AFFiNE, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
await load();
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
export interface FileUpload {
|
||||
filename: string;
|
||||
mimetype: string;
|
||||
encoding: string;
|
||||
createReadStream: () => Readable;
|
||||
}
|
||||
|
||||
export interface ReqContext {
|
||||
req: Express.Request & {
|
||||
res: Express.Response;
|
||||
};
|
||||
}
|
@ -4,14 +4,13 @@ import { randomUUID } from 'node:crypto';
|
||||
import { Transformer } from '@napi-rs/image';
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { hashSync } from '@node-rs/argon2';
|
||||
import { type User } from '@prisma/client';
|
||||
import { PrismaClient, type User } from '@prisma/client';
|
||||
import ava, { type TestFn } from 'ava';
|
||||
import type { Express } from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
import { AppModule } from '../src/app';
|
||||
import { FeatureManagementService } from '../src/modules/features';
|
||||
import { PrismaService } from '../src/prisma/service';
|
||||
import { createTestingApp } from './utils';
|
||||
|
||||
const gql = '/graphql';
|
||||
@ -52,7 +51,7 @@ test.beforeEach(async t => {
|
||||
imports: [AppModule],
|
||||
tapModule(builder) {
|
||||
builder
|
||||
.overrideProvider(PrismaService)
|
||||
.overrideProvider(PrismaClient)
|
||||
.useClass(FakePrisma)
|
||||
.overrideProvider(FeatureManagementService)
|
||||
.useValue({ canEarlyAccess: () => true });
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import ava, { type TestFn } from 'ava';
|
||||
|
||||
import { MailService } from '../src/modules/auth/mailer';
|
||||
import { MailService } from '../src/fundamentals/mailer';
|
||||
import { AuthService } from '../src/modules/auth/service';
|
||||
import {
|
||||
changeEmail,
|
||||
|
@ -2,10 +2,13 @@
|
||||
import { TestingModule } from '@nestjs/testing';
|
||||
import test from 'ava';
|
||||
|
||||
import { ConfigModule } from '../src/config';
|
||||
import { ConfigModule } from '../src/fundamentals/config';
|
||||
import {
|
||||
mintChallengeResponse,
|
||||
verifyChallengeResponse,
|
||||
} from '../src/fundamentals/storage';
|
||||
import { AuthResolver } from '../src/modules/auth/resolver';
|
||||
import { AuthService } from '../src/modules/auth/service';
|
||||
import { mintChallengeResponse, verifyChallengeResponse } from '../src/storage';
|
||||
import { createTestingModule } from './utils';
|
||||
|
||||
let authService: AuthService;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user