From 318723aca2c01e567f46403293c3dbd6fa54e7b3 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Mon, 21 Aug 2023 20:31:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat(books):=20=EB=8F=84=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 도서 추천 컨트롤러 추가 - books.routes.ts에서 도서 추천 라우터 순서 변경 - 도서 추천 컨트롤러 내에서 42 API 통신 테스트 --- backend/src/v1/books/books.controller.ts | 43 +++++- backend/src/v1/routes/books.routes.ts | 189 ++++++++++++----------- 2 files changed, 135 insertions(+), 97 deletions(-) diff --git a/backend/src/v1/books/books.controller.ts b/backend/src/v1/books/books.controller.ts index 290da04d..aad21182 100644 --- a/backend/src/v1/books/books.controller.ts +++ b/backend/src/v1/books/books.controller.ts @@ -7,13 +7,17 @@ import { logger } from '~/logger'; import * as errorCode from '~/v1/utils/error/errorCode'; import ErrorResponse from '~/v1/utils/error/errorResponse'; import isNullish from '~/v1/utils/isNullish'; +import * as parseCheck from '~/v1/utils/parseCheck'; +import { fetchApi } from '@ts-rest/core'; +import { verify } from 'jsonwebtoken'; +import { jwtOption } from '~/config'; +import axios from 'axios'; import * as BooksService from './books.service'; import * as types from './books.type'; import LikesService from './likes.service'; import { searchSchema } from '../users/users.types'; import { User } from '../DTO/users.model'; import UsersService from '../users/users.service'; -import * as parseCheck from '~/v1/utils/parseCheck'; const likesService = new LikesService(); const usersService = new UsersService(); @@ -187,7 +191,7 @@ export const getInfoId: RequestHandler = async ( ) => { const id = parseInt(String(req.params.id), 10); if (Number.isNaN(id)) { - return next(new ErrorResponse(errorCode.INVALID_INPUT, status.BAD_REQUEST)); + // return next(new ErrorResponse(errorCode.INVALID_INPUT, status.BAD_REQUEST)); } try { const bookInfo = await BooksService.getInfo(req.params.id); @@ -443,7 +447,7 @@ export const updateBookDonator = async ( donatorId: user.id, donator: user.nickname, }; - + if (bookDonator.id <= 0 || Number.isNaN(bookDonator.id)) { return next(new ErrorResponse(errorCode.INVALID_INPUT, status.BAD_REQUEST)); } @@ -462,3 +466,36 @@ export const updateBookDonator = async ( } return 0; }; + +export const recommandBook = async ( + req: Request, + res: Response, +) => { + const tokenURL = 'https://api.intra.42.fr/oauth/token'; + const queryString = `grant_type=client_credentials&client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&redirect_uri=${process.env.REDIRECT_URI}`; + const path = 'https://api.intra.42.fr/v2/cursus'; + let accessToken; + await axios(tokenURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + data: queryString, + }).then((response) => { + accessToken = response.data.access_token; + }).catch((error) => { + console.log(error); + }); + + await axios(path, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }).then((response) => { + console.log(response.data); + }).catch((error) => { + console.log(error); + }); + res.status(status.OK).send(); +}; diff --git a/backend/src/v1/routes/books.routes.ts b/backend/src/v1/routes/books.routes.ts index 0e274640..e9a23de1 100644 --- a/backend/src/v1/routes/books.routes.ts +++ b/backend/src/v1/routes/books.routes.ts @@ -13,6 +13,7 @@ import { getLikeInfo, updateBookInfo, updateBookDonator, + recommandBook, } from '~/v1/books/books.controller'; import authValidate from '~/v1/auth/auth.validate'; import authValidateDefaultNullUser from '~/v1/auth/auth.validateDefaultNullUser'; @@ -21,6 +22,100 @@ import { roleSet } from '~/v1/auth/auth.type'; export const path = '/books'; export const router = Router(); +router + /** + * @openapi + * /api/books/recommand: + * get: + * summary: 서클 별 추천 도서를 가져온다 + * description: 사용자가 속한 서클의 추천 도서를 가져온다 + * tags: + * - books + * parameters: + * - name: limit + * in: query + * description: 가져올 추천 도서의 개수 + * schema: + * type: integer + * example: 4 + * - name: type + * in: query + * description: 이너 서클과 아우터 서클을 구분한다 + * schema: + * type: string + * enum: [inner, outer] + * - name: circleOrSubject + * in: query + * description: 서클 또는 과제 명을 받아온다. + * schema: + * type: string + * example: Libft + * responses: + * '200': + * description: 서클 별 추천 도서의 정보를 가져온다. + * content: + * application/json: + * schema: + * type: object + * properties: + * items: + * description: 추천 도서들의 목록 + * type: array + * items: + * type: object + * properties: + * id: + * description: DB 상의 book_info.id + * type: integer + * example: 42 + * title: + * description: 도서 제목 + * type: string + * example: "시작하세요! 도커/쿠버네티스" + * author: + * description: 저자 + * type: string + * example: 용찬호 + * publisher: + * description: 출판사 + * type: string + * example: 위키북스 + * image: + * description: 표지 사진 + * type: string + * example: https://image.kyobobook.co.kr/images/book/xlarge/394/x9791195982394.jpg + * publishedAt: + * description: 출판일자 + * type: string + * format: date + * example: 2020-01-31 + * subjects: + * description: 도서와 관련된 과제들 + * type: array + * example: ["Inception", "Inception-of-Things"] + * meta: + * description: 드롭다운에서 선택할 수 있는 서클과 과제 정보 + * type: array + * example: ["0서클 | Libft", "1서클 | ft_printf", ..., "Outer | ft_ping"] + * '400': + * description: x 서클에서 y 과제의 추천 도서를 가져오려고 했는데, x 서클에 y 과제가 없다. + * content: + * application/json: + * schema: + * type: json + * description: error decription + * example: { errorCode: 400 } + * '500': + * description: 서버 오류 + * content: + * application/json: + * schema: + * type: json + * description: error decription + * example: { errorCode: 500 } + */ + .get('/recommand', recommandBook); + router /** * @openapi @@ -1102,97 +1197,3 @@ router */ .patch('/update', authValidate(roleSet.librarian), updateBookInfo) .patch('/donator', authValidate(roleSet.librarian), updateBookDonator); - - router - /** - * @openapi - * /api/books/recommand: - * get: - * summary: 서클 별 추천 도서를 가져온다 - * description: 사용자가 속한 서클의 추천 도서를 가져온다 - * tags: - * - books - * parameters: - * - name: limit - * in: query - * description: 가져올 추천 도서의 개수 - * schema: - * type: integer - * example: 4 - * - name: type - * in: query - * description: 이너 서클과 아우터 서클을 구분한다 - * schema: - * type: string - * enum: [inner, outer] - * - name: circleOrSubject - * in: query - * description: 서클 또는 과제 명을 받아온다. - * schema: - * type: string - * example: Libft - * responses: - * '200': - * description: 서클 별 추천 도서의 정보를 가져온다. - * content: - * application/json: - * schema: - * type: object - * properties: - * items: - * description: 추천 도서들의 목록 - * type: array - * items: - * type: object - * properties: - * id: - * description: DB 상의 book_info.id - * type: integer - * example: 42 - * title: - * description: 도서 제목 - * type: string - * example: "시작하세요! 도커/쿠버네티스" - * author: - * description: 저자 - * type: string - * example: 용찬호 - * publisher: - * description: 출판사 - * type: string - * example: 위키북스 - * image: - * description: 표지 사진 - * type: string - * example: https://image.kyobobook.co.kr/images/book/xlarge/394/x9791195982394.jpg - * publishedAt: - * description: 출판일자 - * type: string - * format: date - * example: 2020-01-31 - * subjects: - * description: 도서와 관련된 과제들 - * type: array - * example: ["Inception", "Inception-of-Things"] - * meta: - * description: 드롭다운에서 선택할 수 있는 서클과 과제 정보 - * type: array - * example: ["0서클 | Libft", "1서클 | ft_printf", ..., "Outer | ft_ping"] - * '400': - * description: x 서클에서 y 과제의 추천 도서를 가져오려고 했는데, x 서클에 y 과제가 없다. - * content: - * application/json: - * schema: - * type: json - * description: error decription - * example: { errorCode: 400 } - * '500': - * description: 서버 오류 - * content: - * application/json: - * schema: - * type: json - * description: error decription - * example: { errorCode: 500 } - */ - .get('/recommand'/* , recommand */); From 8ca455fbd65a293ff250b15c9bacb601f11c854a Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:12:50 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat(books):=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EB=82=B4=20=ED=95=A8=EC=88=98=EB=A5=BC=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - access token을 가져오는 함수 서비스에 구현 - user의 42 API 상 id를 가져오는 함수 서비스에 구현 --- backend/src/v1/books/books.controller.ts | 33 +++------------ backend/src/v1/books/books.service.ts | 53 +++++++++++++++++++++++- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/backend/src/v1/books/books.controller.ts b/backend/src/v1/books/books.controller.ts index aad21182..4073406a 100644 --- a/backend/src/v1/books/books.controller.ts +++ b/backend/src/v1/books/books.controller.ts @@ -9,7 +9,7 @@ import ErrorResponse from '~/v1/utils/error/errorResponse'; import isNullish from '~/v1/utils/isNullish'; import * as parseCheck from '~/v1/utils/parseCheck'; import { fetchApi } from '@ts-rest/core'; -import { verify } from 'jsonwebtoken'; +import { JwtPayload, verify } from 'jsonwebtoken'; import { jwtOption } from '~/config'; import axios from 'axios'; import * as BooksService from './books.service'; @@ -471,31 +471,10 @@ export const recommandBook = async ( req: Request, res: Response, ) => { - const tokenURL = 'https://api.intra.42.fr/oauth/token'; - const queryString = `grant_type=client_credentials&client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&redirect_uri=${process.env.REDIRECT_URI}`; - const path = 'https://api.intra.42.fr/v2/cursus'; - let accessToken; - await axios(tokenURL, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - data: queryString, - }).then((response) => { - accessToken = response.data.access_token; - }).catch((error) => { - console.log(error); - }); - - await axios(path, { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }).then((response) => { - console.log(response.data); - }).catch((error) => { - console.log(error); - }); + // TODO => 사용자의 과제 정보 가져오는 서비스 함수 호출하게 분리 + const accessToken: string = await BooksService.getAccessToken(); + const userId: string = await BooksService.getUserIdFrom42API(accessToken); + const userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); + console.log(userId); res.status(status.OK).send(); }; diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index c666eaef..86a95134 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -7,6 +7,7 @@ import { logger } from '~/logger'; import { executeQuery } from '~/mysql'; import * as errorCode from '~/v1/utils/error/errorCode'; import { StringRows } from '~/v1/utils/types'; +import { VSearchBookByTag } from '~/entity/entities'; import * as models from './books.model'; import BooksRepository from './books.repository'; import { @@ -14,7 +15,6 @@ import { categoryIds, UpdateBookDonator, } from './books.type'; import { categoryWithBookCount } from '../DTO/common.interface'; -import { VSearchBookByTag } from '~/entity/entities'; const getInfoInNationalLibrary = async (isbn: string) => { let book; @@ -425,4 +425,53 @@ export const updateBook = async (book: UpdateBook) => { export const updateBookDonator = async (bookDonator: UpdateBookDonator) => { const booksRepository = new BooksRepository(); await booksRepository.updateBookDonator(bookDonator); -} +}; + +export const getAccessToken = async (): Promise => { + const tokenURL = 'https://api.intra.42.fr/oauth/token'; + const queryString = { + grant_type: 'client_credentials', + client_id: process.env.CLIENT_ID, + client_secret: process.env.CLIENT_SECRET, + redirect_uri: process.env.REDIRECT_URL, + }; + let accessToken: string = ''; + await axios(tokenURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: queryString, + }).then((response) => { + accessToken = response.data.access_token; + }).catch((error) => { + console.log(error.message); + }); + return accessToken; +}; + +export const getUserIdFrom42API = async ( + accessToken: string, +): Promise => { + const userURL = 'https://api.intra.42.fr/v2/users'; + const queryString = 'filter[login]=yena'; + let userId: string = ''; + await axios(`${userURL}?${queryString}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + }).then((response) => { + userId = response.data[0].id; + }).catch((error) => { + console.log(error.message); + }); + return userId; +}; + +export const getUserProject = async ( + accessToken: string, + userId: string, +): Promise => { +}; From da118afe2b82ac23a529536c51a0006af7bca623 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:55:13 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat(books):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EA=B3=BC=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=AA=A8=EB=93=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자의 과제 정보를 가져오는 서비스 함수 추가 - 사용자의 과제 정보를 담을 DTO 정의(추후 schema로 변경) --- backend/src/v1/DTO/cursus.model.ts | 57 ++++++++++++++++++++++++ backend/src/v1/books/books.controller.ts | 3 +- backend/src/v1/books/books.service.ts | 30 ++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 backend/src/v1/DTO/cursus.model.ts diff --git a/backend/src/v1/DTO/cursus.model.ts b/backend/src/v1/DTO/cursus.model.ts new file mode 100644 index 00000000..fff0b72f --- /dev/null +++ b/backend/src/v1/DTO/cursus.model.ts @@ -0,0 +1,57 @@ +export type RawProject = { + id: number; + occurrence: number; + final_mark: number; + status: string; + 'validated?': boolean; + current_team_id: number; + project: { + id: number; + name: string; + slug: string; + parent_id: number; + }; + cursus_ids: number[]; + marked_at: string; + marked: boolean; + retriable_at: string; + created_at: string; + updated_at: string; + user: { + id: number; + email: string; + login: string; + first_name: string; + last_name: string; + usual_full_name: string; + usual_first_name: string; + url: string; + phone: string; + displayname: string; + kind: string; + image: object; + 'staff?': boolean; + correction_point: number; + pool_month: string; + pool_year: string; + location: string; + wallet: number; + anonymize_date: string; + data_erasure_date: string; + created_at: string; + updated_at: string; + alumnized_at: string; + 'alumni?': boolean; + 'active?': boolean; + }; + teams: object[]; +} + +export type Project = { + id: RawProject['id']; + status: RawProject['status']; + validated: RawProject['validated?']; + project: RawProject['project']; + cursus_ids: RawProject['cursus_ids']; + marked: RawProject['marked']; +} diff --git a/backend/src/v1/books/books.controller.ts b/backend/src/v1/books/books.controller.ts index 4073406a..3ef0dddf 100644 --- a/backend/src/v1/books/books.controller.ts +++ b/backend/src/v1/books/books.controller.ts @@ -471,10 +471,9 @@ export const recommandBook = async ( req: Request, res: Response, ) => { - // TODO => 사용자의 과제 정보 가져오는 서비스 함수 호출하게 분리 const accessToken: string = await BooksService.getAccessToken(); + // TODO => accessToken이 없을 경우를 분리해야 함 const userId: string = await BooksService.getUserIdFrom42API(accessToken); const userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); - console.log(userId); res.status(status.OK).send(); }; diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index 86a95134..0c3583a7 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -15,6 +15,7 @@ import { categoryIds, UpdateBookDonator, } from './books.type'; import { categoryWithBookCount } from '../DTO/common.interface'; +import { Project, RawProject } from '../DTO/cursus.model'; const getInfoInNationalLibrary = async (isbn: string) => { let book; @@ -454,6 +455,7 @@ export const getUserIdFrom42API = async ( accessToken: string, ): Promise => { const userURL = 'https://api.intra.42.fr/v2/users'; + // TODO => login 이름 변수로 받아오게 해야함 const queryString = 'filter[login]=yena'; let userId: string = ''; await axios(`${userURL}?${queryString}`, { @@ -470,8 +472,32 @@ export const getUserIdFrom42API = async ( return userId; }; -export const getUserProject = async ( +export const getUserProjectFrom42API = async ( accessToken: string, userId: string, -): Promise => { +): Promise => { + const projectURL = `https://api.intra.42.fr/v2/users/${userId}/projects_users`; + const userProject: Array = []; + await axios(projectURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + }).then((response) => { + const rawData: RawProject[] = response.data; + rawData.forEach((data: RawProject) => { + userProject.push({ + id: data.id, + status: data.status, + validated: data['validated?'], + project: data.project, + cursus_ids: data.cursus_ids, + marked: data.marked, + }); + }); + }).catch((error) => { + console.log(error.message); + }); + return userProject; }; From dbc3246c7a0ab15915261671b4780c18f9b4ee9b Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:08:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat(books):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20login=EC=9D=84=20=EC=9D=B8=EC=9E=90=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=A4=EA=B2=8C=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/books/books.controller.ts | 3 ++- backend/src/v1/books/books.service.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/v1/books/books.controller.ts b/backend/src/v1/books/books.controller.ts index 3ef0dddf..eb97bdb2 100644 --- a/backend/src/v1/books/books.controller.ts +++ b/backend/src/v1/books/books.controller.ts @@ -471,9 +471,10 @@ export const recommandBook = async ( req: Request, res: Response, ) => { + const { nickname: login } = req.user as any; const accessToken: string = await BooksService.getAccessToken(); // TODO => accessToken이 없을 경우를 분리해야 함 - const userId: string = await BooksService.getUserIdFrom42API(accessToken); + const userId: string = await BooksService.getUserIdFrom42API(accessToken, login); const userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); res.status(status.OK).send(); }; diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index 0c3583a7..30847ee2 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -453,10 +453,10 @@ export const getAccessToken = async (): Promise => { export const getUserIdFrom42API = async ( accessToken: string, + login: string, ): Promise => { const userURL = 'https://api.intra.42.fr/v2/users'; - // TODO => login 이름 변수로 받아오게 해야함 - const queryString = 'filter[login]=yena'; + const queryString = `filter[login]=${login}`; let userId: string = ''; await axios(`${userURL}?${queryString}`, { method: 'GET', From a15e2b18eea6bb1e9931d2b924bee892592b0dea Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:09:01 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat(books):=20authvalidate=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C,=20req.user=EC=97=90=20=EC=9C=A0=EC=A0=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EB=8B=B4=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/books.routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/v1/routes/books.routes.ts b/backend/src/v1/routes/books.routes.ts index e9a23de1..8f5314b5 100644 --- a/backend/src/v1/routes/books.routes.ts +++ b/backend/src/v1/routes/books.routes.ts @@ -114,7 +114,7 @@ router * description: error decription * example: { errorCode: 500 } */ - .get('/recommand', recommandBook); + .get('/recommand', authValidate(roleSet.all), recommandBook); router /** From 06f1b178b225fb461b7252b3fd48ba9dfd077e14 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:57:50 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20Project=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=97=90=20marked=5Fat=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/DTO/cursus.model.ts | 1 + backend/src/v1/books/books.service.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/src/v1/DTO/cursus.model.ts b/backend/src/v1/DTO/cursus.model.ts index fff0b72f..f7ac250e 100644 --- a/backend/src/v1/DTO/cursus.model.ts +++ b/backend/src/v1/DTO/cursus.model.ts @@ -54,4 +54,5 @@ export type Project = { project: RawProject['project']; cursus_ids: RawProject['cursus_ids']; marked: RawProject['marked']; + marked_at: RawProject['marked_at']; } diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index 30847ee2..0b7b1700 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -494,6 +494,7 @@ export const getUserProjectFrom42API = async ( project: data.project, cursus_ids: data.cursus_ids, marked: data.marked, + marked_at: data.marked_at, }); }); }).catch((error) => { From 5cd948b44483e6cacd02f53c9b4e7a3176883fe9 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:17:00 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat(books):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20project=20=EC=A0=95=EB=B3=B4=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자의 intraId를 DB에서 조회하게 변경 - access_token을 service의 전역변수로 설정 - access_token값이 만료될 경우, 재발급 후 42API와 통신 --- backend/src/v1/books/books.controller.ts | 27 +++++++++++++++------- backend/src/v1/books/books.service.ts | 29 +++++++++--------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/backend/src/v1/books/books.controller.ts b/backend/src/v1/books/books.controller.ts index eb97bdb2..6e56b388 100644 --- a/backend/src/v1/books/books.controller.ts +++ b/backend/src/v1/books/books.controller.ts @@ -8,19 +8,18 @@ import * as errorCode from '~/v1/utils/error/errorCode'; import ErrorResponse from '~/v1/utils/error/errorResponse'; import isNullish from '~/v1/utils/isNullish'; import * as parseCheck from '~/v1/utils/parseCheck'; -import { fetchApi } from '@ts-rest/core'; -import { JwtPayload, verify } from 'jsonwebtoken'; -import { jwtOption } from '~/config'; -import axios from 'axios'; import * as BooksService from './books.service'; import * as types from './books.type'; import LikesService from './likes.service'; import { searchSchema } from '../users/users.types'; import { User } from '../DTO/users.model'; import UsersService from '../users/users.service'; +import { Project } from '../DTO/cursus.model'; +import { get } from 'http'; const likesService = new LikesService(); const usersService = new UsersService(); +let accessToken: string; const pubdateFormatValidator = (pubdate: string | Date) => { const regexConditon = /^[0-9]{8}$/; @@ -470,11 +469,23 @@ export const updateBookDonator = async ( export const recommandBook = async ( req: Request, res: Response, + next: NextFunction, ) => { const { nickname: login } = req.user as any; - const accessToken: string = await BooksService.getAccessToken(); - // TODO => accessToken이 없을 경우를 분리해야 함 - const userId: string = await BooksService.getUserIdFrom42API(accessToken, login); - const userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); + let userProject: Project[]; + let userId: string; + if (login !== null && login !== undefined) { + userId = await BooksService.getIntraId(login); + try { + userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); + } catch (error: any) { + if (error.status === 401) { + accessToken = await BooksService.getAccessToken(); + userProject = await BooksService.getUserProjectFrom42API(accessToken, userId); + } else { + next(new ErrorResponse(errorCode.UNKNOWN_ERROR, status.INTERNAL_SERVER_ERROR)); + } + } + } res.status(status.OK).send(); }; diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index 0b7b1700..5cd429ac 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -16,6 +16,8 @@ import { } from './books.type'; import { categoryWithBookCount } from '../DTO/common.interface'; import { Project, RawProject } from '../DTO/cursus.model'; +import UsersRepository from '../users/users.repository'; +import ErrorResponse from '../utils/error/errorResponse'; const getInfoInNationalLibrary = async (isbn: string) => { let book; @@ -451,25 +453,12 @@ export const getAccessToken = async (): Promise => { return accessToken; }; -export const getUserIdFrom42API = async ( - accessToken: string, +export const getIntraId = async ( login: string, ): Promise => { - const userURL = 'https://api.intra.42.fr/v2/users'; - const queryString = `filter[login]=${login}`; - let userId: string = ''; - await axios(`${userURL}?${queryString}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, - }).then((response) => { - userId = response.data[0].id; - }).catch((error) => { - console.log(error.message); - }); - return userId; + const usersRepo = new UsersRepository(); + const user = (await usersRepo.searchUserBy({ nickname: login }, 1, 0))[0]; + return user[0].intraId.toString(); }; export const getUserProjectFrom42API = async ( @@ -498,7 +487,11 @@ export const getUserProjectFrom42API = async ( }); }); }).catch((error) => { - console.log(error.message); + if (error.response.status === 401) { + throw new ErrorResponse(errorCode.NO_TOKEN, 401); + } else { + throw new ErrorResponse(errorCode.UNKNOWN_ERROR, 500); + } }); return userProject; };