refactor: fix type guard (#404)
This commit is contained in:
@@ -17,6 +17,7 @@ module.exports = {
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
useESM: true,
|
||||
tsConfig: './test/tsconfig.json',
|
||||
},
|
||||
isDev: process.env.NODE_ENV === 'development',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
|
||||
import { MiddlewareConsumer, Module, NestModule, Type } from '@nestjs/common'
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'
|
||||
|
||||
import { AppController } from './app.controller'
|
||||
import { AllExceptionsFilter } from './common/filters/any-exception.filter'
|
||||
import { RolesGuard } from './common/guard/roles.guard'
|
||||
@@ -82,8 +83,8 @@ import { LoggerModule } from './processors/logger/logger.module'
|
||||
GatewayModule,
|
||||
HelperModule,
|
||||
|
||||
isDev ? DebugModule : null,
|
||||
].filter(Boolean),
|
||||
isDev ? DebugModule : undefined,
|
||||
].filter(Boolean) as Type<NestModule>[],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import cluster from 'cluster'
|
||||
import { performance } from 'perf_hooks'
|
||||
|
||||
import { Logger, RequestMethod, ValidationPipe } from '@nestjs/common'
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||
|
||||
import { API_VERSION, CROSS_DOMAIN, PORT } from './app.config'
|
||||
import { AppModule } from './app.module'
|
||||
import { fastifyApp } from './common/adapters/fastify.adapter'
|
||||
@@ -11,6 +13,7 @@ import { SpiderGuard } from './common/guard/spider.guard'
|
||||
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
|
||||
import { isTest } from './global/env.global'
|
||||
import { MyLogger } from './processors/logger/logger.service'
|
||||
|
||||
const Origin = Array.isArray(CROSS_DOMAIN.allowedOrigins)
|
||||
? CROSS_DOMAIN.allowedOrigins
|
||||
: false
|
||||
@@ -28,7 +31,7 @@ export async function bootstrap() {
|
||||
|
||||
// Origin 如果不是数组就全部允许跨域
|
||||
app.enableCors(
|
||||
Origin
|
||||
hosts
|
||||
? {
|
||||
origin: (origin, callback) => {
|
||||
const allow = hosts.some((host) => host.test(origin))
|
||||
|
||||
@@ -12,7 +12,7 @@ export class Cluster {
|
||||
process.on('SIGINT', () => {
|
||||
consola.info('Cluster shutting down...')
|
||||
for (const id in cluster.workers) {
|
||||
cluster.workers[id].kill()
|
||||
cluster.workers[id]?.kill()
|
||||
}
|
||||
// exit the master process
|
||||
process.exit(0)
|
||||
@@ -26,9 +26,10 @@ export class Cluster {
|
||||
|
||||
cluster.on('fork', (worker) => {
|
||||
worker.on('message', (msg) => {
|
||||
Object.keys(cluster.workers).forEach((id) => {
|
||||
cluster.workers[id].send(msg)
|
||||
})
|
||||
cluster.workers &&
|
||||
Object.keys(cluster.workers).forEach((id) => {
|
||||
cluster.workers?.[id]?.send(msg)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Logger } from '@nestjs/common'
|
||||
import { FastifyAdapter } from '@nestjs/platform-fastify'
|
||||
import type { FastifyRequest } from 'fastify'
|
||||
import fastifyCookie from 'fastify-cookie'
|
||||
import FastifyMultipart from 'fastify-multipart'
|
||||
|
||||
import { Logger } from '@nestjs/common'
|
||||
import { FastifyAdapter } from '@nestjs/platform-fastify'
|
||||
|
||||
import { getIp } from '~/utils'
|
||||
|
||||
const app: FastifyAdapter = new FastifyAdapter({
|
||||
trustProxy: true,
|
||||
})
|
||||
@@ -35,7 +38,7 @@ app.getInstance().addHook('onRequest', (request, reply, done) => {
|
||||
|
||||
return reply.code(418).send()
|
||||
} else if (url.match(/\/(adminer|admin|wp-login|phpMyAdmin|\.env)$/gi)) {
|
||||
const isMxSpaceClient = ua.match('mx-space')
|
||||
const isMxSpaceClient = ua?.match('mx-space')
|
||||
reply.raw.statusMessage = 'Hey, What the fuck are you doing!'
|
||||
reply.raw.statusCode = isMxSpaceClient ? 666 : 200
|
||||
logWarn(
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { UseGuards, applyDecorators } from '@nestjs/common'
|
||||
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger'
|
||||
import { JWTAuthGuard } from '../guard/auth.guard'
|
||||
|
||||
import { SECURITY } from '~/app.config'
|
||||
|
||||
import { JWTAuthGuard } from '../guard/auth.guard'
|
||||
|
||||
export function Auth() {
|
||||
const decorators = []
|
||||
const decorators: (ClassDecorator | PropertyDecorator | MethodDecorator)[] =
|
||||
[]
|
||||
if (!SECURITY.skipAuth) {
|
||||
decorators.push(UseGuards(JWTAuthGuard))
|
||||
}
|
||||
|
||||
7
src/common/exceptions/master-lost.exception.ts
Normal file
7
src/common/exceptions/master-lost.exception.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { InternalServerErrorException } from '@nestjs/common'
|
||||
|
||||
export class MasterLostException extends InternalServerErrorException {
|
||||
constructor() {
|
||||
super('系统异常,站点主人信息已丢失')
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||
import { WriteStream } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
@@ -10,14 +12,15 @@ import {
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { Reflector } from '@nestjs/core'
|
||||
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||
import { getIp } from '../../utils/ip.util'
|
||||
import { LoggingInterceptor } from '../interceptors/logging.interceptor'
|
||||
|
||||
import { HTTP_REQUEST_TIME } from '~/constants/meta.constant'
|
||||
import { LOG_DIR } from '~/constants/path.constant'
|
||||
import { REFLECTOR } from '~/constants/system.constant'
|
||||
import { isDev } from '~/global/env.global'
|
||||
|
||||
import { getIp } from '../../utils/ip.util'
|
||||
import { LoggingInterceptor } from '../interceptors/logging.interceptor'
|
||||
|
||||
type myError = {
|
||||
readonly status: number
|
||||
readonly statusCode?: number
|
||||
@@ -46,6 +49,9 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
||||
(exception as any)?.response?.message ||
|
||||
(exception as myError)?.message ||
|
||||
''
|
||||
|
||||
const url = request.raw.url!
|
||||
|
||||
if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
|
||||
// message && Logger.debug(message, undefined, 'Catch')
|
||||
Logger.error(exception, undefined, 'Catch')
|
||||
@@ -59,7 +65,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
||||
})
|
||||
|
||||
this.errorLogPipe.write(
|
||||
`[${new Date().toISOString()}] ${decodeURI(request.raw.url)}: ${
|
||||
`[${new Date().toISOString()}] ${decodeURI(url)}: ${
|
||||
(exception as any)?.response?.message ||
|
||||
(exception as myError)?.message
|
||||
}\n${(exception as Error).stack}\n`,
|
||||
@@ -68,9 +74,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
||||
} else {
|
||||
const ip = getIp(request)
|
||||
this.logger.warn(
|
||||
`IP: ${ip} 错误信息: (${status}) ${message} Path: ${decodeURI(
|
||||
request.raw.url,
|
||||
)}`,
|
||||
`IP: ${ip} 错误信息: (${status}) ${message} Path: ${decodeURI(url)}`,
|
||||
)
|
||||
}
|
||||
// @ts-ignore
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
* @module interceptor/analyze
|
||||
* @author Innei <https://github.com/Innei>
|
||||
*/
|
||||
import isbot from 'isbot'
|
||||
import { Observable } from 'rxjs'
|
||||
import UAParser from 'ua-parser-js'
|
||||
import { URL } from 'url'
|
||||
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
@@ -12,16 +16,13 @@ import {
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import isbot from 'isbot'
|
||||
import { Observable } from 'rxjs'
|
||||
import UAParser from 'ua-parser-js'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { AnalyzeModel } from '~/modules/analyze/analyze.model'
|
||||
import { OptionModel } from '~/modules/configs/configs.model'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { getNestExecutionContextRequest } from '~/transformers/get-req.transformer'
|
||||
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { getIp } from '~/utils/ip.util'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
@@ -76,7 +77,8 @@ export class AnalyzeInterceptor implements NestInterceptor {
|
||||
|
||||
process.nextTick(async () => {
|
||||
try {
|
||||
this.parser.setUA(request.headers['user-agent'])
|
||||
request.headers['user-agent'] &&
|
||||
this.parser.setUA(request.headers['user-agent'])
|
||||
|
||||
const ua = this.parser.getResult()
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* 把 URL Search 上的 `token` 附加到 Header Authorization 上
|
||||
* @author Innei <https://innei.ren>
|
||||
*/
|
||||
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common'
|
||||
|
||||
import { parseRelativeUrl } from '~/utils/ip.util'
|
||||
|
||||
@Injectable()
|
||||
@@ -15,7 +16,7 @@ export class AttachHeaderTokenMiddleware implements NestMiddleware {
|
||||
const parser = parseRelativeUrl(url)
|
||||
|
||||
if (parser.searchParams.get('token')) {
|
||||
req.headers.authorization = parser.searchParams.get('token')
|
||||
req.headers.authorization = parser.searchParams.get('token') as string
|
||||
}
|
||||
|
||||
next()
|
||||
|
||||
@@ -3,8 +3,8 @@ export interface RSSProps {
|
||||
url: string
|
||||
author: string
|
||||
data: {
|
||||
created: Date
|
||||
modified: Date
|
||||
created: Date | null
|
||||
modified: Date | null
|
||||
link: string
|
||||
title: string
|
||||
text: string
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { URL } from 'url'
|
||||
import { FilterQuery } from 'mongoose'
|
||||
import { pick } from 'lodash'
|
||||
import dayjs from 'dayjs'
|
||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
||||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { pick } from 'lodash'
|
||||
import { FilterQuery } from 'mongoose'
|
||||
import { URL } from 'url'
|
||||
|
||||
import { Inject, Injectable, forwardRef } from '@nestjs/common'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
||||
|
||||
import { CacheKeys, RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { WebEventsGateway } from '~/processors/gateway/web/events.gateway'
|
||||
import { addYearCondition } from '~/transformers/db-query.transformer'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { getShortDate } from '~/utils/time.util'
|
||||
|
||||
import { CategoryModel } from '../category/category.model'
|
||||
import { CategoryService } from '../category/category.service'
|
||||
import { CommentState } from '../comment/comment.model'
|
||||
@@ -21,13 +31,7 @@ import { RecentlyService } from '../recently/recently.service'
|
||||
import { SayService } from '../say/say.service'
|
||||
import { TimelineType } from './aggregate.dto'
|
||||
import { RSSProps } from './aggregate.interface'
|
||||
import { getShortDate } from '~/utils/time.util'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { addYearCondition } from '~/transformers/db-query.transformer'
|
||||
import { WebEventsGateway } from '~/processors/gateway/web/events.gateway'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { CacheKeys, RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
|
||||
@Injectable()
|
||||
export class AggregateService {
|
||||
constructor(
|
||||
@@ -120,7 +124,11 @@ export class AggregateService {
|
||||
return { notes, posts, says }
|
||||
}
|
||||
|
||||
async getTimeline(year: number, type: TimelineType, sortBy: 1 | -1 = 1) {
|
||||
async getTimeline(
|
||||
year: number | undefined,
|
||||
type: TimelineType | undefined,
|
||||
sortBy: 1 | -1 = 1,
|
||||
) {
|
||||
const data: any = {}
|
||||
const getPosts = () =>
|
||||
this.postService.model
|
||||
@@ -186,7 +194,9 @@ export class AggregateService {
|
||||
.then((list) =>
|
||||
list.map((doc) => ({
|
||||
url: new URL(`/${doc.slug}`, baseURL),
|
||||
published_at: new Date(doc.modified),
|
||||
published_at: doc.modified
|
||||
? new Date(doc.modified)
|
||||
: new Date(doc.created!),
|
||||
})),
|
||||
),
|
||||
|
||||
@@ -207,7 +217,9 @@ export class AggregateService {
|
||||
list.map((doc) => {
|
||||
return {
|
||||
url: new URL(`/notes/${doc.nid}`, baseURL),
|
||||
published_at: new Date(doc.modified),
|
||||
published_at: doc.modified
|
||||
? new Date(doc.modified)
|
||||
: new Date(doc.created!),
|
||||
}
|
||||
}),
|
||||
),
|
||||
@@ -224,7 +236,9 @@ export class AggregateService {
|
||||
`/posts/${(doc.category as CategoryModel).slug}/${doc.slug}`,
|
||||
baseURL,
|
||||
),
|
||||
published_at: new Date(doc.modified),
|
||||
published_at: doc.modified
|
||||
? new Date(doc.modified)
|
||||
: new Date(doc.created!),
|
||||
}
|
||||
}),
|
||||
),
|
||||
@@ -276,7 +290,7 @@ export class AggregateService {
|
||||
return {
|
||||
title: post.title,
|
||||
text: post.text,
|
||||
created: post.created,
|
||||
created: post.created!,
|
||||
modified: post.modified,
|
||||
link: new URL(
|
||||
'/posts' + `/${(post.category as CategoryModel).slug}/${post.slug}`,
|
||||
@@ -291,14 +305,14 @@ export class AggregateService {
|
||||
return {
|
||||
title: note.title,
|
||||
text: isSecret ? '这篇文章暂时没有公开呢' : note.text,
|
||||
created: note.created,
|
||||
created: note.created!,
|
||||
modified: note.modified,
|
||||
link: new URL(`/notes/${note.nid}`, baseURL).toString(),
|
||||
}
|
||||
})
|
||||
return postsRss
|
||||
.concat(notesRss)
|
||||
.sort((a, b) => b.created.getTime() - a.created.getTime())
|
||||
.sort((a, b) => b.created!.getTime() - a.created!.getTime())
|
||||
.slice(0, 10)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Controller, Delete, Get, HttpCode, Query, Scope } from '@nestjs/common'
|
||||
import dayjs from 'dayjs'
|
||||
import { AnalyzeDto } from './analyze.dto'
|
||||
import { AnalyzeService } from './analyze.service'
|
||||
|
||||
import { Controller, Delete, Get, HttpCode, Query, Scope } from '@nestjs/common'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { Paginator } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
@@ -11,6 +11,9 @@ import { PagerDto } from '~/shared/dto/pager.dto'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { getTodayEarly, getWeekStart } from '~/utils/time.util'
|
||||
|
||||
import { AnalyzeDto } from './analyze.dto'
|
||||
import { AnalyzeService } from './analyze.service'
|
||||
|
||||
@Controller({ path: 'analyze', scope: Scope.REQUEST })
|
||||
@ApiName
|
||||
@Auth()
|
||||
@@ -151,7 +154,7 @@ export class AnalyzeController {
|
||||
|
||||
return Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const id = key.split('_').pop()
|
||||
const id = key.split('_').pop()!
|
||||
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
import dayjs from 'dayjs'
|
||||
import { isDate, omit } from 'lodash'
|
||||
import { customAlphabet } from 'nanoid/async'
|
||||
import { TokenDto } from './auth.controller'
|
||||
import { JwtPayload } from './interfaces/jwt-payload.interface'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
|
||||
import { MasterLostException } from '~/common/exceptions/master-lost.exception'
|
||||
import {
|
||||
TokenModel,
|
||||
UserModel as User,
|
||||
UserDocument,
|
||||
} from '~/modules/user/user.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { TokenDto } from './auth.controller'
|
||||
import { JwtPayload } from './interfaces/jwt-payload.interface'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -21,7 +25,11 @@ export class AuthService {
|
||||
) {}
|
||||
|
||||
async signToken(_id: string) {
|
||||
const { authCode } = await this.userModel.findById(_id).select('authCode')
|
||||
const user = await this.userModel.findById(_id).select('authCode')
|
||||
if (!user) {
|
||||
throw new MasterLostException()
|
||||
}
|
||||
const authCode = user.authCode
|
||||
const payload = {
|
||||
_id,
|
||||
authCode,
|
||||
@@ -29,17 +37,27 @@ export class AuthService {
|
||||
|
||||
return this.jwtService.sign(payload)
|
||||
}
|
||||
async verifyPayload(payload: JwtPayload): Promise<UserDocument> {
|
||||
async verifyPayload(payload: JwtPayload): Promise<UserDocument | null> {
|
||||
const user = await this.userModel.findById(payload._id).select('+authCode')
|
||||
|
||||
if (!user) {
|
||||
throw new MasterLostException()
|
||||
}
|
||||
|
||||
return user && user.authCode === payload.authCode ? user : null
|
||||
}
|
||||
private async getAccessTokens(): Promise<DocumentType<TokenModel>[]> {
|
||||
|
||||
private async getAccessTokens() {
|
||||
return (await this.userModel.findOne().select('apiToken').lean())
|
||||
.apiToken as any
|
||||
?.apiToken as TokenModel[] | undefined
|
||||
}
|
||||
async getAllAccessToken() {
|
||||
return (await this.getAccessTokens()).map((token) => ({
|
||||
const tokens = await this.getAccessTokens()
|
||||
if (!tokens) {
|
||||
return []
|
||||
}
|
||||
return tokens.map((token) => ({
|
||||
// @ts-ignore
|
||||
id: token._id,
|
||||
...omit(token, ['_id', '__v', 'token']),
|
||||
})) as any as TokenModel[]
|
||||
@@ -47,7 +65,11 @@ export class AuthService {
|
||||
|
||||
async getTokenSecret(id: string) {
|
||||
const tokens = await this.getAccessTokens()
|
||||
if (!tokens) {
|
||||
return null
|
||||
}
|
||||
// note: _id is ObjectId not equal to string
|
||||
// @ts-ignore
|
||||
return tokens.find((token) => String(token._id) === id)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { existsSync, statSync } from 'fs'
|
||||
import { readFile, readdir, rm, writeFile } from 'fs/promises'
|
||||
import mkdirp from 'mkdirp'
|
||||
import { join, resolve } from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import mkdirp from 'mkdirp'
|
||||
import { quiet } from 'zx-cjs'
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { quiet } from 'zx-cjs'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
|
||||
import { MONGO_DB } from '~/app.config'
|
||||
import { BACKUP_DIR, DATA_DIR } from '~/constants/path.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
@@ -19,6 +20,8 @@ import { EventTypes } from '~/processors/gateway/events.types'
|
||||
import { getMediumDateTime } from '~/utils'
|
||||
import { getFolderSize } from '~/utils/system.util'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
|
||||
@Injectable()
|
||||
export class BackupService {
|
||||
private logger: Logger
|
||||
@@ -37,7 +40,7 @@ export class BackupService {
|
||||
return []
|
||||
}
|
||||
const backupFilenames = await readdir(backupPath)
|
||||
const backups = []
|
||||
const backups: { filename: string; path: string }[] = []
|
||||
|
||||
for (const filename of backupFilenames) {
|
||||
const path = resolve(backupPath, filename)
|
||||
@@ -53,6 +56,7 @@ export class BackupService {
|
||||
backups.map(async (item) => {
|
||||
const { path } = item
|
||||
const size = await getFolderSize(path)
|
||||
// @ts-ignore
|
||||
delete item.path
|
||||
return { ...item, size }
|
||||
}),
|
||||
|
||||
@@ -14,16 +14,7 @@ import {
|
||||
} from '@nestjs/common'
|
||||
import { ApiOperation, ApiParam } from '@nestjs/swagger'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { UserModel } from '../user/user.model'
|
||||
import {
|
||||
CommentDto,
|
||||
CommentRefTypesDto,
|
||||
StateDto,
|
||||
TextOnlyDto,
|
||||
} from './comment.dto'
|
||||
import { CommentFilterEmailInterceptor } from './comment.interceptor'
|
||||
import { CommentModel, CommentState } from './comment.model'
|
||||
import { CommentService } from './comment.service'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { CurrentUser } from '~/common/decorator/current-user.decorator'
|
||||
import { IpLocation, IpRecord } from '~/common/decorator/ip.decorator'
|
||||
@@ -36,6 +27,18 @@ import { ReplyMailType } from '~/processors/helper/helper.email.service'
|
||||
import { MongoIdDto } from '~/shared/dto/id.dto'
|
||||
import { PagerDto } from '~/shared/dto/pager.dto'
|
||||
import { transformDataToPaginate } from '~/transformers/paginate.transformer'
|
||||
|
||||
import { UserModel } from '../user/user.model'
|
||||
import {
|
||||
CommentDto,
|
||||
CommentRefTypesDto,
|
||||
StateDto,
|
||||
TextOnlyDto,
|
||||
} from './comment.dto'
|
||||
import { CommentFilterEmailInterceptor } from './comment.interceptor'
|
||||
import { CommentModel, CommentState } from './comment.model'
|
||||
import { CommentService } from './comment.service'
|
||||
|
||||
@Controller({ path: 'comments' })
|
||||
@UseInterceptors(CommentFilterEmailInterceptor)
|
||||
@ApiName
|
||||
@@ -236,6 +239,7 @@ export class CommentController {
|
||||
url,
|
||||
state: CommentState.Read,
|
||||
} as CommentDto
|
||||
// @ts-ignore
|
||||
return await this.replyByCid(params, model, undefined, true, ipLocation)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Query, Types } from 'mongoose'
|
||||
import { URL } from 'url'
|
||||
|
||||
import {
|
||||
DocumentType,
|
||||
Ref,
|
||||
@@ -7,12 +9,13 @@ import {
|
||||
prop,
|
||||
} from '@typegoose/typegoose'
|
||||
import { BeAnObject } from '@typegoose/typegoose/lib/types'
|
||||
import { Query, Types } from 'mongoose'
|
||||
|
||||
import { BaseModel } from '~/shared/model/base.model'
|
||||
import { getAvatar } from '~/utils'
|
||||
|
||||
import { NoteModel } from '../note/note.model'
|
||||
import { PageModel } from '../page/page.model'
|
||||
import { PostModel } from '../post/post.model'
|
||||
import { getAvatar } from '~/utils'
|
||||
import { BaseModel } from '~/shared/model/base.model'
|
||||
|
||||
function autoPopulateSubs(
|
||||
this: Query<
|
||||
@@ -68,7 +71,7 @@ export class CommentModel extends BaseModel {
|
||||
url?: string
|
||||
|
||||
@prop({ required: true })
|
||||
text!: string
|
||||
text: string
|
||||
|
||||
// 0 : 未读
|
||||
// 1 : 已读
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { LeanDocument, Types } from 'mongoose'
|
||||
import { URL } from 'url'
|
||||
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { BeAnObject } from '@typegoose/typegoose/lib/types'
|
||||
import { LeanDocument, Types } from 'mongoose'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { UserService } from '../user/user.service'
|
||||
import BlockedKeywords from './block-keywords.json'
|
||||
import { CommentModel, CommentRefTypes } from './comment.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { CannotFindException } from '~/common/exceptions/cant-find.exception'
|
||||
import { MasterLostException } from '~/common/exceptions/master-lost.exception'
|
||||
import { DatabaseService } from '~/processors/database/database.service'
|
||||
import {
|
||||
EmailService,
|
||||
ReplyMailType,
|
||||
} from '~/processors/helper/helper.email.service'
|
||||
import { WriteBaseModel } from '~/shared/model/base.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { hasChinese } from '~/utils'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { UserService } from '../user/user.service'
|
||||
import BlockedKeywords from './block-keywords.json'
|
||||
import { CommentModel, CommentRefTypes } from './comment.model'
|
||||
|
||||
@Injectable()
|
||||
export class CommentService {
|
||||
private readonly logger: Logger = new Logger(CommentService.name)
|
||||
@@ -44,7 +49,7 @@ export class CommentService {
|
||||
}
|
||||
}
|
||||
|
||||
async checkSpam(doc: Partial<CommentModel>) {
|
||||
async checkSpam(doc: CommentModel) {
|
||||
const res = await (async () => {
|
||||
const commentOptions = await this.configs.get('commentOptions')
|
||||
if (!commentOptions.antiSpam) {
|
||||
@@ -55,7 +60,11 @@ export class CommentService {
|
||||
return false
|
||||
}
|
||||
if (commentOptions.blockIps) {
|
||||
if (!doc.ip) {
|
||||
return false
|
||||
}
|
||||
const isBlock = commentOptions.blockIps.some((ip) =>
|
||||
// @ts-ignore
|
||||
new RegExp(ip, 'ig').test(doc.ip),
|
||||
)
|
||||
if (isBlock) {
|
||||
@@ -99,13 +108,13 @@ export class CommentService {
|
||||
} else {
|
||||
const { type: type_, document } =
|
||||
await this.databaseService.findGlobalById(id)
|
||||
ref = document
|
||||
ref = document as any
|
||||
type = type_ as any
|
||||
}
|
||||
if (!ref) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
const commentIndex = ref.commentsIndex
|
||||
const commentIndex = ref.commentsIndex || 0
|
||||
doc.key = `#${commentIndex + 1}`
|
||||
const comment = await this.commentModel.create({
|
||||
...doc,
|
||||
@@ -166,9 +175,15 @@ export class CommentService {
|
||||
if (type) {
|
||||
const model = this.getModelByRefType(type)
|
||||
const doc = await model.findById(id)
|
||||
if (!doc) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
return doc.allowComment ?? true
|
||||
} else {
|
||||
const { document: doc } = await this.databaseService.findGlobalById(id)
|
||||
if (!doc) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
return doc.allowComment ?? true
|
||||
}
|
||||
}
|
||||
@@ -201,16 +216,26 @@ export class CommentService {
|
||||
}
|
||||
|
||||
this.userService.model.findOne().then(async (master) => {
|
||||
if (!master) {
|
||||
throw new MasterLostException()
|
||||
}
|
||||
|
||||
const refType = model.refType
|
||||
const refModel = this.getModelByRefType(refType)
|
||||
const refDoc = await refModel.findById(model.ref).lean()
|
||||
const time = new Date(model.created)
|
||||
const parent = await this.commentModel.findOne({ _id: model.parent })
|
||||
const time = new Date(model.created!)
|
||||
const parent = await this.commentModel
|
||||
.findOne({ _id: model.parent })
|
||||
.lean()
|
||||
|
||||
const parsedTime = `${time.getDate()}/${
|
||||
time.getMonth() + 1
|
||||
}/${time.getFullYear()}`
|
||||
|
||||
if (!parent || !parent.mail || !refDoc || !master.mail || !model.mail) {
|
||||
return
|
||||
}
|
||||
|
||||
this.mailService.sendCommentNotificationMail({
|
||||
to: type === ReplyMailType.Owner ? master.mail : parent.mail,
|
||||
type,
|
||||
|
||||
@@ -4,27 +4,21 @@ import { DecoratorSchema } from 'class-validator-jsonschema/build/decorators'
|
||||
export const JSONSchemaPasswordField = (
|
||||
title: string,
|
||||
schema?: DecoratorSchema,
|
||||
): PropertyDecorator =>
|
||||
) =>
|
||||
JSONSchema({
|
||||
title,
|
||||
'ui:options': { showPassword: true },
|
||||
...schema,
|
||||
})
|
||||
|
||||
export const JSONSchemaPlainField = (
|
||||
title: string,
|
||||
schema?: DecoratorSchema,
|
||||
): PropertyDecorator =>
|
||||
export const JSONSchemaPlainField = (title: string, schema?: DecoratorSchema) =>
|
||||
JSONSchema({
|
||||
title,
|
||||
// 'ui:options': {},
|
||||
...schema,
|
||||
})
|
||||
|
||||
export const JSONSchemaArrayField = (
|
||||
title: string,
|
||||
schema?: DecoratorSchema,
|
||||
): PropertyDecorator =>
|
||||
export const JSONSchemaArrayField = (title: string, schema?: DecoratorSchema) =>
|
||||
JSONSchema({
|
||||
title,
|
||||
// 'ui:options': {},
|
||||
@@ -34,7 +28,7 @@ export const JSONSchemaArrayField = (
|
||||
export const JSONSchemaToggleField = (
|
||||
title: string,
|
||||
schema?: DecoratorSchema,
|
||||
): PropertyDecorator =>
|
||||
) =>
|
||||
JSONSchema({
|
||||
title,
|
||||
// 'ui:options': {},
|
||||
@@ -44,7 +38,7 @@ export const JSONSchemaToggleField = (
|
||||
export const JSONSchemaNumberField = (
|
||||
title: string,
|
||||
schema?: DecoratorSchema,
|
||||
): PropertyDecorator =>
|
||||
) =>
|
||||
JSONSchema({
|
||||
title,
|
||||
// 'ui:options': {},
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Severity, modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { Schema } from 'mongoose'
|
||||
|
||||
import { Severity, modelOptions, prop } from '@typegoose/typegoose'
|
||||
|
||||
@modelOptions({
|
||||
options: { allowMixed: Severity.ALLOW, customName: 'Option' },
|
||||
schemaOptions: {
|
||||
timestamps: {
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import camelcaseKeys from 'camelcase-keys'
|
||||
import { ClassConstructor, plainToInstance } from 'class-transformer'
|
||||
import { ValidatorOptions, validateSync } from 'class-validator'
|
||||
import cluster from 'cluster'
|
||||
import { cloneDeep, mergeWith } from 'lodash'
|
||||
import { LeanDocument } from 'mongoose'
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
@@ -8,11 +14,14 @@ import {
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter'
|
||||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
import { BeAnObject } from '@typegoose/typegoose/lib/types'
|
||||
import camelcaseKeys from 'camelcase-keys'
|
||||
import { ClassConstructor, plainToInstance } from 'class-transformer'
|
||||
import { ValidatorOptions, validateSync } from 'class-validator'
|
||||
import { cloneDeep, mergeWith } from 'lodash'
|
||||
import { LeanDocument } from 'mongoose'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { sleep } from '~/utils'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import * as optionDtos from '../configs/configs.dto'
|
||||
import { UserModel } from '../user/user.model'
|
||||
import { UserService } from '../user/user.service'
|
||||
@@ -23,12 +32,6 @@ import {
|
||||
} from './configs.dto'
|
||||
import { IConfig, IConfigKeys } from './configs.interface'
|
||||
import { OptionModel } from './configs.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { sleep } from '~/utils'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
const allOptionKeys: Set<IConfigKeys> = new Set()
|
||||
const map: Record<string, any> = Object.entries(optionDtos).reduce(
|
||||
@@ -66,7 +69,7 @@ const generateDefaultConfig: () => IConfig = () => ({
|
||||
title: 'おかえり~',
|
||||
background:
|
||||
'https://gitee.com/xun7788/my-imagination/raw/master/images/88426823_p0.jpg',
|
||||
gaodemapKey: null,
|
||||
gaodemapKey: null!,
|
||||
},
|
||||
terminalOptions: {
|
||||
enable: false,
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { CacheKey, CacheTTL, Controller, Get, Header } from '@nestjs/common'
|
||||
import { minify } from 'html-minifier'
|
||||
import xss from 'xss'
|
||||
import { AggregateService } from '../aggregate/aggregate.service'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { MarkdownService } from '../markdown/markdown.service'
|
||||
|
||||
import { CacheKey, CacheTTL, Controller, Get, Header } from '@nestjs/common'
|
||||
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { CacheKeys } from '~/constants/cache.constant'
|
||||
|
||||
import { AggregateService } from '../aggregate/aggregate.service'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { MarkdownService } from '../markdown/markdown.service'
|
||||
|
||||
@Controller('feed')
|
||||
@ApiName
|
||||
export class FeedController {
|
||||
@@ -43,7 +46,7 @@ export class FeedController {
|
||||
<lastBuildDate>${now.toISOString()}</lastBuildDate>
|
||||
<language>zh-CN</language>
|
||||
<image>
|
||||
<url>${xss(avatar)}</url>
|
||||
<url>${xss(avatar || '')}</url>
|
||||
<title>${title}</title>
|
||||
<link>${xss(url)}</link>
|
||||
</image>
|
||||
|
||||
@@ -19,8 +19,7 @@ export class LogQueryDto {
|
||||
index: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
filename?: string
|
||||
filename: string
|
||||
}
|
||||
|
||||
export class LogTypeDto {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { LinkModel, LinkState, LinkType } from './link.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common'
|
||||
|
||||
import { isDev } from '~/global/env.global'
|
||||
import { AdminEventsGateway } from '~/processors/gateway/admin/events.gateway'
|
||||
import { EventTypes } from '~/processors/gateway/events.types'
|
||||
@@ -10,6 +13,10 @@ import {
|
||||
LinkApplyEmailType,
|
||||
} from '~/processors/helper/helper.email.service'
|
||||
import { HttpService } from '~/processors/helper/helper.http.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { LinkModel, LinkState, LinkType } from './link.model'
|
||||
|
||||
@Injectable()
|
||||
export class LinkService {
|
||||
@@ -51,6 +58,10 @@ export class LinkService {
|
||||
)
|
||||
.lean()
|
||||
|
||||
if (!doc) {
|
||||
throw new NotFoundException()
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import dayjs from 'dayjs'
|
||||
import { render } from 'ejs'
|
||||
import { minify } from 'html-minifier'
|
||||
import JSZip from 'jszip'
|
||||
import { isNil } from 'lodash'
|
||||
import { join } from 'path'
|
||||
import { performance } from 'perf_hooks'
|
||||
import { Readable } from 'stream'
|
||||
import { URL } from 'url'
|
||||
import xss from 'xss'
|
||||
|
||||
import {
|
||||
Body,
|
||||
CacheTTL,
|
||||
@@ -14,12 +22,16 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common'
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import dayjs from 'dayjs'
|
||||
import { render } from 'ejs'
|
||||
import { minify } from 'html-minifier'
|
||||
import JSZip from 'jszip'
|
||||
import { isNil } from 'lodash'
|
||||
import xss from 'xss'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { HttpCache } from '~/common/decorator/cache.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
import { ArticleTypeEnum } from '~/constants/article.constant'
|
||||
import { MongoIdDto } from '~/shared/dto/id.dto'
|
||||
import { getShortDateTime } from '~/utils'
|
||||
|
||||
import { CategoryModel } from '../category/category.model'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { NoteModel } from '../note/note.model'
|
||||
@@ -32,14 +44,6 @@ import {
|
||||
} from './markdown.dto'
|
||||
import { MarkdownYAMLProperty } from './markdown.interface'
|
||||
import { MarkdownService } from './markdown.service'
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { HttpCache } from '~/common/decorator/cache.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
import { ArticleTypeEnum } from '~/constants/article.constant'
|
||||
import { MongoIdDto } from '~/shared/dto/id.dto'
|
||||
import { getShortDateTime } from '~/utils'
|
||||
|
||||
@Controller('markdown')
|
||||
@ApiName
|
||||
@@ -80,7 +84,7 @@ export class MarkdownController {
|
||||
T extends {
|
||||
text: string
|
||||
created?: Date
|
||||
modified: Date
|
||||
modified?: Date | null
|
||||
title: string
|
||||
slug?: string
|
||||
},
|
||||
@@ -89,7 +93,7 @@ export class MarkdownController {
|
||||
extraMetaData: Record<string, any> = {},
|
||||
): MarkdownYAMLProperty => {
|
||||
const meta = {
|
||||
created: item.created,
|
||||
created: item.created!,
|
||||
modified: item.modified,
|
||||
title: item.title,
|
||||
slug: item.slug || item.title,
|
||||
@@ -106,14 +110,14 @@ export class MarkdownController {
|
||||
}
|
||||
// posts
|
||||
const convertPost = posts.map((post) =>
|
||||
convertor(post, {
|
||||
convertor(post!, {
|
||||
categories: (post.category as CategoryModel).name,
|
||||
type: 'post',
|
||||
permalink: `posts/${post.slug}`,
|
||||
}),
|
||||
)
|
||||
const convertNote = notes.map((note) =>
|
||||
convertor(note, {
|
||||
convertor(note!, {
|
||||
mood: note.mood,
|
||||
weather: note.weather,
|
||||
id: note.nid,
|
||||
@@ -123,7 +127,7 @@ export class MarkdownController {
|
||||
}),
|
||||
)
|
||||
const convertPage = pages.map((page) =>
|
||||
convertor(page, {
|
||||
convertor(page!, {
|
||||
subtitle: page.subtitle,
|
||||
type: 'page',
|
||||
permalink: page.slug,
|
||||
@@ -206,7 +210,7 @@ export class MarkdownController {
|
||||
}
|
||||
})()
|
||||
|
||||
const url = new URL(relativePath, webUrl)
|
||||
const url = new URL(relativePath!, webUrl)
|
||||
|
||||
const structure = await this.service.getRenderedMarkdownHtmlStructure(
|
||||
markdown,
|
||||
@@ -271,6 +275,7 @@ export class MarkdownController {
|
||||
async getRenderedMarkdownHtmlStructure(@Param() params: MongoIdDto) {
|
||||
const { id } = params
|
||||
const { html, document } = await this.service.renderArticle(id)
|
||||
|
||||
return this.service.getRenderedMarkdownHtmlStructure(html, document.title)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type MetaType = {
|
||||
created: Date
|
||||
modified: Date
|
||||
created?: Date | null | undefined
|
||||
modified?: Date | null | undefined
|
||||
title: string
|
||||
slug: string
|
||||
} & Record<string, any>
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import { dump } from 'js-yaml'
|
||||
import JSZip from 'jszip'
|
||||
import { omit } from 'lodash'
|
||||
import { marked } from 'marked'
|
||||
import { Types } from 'mongoose'
|
||||
import xss from 'xss'
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
|
||||
import { DatabaseService } from '~/processors/database/database.service'
|
||||
import { AssetService } from '~/processors/helper/helper.asset.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { CategoryModel } from '../category/category.model'
|
||||
import { NoteModel } from '../note/note.model'
|
||||
import { PageModel } from '../page/page.model'
|
||||
import { PostModel } from '../post/post.model'
|
||||
import { DatatypeDto } from './markdown.dto'
|
||||
import { MarkdownYAMLProperty } from './markdown.interface'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { AssetService } from '~/processors/helper/helper.asset.service'
|
||||
import { DatabaseService } from '~/processors/database/database.service'
|
||||
|
||||
@Injectable()
|
||||
export class MarkdownService {
|
||||
constructor(
|
||||
@@ -40,7 +49,7 @@ export class MarkdownService {
|
||||
},
|
||||
)
|
||||
|
||||
const insertOrCreateCategory = async (name: string) => {
|
||||
const insertOrCreateCategory = async (name?: string) => {
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
@@ -70,7 +79,9 @@ export class MarkdownService {
|
||||
const genDate = this.genDate
|
||||
const models = [] as PostModel[]
|
||||
const defaultCategory = await this.categoryModel.findOne()
|
||||
|
||||
if (!defaultCategory) {
|
||||
throw new InternalServerErrorException('分类不存在')
|
||||
}
|
||||
for await (const item of data) {
|
||||
if (!item.meta) {
|
||||
models.push({
|
||||
@@ -214,6 +225,7 @@ ${text.trim()}
|
||||
return {
|
||||
html: this.renderMarkdownContent(doc.document.text),
|
||||
...doc,
|
||||
document: doc.document,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +243,7 @@ ${text.trim()}
|
||||
level: 'inline',
|
||||
name: 'spoiler',
|
||||
start(src) {
|
||||
return src.match(/\|/)?.index
|
||||
return src.match(/\|/)?.index ?? -1
|
||||
},
|
||||
renderer(token) {
|
||||
// @ts-ignore
|
||||
@@ -257,7 +269,7 @@ ${text.trim()}
|
||||
level: 'inline',
|
||||
name: 'mention',
|
||||
start(src) {
|
||||
return src.match(/\(/)?.index
|
||||
return src.match(/\(/)?.index ?? -1
|
||||
},
|
||||
renderer(token) {
|
||||
// @ts-ignore
|
||||
@@ -281,6 +293,10 @@ ${text.trim()}
|
||||
|
||||
renderer: {
|
||||
image(src, title, _alt) {
|
||||
if (typeof src !== 'string') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const alt = _alt?.match(/^[!¡]/) ? _alt.replace(/^[¡!]/, '') : ''
|
||||
if (!alt) {
|
||||
return `<img src="${xss(src)}"/>`
|
||||
|
||||
@@ -13,14 +13,7 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common'
|
||||
import { ApiOperation } from '@nestjs/swagger'
|
||||
import {
|
||||
ListQueryDto,
|
||||
NidType,
|
||||
NoteQueryDto,
|
||||
PasswordQueryDto,
|
||||
} from './note.dto'
|
||||
import { NoteModel, PartialNoteModel } from './note.model'
|
||||
import { NoteService } from './note.service'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { Paginator } from '~/common/decorator/http.decorator'
|
||||
import { IpLocation, IpRecord } from '~/common/decorator/ip.decorator'
|
||||
@@ -35,6 +28,15 @@ import {
|
||||
addYearCondition,
|
||||
} from '~/transformers/db-query.transformer'
|
||||
|
||||
import {
|
||||
ListQueryDto,
|
||||
NidType,
|
||||
NoteQueryDto,
|
||||
PasswordQueryDto,
|
||||
} from './note.dto'
|
||||
import { NoteModel, PartialNoteModel } from './note.model'
|
||||
import { NoteService } from './note.service'
|
||||
|
||||
@ApiName
|
||||
@Controller({ path: 'notes' })
|
||||
export class NoteController {
|
||||
@@ -221,7 +223,7 @@ export class NoteController {
|
||||
) {
|
||||
const id =
|
||||
typeof param.id === 'number'
|
||||
? (await this.noteService.model.findOne({ nid: param.id }).lean())._id
|
||||
? (await this.noteService.model.findOne({ nid: param.id }).lean())?._id
|
||||
: param.id
|
||||
if (!id) {
|
||||
throw new CannotFindException()
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { isDefined, isMongoId } from 'class-validator'
|
||||
import { FilterQuery } from 'mongoose'
|
||||
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { isDefined, isMongoId } from 'class-validator'
|
||||
import { FilterQuery } from 'mongoose'
|
||||
import { NoteModel } from './note.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { CannotFindException } from '~/common/exceptions/cant-find.exception'
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { EventTypes } from '~/processors/gateway/events.types'
|
||||
import { WebEventsGateway } from '~/processors/gateway/web/events.gateway'
|
||||
import { ImageService } from '~/processors/helper/helper.image.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { deleteKeys } from '~/utils'
|
||||
|
||||
import { NoteModel } from './note.model'
|
||||
|
||||
@Injectable()
|
||||
export class NoteService {
|
||||
constructor(
|
||||
@@ -67,7 +70,7 @@ export class NoteService {
|
||||
|
||||
checkPasswordToAccess<T extends NoteModel>(
|
||||
doc: T,
|
||||
password: string,
|
||||
password?: string,
|
||||
): boolean {
|
||||
const hasPassword = doc.password
|
||||
if (!hasPassword) {
|
||||
@@ -113,6 +116,9 @@ export class NoteService {
|
||||
await Promise.all([
|
||||
this.imageService.recordImageDimensions(this.noteModel, id),
|
||||
this.model.findById(id).then((doc) => {
|
||||
if (!doc) {
|
||||
return
|
||||
}
|
||||
delete doc.password
|
||||
this.webGateway.broadcast(EventTypes.NOTE_UPDATE, doc)
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
import { modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { Transform } from 'class-transformer'
|
||||
import {
|
||||
IsEnum,
|
||||
@@ -9,8 +7,13 @@ import {
|
||||
IsString,
|
||||
Min,
|
||||
} from 'class-validator'
|
||||
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
import { modelOptions, prop } from '@typegoose/typegoose'
|
||||
|
||||
import { WriteBaseModel } from '~/shared/model/base.model'
|
||||
import { IsNilOrString } from '~/utils/validator/isNilOrString'
|
||||
|
||||
export enum PageType {
|
||||
'md' = 'md',
|
||||
'html' = 'html',
|
||||
@@ -28,7 +31,7 @@ export class PageModel extends WriteBaseModel {
|
||||
@IsNotEmpty()
|
||||
slug!: string
|
||||
|
||||
@prop({ trim: true })
|
||||
@prop({ trim: true, type: String })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsNilOrString()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { URL } from 'url'
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common'
|
||||
import jsdom from 'jsdom'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { InitService } from '../init/init.service'
|
||||
import { URL } from 'url'
|
||||
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common'
|
||||
|
||||
import PKG from '~/../package.json'
|
||||
import { API_VERSION } from '~/app.config'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { InitService } from '../init/init.service'
|
||||
|
||||
@Injectable()
|
||||
export class PageProxyService {
|
||||
constructor(
|
||||
@@ -39,7 +43,7 @@ export class PageProxyService {
|
||||
from?: string
|
||||
BASE_API?: string
|
||||
GATEWAY?: string
|
||||
[key: string]: string
|
||||
[key: string]: string | undefined
|
||||
},
|
||||
) {
|
||||
const config = await this.configs.waitForConfigReady()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { PartialType } from '@nestjs/swagger'
|
||||
import { modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { Transform } from 'class-transformer'
|
||||
import { IsOptional, IsString, IsUrl, isURL } from 'class-validator'
|
||||
|
||||
import { PartialType } from '@nestjs/swagger'
|
||||
import { modelOptions, prop } from '@typegoose/typegoose'
|
||||
|
||||
import { BaseModel } from '~/shared/model/base.model'
|
||||
|
||||
const validateURL = {
|
||||
@@ -16,6 +18,7 @@ const validateURL = {
|
||||
if (!isURL(v, { require_protocol: true })) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { getRedisKey } from '~/utils'
|
||||
@@ -18,7 +19,8 @@ export class PTYService {
|
||||
)
|
||||
|
||||
return values
|
||||
.map((value) => {
|
||||
.filter(Boolean)
|
||||
.map((value: string) => {
|
||||
const [startTime, ip, endTime] = value.split(',') as [
|
||||
string,
|
||||
string,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import algoliasearch from 'algoliasearch'
|
||||
|
||||
import type { SearchResponse } from '@algolia/client-search'
|
||||
import {
|
||||
BadRequestException,
|
||||
@@ -5,15 +7,16 @@ import {
|
||||
Injectable,
|
||||
forwardRef,
|
||||
} from '@nestjs/common'
|
||||
import algoliasearch from 'algoliasearch'
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { NoteService } from '../note/note.service'
|
||||
import { PostService } from '../post/post.service'
|
||||
|
||||
import { SearchDto } from '~/modules/search/search.dto'
|
||||
import { DatabaseService } from '~/processors/database/database.service'
|
||||
import { Pagination } from '~/shared/interface/paginator.interface'
|
||||
import { transformDataToPaginate } from '~/transformers/paginate.transformer'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { NoteService } from '../note/note.service'
|
||||
import { PostService } from '../post/post.service'
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
constructor(
|
||||
@@ -84,6 +87,13 @@ export class SearchService {
|
||||
if (!algoliaSearchOptions.enable) {
|
||||
throw new BadRequestException('algolia not enable.')
|
||||
}
|
||||
if (
|
||||
!algoliaSearchOptions.appId ||
|
||||
!algoliaSearchOptions.apiKey ||
|
||||
!algoliaSearchOptions.indexName
|
||||
) {
|
||||
throw new BadRequestException('algolia not config.')
|
||||
}
|
||||
const client = algoliasearch(
|
||||
algoliaSearchOptions.appId,
|
||||
algoliaSearchOptions.apiKey,
|
||||
@@ -128,7 +138,7 @@ export class SearchService {
|
||||
if (searchOption.rawAlgolia) {
|
||||
return search
|
||||
}
|
||||
const data = []
|
||||
const data: any[] = []
|
||||
const tasks = search.hits.map((hit) => {
|
||||
const { type, objectID } = hit
|
||||
|
||||
@@ -141,8 +151,10 @@ export class SearchService {
|
||||
.select('_id title created modified categoryId slug nid')
|
||||
.lean()
|
||||
.then((doc) => {
|
||||
Reflect.set(doc, 'type', type)
|
||||
doc && data.push(doc)
|
||||
if (doc) {
|
||||
Reflect.set(doc, 'type', type)
|
||||
data.push(doc)
|
||||
}
|
||||
})
|
||||
})
|
||||
await Promise.all(tasks)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { isURL } from 'class-validator'
|
||||
import fs, { mkdir, stat } from 'fs/promises'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import path from 'path'
|
||||
import { nextTick } from 'process'
|
||||
|
||||
import { TransformOptions, parseAsync, transformAsync } from '@babel/core'
|
||||
import * as t from '@babel/types'
|
||||
import { VariableDeclaration } from '@babel/types'
|
||||
@@ -10,14 +13,7 @@ import {
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { Interval } from '@nestjs/schedule'
|
||||
import { isURL } from 'class-validator'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import PKG from '../../../package.json'
|
||||
import { SnippetModel } from '../snippet/snippet.model'
|
||||
import {
|
||||
FunctionContextRequest,
|
||||
FunctionContextResponse,
|
||||
} from './function.types'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { DATA_DIR, NODE_REQUIRE_PATH } from '~/constants/path.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
@@ -29,6 +25,14 @@ import { UniqueArray } from '~/ts-hepler/unique'
|
||||
import { getRedisKey, safePathJoin } from '~/utils'
|
||||
import { safeEval } from '~/utils/safe-eval.util'
|
||||
import { isBuiltinModule } from '~/utils/sys.util'
|
||||
|
||||
import PKG from '../../../package.json'
|
||||
import { SnippetModel } from '../snippet/snippet.model'
|
||||
import {
|
||||
FunctionContextRequest,
|
||||
FunctionContextResponse,
|
||||
} from './function.types'
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessService {
|
||||
constructor(
|
||||
@@ -253,6 +257,9 @@ export class ServerlessService {
|
||||
}
|
||||
private convertTypescriptCode(code: string) {
|
||||
return transformAsync(code, this.getBabelOptions()).then((res) => {
|
||||
if (!res) {
|
||||
throw new InternalServerErrorException('convert code error')
|
||||
}
|
||||
if (isDev) {
|
||||
console.log(res.code)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { CacheKey, CacheTTL, Controller, Get, Header } from '@nestjs/common'
|
||||
import { minify } from 'html-minifier'
|
||||
import { AggregateService } from '../aggregate/aggregate.service'
|
||||
|
||||
import { CacheKey, CacheTTL, Controller, Get, Header } from '@nestjs/common'
|
||||
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { CacheKeys } from '~/constants/cache.constant'
|
||||
|
||||
import { AggregateService } from '../aggregate/aggregate.service'
|
||||
|
||||
@Controller('sitemap')
|
||||
@ApiName
|
||||
export class SitemapController {
|
||||
@@ -24,7 +28,7 @@ export class SitemapController {
|
||||
.map(
|
||||
(item) => `<url>
|
||||
<loc>${item.url}</loc>
|
||||
<lastmod>${item.published_at.toISOString()}</lastmod>
|
||||
<lastmod>${item.published_at?.toISOString() || 'N/A'}</lastmod>
|
||||
</url>`,
|
||||
)
|
||||
.join('')}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { load } from 'js-yaml'
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Inject,
|
||||
@@ -5,14 +7,15 @@ import {
|
||||
NotFoundException,
|
||||
forwardRef,
|
||||
} from '@nestjs/common'
|
||||
import { load } from 'js-yaml'
|
||||
import { ServerlessService } from '../serverless/serverless.service'
|
||||
import { SnippetModel, SnippetType } from './snippet.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { getRedisKey } from '~/utils'
|
||||
|
||||
import { ServerlessService } from '../serverless/serverless.service'
|
||||
import { SnippetModel, SnippetType } from './snippet.model'
|
||||
|
||||
@Injectable()
|
||||
export class SnippetService {
|
||||
constructor(
|
||||
@@ -58,6 +61,9 @@ export class SnippetService {
|
||||
|
||||
async delete(id: string) {
|
||||
const doc = await this.model.findOneAndDelete({ _id: id }).lean()
|
||||
if (!doc) {
|
||||
throw new NotFoundException()
|
||||
}
|
||||
await this.deleteCachedSnippet(doc.reference, doc.name)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { hashSync } from 'bcrypt'
|
||||
import { Schema } from 'mongoose'
|
||||
|
||||
import {
|
||||
DocumentType,
|
||||
Severity,
|
||||
modelOptions,
|
||||
prop,
|
||||
} from '@typegoose/typegoose'
|
||||
import { hashSync } from 'bcrypt'
|
||||
import { Schema } from 'mongoose'
|
||||
|
||||
import { BaseModel } from '~/shared/model/base.model'
|
||||
|
||||
export type UserDocument = DocumentType<UserModel>
|
||||
@@ -58,7 +60,7 @@ export class UserModel extends BaseModel {
|
||||
password!: string
|
||||
|
||||
@prop()
|
||||
mail?: string
|
||||
mail: string
|
||||
|
||||
@prop()
|
||||
url?: string
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { compareSync } from 'bcrypt'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import { compareSync } from 'bcrypt'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { AuthService } from '../auth/auth.service'
|
||||
import { UserDocument, UserModel } from './user.model'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { MasterLostException } from '~/common/exceptions/master-lost.exception'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { getAvatar, sleep } from '~/utils'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import { AuthService } from '../auth/auth.service'
|
||||
import { UserDocument, UserModel } from './user.model'
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
private Logger = new Logger(UserService.name)
|
||||
@@ -97,6 +103,11 @@ export class UserService {
|
||||
const currentUser = await this.userModel
|
||||
.findById(_id)
|
||||
.select('+password +apiToken')
|
||||
|
||||
if (!currentUser) {
|
||||
throw new MasterLostException()
|
||||
}
|
||||
|
||||
// 1. 验证新旧密码是否一致
|
||||
const isSamePassword = compareSync(password, currentUser.password)
|
||||
if (isSamePassword) {
|
||||
@@ -119,6 +130,9 @@ export class UserService {
|
||||
*/
|
||||
async recordFootstep(ip: string): Promise<Record<string, Date | string>> {
|
||||
const master = await this.userModel.findOne()
|
||||
if (!master) {
|
||||
throw new MasterLostException()
|
||||
}
|
||||
const PrevFootstep = {
|
||||
lastLoginTime: master.lastLoginTime || new Date(1586090559569),
|
||||
lastLoginIp: master.lastLoginIp || null,
|
||||
@@ -138,7 +152,7 @@ export class UserService {
|
||||
})
|
||||
|
||||
this.Logger.warn(`主人已登录, IP: ${ip}`)
|
||||
return PrevFootstep
|
||||
return PrevFootstep as any
|
||||
}
|
||||
|
||||
// TODO 获取最近登陆次数 时间 从 Redis 取
|
||||
|
||||
7
src/processors/cache/cache.config.service.ts
vendored
7
src/processors/cache/cache.config.service.ts
vendored
@@ -4,14 +4,15 @@
|
||||
* @module processor/cache/config.service
|
||||
* @author Surmon <https://github.com/surmon-china>
|
||||
*/
|
||||
import redisStore from 'cache-manager-ioredis'
|
||||
import IORedis from 'ioredis'
|
||||
|
||||
import {
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
Injectable,
|
||||
} from '@nestjs/common'
|
||||
import redisStore from 'cache-manager-ioredis'
|
||||
import IORedis from 'ioredis'
|
||||
|
||||
import { REDIS } from '~/app.config'
|
||||
|
||||
@Injectable()
|
||||
@@ -27,7 +28,7 @@ export class CacheConfigService implements CacheOptionsFactory {
|
||||
}
|
||||
return {
|
||||
store: redisStore,
|
||||
ttl: REDIS.ttl,
|
||||
ttl: REDIS.ttl ?? undefined,
|
||||
// https://github.com/dabroek/node-cache-manager-redis-store/blob/master/CHANGELOG.md#breaking-changes
|
||||
// Any value (undefined | null) return true (cacheable) after redisStore v2.0.0
|
||||
is_cacheable_value: () => true,
|
||||
|
||||
9
src/processors/cache/cache.service.ts
vendored
9
src/processors/cache/cache.service.ts
vendored
@@ -1,14 +1,17 @@
|
||||
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common'
|
||||
import { Cache } from 'cache-manager'
|
||||
import { Redis } from 'ioredis'
|
||||
import type { RedisSubPub } from '../../utils/redis-subpub.util'
|
||||
|
||||
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common'
|
||||
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import type { RedisSubPub } from '../../utils/redis-subpub.util'
|
||||
|
||||
// Cache 客户端管理器
|
||||
|
||||
// 获取器
|
||||
export type TCacheKey = string
|
||||
export type TCacheResult<T> = Promise<T>
|
||||
export type TCacheResult<T> = Promise<T | undefined>
|
||||
|
||||
/**
|
||||
* @class CacheService
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Namespace, Socket } from 'socket.io'
|
||||
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import {
|
||||
@@ -5,12 +7,13 @@ import {
|
||||
OnGatewayDisconnect,
|
||||
WebSocketServer,
|
||||
} from '@nestjs/websockets'
|
||||
import { Namespace, Socket } from 'socket.io'
|
||||
import { BaseGateway } from '../base.gateway'
|
||||
import { EventTypes } from '../events.types'
|
||||
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { AuthService } from '~/modules/auth/auth.service'
|
||||
|
||||
import { BaseGateway } from '../base.gateway'
|
||||
import { EventTypes } from '../events.types'
|
||||
|
||||
export abstract class AuthGateway
|
||||
extends BaseGateway
|
||||
implements OnGatewayConnection, OnGatewayDisconnect
|
||||
@@ -56,7 +59,9 @@ export abstract class AuthGateway
|
||||
async handleConnection(client: Socket) {
|
||||
const token =
|
||||
client.handshake.query.token || client.handshake.headers['authorization']
|
||||
|
||||
if (!token) {
|
||||
return this.authFailed(client)
|
||||
}
|
||||
if (!(await this.authToken(token as string))) {
|
||||
return this.authFailed(client)
|
||||
}
|
||||
@@ -76,7 +81,9 @@ export abstract class AuthGateway
|
||||
handleTokenExpired(token: string) {
|
||||
const server = this.namespace.server
|
||||
const sid = this.tokenSocketIdMap.get(token)
|
||||
|
||||
if (!sid) {
|
||||
return false
|
||||
}
|
||||
const socket = server.of('/admin').sockets.get(sid)
|
||||
if (socket) {
|
||||
socket.disconnect()
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { plainToClass } from 'class-transformer'
|
||||
import { validate } from 'class-validator'
|
||||
import SocketIO from 'socket.io'
|
||||
|
||||
import {
|
||||
ConnectedSocket,
|
||||
GatewayMetadata,
|
||||
@@ -9,17 +14,16 @@ import {
|
||||
WebSocketServer,
|
||||
} from '@nestjs/websockets'
|
||||
import { Emitter } from '@socket.io/redis-emitter'
|
||||
import { plainToClass } from 'class-transformer'
|
||||
import { validate } from 'class-validator'
|
||||
import SocketIO from 'socket.io'
|
||||
import { BaseGateway } from '../base.gateway'
|
||||
import { EventTypes } from '../events.types'
|
||||
import { DanmakuDto } from './dtos/danmaku.dto'
|
||||
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { getShortDate } from '~/utils/time.util'
|
||||
|
||||
import { BaseGateway } from '../base.gateway'
|
||||
import { EventTypes } from '../events.types'
|
||||
import { DanmakuDto } from './dtos/danmaku.dto'
|
||||
|
||||
@WebSocketGateway<GatewayMetadata>({
|
||||
namespace: 'web',
|
||||
})
|
||||
@@ -72,14 +76,15 @@ export class WebEventsGateway
|
||||
+(await redisClient.hget(
|
||||
getRedisKey(RedisKeys.MaxOnlineCount),
|
||||
dateFormat,
|
||||
)) || 0
|
||||
))! || 0
|
||||
await redisClient.hset(
|
||||
getRedisKey(RedisKeys.MaxOnlineCount),
|
||||
dateFormat,
|
||||
Math.max(maxOnlineCount, await this.getcurrentClientCount()),
|
||||
)
|
||||
const key = getRedisKey(RedisKeys.MaxOnlineCount, 'total')
|
||||
const totalCount = +(await redisClient.hget(key, dateFormat)) || 0
|
||||
|
||||
const totalCount = +(await redisClient.hget(key, dateFormat))! || 0
|
||||
await redisClient.hset(key, dateFormat, totalCount + 1)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import cluster from 'cluster'
|
||||
import COS from 'cos-nodejs-sdk-v5'
|
||||
import dayjs from 'dayjs'
|
||||
import { existsSync } from 'fs'
|
||||
import { readdir, rm } from 'fs/promises'
|
||||
import mkdirp from 'mkdirp'
|
||||
import { join } from 'path'
|
||||
|
||||
import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { Cron, CronExpression } from '@nestjs/schedule'
|
||||
import COS from 'cos-nodejs-sdk-v5'
|
||||
import dayjs from 'dayjs'
|
||||
import mkdirp from 'mkdirp'
|
||||
import { CacheService } from '../cache/cache.service'
|
||||
import { HttpService } from './helper.http.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import { isMainCluster } from '~/app.config'
|
||||
import { CronDescription } from '~/common/decorator/cron-description.decorator'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
@@ -24,8 +23,12 @@ import { NoteService } from '~/modules/note/note.service'
|
||||
import { PageService } from '~/modules/page/page.service'
|
||||
import { PostService } from '~/modules/post/post.service'
|
||||
import { SearchService } from '~/modules/search/search.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import { CacheService } from '../cache/cache.service'
|
||||
import { HttpService } from './helper.http.service'
|
||||
|
||||
@Injectable()
|
||||
export class CronService {
|
||||
private logger: Logger
|
||||
@@ -65,7 +68,7 @@ export class CronService {
|
||||
}
|
||||
const originMethod = this[name]
|
||||
this[name] = (...args) => {
|
||||
if (cluster.worker.id === 1 || isMainCluster) {
|
||||
if (cluster.worker?.id === 1 || isMainCluster) {
|
||||
originMethod.call(this, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import cluster from 'cluster'
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { render } from 'ejs'
|
||||
import { createTransport } from 'nodemailer'
|
||||
import { CacheService } from '../cache/cache.service'
|
||||
import { AssetService } from './helper.asset.service'
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
|
||||
import { EventBusEvents } from '~/constants/event.constant'
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
import { LinkModel } from '~/modules/link/link.model'
|
||||
|
||||
import { CacheService } from '../cache/cache.service'
|
||||
import { AssetService } from './helper.asset.service'
|
||||
|
||||
export enum ReplyMailType {
|
||||
Owner = 'owner',
|
||||
Guest = 'guest',
|
||||
@@ -118,9 +124,10 @@ export class EmailService {
|
||||
this.logger.error(message)
|
||||
return j(message)
|
||||
}
|
||||
// @ts-ignore
|
||||
r({
|
||||
host: options?.host,
|
||||
port: +options?.port || 465,
|
||||
port: +options?.port! || 465,
|
||||
auth: { user, pass },
|
||||
} as const)
|
||||
})
|
||||
@@ -203,6 +210,7 @@ export class EmailService {
|
||||
},
|
||||
}
|
||||
if (isDev) {
|
||||
// @ts-ignore
|
||||
delete options.html
|
||||
Object.assign(options, { source })
|
||||
this.logger.log(options)
|
||||
@@ -219,6 +227,7 @@ export class EmailService {
|
||||
},
|
||||
}
|
||||
if (isDev) {
|
||||
// @ts-ignore
|
||||
delete options.html
|
||||
Object.assign(options, { source })
|
||||
this.logger.log(options)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import imageSize from 'image-size'
|
||||
import { HttpService } from './helper.http.service'
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
import { TextImageRecordType, WriteBaseModel } from '~/shared/model/base.model'
|
||||
import { getAverageRGB, pickImagesFromMarkdown } from '~/utils/pic.util'
|
||||
|
||||
import { HttpService } from './helper.http.service'
|
||||
|
||||
@Injectable()
|
||||
export class ImageService {
|
||||
private logger: Logger
|
||||
@@ -22,6 +29,11 @@ export class ImageService {
|
||||
) {
|
||||
const model = _model as any as ReturnModelType<typeof WriteBaseModel>
|
||||
const document = await model.findById(id).lean()
|
||||
if (!document) {
|
||||
throw new InternalServerErrorException(
|
||||
`document not found, can not record image dimensions`,
|
||||
)
|
||||
}
|
||||
const { text } = document
|
||||
const newImages = pickImagesFromMarkdown(text)
|
||||
|
||||
@@ -81,7 +93,7 @@ export class ImageService {
|
||||
await model.updateOne(
|
||||
{ _id: id },
|
||||
// 过滤多余的
|
||||
{ images: result.filter(({ src }) => newImages.includes(src)) },
|
||||
{ images: result.filter(({ src }) => newImages.includes(src!)) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable prefer-rest-params */
|
||||
import cluster from 'cluster'
|
||||
import { performance } from 'perf_hooks'
|
||||
|
||||
import { ConsoleLogger, ConsoleLoggerOptions } from '@nestjs/common'
|
||||
|
||||
export class MyLogger extends ConsoleLogger {
|
||||
constructor(context?: string, options?: ConsoleLoggerOptions) {
|
||||
constructor(context: string, options: ConsoleLoggerOptions) {
|
||||
super(context, options)
|
||||
}
|
||||
private _getColorByLogLevel(logLevel: string) {
|
||||
@@ -71,7 +72,7 @@ export class MyLogger extends ConsoleLogger {
|
||||
const diff = this._updateAndGetTimestampDiff()
|
||||
|
||||
const workerPrefix = cluster.isWorker
|
||||
? chalk.hex('#fab1a0')(`*Worker - ${cluster.worker.id}*`)
|
||||
? chalk.hex('#fab1a0')(`*Worker - ${cluster!.worker!.id}*`)
|
||||
: ''
|
||||
if (context && !argv.length) {
|
||||
print(`${workerPrefix} [${chalk.yellow(context)}] `, formatMessage, diff)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { Expose, Transform } from 'class-transformer'
|
||||
import {
|
||||
IsInt,
|
||||
@@ -11,6 +10,8 @@ import {
|
||||
ValidateIf,
|
||||
} from 'class-validator'
|
||||
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
|
||||
class DbQueryDto {
|
||||
@IsOptional()
|
||||
db_query?: any
|
||||
@@ -24,7 +25,7 @@ export class PagerDto extends DbQueryDto {
|
||||
toClassOnly: true,
|
||||
})
|
||||
@ApiProperty({ example: 10 })
|
||||
size?: number
|
||||
size: number
|
||||
|
||||
@Transform(({ value: val }) => (val ? parseInt(val) : 1), {
|
||||
toClassOnly: true,
|
||||
@@ -33,7 +34,7 @@ export class PagerDto extends DbQueryDto {
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({ example: 1 })
|
||||
page?: number
|
||||
page: number
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ApiHideProperty } from '@nestjs/swagger'
|
||||
import { index, modelOptions, plugin, prop } from '@typegoose/typegoose'
|
||||
import { Type } from 'class-transformer'
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -15,6 +13,9 @@ import LeanId from 'mongoose-lean-id'
|
||||
import { default as mongooseLeanVirtuals } from 'mongoose-lean-virtuals'
|
||||
import Paginate from 'mongoose-paginate-v2'
|
||||
|
||||
import { ApiHideProperty } from '@nestjs/swagger'
|
||||
import { index, modelOptions, plugin, prop } from '@typegoose/typegoose'
|
||||
|
||||
@plugin(mongooseLeanVirtuals)
|
||||
@plugin(Paginate)
|
||||
@plugin(LeanId)
|
||||
@@ -24,7 +25,7 @@ import Paginate from 'mongoose-paginate-v2'
|
||||
toObject: { virtuals: true },
|
||||
timestamps: {
|
||||
createdAt: 'created',
|
||||
updatedAt: null,
|
||||
updatedAt: false,
|
||||
},
|
||||
versionKey: false,
|
||||
},
|
||||
@@ -70,7 +71,7 @@ abstract class ImageModel {
|
||||
@prop()
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
src: string
|
||||
src?: string
|
||||
}
|
||||
|
||||
export abstract class BaseCommentIndexModel extends BaseModel {
|
||||
@@ -105,7 +106,7 @@ export class WriteBaseModel extends BaseCommentIndexModel {
|
||||
@Type(() => ImageModel)
|
||||
images?: ImageModel[]
|
||||
|
||||
@prop({ default: null })
|
||||
@prop({ default: null, type: Date })
|
||||
@ApiHideProperty()
|
||||
modified: Date | null
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { builtinModules } from 'module'
|
||||
|
||||
export const isBuiltinModule = (module: string, ignoreList = []) => {
|
||||
// @ts-ignore
|
||||
return (builtinModules || Object.keys(process.binding('natives')))
|
||||
.filter(
|
||||
(x) =>
|
||||
!/^_|^(internal|v8|node-inspect)\/|\//.test(x) &&
|
||||
!ignoreList.includes(x),
|
||||
)
|
||||
.includes(module)
|
||||
export const isBuiltinModule = (module: string, ignoreList: string[] = []) => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
(builtinModules || (Object.keys(process.binding('natives')) as string[]))
|
||||
.filter(
|
||||
(x) =>
|
||||
!/^_|^(internal|v8|node-inspect)\/|\//.test(x) &&
|
||||
!ignoreList.includes(x),
|
||||
)
|
||||
.includes(module)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { join } from 'path'
|
||||
import { isObject } from 'lodash'
|
||||
import { join } from 'path'
|
||||
|
||||
export const md5 = (text: string) =>
|
||||
require('crypto').createHash('md5').update(text).digest('hex')
|
||||
|
||||
export function getAvatar(mail: string) {
|
||||
export function getAvatar(mail: string | undefined) {
|
||||
if (!mail) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2019",
|
||||
"sourceMap": true,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"module": "CommonJS",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"strictNullChecks": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
|
||||
Reference in New Issue
Block a user