From 0a50c8e6e72c4ba27ce4ad953634e3e8f049ac84 Mon Sep 17 00:00:00 2001 From: Geonoing Date: Sat, 6 Jul 2024 03:11:41 +0900 Subject: [PATCH 1/2] feat: folder list api --- src/common/types/type.d.ts | 2 + src/common/utils/index.ts | 10 ++++ src/common/utils/math.util.ts | 9 +++ src/domains/folder.ts | 9 +++ src/infrastructure/database/schema/index.ts | 1 - .../database/types/folder-type.enum.ts | 4 ++ .../folders/dto/folder-with-count.dto.ts | 7 ++- src/modules/folders/folders.controller.ts | 4 +- src/modules/folders/folders.module.ts | 4 +- src/modules/folders/folders.service.ts | 57 ++++++++++++++----- .../folders/responses/folder-list.response.ts | 17 +++--- .../responses/folder-summary.response.ts | 2 +- .../folders/responses/folder.response.ts | 5 +- .../folders/responses/post.response.ts | 2 +- src/modules/posts/posts.module.ts | 8 ++- src/modules/posts/posts.repository.ts | 15 ++++- 16 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 src/common/utils/math.util.ts create mode 100644 src/domains/folder.ts diff --git a/src/common/types/type.d.ts b/src/common/types/type.d.ts index 44aec31..006d2cb 100644 --- a/src/common/types/type.d.ts +++ b/src/common/types/type.d.ts @@ -26,3 +26,5 @@ export type JwtPayload = { export type ReqUserPayload = { id: string; }; + +export type FunctionType = (...args: any[]) => any; diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index a50fe92..9989810 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -1 +1,11 @@ +import { FunctionType } from '../types/type'; + export * from './parser.util'; +export * from './math.util'; + +export const pipe = (...functions: FunctionType[]) => { + return (initial: unknown) => + functions.reduce((result, func) => { + return func(result); + }, initial); +}; diff --git a/src/common/utils/math.util.ts b/src/common/utils/math.util.ts new file mode 100644 index 0000000..eda0172 --- /dev/null +++ b/src/common/utils/math.util.ts @@ -0,0 +1,9 @@ +export const sum = ( + _list: T[], + selector?: T extends object ? (item: T) => number : never, +): number => { + if (_list.length === 0) return 0; + + const list = (selector ? _list.map(selector) : _list) as number[]; + return list.reduce((sum, cur) => cur + sum, 0); +}; diff --git a/src/domains/folder.ts b/src/domains/folder.ts new file mode 100644 index 0000000..ab3cdeb --- /dev/null +++ b/src/domains/folder.ts @@ -0,0 +1,9 @@ +import { FolderType } from '@src/infrastructure/database/types/folder-type.enum'; + +export class FolderDomain { + userId: string; + + name: string; + + type: FolderType; +} diff --git a/src/infrastructure/database/schema/index.ts b/src/infrastructure/database/schema/index.ts index d233147..dbfecd6 100644 --- a/src/infrastructure/database/schema/index.ts +++ b/src/infrastructure/database/schema/index.ts @@ -3,4 +3,3 @@ export * from './folder.schema'; export * from './keyword.schema'; export * from './post.schema'; export * from './AIClassification.schema'; -export * from './postUserRead.schema'; diff --git a/src/infrastructure/database/types/folder-type.enum.ts b/src/infrastructure/database/types/folder-type.enum.ts index e3003c7..b4508b7 100644 --- a/src/infrastructure/database/types/folder-type.enum.ts +++ b/src/infrastructure/database/types/folder-type.enum.ts @@ -1,4 +1,8 @@ export enum FolderType { CUSTOM = 'custom', DEFAULT = 'default', + + /** 실제 DB에 들어가지는 않고 클라이언트에 내려주기 위해 있는 값 타입 */ + ALL = 'all', + FAVORITE = 'favorite', } diff --git a/src/modules/folders/dto/folder-with-count.dto.ts b/src/modules/folders/dto/folder-with-count.dto.ts index 5d81984..f44622c 100644 --- a/src/modules/folders/dto/folder-with-count.dto.ts +++ b/src/modules/folders/dto/folder-with-count.dto.ts @@ -1,5 +1,8 @@ +import { FolderDomain } from '@src/domains/folder'; import { FolderDocument } from '@src/infrastructure'; -export interface FolderWithCount extends FolderDocument { - postCount: number; +export interface FolderListServiceDto { + /** */ + defaultFolders: any[]; + customFolders: any[]; } diff --git a/src/modules/folders/folders.controller.ts b/src/modules/folders/folders.controller.ts index f969011..61c14ad 100644 --- a/src/modules/folders/folders.controller.ts +++ b/src/modules/folders/folders.controller.ts @@ -51,8 +51,8 @@ export class FoldersController { @FindAFolderListDocs @Get() async findAll(@GetUser() userId: string) { - const folders = await this.foldersService.findAll(userId); - return new FolderListResponse(folders); + const result = await this.foldersService.findAll(userId); + return new FolderListResponse(result); } @FindFolderDocs diff --git a/src/modules/folders/folders.module.ts b/src/modules/folders/folders.module.ts index 8c1dc69..8ddf597 100644 --- a/src/modules/folders/folders.module.ts +++ b/src/modules/folders/folders.module.ts @@ -5,6 +5,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import { Folder, FolderSchema, Post, PostSchema } from '@src/infrastructure'; import { FolderRepository } from './folders.repository'; import { PostsRepository } from '../posts/posts.repository'; +import { PostsModule } from '../posts/posts.module'; import { PostsService } from '../posts/posts.service'; @Module({ @@ -13,8 +14,9 @@ import { PostsService } from '../posts/posts.service'; { name: Post.name, schema: PostSchema }, { name: Folder.name, schema: FolderSchema }, ]), + PostsModule, ], controllers: [FoldersController], - providers: [FoldersService, PostsService, FolderRepository, PostsRepository], + providers: [FoldersService, FolderRepository, PostsRepository], }) export class FoldersModule {} diff --git a/src/modules/folders/folders.service.ts b/src/modules/folders/folders.service.ts index 636aeb1..590d7b4 100644 --- a/src/modules/folders/folders.service.ts +++ b/src/modules/folders/folders.service.ts @@ -1,11 +1,12 @@ import { Injectable } from '@nestjs/common'; import { CreateFolderDto, UpdateFolderDto } from './dto/mutate-folder.dto'; -import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; -import { FolderWithCount } from './dto/folder-with-count.dto'; +import { Schema as MongooseSchema } from 'mongoose'; import { FolderRepository } from './folders.repository'; import { PostsRepository } from '../posts/posts.repository'; import { FolderType } from '@src/infrastructure/database/types/folder-type.enum'; -import { Type } from 'class-transformer'; +import { sum } from '@src/common'; +import { FolderListServiceDto } from './dto/folder-with-count.dto'; +import { Folder, FolderDocument } from '@src/infrastructure'; @Injectable() export class FoldersService { @@ -24,21 +25,51 @@ export class FoldersService { await this.folderRepository.createMany(folders); } - async findAll(userId: string): Promise { + async findAll(userId: string): Promise { const folders = await this.folderRepository.findByUserId(userId); const folderIds = folders.map((folder) => folder._id); - const posts = await this.postRepository.getPostCountByFolderIds(folderIds); + const groupedFolders = + await this.postRepository.getPostCountByFolderIds(folderIds); - const foldersWithCounts = folders.map((folder) => { - const post = posts.find((post) => post._id.equals(folder._id)); - return { - ...folder.toJSON(), - postCount: post?.count ?? 0, - } satisfies FolderWithCount; - }); + const allPostCount = sum(groupedFolders, (folder) => folder.count); + const favoritePostCount = + await this.postRepository.findFavoritePostCount(userId); + + const defaultFolder = folders.find( + (folder) => folder.type === FolderType.DEFAULT, + ); + const customFolders = folders + .filter((folder) => folder.type === FolderType.CUSTOM) + .map((folder) => { + const post = groupedFolders.find((folder) => + folder._id.equals(folder._id), + ); + return { + ...folder.toJSON(), + postCount: post?.count ?? 0, + }; + }); + + const all = { + id: null, + name: '모든 링크', + type: FolderType.ALL, + userId: new MongooseSchema.Types.ObjectId(userId), + postCount: allPostCount, + }; + const favorite = { + id: null, + name: '즐겨찾기', + type: FolderType.FAVORITE, + userId: new MongooseSchema.Types.ObjectId(userId), + postCount: favoritePostCount, + }; - return foldersWithCounts; + const defaultFolders = [all, favorite, defaultFolder].filter( + (folder) => !!folder, + ); + return { defaultFolders, customFolders }; } async findOne(userId: string, folderId: string) { diff --git a/src/modules/folders/responses/folder-list.response.ts b/src/modules/folders/responses/folder-list.response.ts index a388907..ced75c9 100644 --- a/src/modules/folders/responses/folder-list.response.ts +++ b/src/modules/folders/responses/folder-list.response.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { FolderResponse } from './folder.response'; -import { FolderWithCount } from '../dto/folder-with-count.dto'; import { FolderType } from '@src/infrastructure/database/types/folder-type.enum'; +import { FolderListServiceDto } from '../dto/folder-with-count.dto'; export class FolderListResponse { @ApiProperty({ isArray: true, type: FolderResponse }) @@ -10,13 +10,12 @@ export class FolderListResponse { @ApiProperty({ isArray: true, type: FolderResponse }) customFolders: FolderResponse[]; - constructor(data: FolderWithCount[]) { - this.defaultFolders = data - .filter((folder) => folder.type === FolderType.DEFAULT) - .map((folder) => new FolderResponse(folder)); - - this.customFolders = data - .filter((folder) => folder.type === FolderType.CUSTOM) - .map((folder) => new FolderResponse(folder)); + constructor(data: FolderListServiceDto) { + this.defaultFolders = data.defaultFolders.map( + (folder) => new FolderResponse(folder), + ); + this.customFolders = data.customFolders.map( + (folder) => new FolderResponse(folder), + ); } } diff --git a/src/modules/folders/responses/folder-summary.response.ts b/src/modules/folders/responses/folder-summary.response.ts index 0260b69..075ae3e 100644 --- a/src/modules/folders/responses/folder-summary.response.ts +++ b/src/modules/folders/responses/folder-summary.response.ts @@ -16,7 +16,7 @@ export class FolderSummaryResponse { createdAt: Date; constructor(data: FolderDocument) { - this.id = data._id.toString(); + this.id = data._id ? data._id.toString() : data.id; this.name = data.name; this.type = data.type; this.createdAt = data.createdAt; diff --git a/src/modules/folders/responses/folder.response.ts b/src/modules/folders/responses/folder.response.ts index 33c81de..ca30e55 100644 --- a/src/modules/folders/responses/folder.response.ts +++ b/src/modules/folders/responses/folder.response.ts @@ -1,14 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; import { FolderSummaryResponse } from './folder-summary.response'; -import { Types } from 'mongoose'; -import { FolderDocument } from '@src/infrastructure'; -import { FolderWithCount } from '../dto/folder-with-count.dto'; export class FolderResponse extends FolderSummaryResponse { @ApiProperty() postCount: number; - constructor(data: FolderWithCount) { + constructor(data) { super(data); this.postCount = data.postCount; diff --git a/src/modules/folders/responses/post.response.ts b/src/modules/folders/responses/post.response.ts index 16d661b..cae1828 100644 --- a/src/modules/folders/responses/post.response.ts +++ b/src/modules/folders/responses/post.response.ts @@ -38,7 +38,7 @@ export class PostResponse { isFavorite: boolean; @ApiProperty() - createdAt: string; + createdAt: Date; @ApiProperty({ type: Keyword, isArray: true }) keywords: Keyword[]; diff --git a/src/modules/posts/posts.module.ts b/src/modules/posts/posts.module.ts index 837911e..a475a07 100644 --- a/src/modules/posts/posts.module.ts +++ b/src/modules/posts/posts.module.ts @@ -19,6 +19,12 @@ import { AwsLambdaService } from '@src/infrastructure/aws-lambda/aws-lambda.serv AwsLambdaModule, ], controllers: [PostsController], - providers: [PostsService, PostsRepository, FolderRepository, AwsLambdaService], + providers: [ + PostsService, + PostsRepository, + FolderRepository, + AwsLambdaService, + ], + exports: [PostsService], }) export class PostsModule {} diff --git a/src/modules/posts/posts.repository.ts b/src/modules/posts/posts.repository.ts index b555946..f7b216b 100644 --- a/src/modules/posts/posts.repository.ts +++ b/src/modules/posts/posts.repository.ts @@ -29,7 +29,7 @@ export class PostsRepository { } async getPostCountByFolderIds(folderIds: Types.ObjectId[]) { - const posts = await this.postModel + const folders = await this.postModel .aggregate<{ _id: Types.ObjectId; count: number }>([ { $match: { @@ -39,13 +39,13 @@ export class PostsRepository { { $group: { _id: '$folderId', - count: { $sum: 1 }, + postCount: { $sum: 1 }, }, }, ]) .exec(); - return posts; + return folders; } async getCountByFolderId(folderId: string) { @@ -62,4 +62,13 @@ export class PostsRepository { return folders; } + + async findFavoritePostCount(userId: string) { + const count = await this.postModel.countDocuments({ + userId, + isFavorite: true, + }); + + return count; + } } From 99a08ed8c4c8ad5af29cb6424a757aefc03e85fc Mon Sep 17 00:00:00 2001 From: Geonoing Date: Sat, 6 Jul 2024 03:11:59 +0900 Subject: [PATCH 2/2] feat: add default folder --- src/modules/users/users.module.ts | 10 +++++++--- src/modules/users/users.service.ts | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 1e85fd2..83f8798 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -3,16 +3,20 @@ import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { MongooseModule } from '@nestjs/mongoose'; import { JwtStrategy } from './guards/strategy'; -import { User, UserSchema } from '@src/infrastructure'; +import { Folder, FolderSchema, User, UserSchema } from '@src/infrastructure'; import { UsersRepository } from './users.repository'; import { AuthModule } from '../auth/auth.module'; +import { FolderRepository } from '../folders/folders.repository'; @Module({ imports: [ AuthModule, - MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), + MongooseModule.forFeature([ + { name: User.name, schema: UserSchema }, + { name: Folder.name, schema: FolderSchema }, + ]), ], controllers: [UsersController], - providers: [UsersService, UsersRepository, JwtStrategy], + providers: [UsersService, UsersRepository, FolderRepository, JwtStrategy], }) export class UsersModule {} diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 11e0e61..126f87e 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -3,11 +3,14 @@ import { CreateUserDto } from './dto'; import { JwtPayload } from 'src/common/types/type'; import { UsersRepository } from './users.repository'; import { AuthService } from '../auth/auth.service'; +import { FolderRepository } from '../folders/folders.repository'; +import { FolderType } from '@src/infrastructure/database/types/folder-type.enum'; @Injectable() export class UsersService { constructor( private readonly userRepository: UsersRepository, + private readonly folderRepository: FolderRepository, private readonly authService: AuthService, ) {} @@ -20,6 +23,13 @@ export class UsersService { }; // JWT Token 발급 const token = await this.authService.issueAccessToken(tokenPayload); + + await this.folderRepository.create( + user._id.toString(), + '나중에 읽을 링크', + FolderType.DEFAULT, + ); + return token; } }