fix: field compatibility
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -14,5 +14,6 @@
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"material-icon-theme.activeIconPack": "nest"
|
||||
}
|
||||
@@ -1,9 +1,30 @@
|
||||
import { Controller, Get } from '@nestjs/common'
|
||||
import {
|
||||
BadRequestException,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
Post,
|
||||
Req,
|
||||
} from '@nestjs/common'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
import { FastifyReply } from 'fastify'
|
||||
import { InjectModel } from 'nestjs-typegoose'
|
||||
import PKG from '../package.json'
|
||||
import { Auth } from './common/decorator/auth.decorator'
|
||||
import { HttpCache } from './common/decorator/cache.decorator'
|
||||
import { RedisKeys } from './constants/cache.constant'
|
||||
import { OptionModel } from './modules/configs/configs.model'
|
||||
import { CacheService } from './processors/cache/cache.service'
|
||||
import { getIp } from './utils/ip.util'
|
||||
import { getRedisKey } from './utils/redis.util'
|
||||
@Controller()
|
||||
@ApiTags('Root')
|
||||
export class AppController {
|
||||
constructor(
|
||||
private readonly cacheService: CacheService,
|
||||
@InjectModel(OptionModel)
|
||||
private readonly optionModel: MongooseModel<OptionModel>,
|
||||
) {}
|
||||
@Get(['/'])
|
||||
async appInfo() {
|
||||
let hash = ''
|
||||
@@ -30,4 +51,58 @@ export class AppController {
|
||||
ping(): 'pong' {
|
||||
return 'pong'
|
||||
}
|
||||
|
||||
@Post('/like_this')
|
||||
@HttpCache.disable
|
||||
@HttpCode(204)
|
||||
async likeThis(
|
||||
@Req()
|
||||
req: FastifyReply,
|
||||
) {
|
||||
const ip = getIp(req as any)
|
||||
const redis = this.cacheService.getClient()
|
||||
|
||||
const isLikedBefore = await redis.sismember(
|
||||
getRedisKey(RedisKeys.LikeSite),
|
||||
ip,
|
||||
)
|
||||
if (isLikedBefore) {
|
||||
throw new BadRequestException('一天一次就够啦')
|
||||
} else {
|
||||
redis.sadd(getRedisKey(RedisKeys.LikeSite), ip)
|
||||
}
|
||||
|
||||
await this.optionModel.updateOne(
|
||||
{
|
||||
name: 'like',
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
// @ts-ignore
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
{ upsert: true },
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@Get('/like_this')
|
||||
@HttpCache.disable
|
||||
async getLikeNumber() {
|
||||
const doc = await this.optionModel.findOne({ name: 'like' }).lean()
|
||||
return doc ? doc.value : 0
|
||||
}
|
||||
|
||||
@Get('/clean_catch')
|
||||
@HttpCache.disable
|
||||
@Auth()
|
||||
async cleanCatch() {
|
||||
const redis = this.cacheService.getClient()
|
||||
const keys: string[] = await redis.keys('mx*')
|
||||
await Promise.all(keys.map((key) => redis.del(key)))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ export enum RedisKeys {
|
||||
Read = 'read',
|
||||
LoginRecord = 'login_record',
|
||||
MaxOnlineCount = 'max_online_count',
|
||||
IpInfoMap = 'ip_info_map',
|
||||
LikeSite = 'like_site',
|
||||
}
|
||||
|
||||
export enum RedisItems {
|
||||
Ips = 'ips',
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class AnalyzeService {
|
||||
}
|
||||
|
||||
async getRangeAnalyzeData(
|
||||
from = new Date(new Date().getTime() - 1000 * 24 * 3600 * 3),
|
||||
from = new Date('2020-1-1'),
|
||||
to = new Date(),
|
||||
options?: {
|
||||
limit?: number
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { ApiOperation, ApiParam } from '@nestjs/swagger'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { HTTPDecorators, Paginator } from '~/common/decorator/http.decorator'
|
||||
import { IpLocation, IpRecord } from '~/common/decorator/ip.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
@@ -39,14 +40,15 @@ export class CommentController {
|
||||
private readonly gateway: SharedGateway,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@Get('/')
|
||||
@Auth()
|
||||
@Paginator
|
||||
async getRecentlyComments(@Query() query: PagerDto) {
|
||||
const { size = 10, page = 1, state = 0 } = query
|
||||
return await this.commentService.getComments({ size, page, state })
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Get('/:id')
|
||||
@ApiOperation({ summary: '根据 comment id 获取评论, 包括子评论' })
|
||||
async getComments(@Param() params: MongoIdDto) {
|
||||
const { id } = params
|
||||
@@ -62,6 +64,7 @@ export class CommentController {
|
||||
}
|
||||
|
||||
@Get('/ref/:id')
|
||||
@HTTPDecorators.Paginator
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
description: 'refId',
|
||||
@@ -95,7 +98,7 @@ export class CommentController {
|
||||
return comments
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
@Post('/:id')
|
||||
@ApiOperation({ summary: '根据文章的 _id 评论' })
|
||||
async comment(
|
||||
@Param() params: MongoIdDto,
|
||||
@@ -242,7 +245,7 @@ export class CommentController {
|
||||
return await this.replyByCid(params, model, undefined, true, ipLocation)
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Patch('/:id')
|
||||
@ApiOperation({ summary: '修改评论的状态' })
|
||||
@HttpCode(204)
|
||||
@Auth()
|
||||
@@ -267,7 +270,7 @@ export class CommentController {
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Delete('/:id')
|
||||
@Auth()
|
||||
async deleteComment(@Param() params: MongoIdDto) {
|
||||
const { id } = params
|
||||
|
||||
@@ -3,6 +3,12 @@ import { Schema } from 'mongoose'
|
||||
|
||||
@modelOptions({
|
||||
options: { allowMixed: Severity.ALLOW, customName: 'Option' },
|
||||
schemaOptions: {
|
||||
timestamps: {
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class OptionModel {
|
||||
@prop({ unique: true, required: true })
|
||||
|
||||
@@ -8,25 +8,20 @@ import {
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common'
|
||||
import { InjectModel } from 'nestjs-typegoose'
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { BaseCrudFactory } from '~/utils/crud.util'
|
||||
import { SayModel } from '../say/say.model'
|
||||
import { LinkQueryDto } from './link.dto'
|
||||
import { LinkModel } from './link.model'
|
||||
import { LinkService } from './link.service'
|
||||
|
||||
@Controller(['links', 'friends'])
|
||||
export class LinkController extends BaseCrudFactory({
|
||||
export class LinkControllerCrud extends BaseCrudFactory({
|
||||
model: LinkModel,
|
||||
}) {
|
||||
constructor(
|
||||
private readonly linkService: LinkService,
|
||||
// FIXME: dup inject
|
||||
@InjectModel(SayModel) private readonly sayModel: MongooseModel<SayModel>,
|
||||
) {
|
||||
super(sayModel)
|
||||
}
|
||||
}) {}
|
||||
|
||||
@Controller(['links', 'friends'])
|
||||
export class LinkController {
|
||||
constructor(private readonly linkService: LinkService) {}
|
||||
|
||||
@Get('/state')
|
||||
@Auth()
|
||||
|
||||
@@ -33,7 +33,7 @@ export class LinkModel extends BaseModel {
|
||||
name: string
|
||||
|
||||
@prop({ required: true, trim: true, unique: true })
|
||||
@IsUrl({ require_protocol: true })
|
||||
@IsUrl({ require_protocol: true, protocols: ['https'] })
|
||||
url: string
|
||||
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { GatewayModule } from '~/processors/gateway/gateway.module'
|
||||
import { LinkController } from './link.controller'
|
||||
import { LinkController, LinkControllerCrud } from './link.controller'
|
||||
import { LinkService } from './link.service'
|
||||
|
||||
@Module({
|
||||
controllers: [LinkController],
|
||||
controllers: [LinkController, LinkControllerCrud],
|
||||
providers: [LinkService],
|
||||
exports: [LinkService],
|
||||
imports: [GatewayModule],
|
||||
|
||||
@@ -19,7 +19,7 @@ export class PageService {
|
||||
}
|
||||
|
||||
public async create(doc: PageModel) {
|
||||
const res = await this.model.create(doc)
|
||||
const res = await this.model.create({ ...doc, created: new Date() })
|
||||
process.nextTick(async () => {
|
||||
await Promise.all([
|
||||
this.imageService.recordImageDimensions(this.pageModel, res._id),
|
||||
@@ -29,7 +29,7 @@ export class PageService {
|
||||
}
|
||||
|
||||
public async updateById(id: string, doc: Partial<PageModel>) {
|
||||
await this.model.updateOne({ _id: id }, doc)
|
||||
await this.model.updateOne({ _id: id, modified: new Date() }, doc)
|
||||
process.nextTick(async () => {
|
||||
await Promise.all([
|
||||
this.imageService.recordImageDimensions(this.pageModel, id),
|
||||
|
||||
@@ -44,7 +44,7 @@ export class PostController {
|
||||
@Get('/')
|
||||
@Paginator
|
||||
async getPaginate(@Query() query: PostQueryDto, @IsMaster() master: boolean) {
|
||||
const { size, select = '-text', page, year, sortBy, sortOrder } = query
|
||||
const { size, select, page, year, sortBy, sortOrder } = query
|
||||
|
||||
return await this.postService.findWithPaginator(
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ export class SayController extends BaseCrudFactory({ model: SayModel }) {
|
||||
if (!res.length) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
return sample(res)
|
||||
return { data: sample(res) }
|
||||
}
|
||||
|
||||
@Post('/')
|
||||
|
||||
@@ -1,6 +1,41 @@
|
||||
import { Controller } from '@nestjs/common'
|
||||
import { Controller, Get, Param } from '@nestjs/common'
|
||||
import { HttpCache } from '~/common/decorator/cache.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { IpDto } from './tool.dto'
|
||||
import { ToolService } from './tool.service'
|
||||
|
||||
@Controller('tools')
|
||||
@ApiName
|
||||
export class ToolController {}
|
||||
export class ToolController {
|
||||
constructor(
|
||||
private readonly toolService: ToolService,
|
||||
private readonly cacheService: CacheService,
|
||||
) {}
|
||||
|
||||
@Get('/ip/:ip')
|
||||
@HttpCache({ disable: true })
|
||||
async getIpInfo(@Param() params: IpDto) {
|
||||
const { ip } = params
|
||||
const redis = this.cacheService.getClient()
|
||||
|
||||
try {
|
||||
const [ipFromRedis] = await redis.hmget(
|
||||
getRedisKey(RedisKeys.IpInfoMap),
|
||||
ip,
|
||||
)
|
||||
if (ipFromRedis) {
|
||||
return JSON.parse(ipFromRedis)
|
||||
}
|
||||
} catch {}
|
||||
const result = await this.toolService.getIp(ip)
|
||||
await redis.hmset(
|
||||
getRedisKey(RedisKeys.IpInfoMap),
|
||||
ip,
|
||||
JSON.stringify(result),
|
||||
)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
6
src/modules/tool/tool.dto.ts
Normal file
6
src/modules/tool/tool.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsIP } from 'class-validator'
|
||||
|
||||
export class IpDto {
|
||||
@IsIP()
|
||||
ip: string
|
||||
}
|
||||
12
src/modules/tool/tool.interface.ts
Normal file
12
src/modules/tool/tool.interface.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface IP {
|
||||
ip: string
|
||||
countryName: string
|
||||
regionName: string
|
||||
cityName: string
|
||||
ownerDomain: string
|
||||
ispDomain: string
|
||||
range?: {
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,37 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Injectable, UnprocessableEntityException } from '@nestjs/common'
|
||||
import { isIPv4, isIPv6 } from 'net'
|
||||
import { HttpService } from '~/processors/helper/helper.http.service'
|
||||
import { IP } from './tool.interface'
|
||||
|
||||
@Injectable()
|
||||
export class ToolService {}
|
||||
export class ToolService {
|
||||
constructor(private readonly httpService: HttpService) {}
|
||||
|
||||
async getIp(ip: string): Promise<IP> {
|
||||
const isV4 = isIPv4(ip)
|
||||
const isV6 = isIPv6(ip)
|
||||
if (!isV4 && !isV6) {
|
||||
throw new UnprocessableEntityException('Invalid IP')
|
||||
}
|
||||
|
||||
if (isV4) {
|
||||
const { data } = await this.httpService.axiosRef.get(
|
||||
'https://api.i-meto.com/ip/v1/qqwry/' + ip,
|
||||
)
|
||||
return data as IP
|
||||
} else {
|
||||
const { data } = (await this.httpService.axiosRef.get(
|
||||
'http://ip-api.com/json/' + ip,
|
||||
)) as any
|
||||
|
||||
return {
|
||||
cityName: data.city,
|
||||
countryName: data.country,
|
||||
ip: data.query,
|
||||
ispDomain: data.as,
|
||||
ownerDomain: data.org,
|
||||
regionName: data.region_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,26 @@ import { ApiHideProperty } from '@nestjs/swagger'
|
||||
import { modelOptions, plugin, prop } from '@typegoose/typegoose'
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'
|
||||
import LeanId from 'mongoose-lean-id'
|
||||
import mongooseLeanVirtuals from 'mongoose-lean-virtuals'
|
||||
import {
|
||||
default as leanVirtuals,
|
||||
default as mongooseLeanVirtuals,
|
||||
} from 'mongoose-lean-virtuals'
|
||||
import Paginate from 'mongoose-paginate-v2'
|
||||
|
||||
@plugin(leanVirtuals)
|
||||
@plugin(mongooseLeanVirtuals)
|
||||
@plugin(Paginate)
|
||||
@plugin(LeanId)
|
||||
@modelOptions({
|
||||
schemaOptions: {
|
||||
toJSON: { virtuals: true },
|
||||
toObject: { virtuals: true },
|
||||
timestamps: {
|
||||
createdAt: 'created',
|
||||
updatedAt: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ObjectType()
|
||||
export class BaseModel {
|
||||
@ApiHideProperty()
|
||||
@@ -78,14 +93,6 @@ export abstract class BaseCommentIndexModel extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
@modelOptions({
|
||||
schemaOptions: {
|
||||
timestamps: {
|
||||
createdAt: 'created',
|
||||
updatedAt: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ObjectType()
|
||||
export abstract class WriteBaseModel extends BaseCommentIndexModel {
|
||||
@prop({ trim: true, index: true, required: true })
|
||||
|
||||
@@ -51,17 +51,14 @@ export function BaseCrudFactory<
|
||||
@Get('/')
|
||||
@Paginator
|
||||
async gets(@Query() pager: PagerDto) {
|
||||
const { size, page, select } = pager
|
||||
|
||||
return await this._model.paginate(
|
||||
{},
|
||||
{
|
||||
limit: size,
|
||||
page,
|
||||
sort: { created: -1 },
|
||||
select,
|
||||
},
|
||||
)
|
||||
const { size, page, select, state } = pager
|
||||
// @ts-ignore
|
||||
return await this._model.paginate(state !== undefined ? { state } : {}, {
|
||||
limit: size,
|
||||
page,
|
||||
sort: { created: -1 },
|
||||
select,
|
||||
})
|
||||
}
|
||||
@Get('/all')
|
||||
async getAll() {
|
||||
@@ -71,21 +68,31 @@ export function BaseCrudFactory<
|
||||
@Post('/')
|
||||
@Auth()
|
||||
async create(@Body() body: Dto) {
|
||||
return await this._model.create(body)
|
||||
return await this._model.create({ ...body, created: new Date() })
|
||||
}
|
||||
|
||||
@Put('/:id')
|
||||
@Auth()
|
||||
async update(@Body() body: Dto, @Param() param: MongoIdDto) {
|
||||
await this._model.updateOne({ _id: param.id as any }, body as any).lean()
|
||||
return this._model.findById(param.id as any)
|
||||
await this._model
|
||||
.updateOne({ _id: param.id as any }, {
|
||||
...body,
|
||||
modified: new Date(),
|
||||
} as any)
|
||||
.lean()
|
||||
return this._model.findById(param.id as any).lean()
|
||||
}
|
||||
|
||||
@Patch('/:id')
|
||||
@Auth()
|
||||
@HttpCode(204)
|
||||
async patch(@Body() body: PDto, @Param() param: MongoIdDto) {
|
||||
await this._model.updateOne({ _id: param.id as any }, body)
|
||||
await this._model
|
||||
.updateOne({ _id: param.id as any }, {
|
||||
...body,
|
||||
modified: new Date(),
|
||||
} as any)
|
||||
.lean()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export function getAvatar(mail: string) {
|
||||
if (!mail) {
|
||||
return ''
|
||||
}
|
||||
return `https://sdn.geekzu.org/avatar/${md5(mail)}`
|
||||
return `https://sdn.geekzu.org/avatar/${md5(mail)}?d=retro`
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
|
||||
Reference in New Issue
Block a user