Skip to content

Commit

Permalink
Merge pull request #281 from boostcampwm-2022/develop
Browse files Browse the repository at this point in the history
v0.1.6 배포
  • Loading branch information
wkddntjr1123 authored Dec 12, 2022
2 parents 3b76e71 + 93f05ae commit c5b8086
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 152 deletions.
15 changes: 7 additions & 8 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,25 +102,24 @@ export class AuthController {
type: LoginResponseDto,
})
@UseGuards(RefreshGuard)
async refresh(@CurrentUser() currentUser: Payload, @Res() response: Response): Promise<void> {
async refresh(@CurrentUser() currentUser: Payload): Promise<LoginResponseDto> {
const { id, githubToken, refreshToken } = currentUser;
const newRefreshToken = await this.authService.replaceRefreshToken(id, refreshToken, githubToken);
await this.authService.checkRefreshToken(refreshToken);
const accessToken = this.authService.issueAccessToken(id, githubToken);
const user = await this.userService.findOneByFilter({ id: id });
const cookieOption = this.authService.getCookieOption();
const responseData: LoginResponseDto = { accessToken, id, username: user.username, avatarUrl: user.avatarUrl };

response.cookie(this.configService.get('REFRESH_TOKEN_KEY'), newRefreshToken, cookieOption).json(responseData);
return responseData;
}

@Delete('logout')
@ApiOperation({ summary: '로그아웃', description: '클라이언트에 저장된 쿠키를 삭제하고, Redis에서도 삭제한다' })
@ApiOkResponse({ description: '로그아웃 성공' })
async logout(@Req() request: Request, @Res({ passthrough: true }) response: Response): Promise<void> {
@UseGuards(RefreshGuard)
async logout(@CurrentUser() currentUser: Payload, @Res({ passthrough: true }) response: Response): Promise<void> {
const { id, githubToken, refreshToken } = currentUser;
const cookieOption = this.authService.getCookieOption();
const refreshToken = this.authService.extractRefreshToken(request);
if (refreshToken) {
await this.authService.deleteRefreshToken(refreshToken);
await this.authService.deleteRefreshToken(id);
}
response.clearCookie(this.configService.get('REFRESH_TOKEN_KEY'), cookieOption);
}
Expand Down
28 changes: 16 additions & 12 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Request } from 'express';
import * as jwt from 'jsonwebtoken';
import * as URL from 'url';
import { AuthRepository } from './auth.repository';
import { GithubProfile } from './types';
import { GithubProfile, Payload } from './types';

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -58,9 +58,13 @@ export class AuthService {
return request.cookies?.[this.configService.get('REFRESH_TOKEN_KEY')];
}

checkRefreshToken(refreshToken: string): jwt.JwtPayload {
async checkRefreshToken(refreshToken: string): Promise<void> {
try {
return jwt.verify(refreshToken, this.configService.get('JWT_REFRESH_SECRET')) as jwt.JwtPayload;
const payload = jwt.verify(refreshToken, this.configService.get('JWT_REFRESH_SECRET')) as Payload;
const storedRefreshToken = await this.authRepository.findRefreshTokenById(payload.id);
if (refreshToken !== storedRefreshToken) {
throw new UnauthorizedException('invalid token.');
}
} catch {
throw new UnauthorizedException('invalid token.');
}
Expand All @@ -70,15 +74,15 @@ export class AuthService {
await this.authRepository.create(id, refreshToken);
}

async replaceRefreshToken(id: string, refreshToken: string, githubToken: string): Promise<string> {
const storedRefreshToken = await this.authRepository.findRefreshTokenById(id);
if (refreshToken !== storedRefreshToken) {
throw new UnauthorizedException('invalid token.');
}
const newRefreshToken = this.issueRefreshToken(id, githubToken);
await this.authRepository.create(id, newRefreshToken);
return newRefreshToken;
}
// async replaceRefreshToken(id: string, refreshToken: string, githubToken: string): Promise<string> {
// const storedRefreshToken = await this.authRepository.findRefreshTokenById(id);
// if (refreshToken !== storedRefreshToken) {
// throw new UnauthorizedException('invalid token.');
// }
// const newRefreshToken = this.issueRefreshToken(id, githubToken);
// await this.authRepository.create(id, newRefreshToken);
// return newRefreshToken;
// }

async deleteRefreshToken(id: string): Promise<void> {
await this.authRepository.delete(id);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Profile/ProfileCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function ProfileCard({ profileData }: ProfileCardProps) {
<ProfileLabel.Contents>
{organizations?.map(({ avatarUrl, name, url }) => (
<a key={name} href={url}>
<Image src={avatarUrl} width={27} height={27} alt={name} quality={100} style={ImageStyle} />
<Image src={avatarUrl} width={27} height={27} alt={name} style={ImageStyle} />
</a>
))}
</ProfileLabel.Contents>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/common/LanguageIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ function LanguageIcon({ language, width = 50, height = 50 }: LanguageIconProps)
alt={transLang}
width={width}
height={height}
quality={100}
onError={() => setIconUrl('/icons/default-language.svg')}
title={language}
/>
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/components/common/Searchbar/AutoComplete/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,26 @@ interface AutoCompleteSearchbarProps {
width: number;
/** 검색 버튼 위치 (left | right) */
submitAlign: SubmitAlign;
/** 검색 전에 실행할 함수 */
onBeforeSearch?: () => void;
}

function AutoCompleteSearchbar({ type = 'text', placeholder, width, submitAlign }: AutoCompleteSearchbarProps) {
function AutoCompleteSearchbar({
type = 'text',
placeholder,
width,
submitAlign,
onBeforeSearch,
}: AutoCompleteSearchbarProps) {
const { input, setInput, onInputChange, inputReset } = useInput('');
const { searchList, focusIdx, focusControlHandler } = useAutoComplete({ input, setInput, inputReset });
const router = useRouter();

const onSearch = (e: FormEvent) => {
e.preventDefault();
if (onBeforeSearch) {
onBeforeSearch();
}
router.push(`/profile/${input}`);
inputReset();
};
Expand Down
10 changes: 2 additions & 8 deletions frontend/src/pages/404.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GetStaticProps } from 'next';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Head from 'next/head';
import Image from 'next/image';
import { useRouter } from 'next/router';
Expand All @@ -9,6 +8,7 @@ import { useQuery } from '@tanstack/react-query';
import { Button } from '@components/common';
import { requestTokenRefresh } from '@apis/auth';
import { aldrich, lineSeedKR } from '@utils/fonts';
import { ssgWrapper } from '@utils/wrapper';

function NotFound() {
useQuery(['user'], () => requestTokenRefresh(), {
Expand Down Expand Up @@ -45,13 +45,7 @@ function NotFound() {

export default NotFound;

export const getStaticProps: GetStaticProps = async (context) => {
return {
props: {
...(await serverSideTranslations(context.locale as string, ['common', 'footer', 'header', '404', 'meta'])),
},
};
};
export const getStaticProps: GetStaticProps = ssgWrapper(['common', 'footer', 'header', '404', 'meta']);

const Container = styled.div`
${({ theme }) => theme.common.flexCenterColumn};
Expand Down
17 changes: 2 additions & 15 deletions frontend/src/pages/about.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GetStaticProps } from 'next';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Image from 'next/image';
import React from 'react';
import styled from 'styled-components';
Expand All @@ -9,6 +8,7 @@ import { CubeIcon } from '@components/common';
import HeadMeta from '@components/common/HeadMeta';
import { requestTokenRefresh } from '@apis/auth';
import { DEVELOPER_INFORMATION } from '@utils/constants';
import { ssgWrapper } from '@utils/wrapper';

function About() {
useQuery(['user'], () => requestTokenRefresh(), {
Expand Down Expand Up @@ -135,20 +135,7 @@ function About() {

export default About;

export const getStaticProps: GetStaticProps = async (context) => {
return {
props: {
...(await serverSideTranslations(context.locale as string, [
'about',
'tier',
'meta',
'common',
'header',
'footer',
])),
},
};
};
export const getStaticProps: GetStaticProps = ssgWrapper(['about', 'tier', 'meta', 'common', 'header', 'footer']);

const Container = styled.div`
display: flex;
Expand Down
10 changes: 0 additions & 10 deletions frontend/src/pages/callback.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { GetStaticProps } from 'next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import styled from 'styled-components';
Expand Down Expand Up @@ -41,14 +39,6 @@ function Callback() {

export default Callback;

export const getStaticProps: GetStaticProps = async (context) => {
return {
props: {
...(await serverSideTranslations(context.locale as string, ['common', 'footer', 'header'])),
},
};
};

const Container = styled.div`
${({ theme }) => theme.common.flexCenter};
background-color: ${({ theme }) => theme.colors.black1};
Expand Down
105 changes: 59 additions & 46 deletions frontend/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { GetServerSideProps } from 'next';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { QueryClient, dehydrate } from '@tanstack/react-query';
import styled, { keyframes } from 'styled-components';
import LanguageRanking from '@components/Ranking/LanguageRanking';
import OverallRanking from '@components/Ranking/OverallRanking';
import RisingRanking from '@components/Ranking/RisingRanking';
import ViewsRanking from '@components/Ranking/ViewsRanking';
import { Spinner } from '@components/common';
import CubeLogo from '@components/common/CubeLogo';
import HeadMeta from '@components/common/HeadMeta';
import AutoCompleteSearchbar from '@components/common/Searchbar/AutoComplete';
Expand All @@ -20,6 +17,7 @@ import {
requestTopRankingByScore,
requestTopRankingByViews,
} from '@apis/ranking';
import { ssrWrapper } from '@utils/wrapper';

function Home() {
const { t } = useTranslation(['index', 'common', 'meta']);
Expand All @@ -30,28 +28,11 @@ function Home() {
router.push(`/profile/${username}`);
};

useEffect(() => {
const handleStart = () => {
setIsSearchLoading(true);
};

const handleEnd = () => {
setIsSearchLoading(false);
};
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleEnd);
router.events.on('routeChangeError', handleEnd);

return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeStart', handleEnd);
router.events.off('routeChangeError', handleEnd);
};
}, [router]);

return isSearchLoading ? (
<Loading>
<Spinner size={50} />
<div>
사용자 검색 중 입니다<span>.</span>
</div>
</Loading>
) : (
<>
Expand All @@ -65,6 +46,7 @@ function Home() {
width={600}
placeholder={t('index:search-placeholder')}
submitAlign='right'
onBeforeSearch={() => setIsSearchLoading(true)}
/>
<Content>
<OverallRankingSection>
Expand All @@ -87,28 +69,30 @@ function Home() {

export default Home;

export const getServerSideProps: GetServerSideProps = async (context) => {
const queryClient = new QueryClient();

await Promise.allSettled([
queryClient.prefetchQuery(['top-ranking-by-score'], () =>
requestTopRankingByScore({
limit: 12,
}),
),
queryClient.prefetchQuery(['top-ranking-by-rising'], () => requestTopRankingByRising()),
queryClient.prefetchQuery(['top-ranking-by-views'], () => requestTopRankingByViews()),
queryClient.prefetchQuery(['top-ranking-by-programming-lang'], () => requestProgrammingLanguageRanking()),
queryClient.prefetchQuery(['user'], () => requestTokenRefresh(context)),
]);

return {
props: {
dehydratedState: dehydrate(queryClient),
...(await serverSideTranslations(context.locale as string, ['index', 'common', 'header', 'footer', 'meta'])),
},
};
};
export const getServerSideProps: GetServerSideProps = ssrWrapper(
['index', 'common', 'header', 'footer', 'meta'],
async (context, queryClient) => {
await Promise.allSettled([
queryClient.prefetchQuery(['top-ranking-by-score'], () =>
requestTopRankingByScore({
limit: 12,
}),
),
queryClient.prefetchQuery(['top-ranking-by-rising'], () => requestTopRankingByRising()),
queryClient.prefetchQuery(['top-ranking-by-views'], () => requestTopRankingByViews()),
queryClient.prefetchQuery(['top-ranking-by-programming-lang'], () => requestProgrammingLanguageRanking()),
queryClient.prefetchQuery(['user'], () => requestTokenRefresh(context)),
]);

return {
data: {},
redirect: {
trigger: false,
url: '',
},
};
},
);

const Container = styled.div`
${({ theme }) => theme.common.flexCenterColumn};
Expand All @@ -120,8 +104,23 @@ const Container = styled.div`
}
`;

const loading = keyframes`
0% {
content: '';
}
33% {
content: '.';
}
66% {
content: '..';
}
`;

const Loading = styled.div`
${({ theme }) => theme.common.flexCenter};
font-size: ${({ theme }) => theme.fontSize.lg};
background-color: ${({ theme }) => theme.colors.black1};
width: 100%;
height: 100%;
Expand All @@ -131,6 +130,20 @@ const Loading = styled.div`
left: 0;
z-index: 10;
div {
position: relative;
span {
position: absolute;
right: -5px;
&:after {
content: '';
position: absolute;
animation: ${loading} 2s linear infinite;
}
}
}
`;

const Content = styled.div`
Expand Down
Loading

0 comments on commit c5b8086

Please sign in to comment.