Skip to content

Commit

Permalink
Merge pull request #25 from prgrms-be-devcourse/Feature/17-Kim,Cha
Browse files Browse the repository at this point in the history
Feature 17 PR
  • Loading branch information
kjm722 authored Jan 21, 2025
2 parents f1a0a89 + a22a6d9 commit 385ef5b
Show file tree
Hide file tree
Showing 24 changed files with 887 additions and 306 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.scheduling.annotation.EnableScheduling;

import static org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO;

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling // 스케줄링 활성화
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
public class Nbe341Team05Application {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package com.team5.nbe341team05.common.security;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;

import java.util.Collections;
import java.util.List;

import java.io.IOException;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // @PreAuthorize 사용
Expand All @@ -42,7 +48,20 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.formLogin(login -> login
.loginPage("/login") // 로그인 페이지 설정
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/admin/order", true) // 로그인 성공 후 리다이렉트
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
String redirectUrl = request.getParameter("redirectUrl");
if (redirectUrl != null && redirectUrl.startsWith("/admin/")) {
response.sendRedirect(redirectUrl);
} else {
response.sendRedirect("/admin/order");
}

}
}) // 로그인 성공 후 리다이렉트
.permitAll()
)
.logout(logout -> logout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public ResponseMessage<MenuResponseDto> getMenuById(@PathVariable("id") Long id)
);
}

