Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/auth/oauth.v2.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export class OauthV2Controller {
return this.oauthService.googleOauth(oauthRequest);
}

// @ApiOperation({
// summary: '애플 로그인',
// description:
// '애플 로그인 메서드. (회원가입이 안되어 있으면 회원가입 처리 후 로그인 처리)',
// })
// @Get('apple-login')
// async appleLogin(
// @Body() oauthRequest: OAuthLoginRequest,
// ): Promise<LoginOutput> {
// return this.oauthService.appleLogin(oauthRequest);
// }
@ApiOperation({
summary: '애플 로그인',
description:
'애플 로그인 메서드. (회원가입이 안되어 있으면 회원가입 처리 후 로그인 처리)',
})
@Post('apple')
async appleLogin(
@Body() oauthRequest: OAuthLoginRequest,
): Promise<LoginOutput> {
return this.oauthService.appleLogin(oauthRequest);
}
}
27 changes: 10 additions & 17 deletions src/auth/oauth.v2.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
BadRequestException,
Injectable,
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';
import { LoginOutput } from './dtos/login.dto';
Expand All @@ -22,6 +21,10 @@ import { ConfigService } from '@nestjs/config';

@Injectable()
export class OAuthV2Service {
private readonly googleClient = new OAuth2Client(
this.configService.get('GOOGLE_SECRET'),
);

constructor(
private readonly jwtService: customJwtService,
private readonly userRepository: UserRepository,
Expand All @@ -31,10 +34,6 @@ export class OAuthV2Service {
private readonly configService: ConfigService,
) {}

private readonly googleClient = new OAuth2Client(
this.configService.get('GOOGLE_SECRET'),
);

// OAuth Login
async oauthLogin(email: string, provider: PROVIDER): Promise<LoginOutput> {
try {
Expand Down Expand Up @@ -111,6 +110,7 @@ export class OAuthV2Service {
}

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

private encodePasswordFromEmail(email: string, key?: string): string {
return CryptoJS.SHA256(email + key).toString();
}

public async appleLogin(code: string) {
public async appleLogin({ authorizationToken: code }: OAuthLoginRequest) {
const data = await this.oauthUtil.getAppleToken(code);

if (!data.id_token) {
throw new InternalServerErrorException(
`No token: ${JSON.stringify(data)}`,
);
}

const { sub: id, email } = this.jwtService.decode(data.id_token);

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

return this.oauthLogin(newUser.email, PROVIDER.APPLE);
}

private encodePasswordFromEmail(email: string, key?: string): string {
return CryptoJS.SHA256(email + key).toString();
}
}
22 changes: 22 additions & 0 deletions src/categories/category.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export class CategoryRepository extends Repository<Category> {
user: { id: userId },
},
relations: ['contents'],
order: {
createdAt: 'desc',
},
});
}

Expand All @@ -143,4 +146,23 @@ export class CategoryRepository extends Repository<Category> {
},
});
}

