@@ -84,6 +84,7 @@
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.13.2",
|
||||
"class-validator-jsonschema": "npm:@innei/class-validator-jsonschema@3.1.2",
|
||||
"cls-hooked": "^4.2.2",
|
||||
"commander": "11.1.0",
|
||||
"dayjs": "1.11.10",
|
||||
"ejs": "3.1.9",
|
||||
@@ -134,6 +135,7 @@
|
||||
"@types/babel__core": "7.20.5",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cache-manager": "4.0.6",
|
||||
"@types/cls-hooked": "^4.3.8",
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/get-image-colors": "4.0.5",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { LoggerModule } from 'nestjs-pretty-logger'
|
||||
import type { DynamicModule, NestModule, Type } from '@nestjs/common'
|
||||
import type {
|
||||
DynamicModule,
|
||||
MiddlewareConsumer,
|
||||
NestModule,
|
||||
Type,
|
||||
} from '@nestjs/common'
|
||||
|
||||
import { Module } from '@nestjs/common'
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'
|
||||
@@ -15,6 +20,7 @@ import { DbQueryInterceptor } from './common/interceptors/db-query.interceptor'
|
||||
import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor'
|
||||
import { JSONTransformInterceptor } from './common/interceptors/json-transform.interceptor'
|
||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor'
|
||||
import { RequestContextMiddleware } from './common/middlewares/request-context.middleware'
|
||||
import { AckModule } from './modules/ack/ack.module'
|
||||
import { ActivityModule } from './modules/activity/activity.module'
|
||||
import { AggregateModule } from './modules/aggregate/aggregate.module'
|
||||
@@ -157,11 +163,14 @@ import { RedisModule } from './processors/redis/redis.module'
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
export class AppModule implements NestModule {
|
||||
static register(isInit: boolean): DynamicModule {
|
||||
return {
|
||||
module: AppModule,
|
||||
imports: [!isInit && InitModule].filter(Boolean) as Type<NestModule>[],
|
||||
}
|
||||
}
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(RequestContextMiddleware).forRoutes('(.*)')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { LogLevel } from '@nestjs/common'
|
||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||
|
||||
import { ValidationPipe } from '@nestjs/common'
|
||||
import { ContextIdFactory, NestFactory } from '@nestjs/core'
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
|
||||
import { CROSS_DOMAIN, DEBUG_MODE, PORT } from './app.config'
|
||||
import { AppModule } from './app.module'
|
||||
@@ -14,7 +14,6 @@ import { fastifyApp } from './common/adapters/fastify.adapter'
|
||||
import { RedisIoAdapter } from './common/adapters/socket.adapter'
|
||||
import { SpiderGuard } from './common/guards/spider.guard'
|
||||
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
|
||||
import { AggregateByTenantContextIdStrategy } from './common/strategies/context.strategy'
|
||||
import { logger } from './global/consola.global'
|
||||
import { isMainProcess, isTest } from './global/env.global'
|
||||
import { migrateDatabase } from './migration/migrate'
|
||||
@@ -84,8 +83,6 @@ export async function bootstrap() {
|
||||
app.useGlobalGuards(new SpiderGuard())
|
||||
!isTest && app.useWebSocketAdapter(new RedisIoAdapter(app))
|
||||
|
||||
ContextIdFactory.apply(new AggregateByTenantContextIdStrategy())
|
||||
|
||||
await app.listen(+PORT, '0.0.0.0', async () => {
|
||||
app.useLogger(app.get(Logger))
|
||||
logger.info('ENV:', process.env.NODE_ENV)
|
||||
|
||||
70
apps/core/src/common/contexts/request.context.ts
Normal file
70
apps/core/src/common/contexts/request.context.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// @reference https://github.com/ever-co/ever-gauzy/blob/d36b4f40b1446f3c33d02e0ba00b53a83109d950/packages/core/src/core/context/request-context.ts
|
||||
import * as cls from 'cls-hooked'
|
||||
import type { UserDocument } from '~/modules/user/user.model'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
import { UnauthorizedException } from '@nestjs/common'
|
||||
|
||||
type Nullable<T> = T | null
|
||||
export class RequestContext {
|
||||
readonly id: number
|
||||
request: IncomingMessage
|
||||
response: ServerResponse
|
||||
|
||||
constructor(request: IncomingMessage, response: ServerResponse) {
|
||||
this.id = Math.random()
|
||||
this.request = request
|
||||
this.response = response
|
||||
}
|
||||
|
||||
static currentRequestContext(): Nullable<RequestContext> {
|
||||
const session = cls.getNamespace(RequestContext.name)
|
||||
if (session && session.active) {
|
||||
return session.get(RequestContext.name)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
static currentRequest(): Nullable<IncomingMessage> {
|
||||
const requestContext = RequestContext.currentRequestContext()
|
||||
|
||||
if (requestContext) {
|
||||
return requestContext.request
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
static currentUser(throwError?: boolean): Nullable<UserDocument> {
|
||||
const requestContext = RequestContext.currentRequestContext()
|
||||
|
||||
if (requestContext) {
|
||||
const user: UserDocument = requestContext.request['user']
|
||||
|
||||
if (user) {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
if (throwError) {
|
||||
throw new UnauthorizedException()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
static currentIsMaster() {
|
||||
const requestContext = RequestContext.currentRequestContext()
|
||||
|
||||
if (requestContext) {
|
||||
const isMaster = requestContext.request['isMaster']
|
||||
|
||||
if (isMaster) {
|
||||
return isMaster
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { isJWT } from 'class-validator'
|
||||
import type { CanActivate, ExecutionContext } from '@nestjs/common'
|
||||
import type { UserModel } from '~/modules/user/user.model'
|
||||
import type { FastifyBizRequest } from '~/transformers/get-req.transformer'
|
||||
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
||||
|
||||
@@ -38,8 +40,8 @@ export class AuthGuard implements CanActivate {
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('令牌无效')
|
||||
}
|
||||
request.user = userModel
|
||||
request.token = Authorization
|
||||
|
||||
this.attachUserAndToken(request, userModel, Authorization)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -57,12 +59,29 @@ export class AuthGuard implements CanActivate {
|
||||
}
|
||||
}
|
||||
|
||||
request.user = await this.userService.getMaster()
|
||||
request.token = jwt
|
||||
this.attachUserAndToken(
|
||||
request,
|
||||
await this.userService.getMaster(),
|
||||
Authorization,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
getRequest(context: ExecutionContext) {
|
||||
return getNestExecutionContextRequest(context)
|
||||
}
|
||||
|
||||
attachUserAndToken(
|
||||
request: FastifyBizRequest,
|
||||
user: UserModel,
|
||||
token: string,
|
||||
) {
|
||||
request.user = user
|
||||
request.token = token
|
||||
|
||||
Object.assign(request.raw, {
|
||||
user,
|
||||
token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ export class RolesGuard extends AuthGuard implements CanActivate {
|
||||
request.isGuest = !isMaster
|
||||
request.isMaster = isMaster
|
||||
|
||||
Object.assign(request.raw, {
|
||||
isGuest: !isMaster,
|
||||
isMaster,
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// https://github.dev/ever-co/ever-gauzy/packages/core/src/core/context/request-context.middleware.ts
|
||||
|
||||
import * as cls from 'cls-hooked'
|
||||
import type { NestMiddleware } from '@nestjs/common'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { RequestContext } from '../contexts/request.context'
|
||||
|
||||
@Injectable()
|
||||
export class RequestContextMiddleware implements NestMiddleware {
|
||||
use(req: IncomingMessage, res: ServerResponse, next: () => any) {
|
||||
const requestContext = new RequestContext(req, res)
|
||||
|
||||
const session =
|
||||
cls.getNamespace(RequestContext.name) ||
|
||||
cls.createNamespace(RequestContext.name)
|
||||
|
||||
session.run(async () => {
|
||||
session.set(RequestContext.name, requestContext)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type {
|
||||
ContextId,
|
||||
ContextIdStrategy,
|
||||
HostComponentInfo,
|
||||
} from '@nestjs/core'
|
||||
import type { FastifyRequest } from 'fastify'
|
||||
|
||||
import { ContextIdFactory } from '@nestjs/core'
|
||||
|
||||
const tenants = new Map<string, ContextId>()
|
||||
|
||||
export class AggregateByTenantContextIdStrategy implements ContextIdStrategy {
|
||||
attach(contextId: ContextId, request: FastifyRequest) {
|
||||
const tenantId = request.headers['x-tenant-id'] as string
|
||||
let tenantSubTreeId: ContextId
|
||||
|
||||
if (tenants.has(tenantId)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
tenantSubTreeId = tenants.get(tenantId)!
|
||||
} else {
|
||||
tenantSubTreeId = ContextIdFactory.create()
|
||||
tenants.set(tenantId, tenantSubTreeId)
|
||||
}
|
||||
|
||||
// If tree is not durable, return the original "contextId" object
|
||||
return (info: HostComponentInfo) =>
|
||||
info.isTreeDurable ? tenantSubTreeId : contextId
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import dayjs from 'dayjs'
|
||||
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common'
|
||||
|
||||
import { RequestContext } from '~/common/contexts/request.context'
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
import { deepCloneWithFunction } from '~/utils'
|
||||
import { safeEval } from '~/utils/safe-eval.util'
|
||||
@@ -74,9 +75,9 @@ export class TextMacroService {
|
||||
// time utils
|
||||
dayjs: deepCloneWithFunction(dayjs),
|
||||
fromNow: (time: Date | string) => dayjs(time).fromNow(),
|
||||
// onlyMe: (text: string) => {
|
||||
// return this.request.isMaster ? text : ''
|
||||
// },
|
||||
onlyMe: (text: string) => {
|
||||
return RequestContext.currentIsMaster() ? text : ''
|
||||
},
|
||||
|
||||
// typography
|
||||
center: (text: string) => {
|
||||
|
||||
@@ -2,8 +2,12 @@ import type { ExecutionContext } from '@nestjs/common'
|
||||
import type { UserModel } from '~/modules/user/user.model'
|
||||
import type { FastifyRequest } from 'fastify'
|
||||
|
||||
export type FastifyBizRequest = FastifyRequest & { user?: UserModel } & Record<
|
||||
string,
|
||||
any
|
||||
>
|
||||
export function getNestExecutionContextRequest(
|
||||
context: ExecutionContext,
|
||||
): FastifyRequest & { user?: UserModel } & Record<string, any> {
|
||||
): FastifyBizRequest {
|
||||
return context.switchToHttp().getRequest<FastifyRequest>()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
|
||||
import { ContextIdFactory } from '@nestjs/core'
|
||||
import { Test } from '@nestjs/testing'
|
||||
|
||||
import { AggregateByTenantContextIdStrategy } from '~/common/strategies/context.strategy'
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
import { TextMacroService } from '~/processors/helper/helper.macro.service'
|
||||
|
||||
@@ -25,7 +23,6 @@ describe('test TextMarcoService', () => {
|
||||
})
|
||||
},
|
||||
})
|
||||
ContextIdFactory.apply(new AggregateByTenantContextIdStrategy())
|
||||
|
||||
const module = await moduleRef.compile()
|
||||
service = await module.resolve(TextMacroService)
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -181,6 +181,9 @@ importers:
|
||||
class-validator-jsonschema:
|
||||
specifier: npm:@innei/class-validator-jsonschema@3.1.2
|
||||
version: /@innei/class-validator-jsonschema@3.1.2(class-transformer@0.5.1)(class-validator@0.13.2)
|
||||
cls-hooked:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
commander:
|
||||
specifier: 11.1.0
|
||||
version: 11.1.0
|
||||
@@ -333,6 +336,9 @@ importers:
|
||||
'@types/cache-manager':
|
||||
specifier: 4.0.6
|
||||
version: 4.0.6
|
||||
'@types/cls-hooked':
|
||||
specifier: ^4.3.8
|
||||
version: 4.3.8
|
||||
'@types/ejs':
|
||||
specifier: 3.1.5
|
||||
version: 3.1.5
|
||||
@@ -2477,6 +2483,12 @@ packages:
|
||||
resolution: {integrity: sha512-gbiHvCuBS9aXkE3OEDfS69bscNLTYtbbx2TQf6WyOu+4eCH1AH1gPSiDGF2UzwkRFAbqKNsC5F0mY0xcaEHCbg==}
|
||||
dev: true
|
||||
|
||||
/@types/cls-hooked@4.3.8:
|
||||
resolution: {integrity: sha512-tf/7H883gFA6MPlWI15EQtfNZ+oPL0gLKkOlx9UHFrun1fC/FkuyNBpTKq1B5E3T4fbvjId6WifHUdSGsMMuPg==}
|
||||
dependencies:
|
||||
'@types/node': 20.10.4
|
||||
dev: true
|
||||
|
||||
/@types/component-emitter@1.2.11:
|
||||
resolution: {integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==}
|
||||
dev: false
|
||||
@@ -3422,6 +3434,13 @@ packages:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
dev: true
|
||||
|
||||
/async-hook-jl@1.7.6:
|
||||
resolution: {integrity: sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==}
|
||||
engines: {node: ^4.7 || >=6.9 || >=7.3}
|
||||
dependencies:
|
||||
stack-chain: 1.3.7
|
||||
dev: false
|
||||
|
||||
/async-mutex@0.4.0:
|
||||
resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==}
|
||||
requiresBuild: true
|
||||
@@ -3878,6 +3897,15 @@ packages:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
/cls-hooked@4.2.2:
|
||||
resolution: {integrity: sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==}
|
||||
engines: {node: ^4.7 || >=6.9 || >=7.3 || >=8.2.1}
|
||||
dependencies:
|
||||
async-hook-jl: 1.7.6
|
||||
emitter-listener: 1.1.2
|
||||
semver: 7.5.4
|
||||
dev: false
|
||||
|
||||
/cluster-key-slot@1.1.0:
|
||||
resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4378,6 +4406,12 @@ packages:
|
||||
/electron-to-chromium@1.4.610:
|
||||
resolution: {integrity: sha512-mqi2oL1mfeHYtOdCxbPQYV/PL7YrQlxbvFEZ0Ee8GbDdShimqt2/S6z2RWqysuvlwdOrQdqvE0KZrBTipAeJzg==}
|
||||
|
||||
/emitter-listener@1.1.2:
|
||||
resolution: {integrity: sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==}
|
||||
dependencies:
|
||||
shimmer: 1.2.1
|
||||
dev: false
|
||||
|
||||
/emoji-regex@10.3.0:
|
||||
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
|
||||
dev: true
|
||||
@@ -8250,6 +8284,10 @@ packages:
|
||||
rechoir: 0.6.2
|
||||
dev: true
|
||||
|
||||
/shimmer@1.2.1:
|
||||
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
|
||||
dev: false
|
||||
|
||||
/side-channel@1.0.4:
|
||||
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
|
||||
dependencies:
|
||||
@@ -8446,6 +8484,10 @@ packages:
|
||||
through: 2.3.8
|
||||
dev: true
|
||||
|
||||
/stack-chain@1.3.7:
|
||||
resolution: {integrity: sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==}
|
||||
dev: false
|
||||
|
||||
/stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
dev: true
|
||||
|
||||
Reference in New Issue
Block a user