feat: add recent activity api

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei
2024-04-12 21:08:03 +08:00
parent 0078e18c13
commit b4726adfd8
9 changed files with 258 additions and 11 deletions

View File

@@ -7,6 +7,7 @@ import { ApiController } from '~/common/decorators/api-controller.decorator'
import { Auth } from '~/common/decorators/auth.decorator'
import { HTTPDecorators } from '~/common/decorators/http.decorator'
import { IpLocation, IpRecord } from '~/common/decorators/ip.decorator'
import { CollectionRefTypes } from '~/constants/db.constant'
import { PagerDto } from '~/shared/dto/pager.dto'
import { Activity } from './activity.constant'
@@ -14,8 +15,8 @@ import { ActivityService } from './activity.service'
import {
ActivityDeleteDto,
ActivityQueryDto,
ActivityRangeDto,
ActivityTypeParamsDto,
ReadingRangeDto,
} from './dtos/activity.dto'
import { LikeBodyDto } from './dtos/like.dto'
import { GetPresenceQueryDto, UpdatePresenceDto } from './dtos/presence.dto'
@@ -134,7 +135,7 @@ export class ActivityController {
@Auth()
@Get('/reading/rank')
async getReadingRangeRank(@Query() query: ReadingRangeDto) {
async getReadingRangeRank(@Query() query: ActivityRangeDto) {
const startAt = query.start ? new Date(query.start) : undefined
const endAt = query.end ? new Date(query.end) : undefined
@@ -165,4 +166,33 @@ export class ActivityController {
})
})
}
@Get('/recent')
async getRecentActivities() {
const [like, comment, recent] = await Promise.all([
this.service.getLikeActivities(1, 5),
this.service.getRecentComment(),
this.service.getRecentPublish(),
])
const transformedLike = [] as any[]
for (const item of like.data) {
const likeData = pick(item, 'created', 'id') as any
if ('nid' in item.ref) {
likeData.type = CollectionRefTypes.Note
likeData.nid = item.ref.nid
} else {
likeData.type = CollectionRefTypes.Post
likeData.slug = item.ref.slug
}
transformedLike.push(likeData)
}
return {
like: transformedLike,
comment,
...recent,
}
}
}

View File

@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'
import { GatewayModule } from '~/processors/gateway/gateway.module'
import { CommentModule } from '../comment/comment.module'
import { ActivityController } from './activity.controller'
import { ActivityService } from './activity.service'
@@ -9,6 +10,6 @@ import { ActivityService } from './activity.service'
providers: [ActivityService],
controllers: [ActivityController],
exports: [ActivityService],
imports: [GatewayModule],
imports: [GatewayModule, CommentModule],
})
export class ActivityModule {}

View File

@@ -19,8 +19,10 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'
import { ArticleTypeEnum } from '~/constants/article.constant'
import { BusinessEvents, EventScope } from '~/constants/business-event.constant'
import {
CollectionRefTypes,
NOTE_COLLECTION_NAME,
POST_COLLECTION_NAME,
RECENTLY_COLLECTION_NAME,
} from '~/constants/db.constant'
import { DatabaseService } from '~/processors/database/database.service'
import { GatewayService } from '~/processors/gateway/gateway.service'
@@ -30,6 +32,7 @@ import { EventManagerService } from '~/processors/helper/helper.event.service'
import { InjectModel } from '~/transformers/model.transformer'
import { transformDataToPaginate } from '~/transformers/paginate.transformer'
import { CommentService } from '../comment/comment.service'
import { Activity } from './activity.constant'
import { ActivityModel } from './activity.model'
import {
@@ -54,6 +57,8 @@ export class ActivityService implements OnModuleInit, OnModuleDestroy {
@InjectModel(ActivityModel)
private readonly activityModel: MongooseModel<ActivityModel>,
private readonly commentService: CommentService,
private readonly databaseService: DatabaseService,
private readonly webGateway: WebEventsGateway,
@@ -206,11 +211,15 @@ export class ActivityService implements OnModuleInit, OnModuleDestroy {
Reflect.set(nextAc, 'ref', refModelData.get(ac.payload.id))
return nextAc
})
}) as any as (ActivityModel & {
payload: any
ref: PostModel | NoteModel
})[]
// @ts-ignore
transformedPager.data = docsWithRefModel
return transformedPager
return {
...transformedPager,
data: docsWithRefModel,
}
}
async getReadDurationActivities(page = 1, size = 10) {
@@ -485,4 +494,120 @@ export class ActivityService implements OnModuleInit, OnModuleDestroy {
return result
}
async getRecentComment() {
const docs = await this.commentService.model
.find({
isWhispers: false,
})
.populate('ref', 'title nid slug category')
.lean()
.sort({
created: -1,
})
.limit(3)
return docs.map((doc) => {
return Object.assign(
{},
pick(doc, 'created', 'author', 'text'),
doc.ref,
{
type:
'nid' in doc.ref
? CollectionRefTypes.Note
: CollectionRefTypes.Post,
},
)
})
}
async getRecentPublish() {
const [recent, post, note] = await Promise.all([
this.databaseService.db
.collection(RECENTLY_COLLECTION_NAME)
.find()
.project({
content: 1,
created: 1,
up: 1,
down: 1,
})
.sort({
created: -1,
})
.limit(3)
.toArray(),
this.databaseService.db
.collection(POST_COLLECTION_NAME)
.find()
.project({
title: 1,
slug: 1,
created: 1,
modified: 1,
category: 1,
categoryId: 1,
})
.sort({
created: -1,
})
.limit(3)
.toArray(),
// .aggregate([
// {
// $lookup: {
// from: CATEGORY_COLLECTION_NAME,
// localField: 'categoryId',
// foreignField: '_id',
// as: 'category',
// },
// },
// {
// $project: {
// title: 1,
// slug: 1,
// created: 1,
// category: {
// $arrayElemAt: ['$category', 0],
// },
// categoryId: 1,
// id: '$_id',
// },
// },
// {
// $sort: {
// created: -1,
// },
// },
// {
// $limit: 3,
// },
// ])
// .toArray(),
this.databaseService.db
.collection(NOTE_COLLECTION_NAME)
.find()
.sort({
created: -1,
})
.project({
title: 1,
nid: 1,
id: 1,
created: 1,
modified: 1,
})
.limit(3)
.toArray(),
])
return {
recent,
post,
note,
}
}
}

