fix: field compatibility

This commit is contained in:
Innei
2021-09-09 21:18:51 +08:00
parent 95c4b5840f
commit 25b98b2384
19 changed files with 238 additions and 55 deletions

View File

@@ -14,5 +14,6 @@
},
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"material-icon-theme.activeIconPack": "nest"
}

View File

@@ -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
}
}

View File

@@ -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',
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 })

View File

@@ -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()

View File

@@ -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()

View File

@@ -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],

View File

@@ -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),

View File

@@ -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(
{

View File

@@ -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('/')

View File

@@ -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
}
}

View File

@@ -0,0 +1,6 @@
import { IsIP } from 'class-validator'
export class IpDto {
@IsIP()
ip: string
}

View 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
}
}

View File

@@ -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,
}
}
}
}

View File

@@ -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 })

View File

@@ -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
}

View File

@@ -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) {