@Transactional
@PostMapping
public ResponseMessage<MenuResponseDto> createMenu(@RequestPart("menu") MenuRequestDto menuRequestDto
, @RequestPart("image")MultipartFile image
Expand All @@ -64,7 +63,7 @@ public ResponseMessage<MenuResponseDto> createMenu(@RequestPart("menu") MenuRequ

@Transactional
@PostMapping("/{id}")
public ResponseMessage<Menu> updateMenu(@PathVariable("id") Long id, @RequestBody MenuRequestDto menuRequestDto, MultipartFile image) throws IOException {
public ResponseMessage<Menu> updateMenu(@PathVariable("id") Long id, @RequestPart(value = "menuRequestDto") MenuRequestDto menuRequestDto, @RequestPart(value = "image") MultipartFile image) throws IOException {
Menu menu = this.menuService.updateMenu(id, menuRequestDto,image);

return new ResponseMessage<>("상품이 성공적으로 수정되었습니다.", String.valueOf(HttpStatus.OK.value()), menu);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@ public String saveImage(MultipartFile imageFile) throws IOException {
Path filePath = Paths.get(uploadDir, originalFilename);
imageFile.transferTo(filePath.toFile());

return originalFilename;
return savedFilename;
}
}
61 changes: 32 additions & 29 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,40 @@ import EventsScreen from './screens/EventsScreen';

import './App.css';
import LoginPage from "./components/login";
import AddMenuPage from "./screens/addMenu";
import AdminOrderDetail from "./screens/AdminOrderDetail";
import AdminOrderList from './screens/AdminOrderList';
import Login from './components/login';
import AddMenuPage from "./screens/admin/AddMenu";
import AdminOrderDetail from "./screens/admin/AdminOrderDetail";
import AdminOrderList from './screens/admin/AdminOrderList';
import AdminMenuScreen from './screens/admin/AdminMenuScreen';
import ModifyMenuPage from './screens/admin/ModifyMenu';
import Layout from './components/Layout';

function App() {
return (
<Router>
<Routes>
<Route path="/" element={<MainMenuScreen/>}/>
<Route path="/order_list" element={<OrderListScreen/>}/>
<Route path="/notice" element={<NoticeScreen/>}/>
<Route path="/events" element={<EventsScreen/>}/>
<Route path="/order" element={<OrderPage/>}/>
<Route path="/order/email" element={<EmailInput/>}/>
<Route path="/order/list" element={<OrderList/>}/>
<Route path="/order/detail/:id" element={<OrderDetail/>}/>
<Route path="/order/modify/:id" element={<OrderModify/>}/>
{/* /login 경로에 LoginPage 컴포넌트를 렌더링 */}
<Route path="/login" element={<LoginPage/>}/>
{/* 기본 경로 설정 */}
{/* <Route path="/" element={<h1>Welcome to the Home Page</h1>} /> */}
<Route path="/admin" element={<Login/>}/>
<Route path="/admin/addMenu" element={<AddMenuPage/>}/>
<Route path="/admin/order/:id" element={<AdminOrderDetail/>}/>
<Route path="/admin/orders" element={<AdminOrderList/>}/>
<Route path="/" element={<MainMenuScreen/>}/>
<Route path="/order_list" element={<OrderListScreen/>}/>
</Routes>
</Router>
);
return (
<Router>
<Routes>
<Route path="/order_list" element={<OrderListScreen/>}/>
<Route path="/notice" element={<NoticeScreen/>}/>
<Route path="/events" element={<EventsScreen/>}/>
<Route path="/order" element={<OrderPage />} />
<Route path="/order/email" element={<EmailInput />} />
<Route path="/order/list" element={<OrderList />} />
<Route path="/order/detail/:id" element={<OrderDetail />} />
<Route path="/order/modify/:id" element={<OrderModify />} />

{/* /login 경로에 LoginPage 컴포넌트를 렌더링 */}
<Route path="/login" element={<LoginPage />} />
{/* 기본 경로 설정 */}
{/* <Route path="/" element={<h1>Welcome to the Home Page</h1>} /> */}
<Route path="/admin/addMenu" element={<AddMenuPage/>}/>
<Route path="/admin/order/:id" element={<AdminOrderDetail />} />
<Route path="/admin/order" element={<AdminOrderList />} />
<Route path="/" element={<MainMenuScreen />} />
<Route path="/order/list" element={<OrderList />} />
<Route path="/admin/modify/:id" element={<ModifyMenuPage />} />
<Route path="/admin/menus" element={<AdminMenuScreen />} />
</Routes>
</Router>
);
}

export default App;
152 changes: 101 additions & 51 deletions frontend/src/DL/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,137 @@ const API_BASE_URL = 'http://localhost:8080'; // 백엔드 API 주소

const api = axios.create({
baseURL: API_BASE_URL, // baseURL을 사용하여 기본 API 주소 설정
headers:{
"Content-Type" : "application/json",
headers: {
"Content-Type": "application/json",
},
withCredentials: true, // 세션 기반 인증에 필요한 설정 (쿠키 포함)
});

// 요청 인터셉터 - 토큰 추가
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);

export const uploadImage = async (image) => {
if (!image) {
alert("이미지를 선택하세요.");
return;
// 응답 인터셉터 추가
api.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('API 에러:', error.response);
if (error.response?.status === 302) {
// 로그인 페이지로 리다이렉트
window.location.href = '/login';
}
return Promise.reject(error);
}
);

export const addMenu = async (menuData, image) => {
try {

const formData = new FormData();
formData.append("image", image);

const response = await api.post(`/admin/menus`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});

alert("이미지 업로드 성공.");
return response.data; // 서버에서 반환된 이미지 URL
} catch (err) {
console.error("이미지 업로드 실패:", err);
throw new Error("이미지 업로드에 실패했습니다.");
}
};
const menuRequestDto = {
productName: menuData.productName,
description: menuData.description,
price: Number(menuData.price),
stock: Number(menuData.stock)
};

export const addMenu = async (menuData,image) => {
try {
const formData = new FormData();
formData.append("menu", JSON.stringify(menuData)); // JSON으로 변환 후 추가
formData.append("menu", new Blob([JSON.stringify(menuRequestDto)], {
type: 'application/json'
}));
formData.append("image", image);

formData.forEach((value, key) => {
console.log(`${key}:`, value);
});
const response = await api.post(`/admin/menus`, formData, {
headers: {
"Content-Type": "application/form-data",
"Content-Type": "multipart/form-data",
},
withCredentials: true,
});

console.log("메뉴 생성 성공:", response.data);
return response.data; // 서버 응답 데이터
return response.data;
} catch (err) {
console.error("메뉴 추가 실패:", err);
throw new Error("메뉴 추가에 실패했습니다.");
if (err.response?.status === 302) {
window.location.href = '/login';
throw new Error('로그인이 필요합니다.');
}
throw new Error(err.response?.data?.message || "메뉴 추가에 실패했습니다.");
}
};

export const adminApi = {
getAllOrders: () => api.get(`${API_BASE_URL}/admin/order`),
getOrderById: (id) => api.get(`${API_BASE_URL}/admin/order/${id}`),
cancelOrder: (id) => api.delete(`${API_BASE_URL}/admin/order/${id}`)
getAllOrders: async () => {
try {
// API 경로 수정
const response = await api.get('/admin/order'); // 또는 실제 백엔드 경로에 맞게 수정
return response;
} catch (error) {
console.error('getAllOrders Error:', error);
throw error;
}
},
getOrderDetail: async (orderId) => {
try {
// API 경로 수정
const response = await api.get(`/admin/order/${orderId}`); // 또는 실제 백엔드 경로에 맞게 수정
return response;
} catch (error) {
console.error('getOrderDetail Error:', error);
throw error;
}
},
modifyMenu: async (menuId, menuData, image) => {
try {
const formData = new FormData();

formData.append('menuRequestDto', new Blob([JSON.stringify(menuData)], {
type: 'application/json'
}));

// 새로운 이미지가 있는 경우에만 추가
if (image instanceof File) {
formData.append('image', image);
}
const response = await api.post(`/admin/menus/${menuId}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
withCredentials: true,
});
return response.data;
} catch (error) {
console.error('modifyMenu Error:', error);
throw error;
}
},
cancelOrder: (orderId) => api.delete(`/admin/order/${orderId}`),
cancelMenu: (menuId) => api.delete(`/admin/menus/${menuId}`)
};

// 주문 생성
export const createOrder = async (orderData) => {
try {
const response = await api.post(`/order`, orderData);
return response.data; // 성공적으로 받은 데이터 반환
} catch (error) {
// 오류 처리
console.error('주문 생성 실패:', error.response?.data || error.message);
throw error; // 오류를 호출한 쪽으로 다시 던짐
}
};
try {
const response = await api.post(`/order`, orderData);
return response.data; // 성공적으로 받은 데이터 반환
} catch (error) {
// 오류 처리
console.error('주문 생성 실패:', error.response?.data || error.message);
throw error; // 오류를 호출한 쪽으로 다시 던짐
}
};

// 주문 조회
export const orderList = (email) => api.get(`/order/${email}`);
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/components/Layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import Navbar from './Navbar';
import Sidebar from './Sidebar';

const Layout = ({ children, showSidebar = true }) => {
return (
<div className="min-h-screen bg-gray-50">
{/* Navbar */}
<Navbar />

{/* Main Content Area */}
<div> {/* Navbar 높이만큼 상단 여백 */}
{showSidebar && <Sidebar />}

{/* Main Content */}
<div> {/* Sidebar 너비만큼 왼쪽 여백 */}
{children}
</div>
</div>
</div>
);
};

export default Layout;
Loading

0 comments on commit 385ef5b

Please sign in to comment.