Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.2.0 배포 #301

Merged
merged 24 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
767dc6e
Update README.md
Yaminyam Dec 12, 2022
f5fdc3c
refactor: 오늘날짜와 같은 경우 scoreHistory 업데이트
Yaminyam Dec 12, 2022
3ccffaf
fix: cron 실행시간 timeZone 설정
Yaminyam Dec 13, 2022
5b99fa1
feat: 라인차트 선 아래 영역 시작선선 설정
asdf99245 Dec 13, 2022
940a2d0
feat: 스코어 히스토리 차트 데이터 시간 한국시간으로 고정
asdf99245 Dec 13, 2022
ace985b
design 라인 차트 axis 텍스트 스타일 수정
asdf99245 Dec 13, 2022
0eccc5d
design: 라인차트 axis legend font weight 수정
asdf99245 Dec 13, 2022
d3cd0f0
feat: 업데이트 실패한 유저 조회 시 처리 추가
wkddntjr1123 Dec 13, 2022
556ffd1
feat: 업데이트 싪한 유저 updateDelayTime 추가
wkddntjr1123 Dec 13, 2022
0555424
feat: 업데이트 실패 유저는 조회수 상승 X
wkddntjr1123 Dec 13, 2022
4558def
fix: github 권한 설정 변경
tunggary Dec 13, 2022
29b42d2
fix: ProfileUserResponse type 변경
tunggary Dec 13, 2022
18d22cf
chore: invalid cube 이미지 추가
tunggary Dec 13, 2022
062f8dc
refactor: ranking prefetch 오타 수정
tunggary Dec 13, 2022
fed1908
fix: logout 버그 수정
tunggary Dec 13, 2022
a7e20ef
feat: CubeLogo 컴포넌트 invalid option 추가
tunggary Dec 13, 2022
eb07bf6
refactor: invalid cube path 수정
tunggary Dec 13, 2022
e996502
chore: 국제화 적용
tunggary Dec 13, 2022
295978b
refactor: 오타 수정
tunggary Dec 13, 2022
f8c38a2
feat: 국제화 적용
tunggary Dec 13, 2022
80f3a22
Merge pull request #297 from boostcampwm-2022/refactor/score-history
asdf99245 Dec 13, 2022
07940be
Merge pull request #298 from boostcampwm-2022/fix/score-history
asdf99245 Dec 13, 2022
b06d574
Merge pull request #300 from boostcampwm-2022/refactor/frontend-profile
asdf99245 Dec 13, 2022
669d373
Merge pull request #299 from boostcampwm-2022/feature/error-user-profile
asdf99245 Dec 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# <img src="https://user-images.githubusercontent.com/79798443/206173852-a4baafe3-5de7-4afa-b494-f630d5170d54.svg"/> Devrank <img src="https://user-images.githubusercontent.com/79798443/206173852-a4baafe3-5de7-4afa-b494-f630d5170d54.svg"/>

[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fboostcampwm-2022%2Fweb21-devrank&count_bg=%23C455F9&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)

