Skip to content

Commit

Permalink
Merge pull request #185 from GDSC-PKNU-Official/dev
Browse files Browse the repository at this point in the history
부리미 서버 1.10v 배포
  • Loading branch information
pp449 authored Feb 19, 2024
2 parents 4750d8f + 733a02d commit 8ecbaf5
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 65 deletions.
18 changes: 18 additions & 0 deletions src/apis/building-info/controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import express, { Request, Response } from 'express';

import fetchBuildingInfo from './service';

const router = express.Router();

router.get('/', async (req: Request, res: Response) => {
const code = req.query.code;

try {
const buildingInfo = await fetchBuildingInfo(code as string);
res.json(buildingInfo);
} catch (err) {
console.log(err);
}
});

export default router;
99 changes: 99 additions & 0 deletions src/apis/building-info/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import axios, { AxiosResponse } from 'axios';
import notificationToSlack from 'src/hooks/notificateToSlack';

type Floor = 'basement' | 'ground' | 'rooftop';

interface Room {
roomNumber: string;
roomName: string;
}

interface FormattedInfo extends Room {
floor: number;
floorType: Floor;
}

type TotalInfo = {
[key in Floor]: {
[key in string]: Room[];
};
};

const configPaylod = (code: string) => {
const payload = new URLSearchParams();
payload.append('code', code);
payload.append('stat', 'D');

return payload;
};

const handleLayerType = (layerType: string) => {
if (layerType === '0') return 'ground';
if (layerType === '1') return 'basement';
if (layerType === '2') return 'rooftop';
};

const formatTotalInfo = (formattedInfo: FormattedInfo[]) => {
const totalInfo: TotalInfo = {
basement: {},
ground: {},
rooftop: {},
};

formattedInfo.forEach(({ roomNumber, roomName, floor, floorType }) => {
const type = floorType as Floor;

if (!Object.prototype.hasOwnProperty.call(totalInfo[type], floor)) {
totalInfo[type][floor] = [{ roomNumber, roomName }];
return;
}

totalInfo[type][floor].push({ roomNumber, roomName });
});

return totalInfo;
};

const formatFetchedInfo = (data: AxiosResponse['data']) => {
const formattedData: FormattedInfo[] = data.response.deps2.reduce(
(accData: FormattedInfo[], curr: any) => {
const item = {
roomNumber: curr.roomNo,
roomName: curr.roomName,
floor: curr.layer,
floorType: handleLayerType(curr.layerTyp),
};

return [...accData, item];
},
[] as FormattedInfo[],
);

const formattedInfo = formatTotalInfo(formattedData);

return formattedInfo;
};

const fetchBuildingInfo = async (code: string) => {
const REQUEST_URL = 'https://www.pknu.ac.kr/buildingInfoAjax.do';
const HEADERS = {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
};
const payload = configPaylod(code);

try {
const response = await axios.post(REQUEST_URL, payload.toString(), {
headers: HEADERS,
});

const formattedInfo = formatFetchedInfo(response.data);

return formattedInfo;
} catch (error) {
notificationToSlack('건물 정보 요청에 문제가 발생했습니다.');
console.log(error);
}
};

