Skip to content

Commit

Permalink
Revert "[배포] Production v1.0.1" (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeynii authored Dec 15, 2022
1 parent 51e6235 commit 1be54f5
Show file tree
Hide file tree
Showing 27 changed files with 159 additions and 319 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

![Javascript](https://img.shields.io/badge/javascript-ES6+-yellow?logo=javascript)
![NodeJS](https://img.shields.io/badge/node.js-v18-green?logo=node.js)
![데모](https://user-images.githubusercontent.com/25934842/207359316-f7056911-d26a-4671-bc3c-2a80e46f24b8.gif)

</div>

### 서비스 링크 : https://paperef.com
### 서비스 배포

- Dev 서버 : http://49.50.172.204:3000/

- Production 서버 : http://101.101.217.49:3000/

### 팀원

Expand All @@ -36,6 +39,7 @@
</td>
<td align="center"><a href="https://github.com/yeynii">최예윤</a>
</tr>
<tr>
</table>

### 개발 환경 세팅
Expand Down Expand Up @@ -80,7 +84,6 @@ ELASTIC_USER=
ELASTIC_PASSWORD=
ALLOW_UPDATE=
MAIL_TO=
SHOULD_RUN_BATCH=
```

## 기술스택
Expand All @@ -97,7 +100,7 @@ SHOULD_RUN_BATCH=
- 키워드 자동완성 검색 서비스 제공
- 키워드 검색 서비스 제공
- 논문 DOI를 통한 인용관계 시각화 서비스 제공
- 사용자는 키워드 검색시 PRV 데이터베이스에 있는 정보 혹은 Crossref API를 통해 요청한 정보를 조회할 수 있으며, 데이터베이스에 없는 논문에 대한 데이터 수집은 Request batch에 의해 처리되므로 검색 결과를 즉시 받아보지 못할 수 있습니다.
- 사용자는 키워드 검색시 PRV 데이터베이스에 있는 정보만 조회할 수 있으며, 데이터베이스에 없는 논문에 대한 데이터 수집은 Request batch에 의해 처리되므로 검색 결과를 즉시 받아보지 못할 수 있습니다.
- Request batch에 의해 수집된 결과는 데이터베이스에 저장됩니다.
- 추가 문의사항은 [email protected] 로 연락바랍니다.

Expand Down
4 changes: 4 additions & 0 deletions backend/src/ranking/ranking.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export class RankingController {
async getTen() {
return await this.rankingService.getTen();
}
@Get('/insert')
async insertCache(@Query('keyword') searchStr: string) {
return this.rankingService.insertRedis(searchStr);
}
}
16 changes: 8 additions & 8 deletions backend/src/ranking/tests/ranking.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ describe('RankingServiceTest', () => {
describe('/keyword-ranking', () => {
it('redis date가 10개 이하인 경우', async () => {
//Case 1. redis date가 10개 이하인 경우
const topTen = await service.getTen();
const topTen = await controller.getTen();
expect(topTen.length).toBeLessThanOrEqual(10);
});
it('데이터 삽입 후 topTen 체크', async () => {
//Case 2. 데이터 삽입 후 topTen 체크
const flag = await service.insertRedis('9번째 데이터');
const flag = await controller.insertCache('9번째 데이터');
expect(flag).toBe('new');
const topTen = await controller.getTen();
expect(topTen.length).toBe(9);
const flag2 = await service.insertRedis('10번째 데이터');
const flag2 = await controller.insertCache('10번째 데이터');
expect(flag2).toBe('new');
const topTen2 = await controller.getTen();
expect(topTen2.length).toBe(10);
});
it('2위인 "사랑해요" 데이터가 한번 더 검색시 1위로 업데이트', async () => {
//Case 3. 2위인 "사랑해요" 데이터가 한번 더 검색시 1위로 업데이트
const flag = await service.insertRedis('사랑해요');
const flag = await controller.insertCache('사랑해요');
expect(flag).toBe('update');
const topTen = await controller.getTen();
expect(topTen[0].keyword).toBe('부스트캠프');
Expand All @@ -46,23 +46,23 @@ describe('RankingServiceTest', () => {
describe('/keyword-ranking/insert', () => {
// Case1. 기존 redis에 없던 데이터 삽입
it('기존 redis에 없던 데이터 삽입', async () => {
const result = await service.insertRedis('newData');
const result = await controller.insertCache('newData');
expect(result).toBe('new');
});
// Case2. 기존 redis에 있던 데이터 삽입
it('기존 redis에 있던 데이터 삽입', async () => {
const result = await service.insertRedis('부스트캠프');
const result = await controller.insertCache('부스트캠프');
expect(result).toBe('update');
});
//Case3. redis에 빈 검색어 입력
it('빈 검색어 redis에 삽입', async () => {
await expect(service.insertRedis('')).rejects.toEqual(
await expect(controller.insertCache('')).rejects.toEqual(
new BadRequestException({ status: 400, error: 'bad request' }),
);
});
//Case4. insert 실패시 타임 아웃 TimeOut
it('insert 실패시 타임 아웃 TimeOut', async () => {
await expect(service.insertRedis('')).rejects.toEqual(
await expect(controller.insertCache('')).rejects.toEqual(
new BadRequestException({ status: 400, error: 'bad request' }),
);
});
Expand Down
12 changes: 0 additions & 12 deletions backend/src/search/search.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,6 @@ export class SearchController {
const keywordHasSet = await this.batchService.setKeyword(keyword);
if (keywordHasSet) this.batchService.searchBatcher.pushToQueue(0, 0, -1, true, keyword);

// Elasticsearch 검색 결과가 없을 경우, Crossref 검색
if (totalItems === 0) {
const { items: papers, totalItems } = await this.searchService.getPapersFromCrossref(keyword, rows, page);
return {
papers,
pageInfo: {
totalItems,
totalPages: Math.ceil(totalItems / rows),
},
};
}

const papers = data.hits.hits.map((paper) => new PaperInfoExtended(paper._source));
return {
papers,
Expand Down
16 changes: 2 additions & 14 deletions backend/src/search/search.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Injectable, NotFoundException, RequestTimeoutException } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import {
CrossRefItem,
PaperInfoExtended,
PaperInfo,
PaperInfoDetail,
CrossRefPaperResponse,
CrossRefResponse,
} from './entities/crossRef.entity';
import { ElasticsearchService } from '@nestjs/elasticsearch';
import { MgetOperation, SearchHit } from '@elastic/elasticsearch/lib/api/types';
import { HttpService } from '@nestjs/axios';
import { CROSSREF_API_PAPER_URL, CROSSREF_API_URL } from '../util';
import { CROSSREF_API_PAPER_URL } from '../util';
import { ELASTIC_INDEX } from 'src/envLayer';

@Injectable()
Expand Down Expand Up @@ -76,17 +75,6 @@ export class SearchService {

return new PaperInfoDetail(data);
};
async getPapersFromCrossref(keyword: string, rows: number, page: number, selects?: string[]) {
const crossRefdata = await this.httpService.axiosRef
.get<CrossRefResponse>(CROSSREF_API_URL(keyword, rows, page, selects))
.catch((err) => {
throw new RequestTimeoutException(err.message);
});
const items = crossRefdata.data.message.items.map((item) => this.parsePaperInfoExtended(item));
const totalItems = crossRefdata.data.message['total-results'];
return { items, totalItems };
}

async getPaperFromCrossref(doi: string) {
try {
const item = await this.httpService.axiosRef.get<CrossRefPaperResponse>(CROSSREF_API_PAPER_URL(doi));
Expand Down
7 changes: 1 addition & 6 deletions backend/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { MAIL_TO } from './envLayer';

const BASE_URL = 'https://api.crossref.org/works';
export const CROSSREF_API_URL = (
keyword: string,
rows = 5,
page = 1,
selects: string[] = ['title', 'author', 'created', 'is-referenced-by-count', 'references-count', 'DOI'],
) =>
export const CROSSREF_API_URL = (keyword: string, rows = 5, page = 1, selects: string[] = ['author', 'title', 'DOI']) =>
`${BASE_URL}?query=${keyword}&rows=${rows}&select=${selects.join(',')}&offset=${rows * (page - 1)}&mailto=${MAIL_TO}`;

export const MAX_ROWS = 1000;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ it('Footer 렌더링 테스트', () => {
);
});
const span = container?.querySelector('span');
expect(span?.textContent).toBe('문의사항, 버그제보: viewpoint[email protected]');
expect(span?.textContent).toBe('문의사항, 버그제보: vp[email protected]');
});
2 changes: 1 addition & 1 deletion frontend/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface FooterProps {
const Footer = ({ bgColor, contentColor }: FooterProps) => {
return (
<Container bgColor={bgColor} contentColor={contentColor}>
<span>문의사항, 버그제보: viewpoint[email protected]</span>
<span>문의사항, 버그제보: vp[email protected]</span>
<FooterRight>
<DataLink
href="https://insidious-abacus-0a9.notion.site/PRV-eb42bf64ddc5435a8f0f939329e0429c"
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const IconButton = ({ icon, ...rest }: IProps) => {
};

const Button = styled.button`
display: flex;
align-items: center;
background-color: transparent;
cursor: pointer;
`;
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/search/AutoCompletedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ const AutoCompleted = styled.li<{ hovered: boolean }>`

const Title = styled.div`
${({ theme }) => theme.TYPO.body1}
line-height: 1.1em;
`;

const Author = styled.div`
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/components/search/RecentKeywordsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { IconButton } from '@/components';
import { ClockIcon, XIcon } from '@/icons';
import { Ellipsis } from '@/style/styleUtils';
import { setLocalStorage } from '@/utils/storage';
import { isEmpty } from 'lodash-es';
import { Dispatch, SetStateAction, useEffect } from 'react';
Expand Down Expand Up @@ -43,7 +42,7 @@ const RecentKeywordsList = ({
onMouseDown={() => handleMouseDown(keyword)}
>
<ClockIcon />
<KeywordText>{keyword}</KeywordText>
{keyword}
<DeleteButton
icon={<XIcon />}
onMouseDown={(e) => handleRecentKeywordRemove(e, keyword)}
Expand Down Expand Up @@ -72,11 +71,6 @@ const Keyword = styled.li<{ hovered: boolean }>`
background-color: ${({ theme, hovered }) => (hovered ? theme.COLOR.gray1 : 'auto')};
`;

const KeywordText = styled(Ellipsis)`
width: 100%;
display: block;
`;

const NoResult = styled.div`
padding-top: 25px;
text-align: center;
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/hooks/graph/useGraphData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { IPaperDetail } from '@/api/api';
import { Link, Node } from '@/pages/PaperDetail/components/ReferenceGraph';
import { useEffect, useRef, useState } from 'react';

export default function useGraphData(data: IPaperDetail) {
const [links, setLinks] = useState<Link[]>([]);
const nodes = useRef<Node[]>([]);
export default function useGraphData<T>(data: IPaperDetail) {
const [links, setLinks] = useState<any[]>([]);
const nodes = useRef<any[]>([]);
const doiMap = useRef<Map<string, number>>(new Map());

useEffect(() => {
Expand All @@ -28,7 +27,7 @@ export default function useGraphData(data: IPaperDetail) {
citations: v.citations,
publishedYear: v.publishedAt && new Date(v.publishedAt).getFullYear(),
})),
] as Node[];
];

newNodes.forEach((node) => {
const foundIndex = doiMap.current.get(node.key);
Expand All @@ -50,7 +49,7 @@ export default function useGraphData(data: IPaperDetail) {
target: reference.key.toLowerCase(),
}));
setLinks((prev) => [...prev, ...newLinks]);
}, [data, links]);
}, [data]);

