Skip to content

Commit

Permalink
feat: 42 API로부터 사용자의 과제 정보 받아오는 기능 구현 (#690)
Browse files Browse the repository at this point in the history
* feat(books): 도서 추천 컨트롤러 추가

- 도서 추천 컨트롤러 추가
- books.routes.ts에서 도서 추천 라우터 순서 변경
- 도서 추천 컨트롤러 내에서 42 API 통신 테스트

* feat(books): 컨트롤러 내 함수를 서비스 함수로 분리

- access token을 가져오는 함수 서비스에 구현
- user의 42 API 상 id를 가져오는 함수 서비스에 구현

* feat(books): 사용자의 과제 정보 가져오는 모듈 추가

- 사용자의 과제 정보를 가져오는 서비스 함수 추가
- 사용자의 과제 정보를 담을 DTO 정의(추후 schema로 변경)

* feat(books): 사용자 login을 인자로 받아 프로젝트 받아오게 함

* feat(books): authvalidate 호출, req.user에 유저 정보를 담음

* feat: Project 타입에 marked_at 추가

* feat(books): 사용자의 project 정보 가져오는 컨트롤러 및 서비스 수정

- 사용자의 intraId를 DB에서 조회하게 변경
- access_token을 service의 전역변수로 설정
- access_token값이 만료될 경우, 재발급 후 42API와 통신
  • Loading branch information
nyj001012 authored Aug 26, 2023
1 parent 8715139 commit 919fb4a
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 99 deletions.
58 changes: 58 additions & 0 deletions backend/src/v1/DTO/cursus.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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'];
marked_at: RawProject['marked_at'];
}
33 changes: 30 additions & 3 deletions backend/src/v1/books/books.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ 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 * 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';
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}$/;
Expand Down Expand Up @@ -187,7 +190,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);
Expand Down Expand Up @@ -443,7 +446,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));
}
Expand All @@ -462,3 +465,27 @@ export const updateBookDonator = async (
}
return 0;
};

export const recommandBook = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { nickname: login } = req.user as any;
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();
};
73 changes: 71 additions & 2 deletions backend/src/v1/books/books.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ 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 {
CreateBookInfo, LendingBookList, UpdateBook, UpdateBookInfo,
categoryIds, UpdateBookDonator,
} from './books.type';
import { categoryWithBookCount } from '../DTO/common.interface';
import { VSearchBookByTag } from '~/entity/entities';
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;
Expand Down Expand Up @@ -425,4 +428,70 @@ 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<string> => {
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 getIntraId = async (
login: string,
): Promise<string> => {
const usersRepo = new UsersRepository();
const user = (await usersRepo.searchUserBy({ nickname: login }, 1, 0))[0];
return user[0].intraId.toString();
};

export const getUserProjectFrom42API = async (
accessToken: string,
userId: string,
): Promise<Project[]> => {
const projectURL = `https://api.intra.42.fr/v2/users/${userId}/projects_users`;
const userProject: Array<Project> = [];
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,
marked_at: data.marked_at,
});
});
}).catch((error) => {
if (error.response.status === 401) {
throw new ErrorResponse(errorCode.NO_TOKEN, 401);
} else {
throw new ErrorResponse(errorCode.UNKNOWN_ERROR, 500);
}
});
return userProject;
};
Loading

0 comments on commit 919fb4a

Please sign in to comment.