@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export class ActivityQueryDto extends PagerDto {
|
||||
type: Activity
|
||||
}
|
||||
|
||||
export class ReadingRangeDto {
|
||||
export class ActivityRangeDto {
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user