From b4726adfd80a9b229d48d027bc0a1ba3b69c80ee Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 12 Apr 2024 21:08:03 +0800 Subject: [PATCH] feat: add recent activity api Signed-off-by: Innei --- .../modules/activity/activity.controller.ts | 34 ++++- .../src/modules/activity/activity.module.ts | 3 +- .../src/modules/activity/activity.service.ts | 133 +++++++++++++++++- .../src/modules/activity/dtos/activity.dto.ts | 2 +- apps/core/src/modules/post/post.controller.ts | 15 ++ .../__tests__/controllers/post.test.ts | 13 ++ packages/api-client/controllers/activity.ts | 10 +- packages/api-client/controllers/post.ts | 7 +- packages/api-client/models/activity.ts | 52 +++++++ 9 files changed, 258 insertions(+), 11 deletions(-) diff --git a/apps/core/src/modules/activity/activity.controller.ts b/apps/core/src/modules/activity/activity.controller.ts index 14270f1d..f5780702 100644 --- a/apps/core/src/modules/activity/activity.controller.ts +++ b/apps/core/src/modules/activity/activity.controller.ts @@ -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, + } + } } diff --git a/apps/core/src/modules/activity/activity.module.ts b/apps/core/src/modules/activity/activity.module.ts index 40012e6d..f7d2a7bf 100644 --- a/apps/core/src/modules/activity/activity.module.ts +++ b/apps/core/src/modules/activity/activity.module.ts @@ -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 {} diff --git a/apps/core/src/modules/activity/activity.service.ts b/apps/core/src/modules/activity/activity.service.ts index f857b209..0405df41 100644 --- a/apps/core/src/modules/activity/activity.service.ts +++ b/apps/core/src/modules/activity/activity.service.ts @@ -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, + + 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, + } + } } diff --git a/apps/core/src/modules/activity/dtos/activity.dto.ts b/apps/core/src/modules/activity/dtos/activity.dto.ts index b2da20f9..64bbd8e5 100644 --- a/apps/core/src/modules/activity/dtos/activity.dto.ts +++ b/apps/core/src/modules/activity/dtos/activity.dto.ts @@ -27,7 +27,7 @@ export class ActivityQueryDto extends PagerDto { type: Activity } -export class ReadingRangeDto { +export class ActivityRangeDto { @IsInt() @IsOptional() @Type(() => Number) diff --git a/apps/core/src/modules/post/post.controller.ts b/apps/core/src/modules/post/post.controller.ts index c7afc306..9361caf1 100644 --- a/apps/core/src/modules/post/post.controller.ts +++ b/apps/core/src/modules/post/post.controller.ts @@ -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) { diff --git a/packages/api-client/__tests__/controllers/post.test.ts b/packages/api-client/__tests__/controllers/post.test.ts index 4087e129..a8a620d0 100644 --- a/packages/api-client/__tests__/controllers/post.test.ts +++ b/packages/api-client/__tests__/controllers/post.test.ts @@ -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', + }) + }) }) diff --git a/packages/api-client/controllers/activity.ts b/packages/api-client/controllers/activity.ts index 120140f6..efeb0122 100644 --- a/packages/api-client/controllers/activity.ts +++ b/packages/api-client/controllers/activity.ts @@ -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 implements IController { async getRoomsInfo() { return this.proxy.rooms.get() } + + async getRecentActivities() { + return this.proxy.recent.get() + } } diff --git a/packages/api-client/controllers/post.ts b/packages/api-client/controllers/post.ts index 73f25e80..abad1376 100644 --- a/packages/api-client/controllers/post.ts +++ b/packages/api-client/controllers/post.ts @@ -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 implements IController { getLatest() { return this.proxy.latest.get>() } + + getFullUrl(slug: string) { + return this.proxy('get-url')(slug).get<{ path: string }>() + } } diff --git a/packages/api-client/models/activity.ts b/packages/api-client/models/activity.ts index 1658db69..5b8b3741 100644 --- a/packages/api-client/models/activity.ts +++ b/packages/api-client/models/activity.ts @@ -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 +}