Skip to content

Commit

Permalink
Merge branch 'feat/chart' of https://github.com/boostcampwm-2024/web0…
Browse files Browse the repository at this point in the history
…5-Denamu into feat/chart
  • Loading branch information
jungmyunggi committed Dec 2, 2024
2 parents 93e6bb7 + b92357f commit aa569ae
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 170 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/server_test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
name: Test
name: BE Test

on:
pull_request:
paths:
- 'server/**'
branches:
- main
workflow_dispatch:
Expand Down
23 changes: 12 additions & 11 deletions rss-notifier/src/db-access.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logger from "./logger.js";
import { FeedObj, FeedDetail } from "./types.js";
import logger from "./logger";
import { RssObj, FeedDetail } from "./types";
import * as dotenv from "dotenv";
import * as mysql from "mysql2/promise";
import Redis from "ioredis";
Expand Down Expand Up @@ -50,7 +50,7 @@ export const executeQuery = async (query: string, params: any[] = []) => {
}
};

export const selectAllRss = async (): Promise<FeedObj[]> => {
export const selectAllRss = async (): Promise<RssObj[]> => {
const query = `SELECT id, rss_url as rssUrl
FROM rss_accept`;
return executeQuery(query);
Expand All @@ -59,12 +59,12 @@ export const selectAllRss = async (): Promise<FeedObj[]> => {
export const insertFeeds = async (resultData: FeedDetail[]) => {
let successCount = 0;
let lastFeedId;
try {
const query = `
const query = `
INSERT INTO feed (blog_id, created_at, title, path, thumbnail)
VALUES (?, ?, ?, ?, ?)
`;
for (const feed of resultData) {
for (const feed of resultData) {
try {
lastFeedId = (
await executeQuery(query, [
feed.blogId,
Expand All @@ -75,12 +75,13 @@ export const insertFeeds = async (resultData: FeedDetail[]) => {
])
)[INSERT_ID];
successCount++;
} catch (error) {
logger.error(`누락된 피드 데이터가 존재합니다. 에러 내용: ${error}`);
}
} catch (error) {
logger.error(`누락된 피드 데이터가 존재합니다. 에러 내용: ${error}`);
}

logger.info(
`${successCount}개의 피드 데이터가 성공적으로 데이터베이스에 삽입되었습니다.`,
`${successCount}개의 피드 데이터가 성공적으로 데이터베이스에 삽입되었습니다.`
);
lastFeedId = lastFeedId - successCount + 1;
return lastFeedId;
Expand All @@ -96,7 +97,7 @@ export const deleteRecentFeedStartId = async () => {
await redis.set(redisConstant.FEED_RECENT_KEY, "false");
} catch (error) {
logger.error(
`Redis의 feed:recent:*를 삭제하는 도중 에러가 발생했습니다. 에러 내용: ${error}`,
`Redis의 feed:recent:*를 삭제하는 도중 에러가 발생했습니다. 에러 내용: ${error}`
);
} finally {
if (redis) await redis.quit();
Expand Down Expand Up @@ -128,7 +129,7 @@ export const setRecentFeedList = async (startId: number) => {
await pipeLine.exec();
} catch (error) {
logger.error(
`Redis의 feed:recent:*를 저장하는 도중 에러가 발생했습니다. 에러 내용: ${error}`,
`Redis의 feed:recent:*를 저장하는 도중 에러가 발생했습니다. 에러 내용: ${error}`
);
} finally {
if (redis) await redis.quit();
Expand Down
6 changes: 5 additions & 1 deletion rss-notifier/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({
filename: "rss-notifier/rss-notifier.log",
filename: `${
process.env.NODE_ENV === "production"
? "rss-notifier/logs/rss-notifier.log"
: "logs/rss-notifier.log"
}`,
}),
],
});
Expand Down
38 changes: 18 additions & 20 deletions rss-notifier/src/rss-notifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,11 @@ import {
deleteRecentFeedStartId,
setRecentFeedList,
} from "./db-access.js";
import { FeedObj, FeedDetail, RawFeed } from "./types.js";
import { RssObj, FeedDetail, RawFeed } from "./types.js";
import { XMLParser } from "fast-xml-parser";
import { parse } from "node-html-parser";
import { unescape } from "html-escaper";

const htmlEntities = {
"&middot;": "·",
"&nbsp;": " ",
};

const xmlParser = new XMLParser();
const TIME_INTERVAL = process.env.TIME_INTERVAL
? parseInt(process.env.TIME_INTERVAL)
: 1;

const getImageUrl = async (link: string): Promise<string> => {
const response = await fetch(link, {
headers: {
Expand All @@ -42,6 +32,7 @@ const getImageUrl = async (link: string): Promise<string> => {
};

const fetchRss = async (rssUrl: string): Promise<RawFeed[]> => {
const xmlParser = new XMLParser();
const response = await fetch(rssUrl, {
headers: {
Accept: "application/rss+xml, application/xml, text/xml",
Expand All @@ -65,14 +56,17 @@ const fetchRss = async (rssUrl: string): Promise<RawFeed[]> => {
};

const findNewFeeds = async (
rssObj: FeedObj,
now: number,
rssObj: RssObj,
now: number
): Promise<FeedDetail[]> => {
try {
const TIME_INTERVAL = process.env.TIME_INTERVAL
? parseInt(process.env.TIME_INTERVAL)
: 1;
const feeds = await fetchRss(rssObj.rssUrl);

const filteredFeeds = feeds.filter((item) => {
const pubDate = new Date(item.pubDate).setMinutes(0, 0, 0);
const pubDate = new Date(item.pubDate).setSeconds(0, 0);
const timeDiff = (now - pubDate) / (1000 * 60 * TIME_INTERVAL);
return timeDiff >= 0 && timeDiff < 1;
});
Expand All @@ -90,19 +84,23 @@ const findNewFeeds = async (
link: decodeURIComponent(feed.link),
imageUrl: imageUrl,
};
}),
})
);

return detailedFeeds;
} catch (err) {
logger.warn(
`[${rssObj.rssUrl}] 에서 데이터 조회 중 오류 발생으로 인한 스킵 처리. 오류 내용 : ${err}`,
`[${rssObj.rssUrl}] 에서 데이터 조회 중 오류 발생으로 인한 스킵 처리. 오류 내용 : ${err}`
);
return [];
}
};

const customUnescape = (text: string): string => {
const htmlEntities = {
"&middot;": "·",
"&nbsp;": " ",
};
Object.keys(htmlEntities).forEach((entity) => {
const value = htmlEntities[entity];
const regex = new RegExp(entity, "g");
Expand All @@ -113,22 +111,22 @@ const customUnescape = (text: string): string => {
};

export const performTask = async () => {
const currentTime = new Date();
const rssObjects = await selectAllRss();

if (rssObjects.length === 0) {
logger.info("등록된 RSS가 없습니다.");
return;
}
const currentTime = new Date();

let idx = 0;
const newFeeds = await Promise.all(
rssObjects.map(async (rssObj) => {
logger.info(
`[${++idx}번째 rss [${rssObj.rssUrl}] 에서 데이터 조회하는 중...`,
`[${++idx}번째 rss [${rssObj.rssUrl}] 에서 데이터 조회하는 중...`
);
return await findNewFeeds(rssObj, currentTime.setMinutes(0, 0, 0));
}),
return await findNewFeeds(rssObj, currentTime.setSeconds(0, 0));
})
);

const result = newFeeds.flat().sort((currentFeed, nextFeed) => {
Expand Down
2 changes: 1 addition & 1 deletion rss-notifier/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface RawFeed {
pubDate: string;
}

export interface FeedObj {
export interface RssObj {
id: number;
rssUrl: string;
}
Expand Down
2 changes: 1 addition & 1 deletion server/test/fixture/feed.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class FeedFixture {
path: `https://test.com/test${index}`,
thumbnail: `https://test.com/test${index}.png`,
},
rssAccept,
blog: rssAccept,
});
return Object.assign(feed, overwrites);
}
Expand Down
50 changes: 50 additions & 0 deletions server/test/statistic/dto/statistic-query.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { validate } from 'class-validator';
import { StatisticQueryDto } from '../../../src/statistic/dto/statistic-query.dto';

describe('StatisticQueryDto', () => {
it('실수를 입력한다.', async () => {
// given
const dto = new StatisticQueryDto();
dto.limit = 1.1;

// when
const errors = await validate(dto);

// then
expect(errors.length).toBe(1);
expect(errors[0].constraints).toHaveProperty(
'isInt',
'정수로 입력해주세요.',
);
});
it('문자열을 입력한다.', async () => {
// given
const dto = new StatisticQueryDto();
dto.limit = 'test' as unknown as number;

// when
const errors = await validate(dto);

// then
expect(errors.length).toBe(1);
expect(errors[0].constraints).toHaveProperty(
'isInt',
'정수로 입력해주세요.',
);
});
it('음수를 입력한다.', async () => {
// given
const dto = new StatisticQueryDto();
dto.limit = -1;

// when
const errors = await validate(dto);

// then
expect(errors.length).toBe(1);
expect(errors[0].constraints).toHaveProperty(
'min',
'limit 값은 1 이상이어야 합니다.',
);
});
});
Loading

0 comments on commit aa569ae

Please sign in to comment.