Skip to content

Commit a24079b

Browse files
authored
Merge pull request #407 from Quickchive/develop
Develop to main
2 parents 8b4d631 + a6cedd4 commit a24079b

File tree

8 files changed

+273
-90
lines changed

8 files changed

+273
-90
lines changed

src/auth/oauth.v2.controller.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ export class OauthV2Controller {
6161
return this.oauthService.googleOauth(oauthRequest);
6262
}
6363

64-
// @ApiOperation({
65-
// summary: '애플 로그인',
66-
// description:
67-
// '애플 로그인 메서드. (회원가입이 안되어 있으면 회원가입 처리 후 로그인 처리)',
68-
// })
69-
// @Get('apple-login')
70-
// async appleLogin(
71-
// @Body() oauthRequest: OAuthLoginRequest,
72-
// ): Promise<LoginOutput> {
73-
// return this.oauthService.appleLogin(oauthRequest);
74-
// }
64+
@ApiOperation({
65+
summary: '애플 로그인',
66+
description:
67+
'애플 로그인 메서드. (회원가입이 안되어 있으면 회원가입 처리 후 로그인 처리)',
68+
})
69+
@Post('apple')
70+
async appleLogin(
71+
@Body() oauthRequest: OAuthLoginRequest,
72+
): Promise<LoginOutput> {
73+
return this.oauthService.appleLogin(oauthRequest);
74+
}
7575
}

src/auth/oauth.v2.service.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
BadRequestException,
33
Injectable,
4-
InternalServerErrorException,
54
UnauthorizedException,
65
} from '@nestjs/common';
76
import { LoginOutput } from './dtos/login.dto';
@@ -22,6 +21,10 @@ import { ConfigService } from '@nestjs/config';
2221

2322
@Injectable()
2423
export class OAuthV2Service {
24+
private readonly googleClient = new OAuth2Client(
25+
this.configService.get('GOOGLE_SECRET'),
26+
);
27+
2528
constructor(
2629
private readonly jwtService: customJwtService,
2730
private readonly userRepository: UserRepository,
@@ -31,10 +34,6 @@ export class OAuthV2Service {
3134
private readonly configService: ConfigService,
3235
) {}
3336

34-
private readonly googleClient = new OAuth2Client(
35-
this.configService.get('GOOGLE_SECRET'),
36-
);
37-
3837
// OAuth Login
3938
async oauthLogin(email: string, provider: PROVIDER): Promise<LoginOutput> {
4039
try {
@@ -111,6 +110,7 @@ export class OAuthV2Service {
111110
}
112111

113112
// Login with Google account info
113+
// @todo 액세스 토큰 파싱하는 부분 추상화
114114
async googleOauth({
115115
authorizationToken,
116116
}: OAuthLoginRequest): Promise<LoginOutput> {
@@ -150,19 +150,8 @@ export class OAuthV2Service {
150150
return this.oauthLogin(newUser.email, PROVIDER.GOOGLE);
151151
}
152152

153-
private encodePasswordFromEmail(email: string, key?: string): string {
154-
return CryptoJS.SHA256(email + key).toString();
155-
}
156-
157-
public async appleLogin(code: string) {
153+
public async appleLogin({ authorizationToken: code }: OAuthLoginRequest) {
158154
const data = await this.oauthUtil.getAppleToken(code);
159-
160-
if (!data.id_token) {
161-
throw new InternalServerErrorException(
162-
`No token: ${JSON.stringify(data)}`,
163-
);
164-
}
165-
166155
const { sub: id, email } = this.jwtService.decode(data.id_token);
167156

168157
const user = await this.userRepository.findOneByEmailAndProvider(
@@ -189,4 +178,8 @@ export class OAuthV2Service {
189178

190179
return this.oauthLogin(newUser.email, PROVIDER.APPLE);
191180
}
181+
182+
private encodePasswordFromEmail(email: string, key?: string): string {
183+
return CryptoJS.SHA256(email + key).toString();
184+
}
192185
}

src/categories/category.repository.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export class CategoryRepository extends Repository<Category> {
133133
user: { id: userId },
134134
},
135135
relations: ['contents'],
136+
order: {
137+
createdAt: 'desc',
138+
},
136139
});
137140
}
138141

@@ -143,4 +146,23 @@ export class CategoryRepository extends Repository<Category> {
143146
},
144147
});
145148
}
149+
150+
async findByParentId(
151+
parentId: number,
152+
entityManager?: EntityManager,
153+
): Promise<Category[]> {
154+
if (entityManager) {
155+
return entityManager.find(Category, {
156+
where: {
157+
parentId,
158+
},
159+
});
160+
}
161+
162+
return await this.find({
163+
where: {
164+
parentId,
165+
},
166+
});
167+
}
146168
}