![Frame 12023](https://user-images.githubusercontent.com/79798443/206137429-5cb1d269-4bec-4aaa-85ad-053ae564c625.png)

**_Devrank_** 는 게임과 유사한 등급 시스템과 랭킹을 제공하여, Github 활동시 사용자에게 성취감을 주고 오픈소스 활동을 장려하는 서비스 입니다.
Expand Down
2 changes: 1 addition & 1 deletion backend/src/batch/batch.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { UserService } from '../user/user.service';
export class BatchService {
constructor(private readonly userService: UserService, private readonly configService: ConfigService) {}

@Cron('0 0 0 * * *', { name: 'cronTask' })
@Cron('0 0 0 * * *', { name: 'cronTask', timeZone: 'Asia/Seoul' })
handleCron() {
logger.log('Task Called');
this.userService.dailyUpdateAllUsers(this.configService.get('GITHUB_PERSONAL_ACCESS_TOKEN'));
Expand Down
10 changes: 4 additions & 6 deletions backend/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Controller,
DefaultValuePipe,
Get,
NotFoundException,
Param,
ParseIntPipe,
Patch,
Expand Down Expand Up @@ -74,18 +75,15 @@ export class UserController {
@Param('username') username: string,
): Promise<UserProfileDto> {
const lowerUsername = username.toLowerCase();
if (
(await this.userService.findUpdateScoreTimeToLive(lowerUsername)) > 0 &&
(await this.userService.findOneByFilter({ lowerUsername }))?.id !== id
) {
const targetUser = await this.userService.findOneByFilter({ lowerUsername });
if ((await this.userService.findUpdateScoreTimeToLive(lowerUsername)) > 0 && targetUser?.id !== id) {
throw new BadRequestException('user score has been updated recently.');
}
await this.userService.setUpdateScoreDelayTime(lowerUsername, UPDATE_DELAY_TIME);
const user = await this.userService.updateUser(
lowerUsername,
githubToken || this.configService.get('GITHUB_PERSONAL_ACCESS_TOKEN'),
);

await this.userService.setUpdateScoreDelayTime(lowerUsername, UPDATE_DELAY_TIME);
user.updateDelayTime = UPDATE_DELAY_TIME + 3;
return user;
}
Expand Down
76 changes: 33 additions & 43 deletions backend/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,28 @@ export class UserService {
}

async findOneByUsername(githubToken: string, ip: string, lowerUsername: string): Promise<UserProfileDto> {
let user = null;
if (await this.userRepository.isDuplicatedRequestIp(ip, lowerUsername)) {
user = await this.userRepository.findOneByLowerUsername(lowerUsername);
} else {
user = await this.userRepository.findOneByLowerUsernameAndUpdateViews(lowerUsername);
}

let user = await this.userRepository.findOneByLowerUsername(lowerUsername);
if (!user) {
user = await this.updateUser(lowerUsername, githubToken);
if (!user.scoreHistory) {
user.scoreHistory = [];
try {
user = await this.updateUser(lowerUsername, githubToken);
if (!user.scoreHistory) {
user.scoreHistory = [];
}
user.scoreHistory.push({ date: new Date(), score: user.score });
user = await this.userRepository.createOrUpdate(user);
} catch {
throw new HttpException(`can't update this user.`, HttpStatus.NO_CONTENT);
}
user.scoreHistory.push({ date: new Date(), score: user.score });
user = await this.userRepository.createOrUpdate(user);
}
await this.userRepository.createOrUpdate(user);

const { totalRank, tierRank } =
(await this.getUserRelativeRanking(user)) || (await this.setUserRelativeRanking(user));
if (!(await this.userRepository.isDuplicatedRequestIp(ip, lowerUsername)) && user.history) {
user.dailyViews += 1;
await this.userRepository.createOrUpdate(user);
}
this.userRepository.setDuplicatedRequestIp(ip, lowerUsername);
user.updateDelayTime = await this.userRepository.findUpdateScoreTimeToLive(lowerUsername);
user.totalRank = totalRank;
user.tierRank = tierRank;
user.startExp = getStartExp(user.score);
user.needExp = getNeedExp(user.score);
return user;
return { ...user, totalRank, tierRank, startExp: getStartExp(user.score), needExp: getNeedExp(user.score) };
}

async findAllByPrefixUsername(limit: number, lowerUsername: string): Promise<AutoCompleteDto[]> {
Expand All @@ -83,6 +79,24 @@ export class UserService {
organizations,
pinnedRepositories,
};
if (!updatedUser.scoreHistory) {
updatedUser.scoreHistory = [];
}
const KR_TIME_DIFF = 9 * 60 * 60 * 1000;
const utc = updatedUser.scoreHistory[updatedUser.scoreHistory.length - 1].date.getTime();
if (new Date(utc + KR_TIME_DIFF).getDate() == new Date().getDate()) {
updatedUser.scoreHistory.pop();
}
updatedUser.scoreHistory.push({
date: new Date(),
score: updatedUser.score,
});
if (updatedUser.scoreHistory.length > 1) {
updatedUser.scoreDifference =
updatedUser.score - updatedUser.scoreHistory[updatedUser.scoreHistory.length - 2].score;
} else {
updatedUser.scoreDifference = 0;
}
user = await this.userRepository.createOrUpdate(updatedUser);
const { totalRank, tierRank } = await this.setUserRelativeRanking(user);
const userWithRank: UserProfileDto = {
Expand All @@ -102,15 +116,6 @@ export class UserService {
await sleep(GITHUB_API_DELAY);
try {
const updateUser = await this.updateUser(user.lowerUsername, githubToken);
if (!updateUser.scoreHistory) updateUser.scoreHistory = [];
if (updateUser.scoreHistory.length) updateUser.scoreHistory.pop();
updateUser.scoreHistory.push({ date: new Date(), score: updateUser.score });
if (updateUser.scoreHistory.length > 1) {
updateUser.scoreDifference =
updateUser.score - updateUser.scoreHistory[updateUser.scoreHistory.length - 2].score;
} else {
updateUser.scoreDifference = 0;
}
this.userRepository.createOrUpdate(updateUser);
this.userRepository.deleteCachedUserRank(updateUser.lowerUsername + '&');
} catch {
Expand All @@ -126,17 +131,6 @@ export class UserService {
await sleep(GITHUB_API_DELAY);
try {
const updatedUser = await this.updateUser(user.lowerUsername, githubToken);
if (!updatedUser.scoreHistory) updatedUser.scoreHistory = [];
updatedUser.scoreHistory.push({
date: new Date(),
score: updatedUser.score,
});
if (updatedUser.scoreHistory.length > 1) {
updatedUser.scoreDifference =
updatedUser.score - updatedUser.scoreHistory[updatedUser.scoreHistory.length - 2].score;
} else {
updatedUser.scoreDifference = 0;
}
updatedUser.dailyViews = 0;
this.userRepository.createOrUpdate(updatedUser);
} catch {
Expand Down Expand Up @@ -202,20 +196,16 @@ export class UserService {
username: lowerUsername,
id,
});

const personalResponse: any = await octokit.graphql(nonForkRepositoryQuery, {
username: lowerUsername,
id,
});

const followersResponse: any = await octokit.graphql(followersQuery, {
username: lowerUsername,
});

const issuesResponse: any = await octokit.graphql(issueQuery, {
username: lowerUsername,
});

let languagesScore = new Map();
function getCommitScore(acc: number, repository) {
if (!repository.defaultBranchRef) {
Expand Down
21 changes: 0 additions & 21 deletions frontend/public/icons/cube-large-background.svg

This file was deleted.

21 changes: 21 additions & 0 deletions frontend/public/icons/cube/cube-small-invalid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion frontend/public/locales/en/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"user-update-delay": "You can update it in",
"day": "day",
"total": "Total",
"rank": "Rank"
"rank": "Rank",
"invalid-user": "This user cannot get information",
"invalid-rank": "Unable to get the rank"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/ko/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"user-update-delay": "초 후 업데이트 가능합니다.",
"day": "일",
"total": "전체",
"rank": "등급"
"rank": "등급",
"invalid-user": "정보를 가져올 수 없는 유저입니다",
"invalid-rank": "등수를 표시할 수 없습니다"
}
1 change: 1 addition & 0 deletions frontend/src/components/Chart/LineChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function LineChart({ data, min, max, markers, tooltip }: LineChartProps) {
margin={{ top: 60, right: 60, bottom: 60, left: 60 }}
curve='monotoneX'
enableArea={true}
areaBaselineValue={min || 0}
xScale={{
type: 'time',
format: '%Y-%m-%d',
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/Chart/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"legend": {
"text": {
"fontSize": 14,
"fontWeight": 700,
"fontWeight": 400,
"fill": "#FBFBFB"
}
},
Expand All @@ -39,7 +39,8 @@
"strokeWidth": 1
},
"text": {
"fontSize": 14,
"fontSize": 12,
"fontWeight": 100,
"fill": "#FBFBFB"
}
}
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Avatar, Button } from '@components/common';
import Dropdown from '@components/common/Dropdown';
import { requestTokenRefresh, requestUserLogout } from '@apis/auth';
import { GITHUB_AUTH_REQUEST_URL } from '@utils/constants';

function Header() {
const queryClient = useQueryClient();

const { mutate: logout } = useMutation({
mutationFn: requestUserLogout,
onSuccess: () => queryClient.invalidateQueries(['user']),
});

const router = useRouter();

const {
isLoading,
data: userData,
remove: removeUser,
} = useQuery(['user'], () => requestTokenRefresh(), {
const { isLoading, data: userData } = useQuery(['user'], () => requestTokenRefresh(), {
enabled: router.pathname !== '/callback',
cacheTime: Infinity,
});
Expand All @@ -33,7 +33,6 @@ function Header() {

const onClickLogoutButton = () => {
logout();
removeUser();
};

return (
Expand Down
47 changes: 27 additions & 20 deletions frontend/src/components/Profile/ProfileCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface ProfileCardProps {
updateData: () => void;
isLoading: boolean;
isMine: boolean;
isInvalid: boolean;
};
}

Expand All @@ -53,6 +54,7 @@ function ProfileCard({ profileData }: ProfileCardProps) {
updateData,
isLoading,
isMine,
isInvalid,
} = profileData;

const { t } = useTranslation(['profile', 'tier']);
Expand Down Expand Up @@ -119,18 +121,24 @@ function ProfileCard({ profileData }: ProfileCardProps) {
)}
</ProfileInfos>
<ProfileRank>
<CubeLogo tier={tier} size={'sm'} />
<div>
<p>
{t('profile:total')}&nbsp;{totalRank}
{getRankingUnit(locale, totalRank)}
</p>
<p>
<ColorPoint color={CUBE_COLOR_MAP[tier]}>{t(`tier:${tier}`)}</ColorPoint>
&nbsp;{tierRank}
{getRankingUnit(locale, tierRank)}
</p>
</div>
<CubeLogo tier={tier} size={'sm'} isInvalid={isInvalid} />
{isInvalid ? (
<RankText>
<p>{t('profile:invalid-rank')}</p>
</RankText>
) : (
<RankText>
<p>
{t('profile:total')}&nbsp;{totalRank}
{getRankingUnit(locale, totalRank)}
</p>
<p>
<ColorPoint color={CUBE_COLOR_MAP[tier]}>{t(`tier:${tier}`)}</ColorPoint>
&nbsp;{tierRank}
{getRankingUnit(locale, tierRank)}
</p>
</RankText>
)}
</ProfileRank>
</Paper>
);
Expand Down Expand Up @@ -172,15 +180,14 @@ const ProfileInfos = styled.ul`
const ProfileRank = styled.div`
max-width: 300px;
width: 100%;
div {
margin-top: 20px;
${({ theme }) => theme.common.flexCenterColumn};
p {
margin-top: 8px;
}
}
${({ theme }) => theme.common.flexCenterColumn};
`;

transform: translateZ(-50);
const RankText = styled.div`
margin-top: 20px;
p {
margin-top: 8px;
}
`;

const ImageStyle = {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Ranking/NotFound/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function NotFound() {
<Container className={aldrich.className}>
<div>
<CubeImage
src='/icons/cube-large-background.svg'
src='/icons/cube/cube-small-invalid.svg'
alt='큐브 이미지'
width={220}
height={250}
Expand Down Expand Up @@ -44,6 +44,7 @@ const Container = styled.div`
}
`;
const CubeImage = styled(Image)`
z-index: -1;
position: absolute;
top: 40%;
left: 50%;
Expand Down
Loading