Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ env:
REACT_ENV: ${{ secrets.REACT_ENV }}
CHATBOT_ENV: ${{ secrets.CHATBOT_ENV }}
SPRINGBOOT_ENV: ${{ secrets.SPRINGBOOT_ENV }}
APPLICATION_YML: ${{ secrets.APPLTICAION_YML }}
APPLICATION_YML: ${{ secrets.APPLICAION_YML }}

# ECR
AWS_REGION: us-east-1
Expand Down
3 changes: 1 addition & 2 deletions chatbot-app/dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ RUN apt-get update && apt-get upgrade -y \
WORKDIR /chatbot-app

# 요구 사항 파일 복사
COPY requirements.txt ./
COPY entrypoint.sh ./
COPY . .

# 실행 권한 부여 (필요한 경우)
RUN chmod +x entrypoint.sh
Expand Down
21 changes: 18 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# 2024년 하반기 인하대학교 컴퓨터공학 종합설계 (1조) 프로젝트

> Initial written at September 19, 2024 <br/>
> last updated at: November 26, 2024
> last updated at: December 07, 2024

## Current: ver. 1.0.2<br/>
## Current: ver. 1.1.1<br/>

> - ver 1.0.0.
> - Init: 프로젝트 세팅 ( React + Spring Boot )
Expand All @@ -17,6 +17,14 @@
> - react-app: 소셜로그인, 결제 페이지 구현
> - springboot-app: 소셜로그인 보안 개선, 법률 용어 API 구축

> - ver 1.1.0.
> - react-app: 채팅 페이지, 법률 용어 사전 페이지 구현
> - springboot-app: 챗봇 API 연결, FastAPI 환경 세팅
> - chatbot-app: 챗봇 모델 구현 및 서버와 연결
> - ver 1.1.1.
> - react-app: 유저 테스트 피드백 반영 UI 개선, 법률 기관 API 연결
> - springboot-app: 법률 기관 API 구축, 1차 배포

# 1. 프로그램 (프로젝트) 설명

- 본 프로젝트는 2024년 하반기 인하대학교 컴퓨터공학 종합설계 3분반 1조 프로젝트입니다
Expand All @@ -36,7 +44,7 @@

- 본 프로젝트는 Docker를 사용하므로 `.env.template` 파일을 참고하여 `.env` 파일에 환경 변수값을 작성해주세요.

- root, react-app, springboot-app 총 3가지 파일을 모두 작성해주세요.
- root, react-app, springboot-app, chatbot-app 총 4가지 파일을 모두 작성해주세요.
- `HOST_PORT` : 외부에서 컨테이너의 애플리케이션에 접근하는데 사용하는 포트 ( 노출되도 괜찮은 포트 )
- `SERVER_PORT` : 애플리케이션이 컨테이너 내에서 통신하는 포트 ( 노출되면 안되는 포트 )
- Vite에서는 보안이 필요한 환경변수의 유출을 막기 위해서 `VITE_`으로 시작하지 않는 환경변수는 무시되기 때문에 `VITE_SPRINGBOOT_HOST_PORT`가 필요합니다.
Expand Down Expand Up @@ -71,6 +79,13 @@
IP_ADDRESS=localhost
```

- `chatoot-app/.env` : Chatbot 애플리케이션 환경을 실행시키기 위해 필요한 환경 변수 파일입니다.

```
# 예시
SERVER_PORT=8000
```

- 본 프로젝트는 Springboot를 사용하므로 `springboot-app/src/main/resources/application.yml.template` 파일을 참고하여 `application.yml` 파일을 생성해주세요.
- `springboot-app/src/main/resources/application.yml`

Expand Down
2 changes: 0 additions & 2 deletions react-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { RouterProvider } from 'react-router-dom';
import router from './routes';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import './index.css';

// 쿼리 클라이언트 생성
Expand All @@ -11,7 +10,6 @@ const App = () => {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>
);
};
Expand Down
45 changes: 0 additions & 45 deletions react-app/src/apis/jwtAuth.js

This file was deleted.

21 changes: 21 additions & 0 deletions react-app/src/apis/legalInstitution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const apiPort = import.meta.env.VITE_SPRINGBOOT_HOST_PORT;
const ipUrl = import.meta.env.VITE_IP_ADDRESS;
const SERVER = `http://${ipUrl}:${apiPort}`;

export const fetchNearbyLegalInstitutions = async (
longitude,
latitude,
count = 3
) => {
const response = await fetch(`${SERVER}/api/legal-institution/nearby`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ longitude, latitude, count }),
});

if (!response.ok) {
throw new Error('Failed to fetch nearby legal institutions');
}

return response.json();
};
39 changes: 39 additions & 0 deletions react-app/src/apis/logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import accessToken from './accessToken';

const apiPort = import.meta.env.VITE_SPRINGBOOT_HOST_PORT;
const ipUrl = import.meta.env.VITE_IP_ADDRESS;
const SERVER = `http://${ipUrl}:${apiPort}`;

const logout = async () => {
const token = accessToken.getToken();
if (!token) {
console.error('Not have token!');
return false;
}

try {
const apiUrl = `${SERVER}/api/user/logout`;

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (response.ok) {
accessToken.setToken(null); // 로그아웃 시 토큰 삭제
return true;
} else {
console.error('Logout error:', response.error);
return false;
}
} catch (error) {
console.error('Logout error:', error);
return false;
}
};