src/categories/category.service.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,29 @@
11
import {
2-
Injectable,
3-
NotFoundException,
42
ConflictException,
5-
InternalServerErrorException,
63
Inject,
4+
Injectable,
5+
InternalServerErrorException,
6+
NotFoundException,
77
} from '@nestjs/common';
88
import { EntityManager } from 'typeorm';
99
import {
1010
AddCategoryBodyDto,
1111
AddCategoryOutput,
12-
UpdateCategoryBodyDto,
13-
UpdateCategoryOutput,
1412
DeleteCategoryOutput,
1513
RecentCategoryList,
1614
RecentCategoryListWithSaveCount,
15+
UpdateCategoryBodyDto,
16+
UpdateCategoryOutput,
1717
} from './dtos/category.dto';
18-
import {
19-
LoadPersonalCategoriesOutput,
20-
LoadFrequentCategoriesOutput,
21-
} from './dtos/load-personal-categories.dto';
18+
import { LoadFrequentCategoriesOutput, LoadPersonalCategoriesOutput, } from './dtos/load-personal-categories.dto';
2219
import { Category } from './category.entity';
2320
import { Content } from '../contents/entities/content.entity';
2421
import { CategoryRepository } from './category.repository';
2522
import { ContentRepository } from '../contents/repository/content.repository';
2623
import { getLinkContent, getLinkInfo } from '../contents/util/content.util';
2724
import { User } from '../users/entities/user.entity';
2825
import { UserRepository } from '../users/repository/user.repository';
29-
import {
30-
generateCategoriesTree,
31-
generateSlug,
32-
loadLogs,
33-
makeCategoryListWithSaveCount,
34-
} from './utils/category.util';
26+
import { generateCategoriesTree, generateSlug, loadLogs, makeCategoryListWithSaveCount, } from './utils/category.util';
3527
import { Transactional } from '../common/aop/transactional';
3628
import { AiService } from '../ai/ai.service';
3729

@@ -477,7 +469,7 @@ Present your reply options in JSON format below.
477469