async findByParentId(
parentId: number,
entityManager?: EntityManager,
): Promise<Category[]> {
if (entityManager) {
return entityManager.find(Category, {
where: {
parentId,
},
});
}

return await this.find({
where: {
parentId,
},
});
}
}
24 changes: 8 additions & 16 deletions src/categories/category.service.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
import {
Injectable,
NotFoundException,
ConflictException,
InternalServerErrorException,
Inject,
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { EntityManager } from 'typeorm';
import {
AddCategoryBodyDto,
AddCategoryOutput,
UpdateCategoryBodyDto,
UpdateCategoryOutput,
DeleteCategoryOutput,
RecentCategoryList,
RecentCategoryListWithSaveCount,
UpdateCategoryBodyDto,
UpdateCategoryOutput,
} from './dtos/category.dto';
import {
LoadPersonalCategoriesOutput,
LoadFrequentCategoriesOutput,
} from './dtos/load-personal-categories.dto';
import { LoadFrequentCategoriesOutput, LoadPersonalCategoriesOutput, } from './dtos/load-personal-categories.dto';
import { Category } from './category.entity';
import { Content } from '../contents/entities/content.entity';
import { CategoryRepository } from './category.repository';
import { ContentRepository } from '../contents/repository/content.repository';
import { getLinkContent, getLinkInfo } from '../contents/util/content.util';
import { User } from '../users/entities/user.entity';
import { UserRepository } from '../users/repository/user.repository';
import {
generateCategoriesTree,
generateSlug,
loadLogs,
makeCategoryListWithSaveCount,
} from './utils/category.util';
import { generateCategoriesTree, generateSlug, loadLogs, makeCategoryListWithSaveCount, } from './utils/category.util';
import { Transactional } from '../common/aop/transactional';
import { AiService } from '../ai/ai.service';

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

try {
const categoryStr = await this.aiService.chat({
model: 'llama3-8b-8192',
model: 'llama-3.1-8b-instant',
messages: [{ role: 'user', content: question }],
temperature: 0,
responseType: 'json_object',
Expand Down
39 changes: 32 additions & 7 deletions src/contents/contents.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import {
Body,
Controller,
Delete,
Post,
Get,
Param,
UseGuards,
ParseIntPipe,
Patch,
Get,
UseInterceptors,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
Expand All @@ -24,10 +23,7 @@ import {
} from '@nestjs/swagger';
import { AuthUser } from '../auth/auth-user.decorator';
import { JwtAuthGuard } from '../auth/jwt/jwt.guard';
import { TransactionInterceptor } from '../common/interceptors/transaction.interceptor';
import { TransactionManager } from '../common/transaction.decorator';
import { User } from '../users/entities/user.entity';
import { EntityManager } from 'typeorm';
import { ContentsService } from './contents.service';
import {
AddContentBodyDto,
Expand All @@ -38,6 +34,7 @@ import {
toggleFavoriteOutput,
UpdateContentBodyDto,
UpdateContentOutput,
UpdateContentRequest,
} from './dtos/content.dto';
import { ErrorOutput } from '../common/dtos/output.dto';
import {
Expand Down Expand Up @@ -118,6 +115,34 @@ export class ContentsController {
return this.contentsService.updateContent(user, content);
}

@ApiOperation({
summary: '콘텐츠 정보 수정',
description: '콘텐츠을 수정하는 메서드',
})
@ApiCreatedResponse({
description: '콘텐츠 수정 성공 여부를 반환한다.',
type: UpdateContentOutput,
})
@ApiConflictResponse({
description: '동일한 링크의 콘텐츠가 같은 카테고리 내에 존재할 경우',
type: ErrorOutput,
})
@ApiNotFoundResponse({
description: '존재하지 않는 콘텐츠 또는 유저인 경우',
type: ErrorOutput,
})
@Patch(':contentId')
async updateContentV2(
@AuthUser() user: User,
@Body() content: UpdateContentRequest,
@Param('contentId') contentId: number,
): Promise<UpdateContentOutput> {
return this.contentsService.updateContent(user, {
...content,
id: contentId,
});
}

@ApiOperation({
summary: '즐겨찾기 등록 및 해제',
description: '즐겨찾기에 등록 및 해제하는 메서드',
Expand Down
98 changes: 64 additions & 34 deletions src/contents/contents.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
BadRequestException,
ConflictException,
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { DataSource, EntityManager } from 'typeorm';
import { DataSource, EntityManager, In, Not } from 'typeorm';

import {
AddContentBodyDto,
Expand Down Expand Up @@ -86,17 +87,23 @@ export class ContentsService {
const content = new Content();

if (categoryId) {
const category = await this.categoryRepository.findById(
categoryId,
entityManager,
);
const [category, subCategories] = await Promise.all([
(async () => {
const category = await this.categoryRepository.findById(categoryId);

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

await checkContentDuplicateAndAddCategorySaveLog(
link,
category,
userInDb,
return category;
})(),
this.categoryRepository.findByParentId(categoryId),
]);

await this.isDuplicatedContents(
[category, ...subCategories],
content.link,
content.id,
);

content.category = category;
Expand Down Expand Up @@ -184,8 +191,6 @@ export class ContentsService {
reminder,
favorite,
categoryId,
categoryName,
parentId,
}: UpdateContentBodyDto,
entityManager?: EntityManager,
): Promise<AddContentOutput> {
Expand All @@ -197,33 +202,38 @@ export class ContentsService {
reminder,
favorite,
};
const userInDb = await this.userRepository.findOneWithContentsAndCategories(
user.id,
);
if (!userInDb) {
throw new NotFoundException('User not found');
}

const content = userInDb?.contents?.filter(
(content) => content.id === contentId,
)[0];
const content = await this.contentRepository.findOne({
where: {
id: contentId,
},
relations: ['category'],
});

if (!content) {
throw new NotFoundException('Content not found.');
throw new NotFoundException('컨텐츠가 존재하지 않습니다.');
}

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

if (category) {
await checkContentDuplicateAndAddCategorySaveLog(
link,
category,
userInDb,
);
}
if (!category) {
throw new NotFoundException('카테고리가 존재하지 않습니다.');
}

return category;
})(),
this.categoryRepository.findByParentId(categoryId),
]);

await this.isDuplicatedContents(
[category, ...subCategories],
content.link,
content.id,
);

await this.contentRepository.updateOne(
{
Expand Down Expand Up @@ -471,4 +481,24 @@ export class ContentsService {
throw e;
}
}

private async isDuplicatedContents(
categories: Category[],
link: string,
id?: number,
) {
const existingContents = await this.contentRepository.find({
where: {
...(id && { id: Not(id) }),
category: {
id: In(categories.map((category) => category.id)),
},
link,
},
});

if (existingContents.length > 0) {
throw new ConflictException('이미 저장된 컨텐츠입니다.');
}
}
}
Loading
Loading