return { nodes: nodes.current, links };
return { nodes: nodes.current, links } as T;
}
54 changes: 23 additions & 31 deletions frontend/src/hooks/graph/useGraphEmphasize.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Link, Node } from '@/pages/PaperDetail/components/ReferenceGraph';
import * as d3 from 'd3';
import { useCallback, useEffect } from 'react';
import { useTheme } from 'styled-components';
import * as d3 from 'd3';
import { useEffect, useCallback } from 'react';

const styles = {
EMPHASIZE_OPACITY: '1',
BASIC_OPACITY: '0.5',
EMPHASIZE_STROKE_WIDTH: '1.5px',
EMPHASIZE_STROKE_WIDTH: '0.8px',
BASIC_STROKE_WIDTH: '0.5px',
EMPHASIZE_STROKE_DASH: 'none',
BASIC_STROKE_DASH: '1',
Expand All @@ -15,8 +14,8 @@ const styles = {
export default function useGraphEmphasize(
nodeSelector: SVGGElement | null,
linkSelector: SVGGElement | null,
nodes: Node[],
links: Link[],
nodes: any[],
links: any[],
hoveredNode: string,
selectedKey: string,
) {
Expand All @@ -41,53 +40,46 @@ export default function useGraphEmphasize(
d3.select(nodeSelector)
.selectAll('text')
.data(nodes)
.filter((d) => {
return links
.filter((l) => l.source === hoveredNode)
.map((l) => l.target)
.includes(d.key);
})
.filter((d) =>
links
.filter((l) => l.source.key === hoveredNode)
.map((l) => l.target.key)
.includes(d.key),
)
.style('fill-opacity', styles.EMPHASIZE_OPACITY);
}, [hoveredNode, links, nodeSelector, nodes, theme]);

useEffect(() => {
if (nodeSelector === null) return;

// click된 노드 강조
d3.select(nodeSelector)
.selectAll('text')
.data(nodes)
.filter((d) => d.key === selectedKey)
.style('fill', theme.COLOR.secondary2);
.style('fill', theme.COLOR.secondary1);

// click된 노드의 자식 노드들 강조
d3.select(nodeSelector)
.selectAll('text')
.data(nodes)
.filter((d) => {
const result = links
.filter((l) => l.source === selectedKey)
.map((l) => l.target)
.includes(d.key);
return result;
})
.style('fill', theme.COLOR.secondary2);
.filter((d) =>
links
.filter((l) => l.source.key === selectedKey)
.map((l) => l.target.key)
.includes(d.key),
)
.style('fill', theme.COLOR.secondary1);

// click/hover된 노드의 링크 강조
d3.select(linkSelector)
.selectAll('line')
.data(links)
.style('stroke', (d) => getStyles(d.source as string, theme.COLOR.secondary1, theme.COLOR.gray1))
.style('stroke-width', (d) =>
getStyles(d.source as string, styles.EMPHASIZE_STROKE_WIDTH, styles.BASIC_STROKE_WIDTH),
)
.style('stroke', (d) => getStyles(d.source.key, theme.COLOR.secondary1, theme.COLOR.gray1))
.style('stroke-width', (d) => getStyles(d.source.key, styles.EMPHASIZE_STROKE_WIDTH, styles.BASIC_STROKE_WIDTH))
.style('stroke-dasharray', (d) =>
getStyles(d.source as string, styles.EMPHASIZE_STROKE_DASH, styles.BASIC_STROKE_DASH),
getStyles(d.source.key, styles.EMPHASIZE_STROKE_DASH, styles.BASIC_STROKE_DASH),
);

return () => {
d3.select(nodeSelector).selectAll('text').style('fill-opacity', styles.BASIC_OPACITY);
d3.select(nodeSelector).selectAll('text').style('fill', theme.COLOR.offWhite);
};
}, [nodeSelector, nodes, links, selectedKey, linkSelector, getStyles, theme]);
}, [nodeSelector, hoveredNode, nodes, links, selectedKey, linkSelector, getStyles, theme]);
}
18 changes: 18 additions & 0 deletions frontend/src/hooks/graph/useLinkUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as d3 from 'd3';
import { useCallback } from 'react';

export default function useLinkUpdate(links: any[]) {
return useCallback(
(linksSelector: SVGGElement) => {
d3.select(linksSelector)
.selectAll('line')
.data(links)
.join('line')
.attr('x1', (d) => d.source?.x)
.attr('y1', (d) => d.source?.y)
.attr('x2', (d) => d.target?.x)
.attr('y2', (d) => d.target?.y);
},
[links],
);
}
Loading

0 comments on commit 1be54f5

Please sign in to comment.