478470
try {
479471
const categoryStr = await this.aiService.chat({
480-
model: 'llama3-8b-8192',
472+
model: 'llama-3.1-8b-instant',
481473
messages: [{ role: 'user', content: question }],
482474
temperature: 0,
483475
responseType: 'json_object',

src/contents/contents.controller.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import {
22
Body,
33
Controller,
44
Delete,
5-
Post,
5+
Get,
66
Param,
7-
UseGuards,
87
ParseIntPipe,
98
Patch,
10-
Get,
11-
UseInterceptors,
9+
Post,
1210
Query,
11+
UseGuards,
1312
} from '@nestjs/common';
1413
import {
1514
ApiBadRequestResponse,
@@ -24,10 +23,7 @@ import {
2423
} from '@nestjs/swagger';
2524
import { AuthUser } from '../auth/auth-user.decorator';
2625
import { JwtAuthGuard } from '../auth/jwt/jwt.guard';
27-
import { TransactionInterceptor } from '../common/interceptors/transaction.interceptor';
28-
import { TransactionManager } from '../common/transaction.decorator';
2926
import { User } from '../users/entities/user.entity';
30-
import { EntityManager } from 'typeorm';
3127
import { ContentsService } from './contents.service';
3228
import {
3329
AddContentBodyDto,
@@ -38,6 +34,7 @@ import {
3834
toggleFavoriteOutput,
3935
UpdateContentBodyDto,
4036
UpdateContentOutput,
37+
UpdateContentRequest,
4138
} from './dtos/content.dto';
4239
import { ErrorOutput } from '../common/dtos/output.dto';
4340
import {
@@ -118,6 +115,34 @@ export class ContentsController {
118115
return this.contentsService.updateContent(user, content);
119116
}
120117

118+
@ApiOperation({
119+
summary: '콘텐츠 정보 수정',
120+
description: '콘텐츠을 수정하는 메서드',
121+
})
122+
@ApiCreatedResponse({
123+
description: '콘텐츠 수정 성공 여부를 반환한다.',
124+
type: UpdateContentOutput,
125+
})
126+
@ApiConflictResponse({
127+
description: '동일한 링크의 콘텐츠가 같은 카테고리 내에 존재할 경우',
128+
type: ErrorOutput,
129+
})
130+
@ApiNotFoundResponse({
131+
description: '존재하지 않는 콘텐츠 또는 유저인 경우',
132+
type: ErrorOutput,
133+
})
134+
@Patch(':contentId')
135+
async updateContentV2(
136+
@AuthUser() user: User,
137+
@Body() content: UpdateContentRequest,
138+
@Param('contentId') contentId: number,
139+
): Promise<UpdateContentOutput> {
140+
return this.contentsService.updateContent(user, {
141+
...content,
142+
id: contentId,
143+
});
144+
}
145+
121146
@ApiOperation({
122147
summary: '즐겨찾기 등록 및 해제',
123148
description: '즐겨찾기에 등록 및 해제하는 메서드',

src/contents/contents.service.ts

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {
22
BadRequestException,
3+
ConflictException,
34
ForbiddenException,
45
Injectable,
56
NotFoundException,
67
} from '@nestjs/common';
7-
import { DataSource, EntityManager } from 'typeorm';
8+
import { DataSource, EntityManager, In, Not } from 'typeorm';
89

910
import {
1011
AddContentBodyDto,
@@ -86,17 +87,23 @@ export class ContentsService {
8687
const content = new Content();
8788

8889
if (categoryId) {
89-
const category = await this.categoryRepository.findById(
90-
categoryId,
91-
entityManager,
92-
);
90+
const [category, subCategories] = await Promise.all([
91+
(async () => {
92+
const category = await this.categoryRepository.findById(categoryId);
9393

94-
if (!category) throw new NotFoundException('Category not found');
94+
if (!category) {
95+
throw new NotFoundException('카테고리가 존재하지 않습니다.');
96+
}
9597

96-
await checkContentDuplicateAndAddCategorySaveLog(
97-
link,
98-
category,
99-
userInDb,
98+
return category;
99+
})(),
100+
this.categoryRepository.findByParentId(categoryId),
101+
]);
102+
103+
await this.isDuplicatedContents(
104+
[category, ...subCategories],
105+
content.link,
106+
content.id,
100107
);
101108

102109
content.category = category;
@@ -184,8 +191,6 @@ export class ContentsService {
184191
reminder,
185192
favorite,
186193
categoryId,
187-
categoryName,
188-
parentId,
189194
}: UpdateContentBodyDto,
190195
entityManager?: EntityManager,
191196
): Promise<AddContentOutput> {
@@ -197,33 +202,38 @@ export class ContentsService {
197202
reminder,
198203
favorite,
199204
};
200-
const userInDb = await this.userRepository.findOneWithContentsAndCategories(
201-
user.id,
202-
);
203-
if (!userInDb) {
204-
throw new NotFoundException('User not found');
205-
}
206205

207-
const content = userInDb?.contents?.filter(
208-
(content) => content.id === contentId,
209-
)[0];
206+
const content = await this.contentRepository.findOne({
207+
where: {
208+
id: contentId,
209+
},
210+
relations: ['category'],
211+
});
212+
210213
if (!content) {
211-
throw new NotFoundException('Content not found.');
214+
throw new NotFoundException('컨텐츠가 존재하지 않습니다.');
212215
}
213216

214-
if (categoryId !== undefined) {
215-
const category =
216-
categoryId !== null
217-
? await this.categoryRepository.findById(categoryId, entityManager)
218-
: null;
217+
// 카테고리 변경이 발생하는 경우
218+
if (categoryId && !content.isSameCategory(categoryId)) {
219+
const [category, subCategories] = await Promise.all([
220+
(async () => {
221+
const category = await this.categoryRepository.findById(categoryId);
219222

220-
if (category) {
221-
await checkContentDuplicateAndAddCategorySaveLog(
222-
link,
223-
category,
224-
userInDb,
225-
);
226-
}
223+
if (!category) {
224+
throw new NotFoundException('카테고리가 존재하지 않습니다.');
225+
}
226+
227+
return category;
228+
})(),
229+
this.categoryRepository.findByParentId(categoryId),
230+
]);
231+
232+
await this.isDuplicatedContents(
233+
[category, ...subCategories],
234+
content.link,
235+
content.id,
236+
);
227237

228238
await this.contentRepository.updateOne(
229239
{
@@ -471,4 +481,24 @@ export class ContentsService {
471481
throw e;
472482
}
473483
}
484+
485+
private async isDuplicatedContents(
486+
categories: Category[],
487+
link: string,
488+
id?: number,
489+
) {
490+
const existingContents = await this.contentRepository.find({
491+
where: {
492+
...(id && { id: Not(id) }),
493+
category: {
494+
id: In(categories.map((category) => category.id)),
495+
},
496+
link,
497+
},
498+
});
499+
500+
if (existingContents.length > 0) {
501+
throw new ConflictException('이미 저장된 컨텐츠입니다.');
502+
}
503+
}
474504
}

0 commit comments

Comments
 (0)