export default fetchBuildingInfo;
135 changes: 87 additions & 48 deletions src/db/data/noticeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PushNoti {

interface NotiLink {
link: string;
rep_yn: boolean;
}

export const saveDepartmentToDB = async (college: College[]): Promise<void> => {
Expand Down Expand Up @@ -74,81 +75,119 @@ const convertAllNoticeToNormalNotice = async (
}
};

const convertSpecificNoticeToPinnedNotice = async (
const convertSpecificNoticePinned = async (
tableName: string,
noticeLink: string,
isPinned: boolean,
connection?: PoolConnection,
): Promise<void> => {
const query = `UPDATE ${tableName} SET rep_yn = true WHERE link = '${noticeLink}';`;
const query = `UPDATE ${tableName} SET rep_yn = ${isPinned} WHERE link = '${noticeLink}';`;

try {
await db.execute(query);
if (connection) await connection.execute(query);
else await db.execute(query);
} catch (error) {
console.log(error.message + '고정 공지로 변경 실패');
// notificationToSlack(error.message + '\n 고정 공지로 변경 실패');
}
};

export const saveMajorNoticeToDB = async (
connection?: PoolConnection,
): Promise<PushNoti> => {
await convertAllNoticeToNormalNotice('major_notices', connection);
export const saveMajorNoticeToDB = async (): Promise<PushNoti> => {
// await convertAllNoticeToNormalNotice('major_notices', connection);
const query = 'SELECT * FROM departments;';
const colleges = await selectQuery<College[]>(query, connection);

const getNotiLinkQuery = `SELECT link FROM major_notices;`;
const noticeLinksInDB = (
await selectQuery<NotiLink[]>(getNotiLinkQuery, connection)
).map((noticeLink) => noticeLink.link);
const colleges = await selectQuery<College[]>(query);

const savePromises: Promise<void>[] = [];
const newNoticeMajor: PushNoti = {};
const failedMajor: number[] = [];

for (const college of colleges) {
console.log(college.id);
const noticeLink = await noticeCrawling(college);
const noticeLists = await noticeListCrawling(noticeLink);

const normalNotices = noticeLists.normalNotice;
const pinnedNotices = noticeLists.pinnedNotice;

if (normalNotices.length === 0) {
notificationToSlack(`${noticeLink} 크롤링 실패`);
continue;
}

for (const notice of normalNotices) {
const result = await noticeContentCrawling(notice);
if (result.link === '') {
// notificationToSlack(`${notice} 콘텐츠 크롤링 실패`);
continue;
const savePromises = colleges.map(async (college) => {
const connection = await db.getConnection();
await connection.beginTransaction();
try {
const noticeLink = await noticeCrawling(college);
const noticeLists = await noticeListCrawling(noticeLink);
const pinnedNotices = noticeLists.pinnedNotice;
const normalNotices = noticeLists.normalNotice;
if (normalNotices.length === 0) {
notificationToSlack(`${noticeLink} 크롤링 실패`);
connection.release();
return;
}

if (noticeLinksInDB.includes(result.link)) continue;
if (!newNoticeMajor[college.id]) newNoticeMajor[college.id] = [];
newNoticeMajor[college.id].push(result.title);
savePromises.push(saveMajorNotice(result, college.id, false, connection));
}

if (pinnedNotices) {
for (const notice of pinnedNotices) {
const getNotiLinkQuery = `SELECT link, rep_yn FROM major_notices WHERE department_id = '${college.id}'`;
const noticeDataInDB = await selectQuery<NotiLink[]>(
getNotiLinkQuery,
connection,
);
const noticeLinksInDB = noticeDataInDB.map((noti) => noti.link);
const pinnedNoticeLinksInDB = noticeDataInDB
.filter((noti) => noti.rep_yn)
.map((noti) => noti.link);

for (const notice of normalNotices) {
const result = await noticeContentCrawling(notice);
if (result.link === '') {
notificationToSlack(`${notice} 콘텐츠 크롤링 실패`);
// notificationToSlack(`${notice} 콘텐츠 크롤링 실패`);
continue;
}

if (!noticeLinksInDB.includes(result.link)) {
savePromises.push(
saveMajorNotice(result, college.id, true, connection),
if (noticeLinksInDB.includes(result.link)) return;
if (!newNoticeMajor[college.id]) newNoticeMajor[college.id] = [];
newNoticeMajor[college.id].push(result.title);
saveMajorNotice(result, college.id, false, connection);
noticeLinksInDB.push(result.link);
}

if (pinnedNotices) {
await pinnedNoticeLinksInDB
.filter((noti) => !pinnedNotices.includes(noti))
.map(
async (noti) =>
await convertSpecificNoticePinned(
'major_notices',
noti,
false,
connection,
),
);
continue;
for (const notice of pinnedNotices) {
const result = await noticeContentCrawling(notice);
if (result.link === '') {
notificationToSlack(`${notice} 콘텐츠 크롤링 실패`);
continue;
}

if (!noticeLinksInDB.includes(result.link)) {
saveMajorNotice(result, college.id, true, connection);
continue;
}

if (!pinnedNoticeLinksInDB.includes(result.link))
convertSpecificNoticePinned(
'major_notices',
result.link,
true,
connection,
);
}
convertSpecificNoticeToPinnedNotice('major_notices', result.link);
}

connection.commit();
} catch (error) {
notificationToSlack(college.id + '크롤링 실패' + error.message);
failedMajor.push(college.id);
connection.rollback();
} finally {
connection.release();
}
}
});

await Promise.all(savePromises);
if (failedMajor.length !== 0) {
const failedMajorList = failedMajor.join();
notificationToSlack('크롤링 실패한 학과: ' + failedMajorList);
}

return newNoticeMajor;
};

Expand Down Expand Up @@ -188,7 +227,7 @@ export const saveSchoolNoticeToDB = async (): Promise<void> => {

for (const noticeLink of pinnedNotices) {
if (schoolNoticeLinksInDB.includes(noticeLink)) {
await convertSpecificNoticeToPinnedNotice('notices', noticeLink);
await convertSpecificNoticePinned('notices', noticeLink, true);
continue;
}

Expand Down
24 changes: 12 additions & 12 deletions src/hooks/cronNoticeCrawling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,34 @@ const pushToUsers = async (pushNotiToUserLists: PushNoti) => {
if (pushedUserCount.length !== 0) notificationToSlack(pushedUserCount);
};

const cronNoticeCrawling = async () => {
const connection = await db.getConnection();
await connection.beginTransaction();
const cronNoticeCrawling = async (reTryCount = 0) => {
try {
const pushNotiToUserLists = await saveMajorNoticeToDB(connection);
const pushNotiToUserLists = await saveMajorNoticeToDB();
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다.
const day = today.getDate();
notificationToSlack(`${year}-${month}-${day} 크롤링 완료`);
await connection.commit();
pushToUsers(pushNotiToUserLists);
} catch (error) {
await connection.rollback();
notificationToSlack(error.message);
cronNoticeCrawling();
} finally {
connection.release();
if (reTryCount >= 2) {
notificationToSlack(error.message);
return;
}
cronNoticeCrawling(reTryCount + 1);
}
};

const cronExtracurricularCrawling = async () => {
const cronExtracurricularCrawling = async (reTryCount = 0) => {
try {
await saveSchoolNoticeToDB();
await saveLanguageNoticeToDB();
await saveWhalebeToDB();
} catch (error) {
notificationToSlack(error.message);
if (reTryCount >= 2) {
notificationToSlack(error.message);
return;
}
cronExtracurricularCrawling();
}
};
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import buildingInfoRouter from '@apis/building-info/controllers';
import graduationRouter from '@apis/graduation/controller';
import majorRouter from '@apis/majorDecision/controller';
import noticeRouter from '@apis/notice/controller';
import subscriptionRouter from '@apis/subscribe/controller';
import suggestionRouter from '@apis/suggestion/controller';
import env from '@config';
import { saveMajorNoticeToDB } from '@db/data/noticeHandler';
import { corsOptions } from '@middlewares/cors';
import errorHandler from '@middlewares/error-handler';
import cors from 'cors';
Expand All @@ -30,6 +30,7 @@ app.use('/api/majorDecision', majorRouter);
app.use('/api/announcement', noticeRouter);
app.use('/api/graduation', graduationRouter);
app.use('/api/subscription', subscriptionRouter);
app.use('/api/buildingInfo', buildingInfoRouter);

app.get('/test', (req: Request, res: Response) => {
console.log('test');
Expand Down
5 changes: 1 addition & 4 deletions src/utils/majorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { selectQuery } from '@db/query/dbQueryHandler';
import notificationToSlack from 'src/hooks/notificateToSlack';

export const getDepartmentIdByMajor = async (major: string) => {
const [departmentName, departmentSubName] = major.split(' ');
const getDepartmentQuery = `SELECT id FROM departments WHERE department_name = '${departmentName}' ${
departmentSubName ? `AND department_subname = '${departmentSubName}'` : ''
};`;
const getDepartmentQuery = `SELECT id FROM departments WHERE department_name = '${major}' OR department_subname = '${major}'`;

try {
const departmentId = await selectQuery<{ id: number }[]>(
Expand Down

0 comments on commit 8ecbaf5

Please sign in to comment.