View File

@@ -27,7 +27,7 @@ export class ActivityQueryDto extends PagerDto {
type: Activity
}
export class ReadingRangeDto {
export class ActivityRangeDto {
@IsInt()
@IsOptional()
@Type(() => Number)

View File

@@ -121,6 +121,21 @@ export class PostController {
})
}
@Get('/get-url/:slug')
async getBySlug(@Param('slug') slug: string) {
if (typeof slug !== 'string') {
throw new CannotFindException()
}
const doc = await this.postService.model.findOne({ slug })
if (!doc) {
throw new CannotFindException()
}
return {
path: `/${(doc.category as CategoryModel).slug}/${doc.slug}`,
}
}
@Get('/:id')
@Auth()
async getById(@Param() params: MongoIdDto) {

View File

@@ -66,4 +66,17 @@ describe('test post client', () => {
expect(data).toStrictEqual({ title: '1' })
expect(data.$raw).toBeDefined()
})
it('GET /posts/get-url/:slug', async () => {
mockResponse('/posts/get-url/host-an-entire-Mix-Space-using-Docker', {
path: '/website/host-an-entire-Mix-Space-using-Docker',
})
const data = await client.post.getFullUrl(
'host-an-entire-Mix-Space-using-Docker',
)
expect(data).toStrictEqual({
path: '/website/host-an-entire-Mix-Space-using-Docker',
})
})
})

View File

@@ -1,7 +1,11 @@
import type { IRequestAdapter } from '~/interfaces/adapter'
import type { IController } from '~/interfaces/controller'
import type { IRequestHandler } from '~/interfaces/request'
import type { ActivityPresence, RoomsData } from '~/models/activity'
import type {
ActivityPresence,
RecentActivities,
RoomsData,
} from '~/models/activity'
import type { HTTPClient } from '../core'
import { autoBind } from '~/utils/auto-bind'
@@ -87,4 +91,8 @@ export class ActivityController<ResponseWrapper> implements IController {
async getRoomsInfo() {
return this.proxy.rooms.get<RoomsData>()
}
async getRecentActivities() {
return this.proxy.recent.get<RecentActivities>()
}
}

View File

@@ -4,11 +4,10 @@ import type { IRequestHandler, RequestProxyResult } from '~/interfaces/request'
import type { SelectFields } from '~/interfaces/types'
import type { ModelWithLiked, PaginateResult } from '~/models/base'
import type { PostModel } from '~/models/post'
import type { HTTPClient } from '../core/client'
import { autoBind } from '~/utils/auto-bind'
import { HTTPClient } from '../core/client'
declare module '../core/client' {
interface HTTPClient<
T extends IRequestAdapter = IRequestAdapter,
@@ -86,4 +85,8 @@ export class PostController<ResponseWrapper> implements IController {
getLatest() {
return this.proxy.latest.get<ModelWithLiked<PostModel>>()
}
getFullUrl(slug: string) {
return this.proxy('get-url')(slug).get<{ path: string }>()
}
}

View File

@@ -1,3 +1,4 @@
import type { CollectionRefTypes } from '@core/constants/db.constant'
import type { CategoryModel } from './category'
export interface ActivityPresence {
@@ -45,3 +46,54 @@ export interface RoomsData {
pages: RoomOmittedPage[]
}
}
export interface RecentActivities {
like: RecentLike[]
comment: RecentComment[]
recent: RecentRecent[]
post: RecentPost[]
note: RecentNote[]
}
export interface RecentComment {
created: string
author: string
text: string
id: string
title: string
slug: string
type: string
}
export interface RecentLike {
created: string
id: string
type: CollectionRefTypes.Post | CollectionRefTypes.Note
nid?: number
slug?: string
}
export interface RecentNote {
id: string
created: string
title: string
modified: string
nid: number
}
export interface RecentPost {
id: string
created: string
title: string
modified: string
slug: string
}
export interface RecentRecent {
id: string
content: string
up: number
down: number
created: string
}