export default logout;
79 changes: 35 additions & 44 deletions react-app/src/components/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,67 @@
/** @jsxImportSource @emotion/react */
import { useState } from 'react';
import { css } from '@emotion/react';
import { useState, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import Sidebar from './Menu/Sidebar';
import Menu from './Menu/Menu';
import Button from '../Button/Button';
import LogoImage from '../../assets/images/logo/logo.png';
import logout from '../../apis/logout';
import useFetchUserInfo from '../../hooks/useFetchUserInfo';

const Header = () => {
const navigate = useNavigate();
const location = useLocation();
const { userInfo } = useFetchUserInfo();
const [isLogin, setIsLogin] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(false);

useEffect(() => {
setIsLogin(!!userInfo); // userInfo가 있으면 로그인 상태 true로 설정
}, [userInfo]);

const openSidebar = () => setSidebarOpen(true);
const closeSidebar = () => setSidebarOpen(false);

const handleLogin = () => {
if (location.pathname === '/login') {
alert('현재 페이지입니다.');
} else {
navigate('/login');
return;
}
navigate('/login');
};

const handleLogout = async () => {
const logoutSuccess = await logout();
if (logoutSuccess) {
alert('로그아웃 되었습니다.');
// 로그아웃 시 상태 초기화
setIsLogin(false);
navigate('/');
}
};

return (
<>
<Sidebar isOpen={sidebarOpen} closeSidebar={closeSidebar} />
<header css={HeaderWrapper}>
<aside css={LogoWrapper} onClick={openSidebar}>
<img src={LogoImage} alt="Logo" />
<h1>Open-Lawyer</h1>

<header className="fixed top-0 left-0 right-0 z-50 bg-gray-700 py-2 px-5 flex justify-between items-center">
<aside
className="flex items-center cursor-pointer"
onClick={openSidebar}
>
<img src={LogoImage} alt="Logo" className="w-8 h-auto mr-2" />
<h1 className="text-white text-xl font-bold font-ubuntu">
Open-Lawyer
</h1>
</aside>
<Menu />
<Button label="로그인" onClick={handleLogin} />
<Button
label={isLogin ? '로그아웃' : '로그인'}
onClick={isLogin ? handleLogout : handleLogin}
/>
</header>
</>
);
};

const HeaderWrapper = css`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--bluegray2);
padding: 0.625rem 1.25rem; /* 10px 20px */
@media (max-width: 768px) {
position: relative;
}
`;

const LogoWrapper = css`
display: flex;
align-items: center;
cursor: pointer;

img {
width: 2rem;
height: auto;
margin-right: 0.625rem;
}

h1 {
font-size: 1.25rem;
color: var(--white);
text-align: center;
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
line-height: 1.5;
letter-spacing: -0.04rem;
}
`;

export default Header;
27 changes: 24 additions & 3 deletions react-app/src/components/Header/Menu/Menu.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { useNavigate } from 'react-router-dom';
import accessToken from '../../../apis/accessToken';

const MenuWrapper = css`
display: flex;
Expand All @@ -27,14 +28,34 @@ const MenuItem = css`
const Menu = () => {
const navigate = useNavigate();

const checkAuthenticationAndNavigate = (path) => {
const token = accessToken.getToken();
if (!token) {
alert('로그인이 필요합니다!');
navigate('/login');
} else {
navigate(path);
}
};

return (
<nav>
<ul css={MenuWrapper}>
<li css={MenuItem} onClick={() => navigate('/chat')}>
<li
css={MenuItem}
onClick={() => checkAuthenticationAndNavigate('/chat')}
>
챗봇과 대화하기
</li>
<li css={MenuItem} onClick={() => navigate('/dictionary')}>법률 용어 사전</li>
<li css={MenuItem} onClick={() => navigate('/price')}>가격</li>
<li
css={MenuItem}
onClick={() => checkAuthenticationAndNavigate('/dictionary')}
>
법률 용어 사전
</li>
<li css={MenuItem} onClick={() => navigate('/price')}>
가격
</li>
</ul>
</nav>
);
Expand Down
24 changes: 18 additions & 6 deletions react-app/src/components/Header/Menu/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { css } from '@emotion/react';
import Button from '../../Button/Button';
import { useNavigate } from 'react-router-dom';
import accessToken from '../../../apis/accessToken';

const itemStyle = css`
font-size: 1rem;
Expand All @@ -21,9 +22,14 @@ const buttonStyle = css`
const Sidebar = ({ isOpen, closeSidebar }) => {
const navigate = useNavigate();

const handleNavigation = (path) => {
navigate(path);
closeSidebar();
const checkAuthenticationAndNavigate = (path) => {
const token = accessToken.getToken();
if (!token) {
alert('로그인이 필요합니다!');
navigate('/login');
} else {
navigate(path);
}
};

return (
Expand All @@ -43,13 +49,19 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
<section css={buttonStyle}>
<Button onClick={closeSidebar} label="닫기" />
</section>
<div css={itemStyle} onClick={() => handleNavigation('/chat')}>
<div
css={itemStyle}
onClick={() => checkAuthenticationAndNavigate('/chat')}
>
챗봇과 대화하기
</div>
<div css={itemStyle} onClick={() => handleNavigation('/dictionary')}>
<div
css={itemStyle}
onClick={() => checkAuthenticationAndNavigate('/dictionary')}
>
법률 용어 사전
</div>
<div css={itemStyle} onClick={() => handleNavigation('/price')}>
<div css={itemStyle} onClick={() => navigate('/price')}>
가격
</div>
</div>
Expand Down
Loading
Loading