-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat : AI 분류 폴더 이름 리스트get api, AI 분류 링크 리스트 get api #26
Changes from 8 commits
8c60056
1ec8b47
2c99b7e
a5b1ac2
8a9d898
77580bd
e9abb46
63e5f58
ccf6809
68ade94
aea6e40
53e166c
09892b7
9764ef8
6f4f0f3
e4e0f1c
33f950a
0df80c4
bd3c19b
68e4b20
b3c8851
e1f50ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
export class BaseDocument { | ||
createdAt: string; | ||
createdAt: Date; | ||
|
||
updatedAt: string; | ||
updatedAt: Date; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -4,13 +4,14 @@ import { | |||||
Keyword, | ||||||
KeywordSchema, | ||||||
} from '@src/infrastructure/database/schema/keyword.schema'; | ||||||
import { BaseDocument } from './base.schema'; | ||||||
|
||||||
@Schema({ | ||||||
collection: 'post_ai_classifications', | ||||||
timestamps: true, | ||||||
versionKey: false, | ||||||
}) | ||||||
export class PostAIClassification { | ||||||
export class PostAIClassification extends BaseDocument { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
@Prop({ required: true, type: MongooseSchema.Types.ObjectId, ref: 'Folder' }) | ||||||
suggestedFolderId: MongooseSchema.Types.ObjectId; | ||||||
|
||||||
|
@@ -20,13 +21,13 @@ export class PostAIClassification { | |||||
@Prop({ required: true }) | ||||||
description: string; | ||||||
|
||||||
@Prop({ required: true, type: [KeywordSchema] }) | ||||||
keywords: Keyword[]; | ||||||
@Prop({ required: true, type: [String] }) | ||||||
keywords: string[]; | ||||||
|
||||||
@Prop({ type: Date }) | ||||||
completedAt: Date; | ||||||
|
||||||
@Prop({ type: Date }) | ||||||
@Prop({ default: null }) | ||||||
deletedAt: Date; | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,37 @@ | ||||||
import { Controller, Get, UseGuards, Param } from '@nestjs/common'; | ||||||
import { ClassificationService } from './classification.service'; | ||||||
import { GetUser } from '@src/common'; | ||||||
import { | ||||||
ClassificationControllerDocs, | ||||||
GetAIFolderNameListDocs, | ||||||
GetAIPostListDocs, | ||||||
} from './docs'; | ||||||
import { JwtGuard } from '../users/guards'; | ||||||
|
||||||
import { Types } from 'mongoose'; | ||||||
import { AIPostListResponse } from './dto/getAIPostList.dto'; | ||||||
import { AIFolderNameListResponse } from './dto/getAIFolderNameLIst.dto'; | ||||||
|
||||||
@Controller('ai') | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
@UseGuards(JwtGuard) | ||||||
@ClassificationControllerDocs | ||||||
export class ClassificationController { | ||||||
constructor(private readonly classificationService: ClassificationService) {} | ||||||
|
||||||
@Get('/suggestions') //TODO : 정렬 | ||||||
@GetAIFolderNameListDocs | ||||||
async getSuggestedFolderNameList(@GetUser('id') userId: Types.ObjectId) { | ||||||
const folderNames = | ||||||
await this.classificationService.getFolderNameList(userId); | ||||||
|
||||||
return new AIFolderNameListResponse(folderNames); | ||||||
} | ||||||
|
||||||
@Get('/suggestions/:folderId') | ||||||
@GetAIPostListDocs | ||||||
async getSuggestedPostList(@Param('folderId') folderId: string) { | ||||||
const posts = await this.classificationService.getPostList(folderId); | ||||||
|
||||||
return new AIPostListResponse(posts); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { ClassificationService } from './classification.service'; | ||
import { MongooseModule } from '@nestjs/mongoose'; | ||
import { | ||
Folder, | ||
FolderSchema, | ||
Post, | ||
PostAIClassification, | ||
PostAIClassificationSchema, | ||
PostSchema, | ||
} from '@src/infrastructure/database/schema'; | ||
|
||
import { ClassificationController } from './classification.controller'; | ||
|
||
@Module({ | ||
imports: [ | ||
MongooseModule.forFeature([ | ||
{ name: Folder.name, schema: FolderSchema }, | ||
{ name: PostAIClassification.name, schema: PostAIClassificationSchema }, | ||
{ name: Post.name, schema: PostSchema }, | ||
]), | ||
], | ||
controllers: [ClassificationController], | ||
providers: [ClassificationService], | ||
}) | ||
export class ClassificationModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { ClassificationService } from './classification.service'; | ||
|
||
describe('ClassificationService', () => { | ||
let service: ClassificationService; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ClassificationService], | ||
}).compile(); | ||
|
||
service = module.get<ClassificationService>(ClassificationService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,64 @@ | ||||||||||||||||||||||||||||||
import { Injectable } from '@nestjs/common'; | ||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||
Folder, | ||||||||||||||||||||||||||||||
FolderDocument, | ||||||||||||||||||||||||||||||
Post, | ||||||||||||||||||||||||||||||
PostAIClassification, | ||||||||||||||||||||||||||||||
} from '@src/infrastructure/database/schema'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import { InjectModel } from '@nestjs/mongoose'; | ||||||||||||||||||||||||||||||
import { Model, Types } from 'mongoose'; | ||||||||||||||||||||||||||||||
import { AIFolderNameServiceDto } from './dto/getAIFolderNameLIst.dto'; | ||||||||||||||||||||||||||||||
import { AIPostServiceDto } from './dto/getAIPostList.dto'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
@Injectable() | ||||||||||||||||||||||||||||||
export class ClassificationService { | ||||||||||||||||||||||||||||||
constructor( | ||||||||||||||||||||||||||||||
@InjectModel(Folder.name) private folderModel: Model<Folder>, | ||||||||||||||||||||||||||||||
@InjectModel(PostAIClassification.name) | ||||||||||||||||||||||||||||||
private postAiClassificationModel: Model<PostAIClassification>, | ||||||||||||||||||||||||||||||
@InjectModel(Post.name) private postModel: Model<Post>, | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이부분 나중에 repo로 빼보면 좋을 거 같당 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오키!! |
||||||||||||||||||||||||||||||
) {} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
async getFolderNameList( | ||||||||||||||||||||||||||||||
userId: Types.ObjectId, | ||||||||||||||||||||||||||||||
): Promise<AIFolderNameServiceDto[]> { | ||||||||||||||||||||||||||||||
const folders = await this.folderModel.find({ userId }).exec(); | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거 내가 folderRepo에 findByUserId라는 메소드로 만들어놨으~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오빠꺼 아직 머지 안되서 머지되면 수정할께! |
||||||||||||||||||||||||||||||
const folderIds = folders.map((folder) => folder._id); | ||||||||||||||||||||||||||||||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. **(/¯◡‿◡)/¯✧·˚ : *✧·˚ : *** https://www.zerocho.com/category/MongoDB/post/59bd148b1474c800194b695a
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 땡큐 |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const classifications = await this.postAiClassificationModel | ||||||||||||||||||||||||||||||
.find({ suggestedFolderId: { $in: folderIds } }) | ||||||||||||||||||||||||||||||
.exec(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const uniqueFolderIds = [ | ||||||||||||||||||||||||||||||
...new Set( | ||||||||||||||||||||||||||||||
classifications.map((classification) => | ||||||||||||||||||||||||||||||
classification.suggestedFolderId.toString(), | ||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ˵ •̀ ᴗ - ˵ ) ✧
Suggested change
폴더 아이디를 중복을 제외하고 가져오고 싶으면 Set 하지말고 쿼리에서 distinct 쓰는게 맞을 것 같아~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오키! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const matchedFolders = await this.folderModel | ||||||||||||||||||||||||||||||
.find({ _id: { $in: uniqueFolderIds } }) | ||||||||||||||||||||||||||||||
.exec(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return matchedFolders.map((folder) => new AIFolderNameServiceDto(folder)); | ||||||||||||||||||||||||||||||
J-Hoplin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
async getPostList(folderId: string): Promise<AIPostServiceDto[]> { | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혜온아 이부분도 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||
const posts = await this.postModel | ||||||||||||||||||||||||||||||
.find({ folderId }) | ||||||||||||||||||||||||||||||
.populate<{ | ||||||||||||||||||||||||||||||
aiClassificationId: PostAIClassification; | ||||||||||||||||||||||||||||||
}>({ | ||||||||||||||||||||||||||||||
path: 'aiClassificationId', | ||||||||||||||||||||||||||||||
match: { deletedAt: null }, // deletedAt이 null인 것만 필터링 | ||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||
.sort({ createdAt: -1 }) | ||||||||||||||||||||||||||||||
.exec(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return posts | ||||||||||||||||||||||||||||||
.filter((post) => post.aiClassificationId) | ||||||||||||||||||||||||||||||
.map((post) => new AIPostServiceDto(post)); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { applyDecorators } from '@nestjs/common'; | ||
import { ApiTags } from '@nestjs/swagger'; | ||
|
||
export const ClassificationControllerDocs = applyDecorators( | ||
ApiTags('AI classification API'), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { applyDecorators } from '@nestjs/common'; | ||
import { ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger'; | ||
import { AIFolderNameListResponse } from '../dto/getAIFolderNameLIst.dto'; | ||
|
||
export const GetAIFolderNameListDocs = applyDecorators( | ||
ApiOperation({ | ||
summary: '폴더 리스트', | ||
description: 'AI 분류 폴더 리스트.', | ||
}), | ||
ApiResponse({ | ||
type: AIFolderNameListResponse, | ||
}), | ||
ApiBearerAuth(), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { applyDecorators } from '@nestjs/common'; | ||
import { ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger'; | ||
import { AIPostListResponse } from '../dto/getAIPostList.dto'; | ||
|
||
export const GetAIPostListDocs = applyDecorators( | ||
ApiOperation({ | ||
summary: '폴더 안에 들어있는 Post(링크) 리스트', | ||
description: 'AI 분류 추천된 링크 리스트.', | ||
}), | ||
ApiResponse({ | ||
type: AIPostListResponse, | ||
}), | ||
ApiBearerAuth(), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './controller.docs'; | ||
export * from './getAIFolderNameList.docs'; | ||
export * from './getAIPostList.docs'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { FolderDocument } from '@src/infrastructure'; | ||
import { IsNotEmpty, IsString } from 'class-validator'; | ||
|
||
export class AIFolderNameServiceDto { | ||
@ApiProperty({ description: '폴더 id' }) | ||
@IsNotEmpty() | ||
@IsString() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (˵ •̀ ᴗ - ˵ ) ✧ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서비스 dto에서 스웨거 관련 데코레이터도 제거해? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
id: string; | ||
|
||
@ApiProperty({ description: '폴더 이름' }) | ||
@IsNotEmpty() | ||
@IsString() | ||
name: string; | ||
|
||
constructor(data: FolderDocument) { | ||
(this.id = data._id.toString()), (this.name = data.name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분 먼가 포매팅이 잘못 된 거 같당 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오키! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
} | ||
|
||
export class AIFolderNameListResponse { | ||
@ApiProperty({ | ||
type: AIFolderNameServiceDto, | ||
isArray: true, | ||
}) | ||
list: AIFolderNameServiceDto[]; | ||
|
||
constructor(data: AIFolderNameServiceDto[]) { | ||
this.list = data; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { IsNotEmpty, IsString, IsArray, IsDate } from 'class-validator'; | ||
import { Type } from 'class-transformer'; | ||
import { Post, PostAIClassification, PostDocument } from '@src/infrastructure'; | ||
import { Document } from 'mongoose'; | ||
import { MergeType } from 'mongoose'; | ||
import { Types } from 'mongoose'; | ||
|
||
type InputType = Document< | ||
unknown, | ||
{}, | ||
MergeType< | ||
Post, | ||
{ | ||
aiClassificationId: PostAIClassification; | ||
} | ||
> | ||
> & | ||
Omit<Post, 'aiClassificationId'> & { | ||
aiClassificationId: PostAIClassification; | ||
} & { | ||
_id: Types.ObjectId; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (˵ •̀ ᴗ - ˵ ) ✧ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
export class AIPostServiceDto { | ||
@ApiProperty({ description: 'Title' }) | ||
@IsNotEmpty() | ||
@IsString() | ||
title: string; | ||
|
||
@ApiProperty({ description: 'URL' }) | ||
@IsNotEmpty() | ||
@IsString() | ||
url: string; | ||
|
||
@ApiProperty({ description: 'Description' }) | ||
@IsNotEmpty() | ||
@IsString() | ||
description: string; | ||
|
||
@ApiProperty({ description: 'Keywords' }) | ||
@IsArray() | ||
keywords: string[]; | ||
|
||
@ApiProperty({ description: 'Created At' }) | ||
@IsString() | ||
createdAt: Date; | ||
|
||
constructor(data: InputType) { | ||
this.title = data.title; | ||
this.url = data.url; | ||
this.description = data.description; | ||
this.keywords = data.aiClassificationId.keywords; | ||
this.createdAt = data.aiClassificationId.createdAt; | ||
} | ||
} | ||
|
||
export class AIPostListResponse { | ||
@ApiProperty({ type: [AIPostServiceDto] }) | ||
@Type(() => AIPostServiceDto) | ||
list: AIPostServiceDto[]; | ||
|
||
constructor(data: AIPostServiceDto[]) { | ||
this.list = data; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (˵ •̀ ᴗ - ˵ ) ✧ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(˵ •̀ ᴗ - ˵ ) ✧
사진은 프론트에서 url 전달주면 opengraph로 가져오기로함! 필드 없어도 돼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오키!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aea6e40