diff --git a/.github/workflows/be-ci.yml b/.github/workflows/be-ci.yml
index c724bfc..b5b8eb0 100644
--- a/.github/workflows/be-ci.yml
+++ b/.github/workflows/be-ci.yml
@@ -39,4 +39,4 @@ jobs:
- name: Build and analyze (SpringBoot)
run: |
cd uniro_backend
- ./gradlew clean build -x test
\ No newline at end of file
+ ./gradlew clean build
\ No newline at end of file
diff --git a/uniro_admin_frontend/package-lock.json b/uniro_admin_frontend/package-lock.json
index ceaafc1..0a997a1 100644
--- a/uniro_admin_frontend/package-lock.json
+++ b/uniro_admin_frontend/package-lock.json
@@ -11,6 +11,7 @@
"@googlemaps/js-api-loader": "^1.16.8",
"@tailwindcss/vite": "^4.0.0",
"@tanstack/react-query": "^5.66.0",
+ "@tanstack/react-query-devtools": "^5.66.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.1.3",
@@ -1812,6 +1813,16 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tanstack/query-devtools": {
+ "version": "5.65.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.65.0.tgz",
+ "integrity": "sha512-g5y7zc07U9D3esMdqUfTEVu9kMHoIaVBsD0+M3LPdAdD710RpTcLiNvJY1JkYXqkq9+NV+CQoemVNpQPBXVsJg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tanstack/react-query": {
"version": "5.66.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.0.tgz",
@@ -1828,6 +1839,23 @@
"react": "^18 || ^19"
}
},
+ "node_modules/@tanstack/react-query-devtools": {
+ "version": "5.66.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.66.0.tgz",
+ "integrity": "sha512-uB57wA2YZaQ2fPcFW0E9O1zAGDGSbRKRx84uMk/86VyU9jWVxvJ3Uzp+zNm+nZJYsuekCIo2opTdgNuvM3cKgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-devtools": "5.65.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.66.0",
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
diff --git a/uniro_admin_frontend/package.json b/uniro_admin_frontend/package.json
index 70f430a..b40818c 100644
--- a/uniro_admin_frontend/package.json
+++ b/uniro_admin_frontend/package.json
@@ -13,6 +13,7 @@
"@googlemaps/js-api-loader": "^1.16.8",
"@tailwindcss/vite": "^4.0.0",
"@tanstack/react-query": "^5.66.0",
+ "@tanstack/react-query-devtools": "^5.66.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.1.3",
diff --git a/uniro_admin_frontend/src/App.tsx b/uniro_admin_frontend/src/App.tsx
index 416b32f..cd4ba76 100644
--- a/uniro_admin_frontend/src/App.tsx
+++ b/uniro_admin_frontend/src/App.tsx
@@ -1,17 +1,14 @@
+import { Outlet } from "react-router";
import "./App.css";
import NavBar from "./components/navBar";
-import LogListContainer from "./container/logListContainer";
-import MainContainer from "./container/mainContainer";
-import MapContainer from "./container/mapContainer";
+import SubNavBar from "./components/subNavBar";
function App() {
return (
-
-
-
-
+
+
);
}
diff --git a/uniro_admin_frontend/src/AppRouter.tsx b/uniro_admin_frontend/src/AppRouter.tsx
new file mode 100644
index 0000000..61ef199
--- /dev/null
+++ b/uniro_admin_frontend/src/AppRouter.tsx
@@ -0,0 +1,29 @@
+import { BrowserRouter, Route, Routes } from "react-router";
+import App from "./App";
+import LogPage from "./page/logPage";
+import BuildingPage from "./page/buildingPage";
+import SimulationPage from "./page/simulationPage";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+
+const queryClient = new QueryClient();
+
+function AppRouter() {
+ return (
+
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+ );
+}
+
+export default AppRouter;
diff --git a/uniro_admin_frontend/src/api/nodes.ts b/uniro_admin_frontend/src/api/nodes.ts
new file mode 100644
index 0000000..7340192
--- /dev/null
+++ b/uniro_admin_frontend/src/api/nodes.ts
@@ -0,0 +1,34 @@
+import { Building } from "../data/types/node";
+import { getFetch, postFetch } from "../utils/fetch/fetch";
+
+export const getAllBuildings = (
+ univId: number,
+ params: {
+ leftUpLng: number;
+ leftUpLat: number;
+ rightDownLng: number;
+ rightDownLat: number;
+ }
+): Promise => {
+ return getFetch(`/${univId}/nodes/buildings`, {
+ "left-up-lng": params.leftUpLng,
+ "left-up-lat": params.leftUpLat,
+ "right-down-lng": params.rightDownLng,
+ "right-down-lat": params.rightDownLat,
+ });
+};
+
+export const postBuilding = (
+ univId: number,
+ body: {
+ buildingName: string;
+ buildingImageUrl: string;
+ phoneNumber: string;
+ address: string;
+ lat: number;
+ lng: number;
+ level: number;
+ }
+): Promise => {
+ return postFetch(`/${univId}/nodes/building`, body);
+};
diff --git a/uniro_admin_frontend/src/api/route.ts b/uniro_admin_frontend/src/api/route.ts
new file mode 100644
index 0000000..12cb4a5
--- /dev/null
+++ b/uniro_admin_frontend/src/api/route.ts
@@ -0,0 +1,74 @@
+import { IssueTypeKey } from "../constant/enum/reportEnum";
+import { Coord } from "../data/types/coord";
+import { CautionIssueType, DangerIssueType } from "../data/types/enum";
+import { NodeId } from "../data/types/node";
+
+import {
+ CoreRoutesList,
+ NavigationRouteList,
+ RouteId,
+} from "../data/types/route";
+import { getFetch, postFetch } from "../utils/fetch/fetch";
+import { transformAllRoutes } from "./transformer/route";
+import { GetAllRouteRepsonse } from "./type/response/route";
+
+export const getNavigationResult = (
+ univId: number,
+ startNodeId: NodeId,
+ endNodeId: NodeId
+): Promise => {
+ return getFetch(`/${univId}/routes/fastest`, {
+ "start-node-id": startNodeId,
+ "end-node-id": endNodeId,
+ });
+};
+
+export const getAllRoutes = (univId: number): Promise => {
+ return getFetch(`/${univId}/routes`).then((data) =>
+ transformAllRoutes(data)
+ );
+};
+
+export const getSingleRouteRisk = (
+ univId: number,
+ routeId: RouteId
+): Promise<{
+ routeId: NodeId;
+ dangerTypes: IssueTypeKey[];
+ cautionTypes: IssueTypeKey[];
+}> => {
+ return getFetch<{
+ routeId: NodeId;
+ dangerTypes: IssueTypeKey[];
+ cautionTypes: IssueTypeKey[];
+ }>(`/${univId}/routes/${routeId}/risk`);
+};
+
+export const postReport = (
+ univId: number,
+ routeId: RouteId,
+ body: { dangerTypes: DangerIssueType[]; cautionTypes: CautionIssueType[] }
+): Promise => {
+ return postFetch(`/${univId}/route/risk/${routeId}`, body);
+};
+
+export const postReportRoute = (
+ univId: number,
+ body: {
+ startNodeId: NodeId;
+ endNodeId: NodeId | null;
+ coordinates: Coord[];
+ }
+): Promise => {
+ return postFetch(`/${univId}/route`, body);
+};
+
+export const postBuildingRoute = (
+ univId: number,
+ body: {
+ buildingNodeId: NodeId;
+ nodeId: NodeId;
+ }
+): Promise => {
+ return postFetch(`/${univId}/routes/building`, body);
+};
diff --git a/uniro_admin_frontend/src/api/routes.ts b/uniro_admin_frontend/src/api/routes.ts
new file mode 100644
index 0000000..6ac4143
--- /dev/null
+++ b/uniro_admin_frontend/src/api/routes.ts
@@ -0,0 +1,8 @@
+import { CautionRoute, DangerRoute } from "../data/types/route";
+import { getFetch } from "../utils/fetch/fetch";
+
+export const getAllRisks = (
+ univId: number,
+): Promise<{ dangerRoutes: DangerRoute[]; cautionRoutes: CautionRoute[] }> => {
+ return getFetch<{ dangerRoutes: DangerRoute[]; cautionRoutes: CautionRoute[] }>(`/${univId}/routes/risks`);
+};
diff --git a/uniro_admin_frontend/src/api/search.ts b/uniro_admin_frontend/src/api/search.ts
new file mode 100644
index 0000000..d7c47e9
--- /dev/null
+++ b/uniro_admin_frontend/src/api/search.ts
@@ -0,0 +1,8 @@
+import { University } from "../data/types/university";
+import { getFetch } from "../utils/fetch/fetch";
+import { transformGetUniversityList } from "./transformer/search";
+import { GetUniversityListResponse } from "./type/response/search";
+
+export const getUniversityList = (): Promise => {
+ return getFetch("/univ/search").then(transformGetUniversityList);
+};
diff --git a/uniro_admin_frontend/src/api/transformer/route.ts b/uniro_admin_frontend/src/api/transformer/route.ts
new file mode 100644
index 0000000..fbf6508
--- /dev/null
+++ b/uniro_admin_frontend/src/api/transformer/route.ts
@@ -0,0 +1,31 @@
+import { DangerIssueType } from "../../constant/enum/reportEnum";
+import { CoreRoutesList } from "../../data/types/route";
+import { GetAllRouteRepsonse, GetSingleRouteRiskResponse } from "../type/response/route";
+
+export const transformAllRoutes = (data: GetAllRouteRepsonse): CoreRoutesList => {
+ const { nodeInfos, coreRoutes } = data;
+ const nodeInfoMap = new Map(nodeInfos.map((node) => [node.nodeId, node]));
+
+ return coreRoutes.map((coreRoute) => {
+ return {
+ ...coreRoute,
+ routes: coreRoute.routes.map((route) => {
+ const node1 = nodeInfoMap.get(route.startNodeId);
+ const node2 = nodeInfoMap.get(route.endNodeId);
+
+ if (!node1) {
+ throw new Error(`Node not found: ${route.startNodeId}`);
+ }
+ if (!node2) {
+ throw new Error(`Node not found: ${route.endNodeId}`);
+ }
+
+ return {
+ routeId: route.routeId,
+ node1,
+ node2,
+ };
+ }),
+ };
+ });
+};
diff --git a/uniro_admin_frontend/src/api/transformer/search.ts b/uniro_admin_frontend/src/api/transformer/search.ts
new file mode 100644
index 0000000..3d5fc76
--- /dev/null
+++ b/uniro_admin_frontend/src/api/transformer/search.ts
@@ -0,0 +1,6 @@
+import { University } from "../../data/types/university";
+import { GetUniversityListResponse } from "../type/response/search";
+
+export const transformGetUniversityList = (res: GetUniversityListResponse): University[] => {
+ return res.data;
+};
diff --git a/uniro_admin_frontend/src/api/type/request/route.d.ts b/uniro_admin_frontend/src/api/type/request/route.d.ts
new file mode 100644
index 0000000..d42feee
--- /dev/null
+++ b/uniro_admin_frontend/src/api/type/request/route.d.ts
@@ -0,0 +1,3 @@
+export type getAllRouteRequest = {
+ univId: number;
+};
diff --git a/uniro_admin_frontend/src/api/type/response/route.d.ts b/uniro_admin_frontend/src/api/type/response/route.d.ts
new file mode 100644
index 0000000..5120f82
--- /dev/null
+++ b/uniro_admin_frontend/src/api/type/response/route.d.ts
@@ -0,0 +1,23 @@
+import { IssueTypeKey } from "../../../constant/enum/reportEnum";
+import { Node, NodeId } from "../../../data/types/node";
+
+type CoreRoutesResponse = {
+ coreNode1Id: NodeId;
+ coreNode2Id: NodeId;
+ routes: {
+ routeId: NodeId;
+ startNodeId: NodeId;
+ endNodeId: NodeId;
+ }[];
+};
+
+export type GetAllRouteRepsonse = {
+ nodeInfos: Node[];
+ coreRoutes: CoreRoutesResponse[];
+};
+
+export type GetSingleRouteRiskResponse = {
+ routeId: NodeId;
+ dangerTypes?: IssueTypeKey[];
+ cautionTypes?: IssueTypeKey[];
+};
diff --git a/uniro_admin_frontend/src/api/type/response/search.d.ts b/uniro_admin_frontend/src/api/type/response/search.d.ts
new file mode 100644
index 0000000..0b7acde
--- /dev/null
+++ b/uniro_admin_frontend/src/api/type/response/search.d.ts
@@ -0,0 +1,5 @@
+export type GetUniversityListResponse = {
+ data: University[];
+ nextCursor: number | null;
+ hasNext: boolean;
+};
diff --git a/uniro_admin_frontend/src/assets/markers/building.svg b/uniro_admin_frontend/src/assets/markers/building.svg
new file mode 100644
index 0000000..4fc91f3
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/building.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/caution.svg b/uniro_admin_frontend/src/assets/markers/caution.svg
new file mode 100644
index 0000000..556f369
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/caution.svg
@@ -0,0 +1,16 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/danger.svg b/uniro_admin_frontend/src/assets/markers/danger.svg
new file mode 100644
index 0000000..5508d10
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/danger.svg
@@ -0,0 +1,16 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/destination.svg b/uniro_admin_frontend/src/assets/markers/destination.svg
new file mode 100644
index 0000000..80cbe8d
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/destination.svg
@@ -0,0 +1,15 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/origin.svg b/uniro_admin_frontend/src/assets/markers/origin.svg
new file mode 100644
index 0000000..d633fcd
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/origin.svg
@@ -0,0 +1,15 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/report.svg b/uniro_admin_frontend/src/assets/markers/report.svg
new file mode 100644
index 0000000..6fd82e6
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/report.svg
@@ -0,0 +1,20 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/selectedBuilding.svg b/uniro_admin_frontend/src/assets/markers/selectedBuilding.svg
new file mode 100644
index 0000000..1187dc7
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/selectedBuilding.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/university.svg b/uniro_admin_frontend/src/assets/markers/university.svg
new file mode 100644
index 0000000..9fc7685
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/university.svg
@@ -0,0 +1,13 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/waypoint.svg b/uniro_admin_frontend/src/assets/markers/waypoint.svg
new file mode 100644
index 0000000..5107e6a
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/waypoint.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/waypoint_blue.svg b/uniro_admin_frontend/src/assets/markers/waypoint_blue.svg
new file mode 100644
index 0000000..8405f23
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/waypoint_blue.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_admin_frontend/src/assets/markers/waypoint_red.svg b/uniro_admin_frontend/src/assets/markers/waypoint_red.svg
new file mode 100644
index 0000000..d3ec652
--- /dev/null
+++ b/uniro_admin_frontend/src/assets/markers/waypoint_red.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_admin_frontend/src/component/BuildingMap.tsx b/uniro_admin_frontend/src/component/BuildingMap.tsx
new file mode 100644
index 0000000..e8080b5
--- /dev/null
+++ b/uniro_admin_frontend/src/component/BuildingMap.tsx
@@ -0,0 +1,28 @@
+import { useEffect } from "react";
+import useMap from "../hooks/useMap";
+import useSearchBuilding from "../hooks/useUniversityRecord";
+
+type MapProps = {
+ style?: React.CSSProperties;
+};
+const BuildingMap = ({ style }: MapProps) => {
+ const { mapRef, map, mapLoaded } = useMap();
+
+ const { getCurrentUniversityLngLat, currentUniversity } = useSearchBuilding();
+
+ if (!style) {
+ style = { height: "100%", width: "100%" };
+ }
+
+ useEffect(() => {
+ if (!map || !mapLoaded) return;
+ const universityLatLng = getCurrentUniversityLngLat();
+ map.setCenter(universityLatLng);
+ }, [currentUniversity, mapLoaded, getCurrentUniversityLngLat, map]);
+
+ return (
+
+ );
+};
+
+export default BuildingMap;
diff --git a/uniro_admin_frontend/src/component/Map.tsx b/uniro_admin_frontend/src/component/Map.tsx
index 35e798d..05b1fdb 100644
--- a/uniro_admin_frontend/src/component/Map.tsx
+++ b/uniro_admin_frontend/src/component/Map.tsx
@@ -17,9 +17,8 @@ const Map = ({ style }: MapProps) => {
useEffect(() => {
if (!map || !mapLoaded) return;
const universityLatLng = getCurrentUniversityLngLat();
- console.log("Setting center to:", universityLatLng);
map.setCenter(universityLatLng);
- }, [currentUniversity, mapLoaded]);
+ }, [currentUniversity, mapLoaded, getCurrentUniversityLngLat, map]);
return (
diff --git a/uniro_admin_frontend/src/components/buildings/buildingCard.tsx b/uniro_admin_frontend/src/components/buildings/buildingCard.tsx
new file mode 100644
index 0000000..468755c
--- /dev/null
+++ b/uniro_admin_frontend/src/components/buildings/buildingCard.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+type BuildingCardProps = {
+ buildingName: string;
+ nodeId: number;
+ isSelected: boolean;
+ onDelete?: () => void;
+ onClick: () => void;
+};
+
+const BuildingCard = ({
+ buildingName,
+ nodeId,
+ isSelected = false,
+ onDelete,
+ onClick,
+}: BuildingCardProps) => {
+ return (
+ onClick()}
+ >
+
+
건물명 : {buildingName}
+ node ID : {nodeId}
+
+
+
+
+
+
+ );
+};
+
+export default BuildingCard;
diff --git a/uniro_admin_frontend/src/components/buildings/buildingList.tsx b/uniro_admin_frontend/src/components/buildings/buildingList.tsx
new file mode 100644
index 0000000..bfe6a1a
--- /dev/null
+++ b/uniro_admin_frontend/src/components/buildings/buildingList.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+import { Building } from "../../data/types/node";
+import BuildingCard from "./buildingCard";
+import { Coord } from "../../data/types/coord";
+
+// export interface Node extends Coord {
+// nodeId: NodeId;
+// }
+
+// export interface Building extends Node {
+// buildingName: string;
+// buildingImageUrl: string;
+// phoneNumber: string;
+// address: string;
+// }
+
+type BuildingListProps = {
+ buildings: Building[];
+ selectedBuildingId: number | null;
+ setCenterToCoordinate: (nodeId: number, coord: Coord) => void;
+};
+
+const BuildingList = ({
+ selectedBuildingId,
+ buildings,
+ setCenterToCoordinate,
+}: BuildingListProps) => {
+ return (
+
+ {buildings &&
+ buildings.map((building) => {
+ const { nodeId, buildingName } = building;
+ return (
+
+ setCenterToCoordinate(nodeId, {
+ lat: building.lat,
+ lng: building.lng,
+ })
+ }
+ isSelected={selectedBuildingId === building.nodeId}
+ nodeId={nodeId}
+ buildingName={buildingName}
+ />
+ );
+ })}
+
+ );
+};
+
+export default BuildingList;
diff --git a/uniro_admin_frontend/src/components/buildings/buildingListTitle.tsx b/uniro_admin_frontend/src/components/buildings/buildingListTitle.tsx
new file mode 100644
index 0000000..15e2e49
--- /dev/null
+++ b/uniro_admin_frontend/src/components/buildings/buildingListTitle.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+
+type BuildingListTitleProps = {
+ refreshBuildings: () => void;
+ refetching: boolean;
+};
+
+const BuildingListTitle = ({
+ refreshBuildings,
+ refetching,
+}: BuildingListTitleProps) => {
+ return (
+
+
건물 목록
+
+
+ );
+};
+
+export default BuildingListTitle;
diff --git a/uniro_admin_frontend/src/components/map/mapMarkers.tsx b/uniro_admin_frontend/src/components/map/mapMarkers.tsx
new file mode 100644
index 0000000..1977507
--- /dev/null
+++ b/uniro_admin_frontend/src/components/map/mapMarkers.tsx
@@ -0,0 +1,38 @@
+const markerImages = import.meta.glob("/src/assets/markers/*.svg", {
+ eager: true,
+});
+
+function getImage(type: string): string {
+ return (
+ markerImages[`/src/assets/markers/${type}.svg`] as { default: string }
+ )?.default;
+}
+
+function createImageElement(type: string): HTMLElement {
+ const markerImage = document.createElement("img");
+ markerImage.src = getImage(type);
+ return markerImage;
+}
+
+function createContainerElement(className?: string) {
+ const container = document.createElement("div");
+ container.className = `flex flex-col items-center space-y-[7px] ${className}`;
+
+ return container;
+}
+
+export default function createMarkerElement({
+ type,
+ className,
+}: {
+ type: string;
+ className?: string;
+}): HTMLElement {
+ const container = createContainerElement(className);
+
+ const markerImage = createImageElement(type);
+
+ container.appendChild(markerImage);
+
+ return container;
+}
diff --git a/uniro_admin_frontend/src/components/navBar.tsx b/uniro_admin_frontend/src/components/navBar.tsx
index df7c9bd..0f14b11 100644
--- a/uniro_admin_frontend/src/components/navBar.tsx
+++ b/uniro_admin_frontend/src/components/navBar.tsx
@@ -3,9 +3,7 @@ import UNIROLOGO from "../assets/navbar/UNIRO_ADMIN.svg?react";
import DropDownArrow from "../assets/navbar/dropDownArrow.svg?react";
import useSearchBuilding from "../hooks/useUniversityRecord";
-interface Props {}
-
-const NavBar: React.FC = () => {
+const NavBar: React.FC = () => {
const { currentUniversity, getUniversityNameList, setCurrentUniversity } =
useSearchBuilding();
const [selectedUniversity, setSelectedUniversity] =
diff --git a/uniro_admin_frontend/src/components/subNavBar.tsx b/uniro_admin_frontend/src/components/subNavBar.tsx
new file mode 100644
index 0000000..64f1ba6
--- /dev/null
+++ b/uniro_admin_frontend/src/components/subNavBar.tsx
@@ -0,0 +1,32 @@
+import { Link, useLocation } from "react-router";
+
+const SubNavBar = () => {
+ const location = useLocation(); // 📌 현재 경로 감지
+
+ const getLinkStyle = (path: string) =>
+ location.pathname === path
+ ? "text-blue-700"
+ : "text-gray-700 hover:text-black";
+
+ return (
+
+ );
+};
+
+export default SubNavBar;
diff --git a/uniro_admin_frontend/src/constant/bounds.ts b/uniro_admin_frontend/src/constant/bounds.ts
new file mode 100644
index 0000000..7b34585
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/bounds.ts
@@ -0,0 +1,13 @@
+interface Bounds {
+ north: number;
+ south: number;
+ east: number;
+ west: number;
+}
+
+export const HanyangUniversityBounds: Bounds = {
+ north: 37.560645,
+ south: 37.552997,
+ east: 127.051049,
+ west: 127.04111,
+};
diff --git a/uniro_admin_frontend/src/constant/edge.ts b/uniro_admin_frontend/src/constant/edge.ts
new file mode 100644
index 0000000..d55001d
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/edge.ts
@@ -0,0 +1 @@
+export const EDGE_LENGTH = 3;
diff --git a/uniro_admin_frontend/src/constant/enum/markerEnum.ts b/uniro_admin_frontend/src/constant/enum/markerEnum.ts
new file mode 100644
index 0000000..9487ca9
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/enum/markerEnum.ts
@@ -0,0 +1,11 @@
+export const enum Markers {
+ CAUTION = "caution",
+ DANGER = "danger",
+ BUILDING = "building",
+ ORIGIN = "origin",
+ DESTINATION = "destination",
+ SELECTED_BUILDING = "selectedBuilding",
+ WAYPOINT = "waypoint",
+ NUMBERED_WAYPOINT = "numberedWayPoint",
+ REPORT = "report",
+}
diff --git a/uniro_admin_frontend/src/constant/enum/messageEnum.ts b/uniro_admin_frontend/src/constant/enum/messageEnum.ts
new file mode 100644
index 0000000..c2f249d
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/enum/messageEnum.ts
@@ -0,0 +1,6 @@
+export enum ReportRiskMessage {
+ DEFAULT = "선 위를 눌러 제보할 지점을 선택하세요",
+ CREATE = "이 지점으로 새로운 제보를 진행할까요?",
+ UPDATE = "이 지점에 제보된 기존 정보를 바꿀까요?",
+ ERROR = "선 위에서만 선택 가능해요",
+}
diff --git a/uniro_admin_frontend/src/constant/enum/reportEnum.ts b/uniro_admin_frontend/src/constant/enum/reportEnum.ts
new file mode 100644
index 0000000..04ccce6
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/enum/reportEnum.ts
@@ -0,0 +1,28 @@
+export enum PassableStatus {
+ DANGER = "DANGER",
+ CAUTION = "CAUTION",
+ RESTORED = "RESTORED",
+ INITIAL = "INITIAL",
+}
+
+export enum CautionIssue {
+ CURB = "낮은 턱이 있어요",
+ CRACK = "도로에 균열이 있어요",
+ SLOPE = "낮은 비탈길이 있어요",
+ ETC = "그 외 요소",
+}
+
+export enum DangerIssue {
+ CURB = "높은 턱이 있어요",
+ STAIRS = "계단이 있어요",
+ SLOPE = "경사가 매우 높아요",
+ ETC = "그 외 요소",
+}
+
+export enum IssueTypeKey {
+ CURB = "CURB",
+ CRACK = "CRACK",
+ SLOPE = "SLOPE",
+ ETC = "ETC",
+ STAIRS = "STAIRS",
+}
diff --git a/uniro_admin_frontend/src/constant/enum/routeEnum.ts b/uniro_admin_frontend/src/constant/enum/routeEnum.ts
new file mode 100644
index 0000000..aeff459
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/enum/routeEnum.ts
@@ -0,0 +1,4 @@
+export const enum RoutePoint {
+ ORIGIN = "origin",
+ DESTINATION = "destination",
+}
diff --git a/uniro_admin_frontend/src/constant/error.ts b/uniro_admin_frontend/src/constant/error.ts
new file mode 100644
index 0000000..8f89370
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/error.ts
@@ -0,0 +1,19 @@
+export class NotFoundError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "Not Found";
+ }
+}
+
+export class BadRequestError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "Bad Request";
+ }
+}
+
+export enum ERROR_STATUS {
+ NOT_FOUND = 404,
+ BAD_REQUEST = 400,
+ INTERNAL_ERROR = 500,
+}
diff --git a/uniro_admin_frontend/src/constant/fallback.tsx b/uniro_admin_frontend/src/constant/fallback.tsx
new file mode 100644
index 0000000..adf8c73
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/fallback.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+import Loading from "../components/loading/loading";
+
+export const fallbackConfig: Record = {
+ "/": ,
+ "/map": ,
+ "/result": ,
+ "/report/route": ,
+ "/report/hazard": ,
+ "/university": ,
+ "/form": ,
+};
diff --git a/uniro_admin_frontend/src/constant/reportTheme.ts b/uniro_admin_frontend/src/constant/reportTheme.ts
new file mode 100644
index 0000000..8e8d6ac
--- /dev/null
+++ b/uniro_admin_frontend/src/constant/reportTheme.ts
@@ -0,0 +1,8 @@
+import { PassableStatus } from "./enum/reportEnum";
+
+export const THEME_MAP: Record = {
+ [PassableStatus.DANGER]: "border-system-red text-system-red bg-[#FFF5F7]",
+ [PassableStatus.CAUTION]: "border-system-orange text-system-orange bg-[#FFF7EF]",
+ [PassableStatus.RESTORED]: "border-primary-400 text-primary-400 bg-[#F0F5FE]",
+ [PassableStatus.INITIAL]: "bg-gray-100 border-gray-400",
+};
diff --git a/uniro_admin_frontend/src/container/building/buildingAddContainer.tsx b/uniro_admin_frontend/src/container/building/buildingAddContainer.tsx
new file mode 100644
index 0000000..d8e3174
--- /dev/null
+++ b/uniro_admin_frontend/src/container/building/buildingAddContainer.tsx
@@ -0,0 +1,337 @@
+import React, { useEffect, useState } from "react";
+import { Building, Node, NodeId } from "../../data/types/node";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { postBuilding } from "../../api/nodes";
+import { postBuildingRoute } from "../../api/route";
+
+type Coord = {
+ lat: number;
+ lng: number;
+};
+
+type BuildingAddContainerProps = {
+ selectedCoord?: Coord;
+ setSelectedCoord?: React.Dispatch>;
+ markerRef: React.MutableRefObject;
+ selectedBuilding: Building | null;
+ drawSingleRoute: (start: Node, end: Node) => void;
+ mode: "add" | "connect" | "view";
+ selectedNode: Node[];
+ resetConnectMode: () => void;
+};
+
+const BuildingAddContainer: React.FC = ({
+ selectedCoord,
+ setSelectedCoord,
+ markerRef,
+ selectedBuilding,
+ mode,
+ selectedNode,
+ drawSingleRoute,
+ resetConnectMode,
+}) => {
+ const queryClient = useQueryClient();
+ const [buildingName, setBuildingName] = useState("");
+ const [buildingPhoto, setBuildingPhoto] = useState("");
+ const [phone, setPhone] = useState("");
+ const [address, setAddress] = useState("");
+
+ const [isSelectedNode1, setIsSelectedNode1] = useState(true);
+
+ const addBuilding = useMutation({
+ mutationFn: (body: {
+ buildingName: string;
+ buildingImageUrl: string;
+ phoneNumber: string;
+ address: string;
+ lat: number;
+ lng: number;
+ level: number;
+ }) => postBuilding(1001, body),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [1001, "buildings"] });
+ },
+ onError: (error) => {
+ alert("건물 추가에 실패했습니다.");
+ },
+ });
+
+ const addBuildingRoute = useMutation({
+ mutationFn: (body: { buildingNodeId: NodeId; nodeId: NodeId }) =>
+ postBuildingRoute(1001, body),
+ onSuccess: () => {
+ alert("경로가 추가되었습니다.");
+ queryClient.invalidateQueries({ queryKey: [1001, "routes"] });
+ },
+ onError: (error) => {
+ alert("경로 추가에 실패했습니다.");
+ },
+ });
+
+ useEffect(() => {
+ if (selectedBuilding) {
+ setBuildingName(selectedBuilding?.buildingName);
+ setPhone(selectedBuilding?.phoneNumber || "");
+ setBuildingPhoto(selectedBuilding?.buildingImageUrl || "");
+ setAddress(selectedBuilding.address || "");
+ } else {
+ setBuildingName("");
+ setPhone("");
+ setBuildingPhoto("");
+ setAddress("");
+ }
+ }, [selectedBuilding]);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!selectedCoord && !selectedBuilding) {
+ alert("먼저 지도에서 위치를 선택하거나, 기존 건물을 선택해주세요.");
+ return;
+ }
+
+ if (!buildingName || !buildingPhoto || !phone || !address) {
+ alert("모든 필드를 입력해주세요.");
+ return;
+ }
+
+ const payload = {
+ buildingName,
+ buildingImageUrl: buildingPhoto,
+ phoneNumber: phone,
+ address,
+ lat: selectedCoord?.lat ?? selectedBuilding?.lat ?? 0,
+ lng: selectedCoord?.lng ?? selectedBuilding?.lng ?? 0,
+ level: 10,
+ };
+ if (mode === "add") {
+ if (payload.lat && payload.lng) {
+ addBuilding.mutate(payload);
+ }
+ }
+
+ alert(
+ selectedBuilding ? "건물이 수정되었습니다." : "건물이 추가되었습니다."
+ );
+ // 폼 초기화
+ if (setSelectedCoord) setSelectedCoord(undefined);
+ if (markerRef.current) markerRef.current.map = null;
+ setBuildingName("");
+ setBuildingPhoto("");
+ setPhone("");
+ setAddress("");
+ };
+
+ const handleConnectSubmit = () => {
+ if (selectedNode.length === 2 && selectedBuilding) {
+ if (isSelectedNode1) {
+ addBuildingRoute.mutate({
+ buildingNodeId: selectedBuilding.nodeId,
+ nodeId: selectedNode[0].nodeId,
+ });
+
+ resetConnectMode();
+ return;
+ }
+ addBuildingRoute.mutate({
+ buildingNodeId: selectedBuilding.nodeId,
+ nodeId: selectedNode[1].nodeId,
+ });
+ resetConnectMode();
+ return;
+ }
+ alert("노드를 2개 선택해주세요.");
+ return;
+ };
+
+ return (
+
+ {mode === "add" || mode === "view" ? (
+ selectedCoord || selectedBuilding ? (
+ <>
+ {selectedCoord && (
+
+
선택한 위치:
+
lat: {selectedCoord.lat}
+
lng: {selectedCoord.lng}
+
+ )}
+
+
+ >
+ ) : (
+
+ )
+ ) : (
+
+
+ 선택된 정보
+
+
+
+ {selectedBuilding ? (
+
+
+ 🏢 건물명:{" "}
+ {selectedBuilding.buildingName}
+
+
+ ) : (
+
🏢 선택된 건물이 없습니다.
+ )}
+
+ {selectedNode.length > 0 ? (
+ <>
+
+
+ 📍 선택한 노드 정보 1
+
+
+ NODE ID: {selectedNode[0].nodeId}
+
+
+ 위도: {selectedNode[0].lat}
+
+
+ 경도: {selectedNode[0].lng}
+
+
+
+
+
+
+
+ 📍 선택한 노드 정보 2
+
+
+ NODE ID: {selectedNode[1].nodeId}
+
+
+ 위도: {selectedNode[1].lat}
+
+
+ 경도: {selectedNode[1].lng}
+
+
+
+
+
+
+ >
+ ) : (
+
+ 📍 선택된 길 좌표가 없습니다.
+
+ )}
+
+ )}
+
+ );
+};
+
+export default BuildingAddContainer;
diff --git a/uniro_admin_frontend/src/container/building/buildingListContainer.tsx b/uniro_admin_frontend/src/container/building/buildingListContainer.tsx
new file mode 100644
index 0000000..63eac90
--- /dev/null
+++ b/uniro_admin_frontend/src/container/building/buildingListContainer.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import BuildingListTitle from "../../components/buildings/buildingListTitle";
+import BuildingList from "../../components/buildings/buildingList";
+import { Building } from "../../data/types/node";
+import { Coord } from "../../data/types/coord";
+
+type BuildingListContainerProps = {
+ selectedBuildingId: number | null;
+ buildings: Building[];
+ setCenterToCoordinate: (nodeId: number, coord: Coord) => void;
+ refreshBuildings: () => void;
+ refetching: boolean;
+};
+
+const BuildingListContainer = ({
+ selectedBuildingId,
+ buildings,
+ setCenterToCoordinate,
+ refreshBuildings,
+ refetching,
+}: BuildingListContainerProps) => {
+ return (
+
+
+ {buildings ? (
+
+ ) : (
+
+
데이터가 없습니다, undefined or null
+
+ )}
+
+ );
+};
+
+export default BuildingListContainer;
diff --git a/uniro_admin_frontend/src/container/building/buildingMapContainer.tsx b/uniro_admin_frontend/src/container/building/buildingMapContainer.tsx
new file mode 100644
index 0000000..5a4b576
--- /dev/null
+++ b/uniro_admin_frontend/src/container/building/buildingMapContainer.tsx
@@ -0,0 +1,59 @@
+import React, { forwardRef } from "react";
+type BuildingMapContainerProps = {
+ mode: "add" | "connect" | "view";
+ setMode: React.Dispatch>;
+ resetToAddMode: () => void;
+ changeToConnectMode: () => void;
+};
+
+const BuildingMapContainer = forwardRef<
+ HTMLDivElement,
+ BuildingMapContainerProps
+>(
+ (
+ { mode, resetToAddMode, changeToConnectMode }: BuildingMapContainerProps,
+ ref
+ ) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
+
+export default BuildingMapContainer;
diff --git a/uniro_admin_frontend/src/container/mapContainer.tsx b/uniro_admin_frontend/src/container/mapContainer.tsx
index e20f603..8dd0696 100644
--- a/uniro_admin_frontend/src/container/mapContainer.tsx
+++ b/uniro_admin_frontend/src/container/mapContainer.tsx
@@ -1,9 +1,7 @@
import React from "react";
import Map from "../component/Map";
-type Props = {};
-
-const MapContainer = (props: Props) => {
+const MapContainer = () => {
return (
diff --git a/uniro_admin_frontend/src/data/types/coord.d.ts b/uniro_admin_frontend/src/data/types/coord.d.ts
new file mode 100644
index 0000000..625eadd
--- /dev/null
+++ b/uniro_admin_frontend/src/data/types/coord.d.ts
@@ -0,0 +1 @@
+export type Coord = google.maps.LatLngLiteral;
diff --git a/uniro_admin_frontend/src/data/types/node.d.ts b/uniro_admin_frontend/src/data/types/node.d.ts
new file mode 100644
index 0000000..5e653b1
--- /dev/null
+++ b/uniro_admin_frontend/src/data/types/node.d.ts
@@ -0,0 +1,14 @@
+import { Coord } from "./coord";
+
+export type NodeId = number;
+
+export interface Node extends Coord {
+ nodeId: NodeId;
+}
+
+export interface Building extends Node {
+ buildingName: string;
+ buildingImageUrl: string;
+ phoneNumber: string;
+ address: string;
+}
diff --git a/uniro_admin_frontend/src/data/types/route.d.ts b/uniro_admin_frontend/src/data/types/route.d.ts
new file mode 100644
index 0000000..ca0df11
--- /dev/null
+++ b/uniro_admin_frontend/src/data/types/route.d.ts
@@ -0,0 +1,54 @@
+import {
+ CautionIssueType,
+ DangerIssueType,
+} from "../../constant/enum/reportEnum";
+import { RoutePoint } from "../../constant/enum/routeEnum";
+import { Coord } from "./coord";
+import { Node } from "./node";
+
+export type RouteId = number;
+
+export type Route = {
+ routeId: RouteId;
+ node1: Coord;
+ node2: Coord;
+};
+
+export interface CautionRoute extends Route {
+ cautionTypes: CautionIssueType[];
+}
+
+export interface DangerRoute extends Route {
+ dangerTypes: DangerIssueType[];
+}
+
+export interface CoreRoute {
+ routeId: RouteId;
+ node1: Node;
+ node2: Node;
+}
+
+export interface CoreRoutes {
+ coreNode1Id: NodeId;
+ coreNode2Id: NodeId;
+ routes: CoreRoute[];
+}
+
+export type CoreRoutesList = CoreRoutes[];
+
+export type RoutePointType = RoutePoint.ORIGIN | RoutePoint.DESTINATION;
+
+export type RouteDetail = {
+ dist: number;
+ directionType: Direction;
+ coordinates: Coord;
+ cautionFactors: CautionIssueType[];
+};
+
+export type NavigationRouteList = {
+ hasCaution: boolean;
+ totalDistance: number;
+ totalCost: number;
+ routes: Route[];
+ routeDetails: RouteDetail[];
+};
diff --git a/uniro_admin_frontend/src/main.tsx b/uniro_admin_frontend/src/main.tsx
index bef5202..5a81f29 100644
--- a/uniro_admin_frontend/src/main.tsx
+++ b/uniro_admin_frontend/src/main.tsx
@@ -1,10 +1,10 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import AppRouter from "./AppRouter.tsx";
-createRoot(document.getElementById('root')!).render(
+createRoot(document.getElementById("root")!).render(
-
- ,
-)
+
+
+);
diff --git a/uniro_admin_frontend/src/page/buildingPage.tsx b/uniro_admin_frontend/src/page/buildingPage.tsx
new file mode 100644
index 0000000..7af7627
--- /dev/null
+++ b/uniro_admin_frontend/src/page/buildingPage.tsx
@@ -0,0 +1,476 @@
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import MainContainer from "../container/mainContainer";
+import BuildingListContainer from "../container/building/buildingListContainer";
+import BuildingMapContainer from "../container/building/buildingMapContainer";
+import BuildingAddContainer from "../container/building/buildingAddContainer";
+import useMap from "../hooks/useMap";
+import { Coord } from "../data/types/coord";
+import useSearchBuilding from "../hooks/useUniversityRecord";
+
+import createMarkerElement from "../components/map/mapMarkers";
+import { QueryClient, useQueries } from "@tanstack/react-query";
+import { getAllRoutes } from "../api/route";
+import { CoreRoute, CoreRoutesList } from "../data/types/route";
+import { getAllBuildings } from "../api/nodes";
+import createAdvancedMarker from "../utils/markers/createAdvanedMarker";
+import { Node, NodeId } from "../data/types/node";
+import { Markers } from "../constant/enum/markerEnum";
+import findNearestSubEdge from "../utils/polylines/findNearestEdge";
+
+const BuildingPage = () => {
+ const queryClient = new QueryClient();
+ const result = useQueries({
+ queries: [
+ { queryKey: ["1001", "routes"], queryFn: () => getAllRoutes(1001) },
+ {
+ queryKey: [1001, "buildings"],
+ queryFn: () =>
+ getAllBuildings(1001, {
+ leftUpLat: 38,
+ leftUpLng: 127,
+ rightDownLat: 37,
+ rightDownLng: 128,
+ }),
+ },
+ ],
+ });
+
+ // 선택한 지점은 가장 높은 레벨의 컴포넌트에서 관리
+ const [routes, buildings] = result;
+
+ // mode는 두개로 분류 (건물 추가 / 수정 모드 , 건물과 주변 길 잇는 모드(출입문 연결)
+ const [mode, setMode] = useState<"add" | "connect" | "view">("add");
+
+ const { map, mapLoaded, mapRef, AdvancedMarker, Polyline } = useMap();
+
+ const [selectedBuildingId, setSelectedBuildingId] = useState
(
+ 0
+ );
+ const { getCurrentUniversityLngLat } = useSearchBuilding();
+
+ // mode = add or view일 때 선택한 빌딩 좌표
+ const [selectedCoord, setSelectedCoord] = useState(
+ undefined
+ );
+
+ // mode = connect일 때 선택한 좌표들
+ const [selectedNode, setSelectedNode] = useState([]);
+ // mode = connect일 때 연결한 하나의 좌표
+ const [_, setSelectedSingleNode] = useState(null);
+
+ // mode = connect일 때 선택한 Edge
+ const [selectedEdge, setSelectedEdge] = useState<{
+ info: CoreRoute;
+ marker1: google.maps.marker.AdvancedMarkerElement;
+ marker2: google.maps.marker.AdvancedMarkerElement;
+ polyline: google.maps.Polyline;
+ }>();
+
+ /// mode = connect일 때 그려진 작은 하나의 Edge(선택한 Edge)
+ const [singleRoute, setSingleRoute] = useState<{
+ start: Coord;
+ end: Coord;
+ polyline: google.maps.Polyline;
+ }>();
+
+ // event의 mode가 참조가 안되어서 만든 State
+ const [buildingMarkers, setBuildingMarkers] = useState<
+ google.maps.marker.AdvancedMarkerElement[]
+ >([]);
+
+ const [polylineList, setPolylineList] = useState([]);
+
+ // 새로 선택한 건물 마커를 관리하기 위한 ref
+ const markerRef = useRef(
+ null
+ );
+
+ // 마커 생성
+ const createMarker = useCallback(
+ (coord: Coord) => {
+ if (!map || !AdvancedMarker) return null;
+ return new AdvancedMarker({ position: coord, map });
+ },
+ [map, AdvancedMarker]
+ );
+
+ const handleMapClick = (e: google.maps.MapMouseEvent) => {
+ if (!e.latLng) return;
+
+ setSelectedBuildingId(0);
+ setMode("add");
+ setSelectedCoord({
+ lat: e.latLng.lat(),
+ lng: e.latLng.lng(),
+ });
+ };
+
+ const drawRoute = (
+ coreRouteList: CoreRoutesList,
+ mode: "add" | "connect" | "view"
+ ) => {
+ if (!Polyline || !AdvancedMarker || !map) return;
+
+ for (const coreRoutes of coreRouteList) {
+ const { routes: subRoutes } = coreRoutes;
+
+ // 가장 끝쪽 Core Node 그리기
+ const endNode = subRoutes[subRoutes.length - 1].node2;
+
+ const endMarker = new AdvancedMarker({
+ map: map,
+ position: endNode,
+ content: createMarkerElement({
+ type: "waypoint",
+ className: "translate-waypoint",
+ }),
+ });
+
+ endMarker.addListener("click", () => {
+ if (mode === "view" || mode === "add") setSelectedBuildingId(0);
+ });
+
+ const subNodes = [subRoutes[0].node1, ...subRoutes.map((el) => el.node2)];
+
+ const polyline = new Polyline({
+ map: map,
+ path: subNodes.map((el) => {
+ return { lat: el.lat, lng: el.lng };
+ }),
+ strokeColor: "#808080",
+ });
+
+ //polyline을 선택 방지
+ google.maps.event.addListener(
+ polyline,
+ "click",
+ (e: { latLng: google.maps.LatLng }) => {
+ if (mode === "view" || mode === "add") setSelectedBuildingId(0);
+ if (mode === "connect") {
+ const edges: CoreRoute[] = subRoutes.map(
+ ({ routeId, node1, node2 }) => {
+ return { routeId, node1, node2 };
+ }
+ );
+
+ const { edge: nearestEdge } = findNearestSubEdge(edges, {
+ lat: e.latLng.lat(),
+ lng: e.latLng.lng(),
+ });
+ const { node1, node2 } = nearestEdge;
+
+ const polyline = new Polyline({
+ map: map,
+ path: [
+ { lat: node1.lat, lng: node1.lng },
+ { lat: node2.lat, lng: node2.lng },
+ ],
+ strokeColor: "#000000",
+ zIndex: 100,
+ });
+
+ const marker1 = new AdvancedMarker({
+ map: map,
+ position: { lat: node1.lat, lng: node1.lng },
+ content: createMarkerElement({
+ type: "waypoint_red",
+ className: "translate-waypoint",
+ }),
+ zIndex: 100,
+ });
+
+ const marker2 = new AdvancedMarker({
+ map: map,
+ position: { lat: node2.lat, lng: node2.lng },
+ content: createMarkerElement({
+ type: "waypoint_blue",
+ className: "translate-waypoint",
+ }),
+ zIndex: 100,
+ });
+ setSelectedEdge({
+ info: nearestEdge,
+ marker1,
+ marker2,
+ polyline,
+ });
+
+ setSelectedNode([node1, node2]);
+ }
+ }
+ );
+ setPolylineList((prev) => [...prev, polyline]);
+
+ const startNode = subRoutes[0].node1;
+
+ const startMarker = new AdvancedMarker({
+ map: map,
+ position: startNode,
+ content: createMarkerElement({
+ type: "waypoint",
+ className: "translate-waypoint",
+ }),
+ });
+
+ startMarker.addListener("click", () => {
+ if (mode === "view" || mode === "add") setSelectedBuildingId(0);
+ });
+ }
+ };
+
+ const drawSingleRoute = (start: Coord, end: Coord) => {
+ if (!Polyline || !AdvancedMarker || !map) return;
+
+ if (singleRoute) {
+ singleRoute.polyline.setMap(null);
+ }
+
+ if (selectedBuildingId === 0) {
+ return;
+ }
+
+ const polyline = new Polyline({
+ map: map,
+ path: [start, end],
+ strokeColor: "#808080",
+ });
+
+ setSingleRoute({ start, end, polyline });
+ };
+
+ const eraseRoute = () => {
+ if (singleRoute) {
+ singleRoute.polyline.setMap(null);
+ }
+ };
+
+ const erasePolylineList = () => {
+ for (const polyline of polylineList) {
+ polyline.setMap(null);
+ }
+ setPolylineList([]);
+ };
+
+ const addBuildings = () => {
+ if (google.maps.marker.AdvancedMarkerElement === null || map === null)
+ return;
+
+ const buildingList = buildings.data;
+
+ if (buildingList === undefined) return;
+ console.log(buildingList);
+ for (const building of buildingList) {
+ const { nodeId, lat, lng } = building;
+
+ const buildingMarker = createAdvancedMarker(
+ google.maps.marker.AdvancedMarkerElement,
+ map,
+ new google.maps.LatLng(lat, lng),
+ createMarkerElement({
+ type: Markers.BUILDING,
+ className: "translate-marker",
+ })
+ );
+
+ buildingMarker.addListener("click", () => {
+ setSelectedBuildingId(nodeId);
+ map.setCenter({ lat, lng });
+ map.setZoom(18);
+ console.log("click", mode);
+ if (mode === "view" || mode === "add") {
+ setMode("view");
+ }
+ });
+
+ setBuildingMarkers((prev) => [...prev, buildingMarker]);
+ }
+ };
+
+ const clearBuildingMarkers = () => {
+ for (const marker of buildingMarkers) {
+ marker.map = null;
+ }
+ setBuildingMarkers([]);
+ };
+
+ // 지도가 로드되면 클릭 이벤트를 추가
+ useEffect(() => {
+ if (!map || !mapLoaded) return;
+
+ if (routes.data) {
+ if (routes.data) {
+ drawRoute(routes.data, mode);
+ }
+ }
+ if (buildings.data) {
+ addBuildings();
+ }
+
+ if (mode === "add") {
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ map.addListener("click", handleMapClick);
+ map.addListener("rightclick", () => {
+ setSelectedCoord(undefined);
+ setSelectedBuildingId(0);
+
+ if (markerRef.current) markerRef.current.map = null;
+ });
+ }
+
+ if (mode === "connect") {
+ setSelectedCoord(undefined);
+ setSelectedBuildingId(0);
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ }
+
+ map.setCenter(getCurrentUniversityLngLat());
+ eraseRoute();
+
+ return () => {
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ clearBuildingMarkers();
+ };
+ }, [map, mapLoaded, getCurrentUniversityLngLat, routes.data, buildings.data]);
+
+ useEffect(() => {
+ if (!map || !mapLoaded) return;
+
+ if (routes.data) {
+ clearBuildingMarkers();
+ erasePolylineList();
+ drawRoute(routes.data, mode);
+ }
+
+ if (mode === "add") {
+ addBuildings();
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ map.addListener("click", handleMapClick);
+ map.addListener("rightclick", () => {
+ setSelectedCoord(undefined);
+ setSelectedBuildingId(0);
+
+ if (markerRef.current) markerRef.current.map = null;
+ });
+ }
+
+ if (mode === "connect") {
+ addBuildings();
+ setSelectedCoord(undefined);
+ setSelectedBuildingId(0);
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ }
+ return () => {
+ google.maps.event.clearListeners(map, "click");
+ google.maps.event.clearListeners(map, "rightclick");
+ };
+ }, [mode]);
+
+ // 선택한 좌표가 바뀌면 마커를 생성하거나 제거
+ useEffect(() => {
+ if (!selectedCoord || !map) return;
+
+ if (markerRef.current) markerRef.current.map = null;
+ markerRef.current = createMarker(selectedCoord);
+
+ return () => {
+ if (markerRef.current) markerRef.current.map = null;
+ };
+ }, [selectedCoord, map, AdvancedMarker, createMarker]);
+
+ const setCenterToCoordinate = (nodeId: number, coord: Coord) => {
+ if (map) {
+ map.setZoom(18);
+ map.setCenter(coord);
+ setSelectedBuildingId(nodeId);
+ setMode("view");
+ }
+ };
+
+ // 새로고침 기능
+ const refreshBuildings = () => {
+ queryClient.invalidateQueries({ queryKey: [1001, "buildings"] });
+ buildings.refetch();
+ };
+
+ const changeToConnectMode = () => {
+ setMode("connect");
+ setSelectedBuildingId(0);
+ if (map) {
+ map.setZoom(17);
+ map.setCenter(getCurrentUniversityLngLat());
+ }
+ };
+
+ const resetConnectMode = () => {
+ setSelectedBuildingId(0);
+ setSelectedEdge(undefined);
+ setSelectedNode([]);
+ setSelectedSingleNode(null);
+ eraseRoute();
+ };
+
+ const resetToAddMode = () => {
+ setMode("add");
+ setSelectedBuildingId(0);
+ setSelectedEdge(undefined);
+ setSelectedNode([]);
+ setSelectedSingleNode(null);
+ if (map) {
+ map.setZoom(17);
+ map.setCenter(getCurrentUniversityLngLat());
+ }
+ };
+
+ useEffect(() => {
+ return () => {
+ if (!selectedEdge) return;
+ const { marker1, marker2, polyline } = selectedEdge;
+ marker1.map = null;
+ marker2.map = null;
+ polyline.setMap(null);
+ if (singleRoute) {
+ eraseRoute();
+ }
+ };
+ }, [selectedEdge]);
+
+ return (
+
+
+
+ building.nodeId === selectedBuildingId
+ ) || null
+ }
+ />
+
+ );
+};
+
+export default BuildingPage;
diff --git a/uniro_admin_frontend/src/page/logPage.tsx b/uniro_admin_frontend/src/page/logPage.tsx
new file mode 100644
index 0000000..d0f7365
--- /dev/null
+++ b/uniro_admin_frontend/src/page/logPage.tsx
@@ -0,0 +1,15 @@
+import React from "react";
+import MainContainer from "../container/mainContainer";
+import LogListContainer from "../container/logListContainer";
+import MapContainer from "../container/mapContainer";
+
+const LogPage = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default LogPage;
diff --git a/uniro_admin_frontend/src/page/simulationPage.tsx b/uniro_admin_frontend/src/page/simulationPage.tsx
new file mode 100644
index 0000000..36431ed
--- /dev/null
+++ b/uniro_admin_frontend/src/page/simulationPage.tsx
@@ -0,0 +1,186 @@
+import React, { useEffect, useState } from "react";
+import MainContainer from "../container/mainContainer";
+import { useQueries } from "@tanstack/react-query";
+import { transformAllRoutes } from "../utils/route";
+import useMap from "../hooks/useMap";
+import useSearchBuilding from "../hooks/useUniversityRecord";
+import { CoreRoute, CoreRoutesList } from "../data/types/route";
+import createMarkerElement from "../components/map/mapMarkers";
+import findNearestSubEdge from "../utils/polylines/findNearestEdge";
+
+type AdvancedMarker = google.maps.marker.AdvancedMarkerElement;
+type Polyline = google.maps.Polyline;
+
+const getRoutes = () => {
+ return fetch(`${import.meta.env.VITE_REACT_SERVER_BASE_URL}/1001/routes`)
+ .then((res) => res.json())
+ .then(transformAllRoutes);
+};
+
+const SimulationPage = () => {
+ const result = useQueries({
+ queries: [{ queryKey: ["1001", "routes"], queryFn: getRoutes }],
+ });
+ const { currentUniversity, getCurrentUniversityLngLat } = useSearchBuilding();
+
+ const { mapRef, map, mapLoaded, AdvancedMarker, Polyline } = useMap();
+ const [routes] = result;
+ const [selectedEdge, setSelectedEdge] = useState<{
+ info: CoreRoute;
+ marker1: AdvancedMarker;
+ marker2: AdvancedMarker;
+ polyline: Polyline;
+ }>();
+
+ const drawRoute = (coreRouteList: CoreRoutesList) => {
+ if (!Polyline || !AdvancedMarker || !map) return;
+
+ for (const coreRoutes of coreRouteList) {
+ const { coreNode1Id, coreNode2Id, routes: subRoutes } = coreRoutes;
+
+ // 가장 끝쪽 Core Node 그리기
+ const endNode = subRoutes[subRoutes.length - 1].node2;
+
+ new AdvancedMarker({
+ map: map,
+ position: endNode,
+ content: createMarkerElement({
+ type: "waypoint",
+ className: "translate-waypoint",
+ }),
+ });
+
+ const subNodes = [subRoutes[0].node1, ...subRoutes.map((el) => el.node2)];
+
+ const routePolyLine = new Polyline({
+ map: map,
+ path: subNodes.map((el) => {
+ return { lat: el.lat, lng: el.lng };
+ }),
+ strokeColor: "#808080",
+ });
+
+ routePolyLine.addListener(
+ "click",
+ (e: { latLng: google.maps.LatLng }) => {
+ const edges: CoreRoute[] = subRoutes.map(
+ ({ routeId, node1, node2 }) => {
+ return { routeId, node1, node2 };
+ }
+ );
+
+ const { edge: nearestEdge } = findNearestSubEdge(edges, {
+ lat: e.latLng.lat(),
+ lng: e.latLng.lng(),
+ });
+
+ const { node1, node2 } = nearestEdge;
+
+ const newPolyline = new Polyline({
+ map: map,
+ path: [
+ { lat: node1.lat, lng: node1.lng },
+ { lat: node2.lat, lng: node2.lng },
+ ],
+ strokeColor: "#000000",
+ zIndex: 100,
+ });
+
+ const marker1 = new AdvancedMarker({
+ map: map,
+ position: { lat: node1.lat, lng: node1.lng },
+ content: createMarkerElement({
+ type: "waypoint_red",
+ className: "translate-waypoint",
+ }),
+ zIndex: 100,
+ });
+
+ const marker2 = new AdvancedMarker({
+ map: map,
+ position: { lat: node2.lat, lng: node2.lng },
+ content: createMarkerElement({
+ type: "waypoint_blue",
+ className: "translate-waypoint",
+ }),
+ zIndex: 100,
+ });
+
+ setSelectedEdge({
+ info: nearestEdge,
+ marker1: marker1,
+ marker2: marker2,
+ polyline: newPolyline,
+ });
+ }
+ );
+
+ const startNode = subRoutes[0].node1;
+
+ new AdvancedMarker({
+ map: map,
+ position: startNode,
+ content: createMarkerElement({
+ type: "waypoint",
+ className: "translate-waypoint",
+ }),
+ });
+ }
+ };
+
+ useEffect(() => {
+ if (routes.data) {
+ drawRoute(routes.data);
+ }
+ }, [routes]);
+
+ useEffect(() => {
+ if (!map || !mapLoaded) return;
+ const universityLatLng = getCurrentUniversityLngLat();
+ console.log("Setting center to:", universityLatLng);
+ map.setCenter(universityLatLng);
+ }, [currentUniversity, mapLoaded]);
+
+ useEffect(() => {
+ return () => {
+ if (!selectedEdge) return;
+ const { marker1, marker2, polyline } = selectedEdge;
+ marker1.map = null;
+ marker2.map = null;
+ polyline.setMap(null);
+ };
+ }, [selectedEdge]);
+
+ return (
+
+
+
+
+
+ | 선택된 간선 정보 | {" "}
+ ID |
+
+
+ | node1 (red) | {selectedEdge?.info.node1.nodeId} |
+
+
+ | node2 (red) | {selectedEdge?.info.node2.nodeId} |
+
+
+ | route (black) | {selectedEdge?.info.routeId} |
+
+
+
+
+
+
+ );
+};
+
+export default SimulationPage;
diff --git a/uniro_admin_frontend/src/utils/coordinates/centerCoordinate.ts b/uniro_admin_frontend/src/utils/coordinates/centerCoordinate.ts
new file mode 100644
index 0000000..eb06c12
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/coordinates/centerCoordinate.ts
@@ -0,0 +1,8 @@
+import { Coord } from "../../data/types/coord";
+
+export default function centerCoordinate(point1: Coord, point2: Coord): Coord {
+ return {
+ lat: (point1.lat + point2.lat) / 2,
+ lng: (point1.lng + point2.lng) / 2,
+ };
+}
diff --git a/uniro_admin_frontend/src/utils/coordinates/coordinateTransform.ts b/uniro_admin_frontend/src/utils/coordinates/coordinateTransform.ts
new file mode 100644
index 0000000..088eab8
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/coordinates/coordinateTransform.ts
@@ -0,0 +1,6 @@
+export function LatLngToLiteral(coordinate: google.maps.LatLng): google.maps.LatLngLiteral {
+ return {
+ lat: coordinate.lat(),
+ lng: coordinate.lng(),
+ };
+}
diff --git a/uniro_admin_frontend/src/utils/coordinates/distance.ts b/uniro_admin_frontend/src/utils/coordinates/distance.ts
new file mode 100644
index 0000000..44bad78
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/coordinates/distance.ts
@@ -0,0 +1,16 @@
+/** 하버사인 공식 */
+export default function distance(point1: google.maps.LatLngLiteral, point2: google.maps.LatLngLiteral): number {
+ const { lat: lat1, lng: lng1 } = point1;
+ const { lat: lat2, lng: lng2 } = point2;
+
+ const R = 6371000;
+ const toRad = (angle: number) => (angle * Math.PI) / 180;
+
+ const dLat = toRad(lat2 - lat1);
+ const dLon = toRad(lng2 - lng1);
+
+ const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return R * c;
+}
diff --git a/uniro_admin_frontend/src/utils/fetch/fetch.ts b/uniro_admin_frontend/src/utils/fetch/fetch.ts
new file mode 100644
index 0000000..b31bbf4
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/fetch/fetch.ts
@@ -0,0 +1,57 @@
+export default function Fetch() {
+ const baseURL = import.meta.env.VITE_REACT_SERVER_BASE_URL;
+
+ const get = async (url: string, params?: Record): Promise => {
+ const paramsURL = new URLSearchParams(
+ Object.entries(params || {}).map(([key, value]) => [key, String(value)]),
+ ).toString();
+
+ const response = await fetch(`${baseURL}${url}?${paramsURL}`, {
+ method: "GET",
+ });
+
+ if (!response.ok) {
+ throw new Error(`${response.status}-${response.statusText}`);
+ }
+
+ return response.json();
+ };
+
+ const post = async (url: string, body?: Record): Promise => {
+ const response = await fetch(`${baseURL}${url}`, {
+ method: "POST",
+ body: JSON.stringify(body),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`${response.status}-${response.statusText}`);
+ }
+
+ return response.ok;
+ };
+
+ const put = async (url: string, body?: Record): Promise => {
+ const response = await fetch(`${baseURL}${url}`, {
+ method: "PUT",
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error(`${response.status}-${response.statusText}`);
+ }
+
+ return response.json();
+ };
+
+ return {
+ get,
+ post,
+ put,
+ };
+}
+
+const { get, post, put } = Fetch();
+export { get as getFetch, post as postFetch, put as putFetch };
diff --git a/uniro_admin_frontend/src/utils/fetch/mockFetch.ts b/uniro_admin_frontend/src/utils/fetch/mockFetch.ts
new file mode 100644
index 0000000..d6780aa
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/fetch/mockFetch.ts
@@ -0,0 +1,27 @@
+export function mockRealisticFetch(
+ data: T,
+ minDelay: number = 1000,
+ maxDelay: number = 4000,
+ failRate: number = 0.2,
+): Promise {
+ const delay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
+ const shouldFail = Math.random() < failRate;
+
+ return new Promise((resolve, reject) => {
+ console.log(`${delay / 1000}초 동안 로딩 중...`);
+
+ setTimeout(() => {
+ if (shouldFail) {
+ console.error("네트워크 오류 발생!");
+ reject(new Error("네트워크 오류 발생"));
+ } else {
+ console.log("데이터 로드 완료:", data);
+ resolve(data);
+ }
+ }, delay);
+ });
+}
+
+export const getMockTest = async () => {
+ return mockRealisticFetch({ message: "Hello from Mock API" });
+};
diff --git a/uniro_admin_frontend/src/utils/markers/createAdvanedMarker.ts b/uniro_admin_frontend/src/utils/markers/createAdvanedMarker.ts
new file mode 100644
index 0000000..28fb887
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/markers/createAdvanedMarker.ts
@@ -0,0 +1,47 @@
+export default function createAdvancedMarker(
+ AdvancedMarker: typeof google.maps.marker.AdvancedMarkerElement,
+ map: google.maps.Map | null,
+ position: google.maps.LatLng | google.maps.LatLngLiteral,
+ content: HTMLElement,
+ onClick?: () => void,
+) {
+ const newMarker = new AdvancedMarker({
+ map: map,
+ position: position,
+ content: content,
+ });
+
+ if (onClick) newMarker.addListener("click", onClick);
+
+ return newMarker;
+}
+
+export function createUniversityMarker(
+ AdvancedMarker: typeof google.maps.marker.AdvancedMarkerElement,
+ map: google.maps.Map | null,
+ position: google.maps.LatLng | google.maps.LatLngLiteral,
+ university: string,
+) {
+ const container = document.createElement("div");
+ container.className = `flex flex-col items-center`;
+ const markerTitle = document.createElement("p");
+ markerTitle.innerText = university ? university : "";
+ markerTitle.className =
+ "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-primary-500 text-center rounded-200";
+ container.appendChild(markerTitle);
+ const markerImage = document.createElement("img");
+
+ const markerImages = import.meta.glob("/src/assets/markers/*.svg", { eager: true });
+
+ markerImage.className = "border-0 translate-y-[-1px]";
+ markerImage.src = (markerImages[`/src/assets/markers/university.svg`] as { default: string })?.default;
+ container.appendChild(markerImage);
+
+ const newMarker = new AdvancedMarker({
+ map: map,
+ position: position,
+ content: container,
+ });
+
+ return newMarker;
+}
diff --git a/uniro_admin_frontend/src/utils/markers/toggleMarkers.ts b/uniro_admin_frontend/src/utils/markers/toggleMarkers.ts
new file mode 100644
index 0000000..bcfc52a
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/markers/toggleMarkers.ts
@@ -0,0 +1,14 @@
+import { AdvancedMarker } from "../../data/types/marker";
+
+/** Marker 보이기 안보이기 토글 */
+export default function toggleMarkers(isActive: boolean, markers: AdvancedMarker[], map: google.maps.Map) {
+ if (isActive) {
+ for (const marker of markers) {
+ marker.map = map;
+ }
+ } else {
+ for (const marker of markers) {
+ marker.map = null;
+ }
+ }
+}
diff --git a/uniro_admin_frontend/src/utils/navigation/formatDistance.ts b/uniro_admin_frontend/src/utils/navigation/formatDistance.ts
new file mode 100644
index 0000000..f2e81c1
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/navigation/formatDistance.ts
@@ -0,0 +1,9 @@
+export const formatDistance = (distance: number) => {
+ if (distance < 1) {
+ return `${Math.ceil(distance * 1000) / 1000}m`;
+ }
+
+ return distance >= 1000
+ ? `${(distance / 1000).toFixed(1)} km` // 1000m 이상이면 km 단위로 변환
+ : `${distance} m`; // 1000m 미만이면 m 단위 유지
+};
diff --git a/uniro_admin_frontend/src/utils/polylines/createSubnodes.ts b/uniro_admin_frontend/src/utils/polylines/createSubnodes.ts
new file mode 100644
index 0000000..4560fd3
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/polylines/createSubnodes.ts
@@ -0,0 +1,33 @@
+import { EDGE_LENGTH } from "../../constant/edge";
+import { Coord } from "../../data/types/coord";
+import { LatLngToLiteral } from "../coordinates/coordinateTransform";
+import distance from "../coordinates/distance";
+
+/** 구면 보간 없이 계산한 결과 */
+export default function createSubNodes(polyLine: google.maps.Polyline): Coord[] {
+ const paths = polyLine.getPath();
+ const [startNode, endNode] = paths.getArray().map((el) => LatLngToLiteral(el));
+
+ const length = distance(startNode, endNode);
+
+ const subEdgesCount = Math.ceil(length / EDGE_LENGTH);
+
+ const interval = {
+ lat: (endNode.lat - startNode.lat) / subEdgesCount,
+ lng: (endNode.lng - startNode.lng) / subEdgesCount,
+ };
+
+ const subNodes = [];
+
+ for (let i = 0; i < subEdgesCount; i++) {
+ const subNode: Coord = {
+ lat: startNode.lat + interval.lat * i,
+ lng: startNode.lng + interval.lng * i,
+ };
+ subNodes.push(subNode);
+ }
+
+ subNodes.push(endNode);
+
+ return subNodes;
+}
diff --git a/uniro_admin_frontend/src/utils/polylines/findNearestEdge.ts b/uniro_admin_frontend/src/utils/polylines/findNearestEdge.ts
new file mode 100644
index 0000000..a479280
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/polylines/findNearestEdge.ts
@@ -0,0 +1,36 @@
+import { Coord } from "../../data/types/coord";
+import { Node } from "../../data/types/node";
+import { CoreRoute } from "../../data/types/route";
+import centerCoordinate from "../coordinates/centerCoordinate";
+import distance from "../coordinates/distance";
+
+export default function findNearestSubEdge(
+ edges: CoreRoute[],
+ point: Coord
+): {
+ edge: CoreRoute;
+ point: Node;
+} {
+ const edgesWithDistance = edges
+ .map((edge) => {
+ return {
+ edge,
+ distance: distance(point, centerCoordinate(edge.node1, edge.node2)),
+ };
+ })
+ .sort((a, b) => {
+ return a.distance - b.distance;
+ });
+
+ const nearestEdge = edgesWithDistance[0].edge;
+
+ const { node1, node2 } = nearestEdge;
+ const distance0 = distance(node1, point);
+ const distance1 = distance(node2, point);
+ const nearestPoint = distance0 > distance1 ? node2 : node1;
+
+ return {
+ edge: nearestEdge,
+ point: nearestPoint,
+ };
+}
diff --git a/uniro_admin_frontend/src/utils/report/getThemeByPassableStatus.ts b/uniro_admin_frontend/src/utils/report/getThemeByPassableStatus.ts
new file mode 100644
index 0000000..f58b320
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/report/getThemeByPassableStatus.ts
@@ -0,0 +1,6 @@
+import { PassableStatus } from "../../constant/enum/reportEnum";
+import { THEME_MAP } from "../../constant/reportTheme";
+
+export const getThemeByPassableStatus = (status: PassableStatus): string => {
+ return THEME_MAP[status];
+};
diff --git a/uniro_admin_frontend/src/utils/route.ts b/uniro_admin_frontend/src/utils/route.ts
new file mode 100644
index 0000000..fead5dd
--- /dev/null
+++ b/uniro_admin_frontend/src/utils/route.ts
@@ -0,0 +1,43 @@
+import { Node, NodeId } from "../data/types/node";
+import { CoreRoutesList } from "../data/types/route";
+
+type CoreRoutesResponse = {
+ coreNode1Id: NodeId;
+ coreNode2Id: NodeId;
+ routes: {
+ routeId: NodeId;
+ startNodeId: NodeId;
+ endNodeId: NodeId;
+ }[];
+};
+
+export const transformAllRoutes = (data: {
+ nodeInfos: Node[];
+ coreRoutes: CoreRoutesResponse[];
+}): CoreRoutesList => {
+ const { nodeInfos, coreRoutes } = data;
+ const nodeInfoMap = new Map(nodeInfos.map((node) => [node.nodeId, node]));
+
+ return coreRoutes.map((coreRoute) => {
+ return {
+ ...coreRoute,
+ routes: coreRoute.routes.map((route) => {
+ const node1 = nodeInfoMap.get(route.startNodeId);
+ const node2 = nodeInfoMap.get(route.endNodeId);
+
+ if (!node1) {
+ throw new Error(`Node not found: ${route.startNodeId}`);
+ }
+ if (!node2) {
+ throw new Error(`Node not found: ${route.endNodeId}`);
+ }
+
+ return {
+ routeId: route.routeId,
+ node1,
+ node2,
+ };
+ }),
+ };
+ });
+};
diff --git a/uniro_backend/.gitignore b/uniro_backend/.gitignore
index 39864c4..9c3a336 100644
--- a/uniro_backend/.gitignore
+++ b/uniro_backend/.gitignore
@@ -38,3 +38,5 @@ out/
### properties
*.properties
+*.sql
+!src/test/resources/sql/*.sql
\ No newline at end of file
diff --git a/uniro_backend/build.gradle b/uniro_backend/build.gradle
index ea48f8e..d859c80 100644
--- a/uniro_backend/build.gradle
+++ b/uniro_backend/build.gradle
@@ -71,6 +71,19 @@ dependencies {
// actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
+
+ // test container
+ testImplementation 'org.testcontainers:testcontainers:1.19.5'
+ testImplementation 'org.testcontainers:junit-jupiter:1.19.5'
+ testImplementation 'org.testcontainers:jdbc:1.19.5'
+ testImplementation 'org.testcontainers:mysql:1.19.5'
+
+ // jwt
+ implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
+ implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
+ implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
+ implementation 'com.auth0:java-jwt:4.4.0'
+
}
tasks.named('test') {
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/UniroBackendApplication.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/UniroBackendApplication.java
index b42e9b4..c95e7ab 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/UniroBackendApplication.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/UniroBackendApplication.java
@@ -2,8 +2,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
+@EnableJpaAuditing
public class UniroBackendApplication {
public static void main(String[] args) {
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/DisableAudit.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/DisableAudit.java
new file mode 100644
index 0000000..7298f5e
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/DisableAudit.java
@@ -0,0 +1,11 @@
+package com.softeer5.uniro_backend.admin.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisableAudit {
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java
index a767ece..b84dced 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java
@@ -1,6 +1,6 @@
package com.softeer5.uniro_backend.admin.annotation;
-import com.softeer5.uniro_backend.admin.entity.RevisionOperationType;
+import com.softeer5.uniro_backend.admin.enums.RevisionOperationType;
import java.lang.annotation.*;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/CustomPostUpdateListener.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/CustomPostUpdateListener.java
new file mode 100644
index 0000000..6186582
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/CustomPostUpdateListener.java
@@ -0,0 +1,25 @@
+package com.softeer5.uniro_backend.admin.aspect;
+
+import org.hibernate.envers.boot.internal.EnversService;
+import org.hibernate.envers.event.spi.EnversPostUpdateEventListenerImpl;
+import org.hibernate.event.spi.PostUpdateEvent;
+
+import com.softeer5.uniro_backend.admin.setting.RevisionContext;
+import com.softeer5.uniro_backend.admin.setting.RevisionType;
+
+public class CustomPostUpdateListener extends EnversPostUpdateEventListenerImpl {
+ public CustomPostUpdateListener(EnversService enversService) {
+ super(enversService);
+ }
+
+ @Override
+ public void onPostUpdate(PostUpdateEvent event) {
+ RevisionType revisionType = RevisionContext.getRevisionType();
+
+ if (revisionType == RevisionType.IGNORE) {
+ // Ignore this entity
+ return;
+ }
+ super.onPostUpdate(event);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/DisableEnversAspect.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/DisableEnversAspect.java
new file mode 100644
index 0000000..437f594
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/DisableEnversAspect.java
@@ -0,0 +1,30 @@
+package com.softeer5.uniro_backend.admin.aspect;
+
+import static com.softeer5.uniro_backend.common.constant.UniroConst.*;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import com.softeer5.uniro_backend.admin.setting.RevisionContext;
+import com.softeer5.uniro_backend.admin.setting.RevisionType;
+
+@Component
+@Aspect
+@Order(BEFORE_DEFAULT_ORDER)
+public class DisableEnversAspect {
+ @Around("@annotation(com.softeer5.uniro_backend.admin.annotation.DisableAudit)")
+ public Object disableAudit(ProceedingJoinPoint joinPoint) throws Throwable {
+ // 현재 스레드에서 Envers 감사 비활성화
+ RevisionContext.setRevisionType(RevisionType.IGNORE);
+ try {
+ return joinPoint.proceed(); // 트랜잭션 실행
+ } finally {
+ // 트랜잭션 종료 후 다시 감사 활성화
+ RevisionContext.clear();
+ }
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java
index 86e304f..2fd0a0b 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java
@@ -1,10 +1,9 @@
package com.softeer5.uniro_backend.admin.aspect;
import com.softeer5.uniro_backend.admin.annotation.RevisionOperation;
-import com.softeer5.uniro_backend.admin.entity.RevisionOperationType;
+import com.softeer5.uniro_backend.admin.enums.RevisionOperationType;
import com.softeer5.uniro_backend.admin.setting.RevisionContext;
-import com.softeer5.uniro_backend.route.dto.request.CreateRoutesReqDTO;
-import com.softeer5.uniro_backend.route.dto.request.PostRiskReqDTO;
+import com.softeer5.uniro_backend.map.dto.request.PostRiskReqDTO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -28,6 +27,8 @@ public Object around(ProceedingJoinPoint joinPoint, RevisionOperation revisionOp
switch (opType) {
case UPDATE_RISK -> result = updateRiskHandler(joinPoint);
case CREATE_ROUTE -> result = updateRouteHandler(joinPoint);
+ case CREATE_BUILDING_NODE -> result = createBuildingNodeHandler(joinPoint);
+ case CREATE_BUILDING_ROUTE -> result = createBuildingRouteHandler(joinPoint);
default -> result = joinPoint.proceed();
}
@@ -46,8 +47,8 @@ private Object updateRiskHandler(ProceedingJoinPoint joinPoint) throws Throwable
univId = (Long) args[i];
}
else if(args[i] instanceof PostRiskReqDTO postRiskReqDTO){
- int cautionSize = postRiskReqDTO.getCautionTypes().size();
- int dangerSize = postRiskReqDTO.getDangerTypes().size();
+ int cautionSize = postRiskReqDTO.getCautionFactors().size();
+ int dangerSize = postRiskReqDTO.getDangerFactors().size();
if (cautionSize > 0) {
action = "주의요소 업데이트";
@@ -90,4 +91,51 @@ private Object updateRouteHandler(ProceedingJoinPoint joinPoint) throws Throwabl
RevisionContext.clear();
}
}
+
+ private Object createBuildingNodeHandler(ProceedingJoinPoint joinPoint) throws Throwable {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ String[] parameterNames = signature.getParameterNames();
+ Object[] args = joinPoint.getArgs();
+
+ Long univId = null;
+ String action = "빌딩 노드 추가";
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] instanceof Long && "univId".equals(parameterNames[i])) {
+ univId = (Long) args[i];
+ }
+ }
+ RevisionContext.setUnivId(univId);
+ RevisionContext.setAction(action);
+ try{
+ return joinPoint.proceed();
+ }
+ finally {
+ RevisionContext.clear();
+ }
+ }
+
+ private Object createBuildingRouteHandler(ProceedingJoinPoint joinPoint) throws Throwable{
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ String[] parameterNames = signature.getParameterNames();
+ Object[] args = joinPoint.getArgs();
+
+ Long univId = null;
+ String action = "빌딩 route 추가";
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] instanceof Long && "univId".equals(parameterNames[i])) {
+ univId = (Long) args[i];
+ }
+ }
+ RevisionContext.setUnivId(univId);
+ RevisionContext.setAction(action);
+ try{
+ return joinPoint.proceed();
+ }
+ finally {
+ RevisionContext.clear();
+ }
+ }
+
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java
index e28d4d1..53e1066 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java
@@ -1,6 +1,7 @@
package com.softeer5.uniro_backend.admin.controller;
-import com.softeer5.uniro_backend.admin.dto.RevInfoDTO;
+import com.softeer5.uniro_backend.admin.dto.response.GetAllRoutesByRevisionResDTO;
+import com.softeer5.uniro_backend.admin.dto.response.RevInfoDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -21,4 +22,21 @@ public interface AdminApi {
})
ResponseEntity> getAllRev(@PathVariable("univId") Long univId);
+ @Operation(summary = "특정 버전으로 롤백")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "특정 버전으로 롤백 성공"),
+ @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
+ })
+ ResponseEntity rollbackRev(
+ @PathVariable("univId") Long univId,
+ @PathVariable("versionId") Long versionId);
+
+ @Operation(summary = "특정 버전 지도 조회")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "특정 버전 지도 조회 성공"),
+ @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
+ })
+ ResponseEntity getAllRoutesByRevision(
+ @PathVariable("univId") Long univId,
+ @PathVariable("versionId") Long versionId);
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java
index 1ed101a..a59bc03 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java
@@ -1,10 +1,13 @@
package com.softeer5.uniro_backend.admin.controller;
-import com.softeer5.uniro_backend.admin.dto.RevInfoDTO;
+import com.softeer5.uniro_backend.admin.dto.response.GetAllRoutesByRevisionResDTO;
+import com.softeer5.uniro_backend.admin.dto.response.RevInfoDTO;
import com.softeer5.uniro_backend.admin.service.AdminService;
import lombok.RequiredArgsConstructor;
+
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@@ -16,8 +19,29 @@ public class AdminController implements AdminApi {
private final AdminService adminService;
@Override
- @GetMapping("/admin/revision/{univId}")
+ @GetMapping("/admin/{univId}/revisions")
public ResponseEntity> getAllRev(@PathVariable("univId") Long univId) {
return ResponseEntity.ok().body(adminService.getAllRevInfo(univId));
}
+
+ @Override
+ @PatchMapping("/admin/{univId}/revisions/{versionId}")
+ public ResponseEntity rollbackRev(
+ @PathVariable("univId") Long univId,
+ @PathVariable("versionId") Long versionId) {
+
+ adminService.rollbackRev(univId, versionId);
+
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ @GetMapping("/admin/{univId}/revisions/{versionId}")
+ public ResponseEntity getAllRoutesByRevision(
+ @PathVariable("univId") Long univId,
+ @PathVariable("versionId") Long versionId) {
+
+ GetAllRoutesByRevisionResDTO resDTO = adminService.getAllRoutesByRevision(univId, versionId);
+ return ResponseEntity.ok().body(resDTO);
+ }
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthApi.java
new file mode 100644
index 0000000..ec3d93d
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthApi.java
@@ -0,0 +1,24 @@
+package com.softeer5.uniro_backend.admin.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import com.softeer5.uniro_backend.admin.dto.request.LoginReqDTO;
+import com.softeer5.uniro_backend.admin.dto.response.LoginResDTO;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+
+@Tag(name = "admin 로그인 페이지 API")
+public interface AuthApi {
+ @Operation(summary = "로그인")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "로그인 성공"),
+ @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
+ })
+ ResponseEntity login(@RequestBody @Valid LoginReqDTO loginReqDTO);
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthController.java
new file mode 100644
index 0000000..4e16dfe
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AuthController.java
@@ -0,0 +1,25 @@
+package com.softeer5.uniro_backend.admin.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.softeer5.uniro_backend.admin.dto.request.LoginReqDTO;
+import com.softeer5.uniro_backend.admin.dto.response.LoginResDTO;
+import com.softeer5.uniro_backend.admin.service.AuthService;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequiredArgsConstructor
+public class AuthController implements AuthApi {
+ private final AuthService authService;
+ @Override
+ @PostMapping("/admin/auth/login")
+ public ResponseEntity login(@RequestBody @Valid LoginReqDTO loginReqDTO) {
+ LoginResDTO loginResDTO = authService.login(loginReqDTO);
+ return ResponseEntity.ok(loginResDTO);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/request/LoginReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/request/LoginReqDTO.java
new file mode 100644
index 0000000..4077e87
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/request/LoginReqDTO.java
@@ -0,0 +1,20 @@
+package com.softeer5.uniro_backend.admin.dto.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@Schema(name = "LoginReqDTO", description = "로그인 요청 DTO")
+public class LoginReqDTO {
+ @Schema(description = "대학교 id", example = "1001")
+ @NotNull
+ private final Long univId;
+
+ @Schema(description = "인증 코드", example = "abcd123")
+ @NotNull
+ private final String code;
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/ChangedRouteDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/ChangedRouteDTO.java
new file mode 100644
index 0000000..670809d
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/ChangedRouteDTO.java
@@ -0,0 +1,22 @@
+package com.softeer5.uniro_backend.admin.dto.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@Schema(name = "ChangedRouteDTO", description = "위험요소, 주의요소, cost가 변경된 길의 현재&과거 정보 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class ChangedRouteDTO {
+ @Schema(description = "route ID", example = "17")
+ private final Long routeId;
+ @Schema(description = "현재", example = "")
+ private final RouteDifferInfo current;
+ @Schema(description = "해당 버전 (과거)", example = "")
+ private final RouteDifferInfo revision;
+
+ public static ChangedRouteDTO of(Long id, RouteDifferInfo current, RouteDifferInfo revision) {
+ return new ChangedRouteDTO(id, current, revision);
+ }
+}
\ No newline at end of file
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/GetAllRoutesByRevisionResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/GetAllRoutesByRevisionResDTO.java
new file mode 100644
index 0000000..4f8c383
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/GetAllRoutesByRevisionResDTO.java
@@ -0,0 +1,29 @@
+package com.softeer5.uniro_backend.admin.dto.response;
+
+import com.softeer5.uniro_backend.map.dto.response.GetAllRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskRoutesResDTO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@Schema(name = "GetAllRoutesByRevisionResDTO", description = "특정 버전 map 조회 응답 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class GetAllRoutesByRevisionResDTO {
+ @Schema(description = "특정 버전에 존재하는 길&노드 스냅샷 정보", example = "")
+ private final GetAllRoutesResDTO routesInfo;
+ @Schema(description = "특정 버전에 존재하는 위험 요소 스냅샷 정보", example = "")
+ private final GetRiskRoutesResDTO getRiskRoutesResDTO;
+ @Schema(description = "삭제된 길&노드 정보 정보", example = "")
+ private final LostRoutesDTO lostRoutes;
+ @Schema(description = "현재 버전과 비교하여 변경된 주의/위험 요소 정보", example = "")
+ private final List changedList;
+
+ public static GetAllRoutesByRevisionResDTO of(GetAllRoutesResDTO routesInfo, GetRiskRoutesResDTO getRiskRoutesResDTO,
+ LostRoutesDTO lostRoutes, List changedList) {
+ return new GetAllRoutesByRevisionResDTO(routesInfo, getRiskRoutesResDTO, lostRoutes, changedList);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LoginResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LoginResDTO.java
new file mode 100644
index 0000000..d01157f
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LoginResDTO.java
@@ -0,0 +1,17 @@
+package com.softeer5.uniro_backend.admin.dto.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@Schema(name = "LoginResDTO", description = "로그인 응답 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginResDTO {
+ private final String accessToken;
+
+ public static LoginResDTO of(String accessToken) {
+ return new LoginResDTO(accessToken);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LostRoutesDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LostRoutesDTO.java
new file mode 100644
index 0000000..82fe21a
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/LostRoutesDTO.java
@@ -0,0 +1,24 @@
+package com.softeer5.uniro_backend.admin.dto.response;
+
+import com.softeer5.uniro_backend.map.dto.response.CoreRouteResDTO;
+import com.softeer5.uniro_backend.map.dto.response.NodeInfoResDTO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@Schema(name = "LostRoutesDTO", description = "삭제된 길의 코어 route DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class LostRoutesDTO {
+ @Schema(description = "노드 정보 (id, 좌표)", example = "")
+ private final List nodeInfos;
+ @Schema(description = "루트 정보 (id, startNodeId, endNodeId)", example = "")
+ private final List coreRoutes;
+
+ public static LostRoutesDTO of(List nodeInfos, List coreRoutes){
+ return new LostRoutesDTO(nodeInfos, coreRoutes);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RevInfoDTO.java
similarity index 90%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RevInfoDTO.java
index 40f287c..b550ae6 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RevInfoDTO.java
@@ -1,6 +1,5 @@
-package com.softeer5.uniro_backend.admin.dto;
+package com.softeer5.uniro_backend.admin.dto.response;
-import com.softeer5.uniro_backend.admin.entity.RevInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RouteDifferInfo.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RouteDifferInfo.java
new file mode 100644
index 0000000..47dc779
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/response/RouteDifferInfo.java
@@ -0,0 +1,27 @@
+package com.softeer5.uniro_backend.admin.dto.response;
+
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
+import com.softeer5.uniro_backend.map.entity.Route;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@Schema(name = "RouteDifferInfo", description = "cost, 위험요소, 주의요소 정보 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class RouteDifferInfo {
+ @Schema(description = "주의요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
+ private final List cautionFactors;
+ @Schema(description = "위험 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
+ private final List dangerFactors;
+ @Schema(description = "가중치", example = "3.021742")
+ private final double cost;
+
+ public static RouteDifferInfo of(Route route) {
+ return new RouteDifferInfo(route.getCautionFactorsByList(), route.getDangerFactorsByList(), route.getDistance());
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/Admin.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/Admin.java
new file mode 100644
index 0000000..9f52483
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/Admin.java
@@ -0,0 +1,33 @@
+package com.softeer5.uniro_backend.admin.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.validation.constraints.NotNull;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class Admin {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ private String code;
+
+ @NotNull
+ private Long univId;
+
+ @Builder
+ private Admin(Long id, String code, Long univId) {
+ this.id = id;
+ this.code = code;
+ this.univId = univId;
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java
deleted file mode 100644
index 070e6ab..0000000
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.softeer5.uniro_backend.admin.entity;
-
-public enum RevisionOperationType {
- UPDATE_RISK,
- CREATE_ROUTE,
- DEFAULT;
-}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/enums/RevisionOperationType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/enums/RevisionOperationType.java
new file mode 100644
index 0000000..09bced8
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/enums/RevisionOperationType.java
@@ -0,0 +1,9 @@
+package com.softeer5.uniro_backend.admin.enums;
+
+public enum RevisionOperationType {
+ UPDATE_RISK,
+ CREATE_ROUTE,
+ CREATE_BUILDING_NODE,
+ CREATE_BUILDING_ROUTE,
+ DEFAULT;
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/AdminInterceptor.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/AdminInterceptor.java
new file mode 100644
index 0000000..93d5f5c
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/AdminInterceptor.java
@@ -0,0 +1,38 @@
+package com.softeer5.uniro_backend.admin.interceptor;
+
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import com.softeer5.uniro_backend.admin.jwt.SecurityContext;
+import com.softeer5.uniro_backend.common.exception.custom.AdminException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@Component
+public class AdminInterceptor implements HandlerInterceptor {
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ String requestURI = request.getRequestURI();
+
+ // "/admin/{univId}/" 패턴만 검증
+ Pattern pattern = Pattern.compile("^/admin/(\\d+)/.*$");
+ Matcher matcher = pattern.matcher(requestURI);
+
+ if (matcher.matches()) {
+ Long univId = Long.valueOf(matcher.group(1)); // URL에서 univId 추출
+ Long adminUnivId = SecurityContext.getUnivId(); // JWT에서 가져온 univId
+
+ if (!univId.equals(adminUnivId)) {
+ throw new AdminException("Unauthorized university access", UNAUTHORIZED_UNIV);
+ }
+ }
+ return true;
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/JwtInterceptor.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/JwtInterceptor.java
new file mode 100644
index 0000000..16a44d8
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/interceptor/JwtInterceptor.java
@@ -0,0 +1,49 @@
+package com.softeer5.uniro_backend.admin.interceptor;
+
+import com.softeer5.uniro_backend.admin.jwt.JwtTokenProvider;
+import com.softeer5.uniro_backend.admin.jwt.SecurityContext;
+import com.softeer5.uniro_backend.common.exception.custom.AdminException;
+import com.softeer5.uniro_backend.common.error.ErrorCode;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JwtInterceptor implements HandlerInterceptor {
+ private final JwtTokenProvider jwtTokenProvider;
+
+ public JwtInterceptor(JwtTokenProvider jwtTokenProvider) {
+ this.jwtTokenProvider = jwtTokenProvider;
+ }
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ // Authorization 헤더에서 JWT 토큰 추출
+
+ String authHeader = request.getHeader("Authorization");
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ throw new AdminException("Invalid token", ErrorCode.INVALID_TOKEN);
+ }
+
+ String token = authHeader.substring(7); // "Bearer " 제거
+
+ if (!jwtTokenProvider.validateToken(token)) {
+ throw new AdminException("Invalid token", ErrorCode.INVALID_TOKEN);
+ }
+
+ Long univId = jwtTokenProvider.getUnivId(token);
+
+ // ✅ SecurityContext에 저장 (ThreadLocal 방식)
+ SecurityContext.setUnivId(univId);
+
+ return true; // 컨트롤러 실행 허용
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ // 요청 처리 후 SecurityContext 비우기
+ SecurityContext.clear();
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/JwtTokenProvider.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/JwtTokenProvider.java
new file mode 100644
index 0000000..dc31562
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/JwtTokenProvider.java
@@ -0,0 +1,50 @@
+package com.softeer5.uniro_backend.admin.jwt;
+
+import java.util.Date;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+@Component
+public class JwtTokenProvider {
+
+ @Value("${jwt.secret}")
+ private String secretKey; // 실제로는 안전한 방법으로 관리해야 함
+ private final long validityInMilliseconds = 36000000000L; // 10000 hour
+
+ public String createToken(Long univId) {
+ final Claims claims = Jwts.claims().setSubject(String.valueOf(univId));
+ Date now = new Date();
+ Date validity = new Date(now.getTime() + validityInMilliseconds);
+
+ return Jwts.builder()
+ .setClaims(claims)
+ .setIssuedAt(now)
+ .setExpiration(validity)
+ .signWith(SignatureAlgorithm.HS256, secretKey)
+ .compact();
+ }
+
+ public Long getUnivId(String token) {
+ return Long.parseLong(Jwts.parser()
+ .setSigningKey(secretKey)
+ .parseClaimsJws(token)
+ .getBody()
+ .getSubject());
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
+ return true;
+ } catch (JwtException | IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/SecurityContext.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/SecurityContext.java
new file mode 100644
index 0000000..b910518
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/jwt/SecurityContext.java
@@ -0,0 +1,18 @@
+package com.softeer5.uniro_backend.admin.jwt;
+
+public class SecurityContext {
+ private static final ThreadLocal univIdHolder = new ThreadLocal<>();
+
+ public static void setUnivId(Long univId) {
+ univIdHolder.set(univId);
+ }
+
+ public static Long getUnivId() {
+ return univIdHolder.get();
+ }
+
+ public static void clear() {
+ univIdHolder.remove();
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/AdminRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/AdminRepository.java
new file mode 100644
index 0000000..800bf10
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/AdminRepository.java
@@ -0,0 +1,12 @@
+package com.softeer5.uniro_backend.admin.repository;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import com.softeer5.uniro_backend.admin.entity.Admin;
+
+public interface AdminRepository extends JpaRepository {
+
+ Optional findByUnivId(Long univId);
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/NodeAuditRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/NodeAuditRepository.java
new file mode 100644
index 0000000..86d9fb1
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/NodeAuditRepository.java
@@ -0,0 +1,36 @@
+package com.softeer5.uniro_backend.admin.repository;
+
+import java.util.List;
+
+import org.hibernate.envers.AuditReader;
+import org.hibernate.envers.AuditReaderFactory;
+import org.hibernate.envers.query.AuditEntity;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.map.entity.Node;
+
+import jakarta.persistence.EntityManager;
+import lombok.RequiredArgsConstructor;
+
+@Repository
+@RequiredArgsConstructor
+@Transactional
+public class NodeAuditRepository {
+ private final EntityManager entityManager;
+
+ public List getAllNodesAtRevision(Long univId, Long versionId) {
+ AuditReader auditReader = AuditReaderFactory.get(entityManager);
+ return auditReader.createQuery()
+ .forEntitiesAtRevision(Node.class, versionId)
+ .add(AuditEntity.property("univId").eq(univId))
+ .getResultList();
+ }
+
+ public void deleteAllAfterVersionId(Long univId, Long versionId) {
+ entityManager.createNativeQuery("DELETE FROM node_aud WHERE univ_id = :univId AND rev > :versionId")
+ .setParameter("univId", univId)
+ .setParameter("versionId", versionId)
+ .executeUpdate();
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java
index a7ab85c..fbb1b7e 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java
@@ -2,10 +2,18 @@
import com.softeer5.uniro_backend.admin.entity.RevInfo;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface RevInfoRepository extends JpaRepository {
List findAllByUnivId(Long univId);
+
+ @Modifying(clearAutomatically = true)
+ @Transactional
+ @Query("DELETE FROM RevInfo r WHERE r.univId = :univId AND r.rev > :versionId")
+ void deleteAllAfterVersionId(Long univId, Long versionId);
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RouteAuditRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RouteAuditRepository.java
new file mode 100644
index 0000000..ce0c486
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RouteAuditRepository.java
@@ -0,0 +1,38 @@
+package com.softeer5.uniro_backend.admin.repository;
+
+import java.util.List;
+
+import org.hibernate.envers.AuditReader;
+import org.hibernate.envers.AuditReaderFactory;
+import org.hibernate.envers.query.AuditEntity;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.map.entity.Route;
+
+import jakarta.persistence.EntityManager;
+import lombok.RequiredArgsConstructor;
+
+@Repository
+@RequiredArgsConstructor
+@Transactional
+public class RouteAuditRepository {
+
+ private final EntityManager entityManager;
+
+ public List getAllRoutesAtRevision(Long univId, Long versionId) {
+ AuditReader auditReader = AuditReaderFactory.get(entityManager);
+ return auditReader.createQuery()
+ .forEntitiesAtRevision(Route.class, versionId)
+ .add(AuditEntity.property("univId").eq(univId))
+ .getResultList();
+ }
+
+ public void deleteAllAfterVersionId(Long univId, Long versionId) {
+ entityManager.createNativeQuery("DELETE FROM route_aud WHERE univ_id = :univId AND rev > :versionId")
+ .setParameter("univId", univId)
+ .setParameter("versionId", versionId)
+ .executeUpdate();
+ }
+
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java
index 8dbd32a..7d43ccb 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java
@@ -1,18 +1,47 @@
package com.softeer5.uniro_backend.admin.service;
-import com.softeer5.uniro_backend.admin.dto.RevInfoDTO;
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+
+import com.softeer5.uniro_backend.admin.annotation.DisableAudit;
+import com.softeer5.uniro_backend.admin.dto.response.*;
+import com.softeer5.uniro_backend.admin.entity.RevInfo;
+import com.softeer5.uniro_backend.admin.repository.NodeAuditRepository;
import com.softeer5.uniro_backend.admin.repository.RevInfoRepository;
+import com.softeer5.uniro_backend.admin.repository.RouteAuditRepository;
+import com.softeer5.uniro_backend.building.repository.BuildingRepository;
+import com.softeer5.uniro_backend.common.exception.custom.AdminException;
+import com.softeer5.uniro_backend.common.exception.custom.RouteException;
+import com.softeer5.uniro_backend.map.dto.response.GetAllRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.response.NodeInfoResDTO;
+import com.softeer5.uniro_backend.map.entity.Node;
+import com.softeer5.uniro_backend.map.entity.Route;
+import com.softeer5.uniro_backend.map.repository.NodeRepository;
+import com.softeer5.uniro_backend.map.repository.RouteRepository;
+import com.softeer5.uniro_backend.map.service.RouteCalculator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AdminService {
private final RevInfoRepository revInfoRepository;
+ private final RouteRepository routeRepository;
+ private final NodeRepository nodeRepository;
+ private final BuildingRepository buildingRepository;
+
+ private final RouteAuditRepository routeAuditRepository;
+ private final NodeAuditRepository nodeAuditRepository;
+
+ private final RouteCalculator routeCalculator;
public List getAllRevInfo(Long univId){
return revInfoRepository.findAllByUnivId(univId).stream().map(r -> RevInfoDTO.of(r.getRev(),
@@ -20,4 +49,129 @@ public List getAllRevInfo(Long univId){
r.getUnivId(),
r.getAction())).toList();
}
+
+ @Transactional
+ @DisableAudit
+ public void rollbackRev(Long univId, Long versionId){
+ RevInfo revInfo = revInfoRepository.findById(versionId)
+ .orElseThrow(() -> new AdminException("invalid version id", INVALID_VERSION_ID));
+
+ List revRoutes = routeAuditRepository.getAllRoutesAtRevision(univId, versionId);
+ List revNodes = nodeAuditRepository.getAllNodesAtRevision(univId, versionId);
+
+ routeRepository.deleteAllByCreatedAt(univId, revInfo.getRevTimeStamp());
+ nodeRepository.deleteAllByCreatedAt(univId, revInfo.getRevTimeStamp());
+ buildingRepository.deleteAllByCreatedAt(univId, revInfo.getRevTimeStamp());
+
+ routeAuditRepository.deleteAllAfterVersionId(univId, versionId);
+ nodeAuditRepository.deleteAllAfterVersionId(univId, versionId);
+ revInfoRepository.deleteAllAfterVersionId(univId, versionId);
+
+ List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId);
+
+ Map routeMap = new HashMap<>();
+ Map nodeMap = new HashMap<>();
+
+ for(Route route : routes){
+ routeMap.put(route.getId(), route);
+ nodeMap.put(route.getNode1().getId(), route.getNode1());
+ nodeMap.put(route.getNode2().getId(), route.getNode2());
+ }
+
+ // 매핑하여 업데이트 또는 새로 저장
+ for (Route revRoute : revRoutes) {
+ Route currentRoute = routeMap.get(revRoute.getId());
+
+ if (currentRoute != null) {
+ currentRoute.updateFromRevision(revRoute);
+ }
+ }
+
+ for (Node revNode : revNodes) {
+ Node currentNode = nodeMap.get(revNode.getId());
+
+ if (currentNode != null) {
+ currentNode.updateFromRevision(revNode.isCore());
+ }
+ }
+
+ }
+
+ public GetAllRoutesByRevisionResDTO getAllRoutesByRevision(Long univId, Long versionId){
+ revInfoRepository.findById(versionId)
+ .orElseThrow(() -> new AdminException("invalid version id", INVALID_VERSION_ID));
+
+ List revRoutes = routeAuditRepository.getAllRoutesAtRevision(univId, versionId);
+
+ if(revRoutes.isEmpty()) {
+ throw new RouteException("Route Not Found", ROUTE_NOT_FOUND);
+ }
+ GetAllRoutesResDTO routesInfo = routeCalculator.assembleRoutes(revRoutes);
+
+ Map revRouteMap = new HashMap<>();
+ for(Route revRoute : revRoutes){
+ revRouteMap.put(revRoute.getId(), revRoute);
+ }
+
+ List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId);
+
+ Map> lostAdjMap = new HashMap<>();
+ Map lostNodeMap = new HashMap<>();
+ List changedRoutes = new ArrayList<>();
+ List riskRoutes = new ArrayList<>();
+
+ for(Route route : routes){
+ if(revRouteMap.containsKey(route.getId())){
+ Route revRoute = revRouteMap.get(route.getId());
+ handleRevisionRoute(revRoute, route, changedRoutes, riskRoutes);
+ continue;
+ }
+ //해당 시점 이후에 생성된 루트들 (과거 시점엔 보이지 않는 루트)
+ handleLostRoute(route, lostAdjMap, lostNodeMap);
+ }
+
+ GetRiskRoutesResDTO getRiskRoutesResDTO = routeCalculator.mapRisks(riskRoutes);
+
+ //시작점이 1개인 nodeList 생성
+ List endNodes = determineEndNodes(lostAdjMap, lostNodeMap);
+
+ List lostNodeInfos = lostNodeMap.entrySet().stream()
+ .map(entry -> {
+ Node node = entry.getValue();
+ return NodeInfoResDTO.of(entry.getKey(), node.getX(), node.getY());
+ })
+ .toList();
+
+ LostRoutesDTO lostRouteDTO = LostRoutesDTO.of(lostNodeInfos, routeCalculator.getCoreRoutes(lostAdjMap, endNodes));
+
+ return GetAllRoutesByRevisionResDTO.of(routesInfo, getRiskRoutesResDTO, lostRouteDTO, changedRoutes);
+ }
+
+ private void handleRevisionRoute(Route revRoute, Route route, List changedRoutes, List riskRoutes) {
+ //변경사항이 있는 경우
+ if(!route.isEqualRoute(revRoute)){
+ changedRoutes.add(ChangedRouteDTO.of(route.getId(), RouteDifferInfo.of(route), RouteDifferInfo.of(revRoute)));
+ }
+ //변경사항이 없으면서 risk가 존재하는 route의 경우 riskRoutes에 추가
+ else if(!route.getCautionFactors().isEmpty() || !route.getDangerFactors().isEmpty()){
+ riskRoutes.add(route);
+ }
+ }
+
+ private void handleLostRoute(Route route, Map> lostAdjMap, Map lostNodeMap) {
+ lostAdjMap.computeIfAbsent(route.getNode1().getId(), k -> new ArrayList<>()).add(route);
+ lostAdjMap.computeIfAbsent(route.getNode2().getId(), k -> new ArrayList<>()).add(route);
+ lostNodeMap.put(route.getNode1().getId(), route.getNode1());
+ lostNodeMap.put(route.getNode2().getId(), route.getNode2());
+ }
+
+ private List determineEndNodes(Map> lostAdjMap, Map lostNodeMap) {
+ return lostAdjMap.entrySet()
+ .stream()
+ .filter(entry -> (entry.getValue().size() == 1) || lostNodeMap.get(entry.getKey()).isCore())
+ .map(Map.Entry::getKey)
+ .map(lostNodeMap::get)
+ .collect(Collectors.toList());
+ }
+
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AuthService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AuthService.java
new file mode 100644
index 0000000..75b3cd3
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AuthService.java
@@ -0,0 +1,36 @@
+package com.softeer5.uniro_backend.admin.service;
+
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.admin.dto.request.LoginReqDTO;
+import com.softeer5.uniro_backend.admin.dto.response.LoginResDTO;
+import com.softeer5.uniro_backend.admin.entity.Admin;
+import com.softeer5.uniro_backend.admin.jwt.JwtTokenProvider;
+import com.softeer5.uniro_backend.admin.repository.AdminRepository;
+import com.softeer5.uniro_backend.common.exception.custom.AdminException;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class AuthService {
+
+ private final AdminRepository adminRepository;
+ private final JwtTokenProvider jwtTokenProvider;
+
+ public LoginResDTO login(LoginReqDTO loginReqDTO) {
+
+ Admin admin = adminRepository.findByUnivId(loginReqDTO.getUnivId())
+ .orElseThrow(() -> new AdminException("invalid univ id", INVALID_UNIV_ID));
+
+ if (!admin.getCode().equals(loginReqDTO.getCode())) {
+ throw new AdminException("invalid code", INVALID_ADMIN_CODE);
+ }
+
+ return LoginResDTO.of(jwtTokenProvider.createToken(admin.getUnivId()));
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java
index 9af5ca4..d6cbdd0 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java
@@ -7,6 +7,7 @@ public class CustomReversionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
RevInfo revinfo = (RevInfo) revisionEntity;
+
revinfo.setUnivId(RevisionContext.getUnivId());
revinfo.setAction(RevisionContext.getAction());
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java
index 7bc21ee..abb05a2 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java
@@ -3,6 +3,7 @@
public class RevisionContext {
private static final ThreadLocal univIdHolder = new ThreadLocal<>();
private static final ThreadLocal actionHolder = new ThreadLocal<>();
+ private static final ThreadLocal REVISION_TYPE_THREAD_LOCAL = ThreadLocal.withInitial(() -> RevisionType.DEFAULT);
public static void setAction(String action) {
actionHolder.set(action);
@@ -20,6 +21,14 @@ public static Long getUnivId() {
return univIdHolder.get();
}
+ public static void setRevisionType(RevisionType revisionType) {
+ REVISION_TYPE_THREAD_LOCAL.set(revisionType);
+ }
+
+ public static RevisionType getRevisionType() {
+ return REVISION_TYPE_THREAD_LOCAL.get();
+ }
+
public static void clear() {
univIdHolder.remove();
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionType.java
new file mode 100644
index 0000000..a5d987e
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionType.java
@@ -0,0 +1,5 @@
+package com.softeer5.uniro_backend.admin.setting;
+
+public enum RevisionType {
+ DEFAULT, IGNORE
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingApi.java
similarity index 73%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeApi.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingApi.java
index df098fe..bf4d724 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeApi.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingApi.java
@@ -1,13 +1,15 @@
-package com.softeer5.uniro_backend.node.controller;
+package com.softeer5.uniro_backend.building.controller;
import java.util.List;
+import com.softeer5.uniro_backend.building.dto.request.CreateBuildingNodeReqDTO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
-import com.softeer5.uniro_backend.node.dto.GetBuildingResDTO;
-import com.softeer5.uniro_backend.node.dto.SearchBuildingResDTO;
+import com.softeer5.uniro_backend.building.dto.response.GetBuildingResDTO;
+import com.softeer5.uniro_backend.building.dto.response.SearchBuildingResDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
@@ -16,7 +18,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
@Tag(name = "노드(코어노드, 서브노드, 건물노드) 관련 Api")
-public interface NodeApi {
+public interface BuildingApi {
@Operation(summary = "건물 노드 조회")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "건물 노드 조회 성공"),
@@ -52,4 +54,13 @@ ResponseEntity getBuilding(
@PathVariable("univId") Long univId,
@PathVariable("nodeId") Long nodeId
);
+
+ @Operation(summary = "건물 노드 생성")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "건물 노드 생성 성공"),
+ @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
+ })
+ ResponseEntity createBuildingNode(@PathVariable("univId") Long univId,
+ @RequestBody CreateBuildingNodeReqDTO createBuildingNodeReqDTO);
+
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingController.java
similarity index 53%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeController.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingController.java
index 47344bc..aaa6a57 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/controller/NodeController.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/controller/BuildingController.java
@@ -1,23 +1,23 @@
-package com.softeer5.uniro_backend.node.controller;
+package com.softeer5.uniro_backend.building.controller;
import java.util.List;
+import com.softeer5.uniro_backend.building.dto.request.CreateBuildingNodeReqDTO;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
-import com.softeer5.uniro_backend.node.dto.GetBuildingResDTO;
-import com.softeer5.uniro_backend.node.dto.SearchBuildingResDTO;
-import com.softeer5.uniro_backend.node.service.NodeService;
+import com.softeer5.uniro_backend.building.dto.response.GetBuildingResDTO;
+import com.softeer5.uniro_backend.building.dto.response.SearchBuildingResDTO;
+import com.softeer5.uniro_backend.building.service.BuildingService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
-public class NodeController implements NodeApi {
- private final NodeService nodeService;
+public class BuildingController implements BuildingApi {
+ private final BuildingService buildingService;
@Override
@GetMapping("/{univId}/nodes/buildings")
@@ -29,7 +29,7 @@ public ResponseEntity> getBuildings(
@RequestParam(value = "right-down-lat") double rightDownLat,
@RequestParam(value = "right-down-lng") double rightDownLng
) {
- List buildingResDTOS = nodeService.getBuildings(univId, level, leftUpLat, leftUpLng,
+ List buildingResDTOS = buildingService.getBuildings(univId, level, leftUpLat, leftUpLng,
rightDownLat, rightDownLng);
return ResponseEntity.ok().body(buildingResDTOS);
}
@@ -42,7 +42,7 @@ public ResponseEntity searchBuildings(
@RequestParam(value = "cursor-id", required = false) Long cursorId,
@RequestParam(value = "page-size", required = false, defaultValue = "6") Integer pageSize
) {
- SearchBuildingResDTO searchBuildingResDTO = nodeService.searchBuildings(univId, name, cursorId, pageSize);
+ SearchBuildingResDTO searchBuildingResDTO = buildingService.searchBuildings(univId, name, cursorId, pageSize);
return ResponseEntity.ok().body(searchBuildingResDTO);
}
@@ -52,8 +52,17 @@ public ResponseEntity getBuilding(
@PathVariable("univId") Long univId,
@PathVariable("nodeId") Long nodeId) {
- GetBuildingResDTO buildingResDTO = nodeService.getBuilding(nodeId);
+ GetBuildingResDTO buildingResDTO = buildingService.getBuilding(nodeId);
return ResponseEntity.ok().body(buildingResDTO);
}
+ @Override
+ @PostMapping("{univId}/nodes/building")
+ public ResponseEntity createBuildingNode(@PathVariable("univId") Long univId,
+ @RequestBody @Valid CreateBuildingNodeReqDTO createBuildingNodeReqDTO){
+ buildingService.createBuildingNode(univId, createBuildingNodeReqDTO);
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+
+
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/request/CreateBuildingNodeReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/request/CreateBuildingNodeReqDTO.java
new file mode 100644
index 0000000..7047c26
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/request/CreateBuildingNodeReqDTO.java
@@ -0,0 +1,29 @@
+package com.softeer5.uniro_backend.building.dto.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+@Schema(name = "CreateBuildingNodeReqDTO", description = "건물 노드 생성 요청 DTO")
+public class CreateBuildingNodeReqDTO {
+ @Schema(description = "x 좌표", example = "127.123456")
+ @NotNull
+ private final double lng;
+ @Schema(description = "y 좌표", example = "37.123456")
+ @NotNull
+ private final double lat;
+ @Schema(description = "건물명", example = "공학관")
+ @NotNull
+ private final String buildingName;
+ @Schema(description = "전화번호", example = "02-1234-1234")
+ private final String phoneNumber;
+ @Schema(description = "주소", example = "한양로 123번길123")
+ private final String address;
+ @Schema(description = "이미지", example = "")
+ private final String buildingImageUrl; // 추후 S3학습 후 변경예정
+ @Schema(description = "레벨(지도 축척에 따른 노출정도, 1~10)", example = "3")
+ private final int level;
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/GetBuildingResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/GetBuildingResDTO.java
similarity index 88%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/GetBuildingResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/GetBuildingResDTO.java
index e6a92b5..79d9b3b 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/GetBuildingResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/GetBuildingResDTO.java
@@ -1,7 +1,7 @@
-package com.softeer5.uniro_backend.node.dto;
+package com.softeer5.uniro_backend.building.dto.response;
-import com.softeer5.uniro_backend.node.entity.Building;
-import com.softeer5.uniro_backend.node.entity.Node;
+import com.softeer5.uniro_backend.building.entity.Building;
+import com.softeer5.uniro_backend.map.entity.Node;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/SearchBuildingResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/SearchBuildingResDTO.java
similarity index 93%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/SearchBuildingResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/SearchBuildingResDTO.java
index f3b12b6..c2f6b70 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/SearchBuildingResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/dto/response/SearchBuildingResDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.node.dto;
+package com.softeer5.uniro_backend.building.dto.response;
import java.util.List;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Building.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/entity/Building.java
similarity index 63%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Building.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/entity/Building.java
index f6703a8..bf0ede4 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Building.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/entity/Building.java
@@ -1,18 +1,25 @@
-package com.softeer5.uniro_backend.node.entity;
+package com.softeer5.uniro_backend.building.entity;
+
+import java.time.LocalDateTime;
+
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
+import lombok.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@Builder
+@EntityListeners(AuditingEntityListener.class)
public class Building {
@Id
@@ -41,4 +48,6 @@ public class Building {
@NotNull
private Long univId;
+ @CreatedDate
+ private LocalDateTime createdAt;
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepository.java
similarity index 65%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepository.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepository.java
index c364277..a357414 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepository.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepository.java
@@ -1,9 +1,9 @@
-package com.softeer5.uniro_backend.node.repository;
+package com.softeer5.uniro_backend.building.repository;
import java.util.List;
import com.softeer5.uniro_backend.common.CursorPage;
-import com.softeer5.uniro_backend.node.dto.BuildingNode;
+import com.softeer5.uniro_backend.building.service.vo.BuildingNode;
public interface BuildingCustomRepository {
CursorPage> searchBuildings(Long univId, String name, Long cursorId, Integer pageSize);
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepositoryImpl.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepositoryImpl.java
similarity index 83%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepositoryImpl.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepositoryImpl.java
index 83541ed..a4b53a3 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/repository/BuildingCustomRepositoryImpl.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingCustomRepositoryImpl.java
@@ -1,20 +1,22 @@
-package com.softeer5.uniro_backend.node.repository;
+package com.softeer5.uniro_backend.building.repository;
-import static com.softeer5.uniro_backend.node.entity.QBuilding.*;
-import static com.softeer5.uniro_backend.node.entity.QNode.*;
+import static com.softeer5.uniro_backend.map.entity.QNode.node;
import java.util.List;
+import com.softeer5.uniro_backend.building.service.vo.QBuildingNode;
import org.springframework.stereotype.Repository;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
+
import com.softeer5.uniro_backend.common.CursorPage;
-import com.softeer5.uniro_backend.node.dto.BuildingNode;
-import com.softeer5.uniro_backend.node.dto.QBuildingNode;
+import com.softeer5.uniro_backend.building.service.vo.BuildingNode;
import lombok.RequiredArgsConstructor;
+import static com.softeer5.uniro_backend.building.entity.QBuilding.building;
+
@Repository
@RequiredArgsConstructor
public class BuildingCustomRepositoryImpl implements BuildingCustomRepository {
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingRepository.java
new file mode 100644
index 0000000..521d1d6
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/repository/BuildingRepository.java
@@ -0,0 +1,44 @@
+package com.softeer5.uniro_backend.building.repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.building.service.vo.BuildingNode;
+import com.softeer5.uniro_backend.building.entity.Building;
+
+public interface BuildingRepository extends JpaRepository, BuildingCustomRepository {
+
+ // 추후에 인덱싱 작업 필요.
+ @Query("""
+ SELECT new com.softeer5.uniro_backend.building.service.vo.BuildingNode(b, n)
+ FROM Building b
+ JOIN FETCH Node n ON b.nodeId = n.id
+ WHERE b.univId = :univId
+ AND b.level >= :level
+ AND ST_Within(n.coordinates, ST_PolygonFromText((:polygon),4326))
+ """)
+ List findByUnivIdAndLevelWithNode(Long univId, int level, String polygon);
+
+ @Query("""
+ SELECT new com.softeer5.uniro_backend.building.service.vo.BuildingNode(b, n)
+ FROM Building b
+ JOIN FETCH Node n ON b.nodeId = n.id
+ WHERE b.nodeId = :nodeId
+ """)
+ Optional findByNodeIdWithNode(Long nodeId);
+
+ List findAllByNodeIdIn(List nodeIds);
+
+ boolean existsByNodeIdAndUnivId(Long nodeId, Long univId);
+
+ @Modifying(clearAutomatically = true)
+ @Transactional
+ @Query("DELETE FROM Building b WHERE b.univId = :univId AND b.createdAt > :versionTimeStamp")
+ void deleteAllByCreatedAt(Long univId, LocalDateTime versionTimeStamp);
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/BuildingService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/BuildingService.java
new file mode 100644
index 0000000..bcbc609
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/BuildingService.java
@@ -0,0 +1,90 @@
+package com.softeer5.uniro_backend.building.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.softeer5.uniro_backend.admin.annotation.RevisionOperation;
+import com.softeer5.uniro_backend.admin.enums.RevisionOperationType;
+import com.softeer5.uniro_backend.external.MapClient;
+import com.softeer5.uniro_backend.building.dto.request.CreateBuildingNodeReqDTO;
+import com.softeer5.uniro_backend.building.entity.Building;
+import com.softeer5.uniro_backend.map.entity.Node;
+import com.softeer5.uniro_backend.map.repository.NodeRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.common.CursorPage;
+import com.softeer5.uniro_backend.common.error.ErrorCode;
+import com.softeer5.uniro_backend.common.exception.custom.BuildingException;
+import com.softeer5.uniro_backend.common.utils.GeoUtils;
+import com.softeer5.uniro_backend.building.service.vo.BuildingNode;
+import com.softeer5.uniro_backend.building.dto.response.GetBuildingResDTO;
+import com.softeer5.uniro_backend.building.dto.response.SearchBuildingResDTO;
+import com.softeer5.uniro_backend.building.repository.BuildingRepository;
+
+import lombok.RequiredArgsConstructor;
+
+import static com.softeer5.uniro_backend.common.utils.GeoUtils.convertDoubleToPoint;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class BuildingService {
+ private final BuildingRepository buildingRepository;
+ private final NodeRepository nodeRepository;
+ private final MapClient mapClient;
+
+ public List getBuildings(
+ Long univId, int level,
+ double leftUpLat, double leftUpLng, double rightDownLat, double rightDownLng) {
+
+ String polygon = GeoUtils.makeSquarePolygonString(leftUpLat, leftUpLng, rightDownLat, rightDownLng);
+ List buildingNodes = buildingRepository.findByUnivIdAndLevelWithNode(univId, level, polygon);
+
+ return buildingNodes.stream()
+ .map(buildingNode -> GetBuildingResDTO.of(buildingNode.getBuilding(), buildingNode.getNode()))
+ .toList();
+ }
+
+ public SearchBuildingResDTO searchBuildings(Long univId, String name, Long cursorId, Integer pageSize){
+
+ CursorPage> buildingNodes = buildingRepository.searchBuildings(univId, name, cursorId, pageSize);
+
+ List data = buildingNodes.getData().stream()
+ .map(buildingNode -> GetBuildingResDTO.of(buildingNode.getBuilding(), buildingNode.getNode()))
+ .toList();
+
+ return SearchBuildingResDTO.of(data, buildingNodes.getNextCursor(), buildingNodes.isHasNext());
+ }
+
+ public GetBuildingResDTO getBuilding(Long nodeId){
+ Optional buildingNode = buildingRepository.findByNodeIdWithNode(nodeId);
+ if(buildingNode.isEmpty()){
+ throw new BuildingException("Building Not Found", ErrorCode.BUILDING_NOT_FOUND);
+ }
+
+ return GetBuildingResDTO.of(buildingNode.get().getBuilding(), buildingNode.get().getNode());
+ }
+
+ @RevisionOperation(RevisionOperationType.CREATE_BUILDING_NODE)
+ @Transactional
+ public void createBuildingNode(Long univId, CreateBuildingNodeReqDTO createBuildingNodeReqDTO) {
+ Node node = Node.builder()
+ .coordinates(convertDoubleToPoint(createBuildingNodeReqDTO.getLng(), createBuildingNodeReqDTO.getLat()))
+ .isCore(false)
+ .univId(univId).build();
+ mapClient.fetchHeights(List.of(node));
+ nodeRepository.save(node);
+
+ Building building = Building.builder()
+ .phoneNumber(createBuildingNodeReqDTO.getPhoneNumber())
+ .address(createBuildingNodeReqDTO.getAddress())
+ .name(createBuildingNodeReqDTO.getBuildingName())
+ .imageUrl(createBuildingNodeReqDTO.getBuildingImageUrl())
+ .level(createBuildingNodeReqDTO.getLevel())
+ .nodeId(node.getId())
+ .univId(univId).build();
+ buildingRepository.save(building);
+ }
+
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/BuildingNode.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/vo/BuildingNode.java
similarity index 64%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/BuildingNode.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/vo/BuildingNode.java
index f5f6091..531b18e 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/dto/BuildingNode.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/building/service/vo/BuildingNode.java
@@ -1,8 +1,8 @@
-package com.softeer5.uniro_backend.node.dto;
+package com.softeer5.uniro_backend.building.service.vo;
import com.querydsl.core.annotations.QueryProjection;
-import com.softeer5.uniro_backend.node.entity.Building;
-import com.softeer5.uniro_backend.node.entity.Node;
+import com.softeer5.uniro_backend.building.entity.Building;
+import com.softeer5.uniro_backend.map.entity.Node;
import lombok.Getter;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/EnversConfig.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/EnversConfig.java
new file mode 100644
index 0000000..5f870f4
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/EnversConfig.java
@@ -0,0 +1,30 @@
+package com.softeer5.uniro_backend.common.config;
+
+import org.hibernate.envers.boot.internal.EnversService;
+import org.hibernate.event.service.spi.EventListenerRegistry;
+import org.hibernate.event.spi.EventType;
+import org.hibernate.internal.SessionFactoryImpl;
+import org.hibernate.service.spi.ServiceRegistryImplementor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.softeer5.uniro_backend.admin.aspect.CustomPostUpdateListener;
+
+import jakarta.persistence.EntityManagerFactory;
+
+@Configuration
+public class EnversConfig {
+
+ @Bean
+ public EventListenerRegistry listenerRegistry(EntityManagerFactory entityManagerFactory) {
+ ServiceRegistryImplementor serviceRegistry = entityManagerFactory.unwrap(SessionFactoryImpl.class).getServiceRegistry();
+
+ EnversService enversService = serviceRegistry.getService(EnversService.class);
+ EventListenerRegistry listenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
+
+ // listenerRegistry.setListeners(EventType.POST_INSERT, new CustomPostInsertListener(enversService));
+ listenerRegistry.setListeners(EventType.POST_UPDATE, new CustomPostUpdateListener(enversService));
+
+ return listenerRegistry;
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java
new file mode 100644
index 0000000..b227431
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java
@@ -0,0 +1,36 @@
+package com.softeer5.uniro_backend.common.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.softeer5.uniro_backend.admin.interceptor.AdminInterceptor;
+import com.softeer5.uniro_backend.admin.interceptor.JwtInterceptor;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+ private final AdminInterceptor adminInterceptor;
+ private final JwtInterceptor jwtInterceptor; // JWT 인터셉터 추가
+
+ public WebMvcConfig(AdminInterceptor adminInterceptor, JwtInterceptor jwtInterceptor) {
+ this.adminInterceptor = adminInterceptor;
+ this.jwtInterceptor = jwtInterceptor;
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // JWT 인터셉터는 더 먼저 실행되도록 우선순위 낮춤
+ registry.addInterceptor(jwtInterceptor)
+ .addPathPatterns("/admin/{univId}/**") // "/admin/{univId}/" 패턴만 적용
+ .excludePathPatterns("/admin/auth/login")
+ .order(0); // 가장 먼저 실행되도록 설정
+
+ // AdminInterceptor는 그 다음에 실행
+ registry.addInterceptor(adminInterceptor)
+ .addPathPatterns("/admin/{univId}/**") // "/admin/{univId}/" 패턴만 적용
+ .excludePathPatterns("/admin/auth/login")
+ .order(1); // JWT 이후에 실행되도록 설정
+ }
+}
+
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java
index 8142946..ae802da 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java
@@ -4,6 +4,10 @@ public final class UniroConst {
public static final String NODE_KEY_DELIMITER = " ";
public static final int CORE_NODE_CONDITION = 3;
public static final int BEFORE_DEFAULT_ORDER = -1;
- public static final double SECONDS_PER_MITER = 1.0;
- public static final double METERS_PER_DEGREE = 113000.0;
+ public static final double PEDESTRIAN_SECONDS_PER_MITER = 1.2;
+ public static final double MANUAL_WHEELCHAIR_SECONDS_PER_MITER = 2.5;
+ public static final double ELECTRIC_WHEELCHAIR_SECONDS_PER_MITER = 1.0;
+ public static final double EARTH_RADIUS = 6378137;
+ public static final double BUILDING_ROUTE_DISTANCE = 1e8;
+ public static final int IS_SINGLE_ROUTE = 2;
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/error/ErrorCode.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/error/ErrorCode.java
index 6ed116c..d60712b 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/error/ErrorCode.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/error/ErrorCode.java
@@ -1,13 +1,15 @@
package com.softeer5.uniro_backend.common.error;
-import org.springframework.http.HttpStatus;
-
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ErrorCode {
+
+ // common
+ INVALID_INPUT_VALUE(400, "적절하지 않은 요청값입니다."),
+
// 길찾기
FASTEST_ROUTE_NOT_FOUND(422, "경로가 없습니다."),
SAME_START_AND_END_POINT(400, "출발지와 도착지가 같습니다."),
@@ -19,15 +21,24 @@ public enum ErrorCode {
//길 생성
ELEVATION_API_ERROR(500, "구글 해발고도 API에서 오류가 발생했습니다."),
+ DUPLICATE_NEAREST_NODE(400, "중복된 인접 노드가 존재합니다."),
// 건물 노드
BUILDING_NOT_FOUND(404, "유효한 건물을 찾을 수 없습니다."),
+ NOT_BUILDING_NODE(400, "빌딩 노드가 아닙니다."),
// 노드
NODE_NOT_FOUND(404, "유효한 노드를 찾을 수 없습니다."),
// 경로 계산
- INTERSECTION_ONLY_ALLOWED_POINT(400, "기존 경로와 겹칠 수 없습니다.")
+ INTERSECTION_ONLY_ALLOWED_POINT(400, "기존 경로와 겹칠 수 없습니다."),
+
+ // 어드민
+ INVALID_VERSION_ID(400, "유효하지 않은 버전 id 입니다."),
+ INVALID_ADMIN_CODE(403, "유효하지 않은 어드민 코드 입니다."),
+ INVALID_TOKEN(401, "유효하지 않은 토큰입니다."),
+ UNAUTHORIZED_UNIV(401, "해당 대학교의 권한이 없습니다."),
+ INVALID_UNIV_ID(400, "유효하지 않은 대학교 id 입니다."),
;
private final int httpStatus;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/GlobalExceptionHandler.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/GlobalExceptionHandler.java
index 3906221..e9bb8e8 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/GlobalExceptionHandler.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/GlobalExceptionHandler.java
@@ -1,10 +1,13 @@
package com.softeer5.uniro_backend.common.exception;
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+
import com.softeer5.uniro_backend.common.error.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -17,4 +20,12 @@ public ResponseEntity handleCustomException(CustomException ex) {
ErrorResponse response = new ErrorResponse(ex.getErrorCode());
return new ResponseEntity<>(response, HttpStatus.valueOf(ex.getErrorCode().getHttpStatus()));
}
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleCustomException(MethodArgumentNotValidException ex) {
+ log.error(ex.getMessage());
+ ErrorResponse response = new ErrorResponse(INVALID_INPUT_VALUE);
+ return new ResponseEntity<>(response, HttpStatus.valueOf(INVALID_INPUT_VALUE.getHttpStatus()));
+ }
+
}
\ No newline at end of file
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeNotFoundException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/AdminException.java
similarity index 66%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeNotFoundException.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/AdminException.java
index 5489fa9..a548188 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeNotFoundException.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/AdminException.java
@@ -6,8 +6,8 @@
import lombok.Getter;
@Getter
-public class NodeNotFoundException extends CustomException {
- public NodeNotFoundException(String message, ErrorCode errorCode) {
+public class AdminException extends CustomException {
+ public AdminException(String message, ErrorCode errorCode) {
super(message, errorCode);
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingNotFoundException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingException.java
similarity index 64%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingNotFoundException.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingException.java
index 1c6e74a..d78bffb 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingNotFoundException.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/BuildingException.java
@@ -6,8 +6,8 @@
import lombok.Getter;
@Getter
-public class BuildingNotFoundException extends CustomException {
- public BuildingNotFoundException(String message, ErrorCode errorCode) {
+public class BuildingException extends CustomException {
+ public BuildingException(String message, ErrorCode errorCode) {
super(message, errorCode);
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/DangerCautionConflictException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/DangerCautionConflictException.java
deleted file mode 100644
index c2bd504..0000000
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/DangerCautionConflictException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.softeer5.uniro_backend.common.exception.custom;
-
-import com.softeer5.uniro_backend.common.error.ErrorCode;
-import com.softeer5.uniro_backend.common.exception.CustomException;
-
-public class DangerCautionConflictException extends CustomException {
- public DangerCautionConflictException(String message, ErrorCode errorCode) {
- super(message, errorCode);
- }
-}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/InvalidMapException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/InvalidMapException.java
deleted file mode 100644
index 24c03f5..0000000
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/InvalidMapException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.softeer5.uniro_backend.common.exception.custom;
-
-import com.softeer5.uniro_backend.common.error.ErrorCode;
-import com.softeer5.uniro_backend.common.exception.CustomException;
-
-public class InvalidMapException extends CustomException {
- public InvalidMapException(String message, ErrorCode errorCode) {
- super(message, errorCode);
- }
-}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/SameStartAndEndPointException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeException.java
similarity index 53%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/SameStartAndEndPointException.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeException.java
index 8eb52d4..6ab56d8 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/SameStartAndEndPointException.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/NodeException.java
@@ -2,11 +2,12 @@
import com.softeer5.uniro_backend.common.error.ErrorCode;
import com.softeer5.uniro_backend.common.exception.CustomException;
+
import lombok.Getter;
@Getter
-public class SameStartAndEndPointException extends CustomException {
- public SameStartAndEndPointException(String message, ErrorCode errorCode) {
- super(message, errorCode);
- }
-}
\ No newline at end of file
+public class NodeException extends CustomException {
+ public NodeException(String message, ErrorCode errorCode) {
+ super(message, errorCode);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnreachableDestinationException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteException.java
similarity index 52%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnreachableDestinationException.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteException.java
index 7d2d81e..cfef4e9 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnreachableDestinationException.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteException.java
@@ -1,13 +1,13 @@
package com.softeer5.uniro_backend.common.exception.custom;
-
import com.softeer5.uniro_backend.common.error.ErrorCode;
import com.softeer5.uniro_backend.common.exception.CustomException;
+
import lombok.Getter;
@Getter
-public class UnreachableDestinationException extends CustomException {
- public UnreachableDestinationException(String message, ErrorCode errorCode) {
- super(message, errorCode);
- }
-}
\ No newline at end of file
+public class RouteException extends CustomException {
+ public RouteException(String message, ErrorCode errorCode) {
+ super(message, errorCode);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteNotFoundException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteNotFoundException.java
deleted file mode 100644
index a7efd57..0000000
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/RouteNotFoundException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.softeer5.uniro_backend.common.exception.custom;
-
-import com.softeer5.uniro_backend.common.error.ErrorCode;
-import com.softeer5.uniro_backend.common.exception.CustomException;
-
-public class RouteNotFoundException extends CustomException {
- public RouteNotFoundException(String message, ErrorCode errorCode) {
- super(message, errorCode);
- }
-}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnivException.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnivException.java
new file mode 100644
index 0000000..2c10ca5
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/exception/custom/UnivException.java
@@ -0,0 +1,13 @@
+package com.softeer5.uniro_backend.common.exception.custom;
+
+import com.softeer5.uniro_backend.common.error.ErrorCode;
+import com.softeer5.uniro_backend.common.exception.CustomException;
+
+import lombok.Getter;
+
+@Getter
+public class UnivException extends CustomException {
+ public UnivException(String message, ErrorCode errorCode) {
+ super(message, errorCode);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ArgType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ArgType.java
new file mode 100644
index 0000000..ceed19e
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ArgType.java
@@ -0,0 +1,34 @@
+package com.softeer5.uniro_backend.common.logging;
+
+import java.util.List;
+
+public enum ArgType {
+ NULL,
+ LIST,
+ CUSTOM_DTO,
+ ENTITY,
+ OTHER;
+
+ public static ArgType getArgType(Object arg) {
+ if (arg == null) {
+ return NULL;
+ } else if (arg instanceof List>) {
+ return LIST;
+ } else if (isCustomDto(arg)) {
+ return CUSTOM_DTO;
+ } else if (isEntity(arg)) {
+ return ENTITY;
+ } else {
+ return OTHER;
+ }
+ }
+
+ private static boolean isCustomDto(Object arg) {
+ return arg.getClass().getName().contains("dto");
+ }
+
+ private static boolean isEntity(Object arg) {
+ return arg.getClass().getName().contains("entity");
+ }
+}
+
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java
index 48c0898..5b30196 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java
@@ -1,9 +1,8 @@
package com.softeer5.uniro_backend.common.logging;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
+import java.util.Arrays;
import java.util.Enumeration;
-import java.util.Objects;
+import java.util.List;
import java.util.UUID;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -12,7 +11,6 @@
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
-import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -30,10 +28,8 @@ public class ExecutionLoggingAop {
@Around("execution(* com.softeer5.uniro_backend..*(..)) "
+ "&& !within(com.softeer5.uniro_backend.common..*) "
- + "&& !within(com.softeer5.uniro_backend.resolver..*) "
)
public Object logExecutionTrace(ProceedingJoinPoint pjp) throws Throwable {
- // 요청에서 userId 가져오기 (ThreadLocal 사용)
String userId = userIdThreadLocal.get();
if (userId == null) {
userId = UUID.randomUUID().toString().substring(0, 12);
@@ -41,76 +37,120 @@ public Object logExecutionTrace(ProceedingJoinPoint pjp) throws Throwable {
}
Object target = pjp.getTarget();
- Annotation[] declaredAnnotations = target.getClass().getDeclaredAnnotations();
+ boolean isController = isRestController(target);
String className = pjp.getSignature().getDeclaringType().getSimpleName();
String methodName = pjp.getSignature().getName();
String task = className + "." + methodName;
- for(Annotation annotation : declaredAnnotations){
- if(annotation instanceof RestController){
- logHttpRequest(userId);
-
- HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
- RequestMethod httpMethod = RequestMethod.valueOf(request.getMethod());
-
- log.info("");
- log.info("🚨 [ userId = {} ] {} Start", userId, className);
- log.info("[ userId = {} ] [Call Method] {}: {}", userId, httpMethod, task);
- }
+ if (isController) {
+ logHttpRequest(userId);
}
+ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
+ log.info("✅ [ userId = {} Start] [Call Method] {}: {}", userId, request.getMethod(), task);
- Object[] paramArgs = pjp.getArgs();
- for (Object object : paramArgs) {
- if (Objects.nonNull(object)) {
- log.info("[Parameter] {} {}", object.getClass().getSimpleName(), object);
-
- String packageName = object.getClass().getPackage().getName();
- if (packageName.contains("java")) {
- break;
- }
-
- Field[] fields = object.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- try {
- Object value = field.get(object);
- log.info("[Field] {} = {}", field.getName(), value);
- } catch (IllegalAccessException e) {
- log.warn("[Field Access Error] Cannot access field: {}", field.getName());
- }
- }
+ try{
+ if (isController) {
+ logParameters(pjp.getArgs());
}
}
+ catch (Exception e){
+ // 로깅 중에 발생한 에러는 무시하고 로깅을 계속 진행
+ log.error("🚨🚨🚨 [ userId = {} ] {} 메서드 파라미터 로깅 중 에러 발생 : {} 🚨🚨🚨", userId, task, e.getMessage());
+ }
+ log.info("");
StopWatch sw = new StopWatch();
sw.start();
- Object result = null;
+ Object result;
try {
result = pjp.proceed();
} catch (Exception e) {
log.warn("[ERROR] [ userId = {} ] {} 메서드 예외 발생 : {}", userId, task, e.getMessage());
throw e;
} finally {
- // Controller 클래스일 때만 ThreadLocal 값 삭제
- for(Annotation annotation : declaredAnnotations){
- if(annotation instanceof RestController){
- userIdThreadLocal.remove();
- }
+ if (isController) {
+ userIdThreadLocal.remove();
}
}
sw.stop();
- long executionTime = sw.getTotalTimeMillis();
-
- log.info("[ExecutionTime] {} --> {} (ms)", task, executionTime);
- log.info("🚨 [ userId = {} ] {} End", userId, className);
- log.info("");
+ log.info("[ExecutionTime] {} --> {} (ms)", task, sw.getTotalTimeMillis());
+ log.info("🚨 [ userId = {} ] {} End\n", userId, className);
return result;
}
+ private boolean isRestController(Object target) {
+ return Arrays.stream(target.getClass().getDeclaredAnnotations())
+ .anyMatch(RestController.class::isInstance);
+ }
+
+ private void logParameters(Object[] args) {
+ StringBuilder parametersLogMessage = new StringBuilder();
+
+ Arrays.stream(args)
+ .forEach(arg -> logDetail(arg, "[Parameter]", parametersLogMessage, 0));
+
+ log.info("\n{}", parametersLogMessage.toString());
+ }
+
+ private void logDetail(Object arg, String requestType, StringBuilder logMessage, int depth) {
+ String indent = " ".repeat(depth); // depth 수준에 따른 들여쓰기
+ ArgType argType = ArgType.getArgType(arg);
+
+ switch (argType) {
+ case NULL -> logMessage.append(indent).append(requestType).append(" null\n");
+ case LIST -> {
+ logMessage.append(indent)
+ .append(requestType)
+ .append(" ")
+ .append(arg.getClass().getSimpleName())
+ .append("\n");
+ List> list = (List>)arg;
+ for (int i = 0; i < list.size(); i++) {
+ logDetail(list.get(i), "[List Element " + i + "] ", logMessage, depth + 1);
+ }
+ }
+ case CUSTOM_DTO -> {
+ logMessage.append(indent)
+ .append(requestType)
+ .append("DTO: ")
+ .append(arg.getClass().getSimpleName())
+ .append("\n");
+ logObjectFields(arg, logMessage, depth + 1);
+ }
+ case ENTITY -> {
+ logMessage.append(indent)
+ .append(requestType)
+ .append(arg.getClass().getSimpleName())
+ .append(" : ")
+ .append("\n");
+ logObjectFields(arg, logMessage, depth + 1);
+ }
+ default -> logMessage.append(indent)
+ .append(requestType)
+ .append(" ")
+ .append(arg.getClass().getSimpleName())
+ .append(": ")
+ .append(arg)
+ .append("\n");
+ }
+ }
+
+ private void logObjectFields(Object object, StringBuilder logMessage, int depth) {
+ String indent = " ".repeat(depth); // depth 기반 들여쓰기
+ Arrays.stream(object.getClass().getDeclaredFields()).forEach(field -> {
+ try {
+ field.setAccessible(true);
+ Object value = field.get(object);
+ logDetail(value, "[Field] " + field.getName(), logMessage, depth + 1);
+ } catch (IllegalAccessException e) {
+ logMessage.append(indent).append("[Field Access Error] Cannot access field: ").append(field.getName()).append("\n");
+ }
+ });
+ }
private void logHttpRequest(String userId) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/CautionListConverter.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/CautionListConverter.java
similarity index 82%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/CautionListConverter.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/CautionListConverter.java
index 2feb160..5beeec8 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/CautionListConverter.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/CautionListConverter.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.resolver;
+package com.softeer5.uniro_backend.common.resolver;
import java.io.IOException;
import java.util.Set;
@@ -7,18 +7,18 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.softeer5.uniro_backend.route.entity.CautionType;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter
-public class CautionListConverter implements AttributeConverter, String> {
+public class CautionListConverter implements AttributeConverter, String> {
private static final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
@Override
- public String convertToDatabaseColumn(Set attribute) {
+ public String convertToDatabaseColumn(Set attribute) {
try {
if (attribute == null) {
return "[]"; // List가 null일 경우, DB에 저장할 값은 []
@@ -30,7 +30,7 @@ public String convertToDatabaseColumn(Set attribute) {
}
@Override
- public Set convertToEntityAttribute(String dbData) {
+ public Set convertToEntityAttribute(String dbData) {
try {
if (dbData == null || dbData.trim().isEmpty()) {
return null; // dbData가 null 또는 빈 문자열일 경우 빈 리스트 반환
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/DangerListConverter.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/DangerListConverter.java
similarity index 82%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/DangerListConverter.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/DangerListConverter.java
index f6246cf..abedf03 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/resolver/DangerListConverter.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/resolver/DangerListConverter.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.resolver;
+package com.softeer5.uniro_backend.common.resolver;
import java.io.IOException;
import java.util.Set;
@@ -7,16 +7,16 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.softeer5.uniro_backend.route.entity.DangerType;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
import jakarta.persistence.AttributeConverter;
-public class DangerListConverter implements AttributeConverter, String> {
+public class DangerListConverter implements AttributeConverter, String> {
private static final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
@Override
- public String convertToDatabaseColumn(Set attribute) {
+ public String convertToDatabaseColumn(Set attribute) {
try {
if (attribute == null) {
return "[]"; // List가 null일 경우, DB에 저장할 값은 []
@@ -28,7 +28,7 @@ public String convertToDatabaseColumn(Set attribute) {
}
@Override
- public Set convertToEntityAttribute(String dbData) {
+ public Set convertToEntityAttribute(String dbData) {
try {
if (dbData == null || dbData.trim().isEmpty()) {
return null; // dbData가 null 또는 빈 문자열일 경우 빈 리스트 반환
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/GeoUtils.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/GeoUtils.java
index f63bca1..49a5165 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/GeoUtils.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/GeoUtils.java
@@ -16,8 +16,8 @@ public static GeometryFactory getInstance() {
return geometryFactory;
}
- public static Point convertDoubleToPoint(double lat, double lng) {
- return geometryFactory.createPoint(new Coordinate(lat, lng));
+ public static Point convertDoubleToPoint(double lng, double lat) {
+ return geometryFactory.createPoint(new Coordinate(lng, lat));
}
public static String convertDoubleToPointWTK(double lat, double lng) {
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/RouteUtils.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/RouteUtils.java
new file mode 100644
index 0000000..ed2bbd7
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/utils/RouteUtils.java
@@ -0,0 +1,16 @@
+package com.softeer5.uniro_backend.common.utils;
+
+import com.softeer5.uniro_backend.map.entity.Route;
+
+import static com.softeer5.uniro_backend.common.constant.UniroConst.BUILDING_ROUTE_DISTANCE;
+
+public final class RouteUtils {
+
+ private RouteUtils(){
+ // 인스턴스화 방지
+ }
+
+ public static boolean isBuildingRoute(Route route){
+ return route.getDistance() > BUILDING_ROUTE_DISTANCE - 1;
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClient.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClient.java
index 2c16746..7a079bb 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClient.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClient.java
@@ -1,6 +1,6 @@
package com.softeer5.uniro_backend.external;
-import com.softeer5.uniro_backend.node.entity.Node;
+import com.softeer5.uniro_backend.map.entity.Node;
import java.util.List;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClientImpl.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClientImpl.java
index 6f64e59..3a3fec4 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClientImpl.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/MapClientImpl.java
@@ -2,7 +2,7 @@
import com.softeer5.uniro_backend.common.error.ErrorCode;
import com.softeer5.uniro_backend.common.exception.custom.ElevationApiException;
-import com.softeer5.uniro_backend.node.entity.Node;
+import com.softeer5.uniro_backend.map.entity.Node;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@@ -21,7 +21,7 @@ public class MapClientImpl implements MapClient{
@Value("${map.api.key}")
private String apiKey;
private final String baseUrl = "https://maps.googleapis.com/maps/api/elevation/json";
- private final Integer MAX_BATCH_SIZE = 512;
+ private final Integer MAX_BATCH_SIZE = 100;
private final String SUCCESS_STATUS = "OK";
private final WebClient webClient;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java
similarity index 68%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java
index 5b9dfc8..57ba7c4 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java
@@ -1,12 +1,14 @@
-package com.softeer5.uniro_backend.route.controller;
+package com.softeer5.uniro_backend.map.controller;
-import com.softeer5.uniro_backend.route.dto.request.CreateRoutesReqDTO;
-import com.softeer5.uniro_backend.route.dto.response.FastestRouteResDTO;
-import com.softeer5.uniro_backend.route.dto.response.GetAllRoutesResDTO;
-import com.softeer5.uniro_backend.route.dto.response.GetRiskResDTO;
-import com.softeer5.uniro_backend.route.dto.response.GetRiskRoutesResDTO;
-import com.softeer5.uniro_backend.route.dto.request.PostRiskReqDTO;
+import com.softeer5.uniro_backend.map.dto.request.CreateBuildingRouteReqDTO;
+import com.softeer5.uniro_backend.map.dto.request.CreateRoutesReqDTO;
+import com.softeer5.uniro_backend.map.dto.response.FastestRouteResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetAllRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.request.PostRiskReqDTO;
+import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
@@ -18,8 +20,10 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
+import java.util.List;
+
@Tag(name = "간선 및 위험&주의 요소 관련 Api")
-public interface RouteApi {
+public interface MapApi {
@Operation(summary = "모든 지도(노드,루트) 조회")
@ApiResponses(value = {
@@ -66,7 +70,16 @@ ResponseEntity createRoute (@PathVariable("univId") Long univId,
@ApiResponse(responseCode = "200", description = "빠른 길 계산 성공"),
@ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
})
- ResponseEntity calculateFastestRoute(@PathVariable("univId") Long univId,
- @RequestParam Long startNodeId,
- @RequestParam Long endNodeId);
+ ResponseEntity> findFastestRoute(@PathVariable("univId") Long univId,
+ @RequestParam Long startNodeId,
+ @RequestParam Long endNodeId);
+
+
+ @Operation(summary = "빌딩 노드와 연결된 길 생성")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "빌딩 노드와 연결된 길 생성 성공"),
+ @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content),
+ })
+ ResponseEntity createBuildingRoute(@PathVariable("univId") Long univId,
+ @RequestBody @Valid CreateBuildingRouteReqDTO createBuildingRouteReqDTO);
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java
new file mode 100644
index 0000000..a554e7c
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java
@@ -0,0 +1,86 @@
+package com.softeer5.uniro_backend.map.controller;
+
+import com.softeer5.uniro_backend.map.dto.request.CreateBuildingRouteReqDTO;
+import com.softeer5.uniro_backend.map.dto.request.CreateRoutesReqDTO;
+import com.softeer5.uniro_backend.map.dto.response.FastestRouteResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetAllRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskResDTO;
+import com.softeer5.uniro_backend.map.dto.response.GetRiskRoutesResDTO;
+import com.softeer5.uniro_backend.map.dto.request.PostRiskReqDTO;
+import com.softeer5.uniro_backend.map.service.RouteCalculator;
+
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import com.softeer5.uniro_backend.map.service.MapService;
+
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@RequiredArgsConstructor
+@RestController
+public class MapController implements MapApi {
+
+ private final MapService mapService;
+ private final RouteCalculator routeCalculator;
+
+ @Override
+ @GetMapping("/{univId}/routes")
+ public ResponseEntity getAllRoutesAndNodes(@PathVariable("univId") Long univId){
+ GetAllRoutesResDTO allRoutes = mapService.getAllRoutes(univId);
+ return ResponseEntity.ok().body(allRoutes);
+ }
+
+ @Override
+ @GetMapping("/{univId}/routes/risks")
+ public ResponseEntity getRiskRoutes(@PathVariable("univId") Long univId) {
+ GetRiskRoutesResDTO riskRoutes = mapService.getRiskRoutes(univId);
+ return ResponseEntity.ok().body(riskRoutes);
+ }
+
+ @Override
+ @GetMapping("/{univId}/routes/{routeId}/risk")
+ public ResponseEntity getRisk(@PathVariable("univId") Long univId,
+ @PathVariable(value = "routeId") Long routeId){
+ GetRiskResDTO riskResDTO = mapService.getRisk(univId, routeId);
+ return ResponseEntity.ok().body(riskResDTO);
+ }
+
+ @Override
+ @PostMapping("/{univId}/route/risk/{routeId}")
+ public ResponseEntity updateRisk (@PathVariable("univId") Long univId,
+ @PathVariable("routeId") Long routeId,
+ @RequestBody @Valid PostRiskReqDTO postRiskReqDTO){
+ mapService.updateRisk(univId,routeId,postRiskReqDTO);
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ @PostMapping("/{univId}/route")
+ public ResponseEntity createRoute (@PathVariable("univId") Long univId,
+ @RequestBody @Valid CreateRoutesReqDTO routes){
+ mapService.createRoute(univId, routes);
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+
+ @Override
+ @GetMapping("/{univId}/routes/fastest")
+ public ResponseEntity> findFastestRoute(@PathVariable("univId") Long univId,
+ @RequestParam(value = "start-node-id") Long startNodeId,
+ @RequestParam(value = "end-node-id") Long endNodeId) {
+ List fastestRouteResDTO = mapService.findFastestRoute(univId, startNodeId, endNodeId);
+ return ResponseEntity.ok(fastestRouteResDTO);
+ }
+
+ @Override
+ @PostMapping("/{univId}/routes/building")
+ public ResponseEntity createBuildingRoute(@PathVariable("univId") Long univId,
+ @RequestBody @Valid CreateBuildingRouteReqDTO createBuildingRouteReqDTO){
+ mapService.createBuildingRoute(univId,createBuildingRouteReqDTO);
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateBuildingRouteReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateBuildingRouteReqDTO.java
new file mode 100644
index 0000000..61141c0
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateBuildingRouteReqDTO.java
@@ -0,0 +1,19 @@
+package com.softeer5.uniro_backend.map.dto.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+@Schema(name = "CreateBuildingRouteReqDTO", description = "빌딩과 연결된 길 추가 요청 DTO")
+public class CreateBuildingRouteReqDTO {
+ @Schema(description = "빌딩 노드의 id", example = "16")
+ @NotNull
+ private final Long buildingNodeId;
+
+ @Schema(description = "빌딩 노드와 연결될 노드의 id", example = "27")
+ @NotNull
+ private final Long nodeId;
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRouteReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRouteReqDTO.java
similarity index 89%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRouteReqDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRouteReqDTO.java
index ec4a021..b8f7233 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRouteReqDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRouteReqDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.dto.request;
+package com.softeer5.uniro_backend.map.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRoutesReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRoutesReqDTO.java
similarity index 77%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRoutesReqDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRoutesReqDTO.java
index 9ca0ba7..ac95924 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/CreateRoutesReqDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/CreateRoutesReqDTO.java
@@ -1,8 +1,10 @@
-package com.softeer5.uniro_backend.route.dto.request;
+package com.softeer5.uniro_backend.map.dto.request;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -12,11 +14,13 @@
public class CreateRoutesReqDTO {
@Schema(description = "시작 노드 id", example = "3")
+ @NotNull
private final Long startNodeId;
@Schema(description = "종료 노드 id", example = "4")
private final Long endNodeId;
@Schema(description = "노드 좌표", example = "")
+ @NotEmpty
private final List coordinates;
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/PostRiskReqDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/PostRiskReqDTO.java
similarity index 57%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/PostRiskReqDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/PostRiskReqDTO.java
index 3c65ab3..3205f63 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/request/PostRiskReqDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/request/PostRiskReqDTO.java
@@ -1,12 +1,12 @@
-package com.softeer5.uniro_backend.route.dto.request;
+package com.softeer5.uniro_backend.map.dto.request;
-import com.softeer5.uniro_backend.route.entity.CautionType;
-import com.softeer5.uniro_backend.route.entity.DangerType;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import lombok.Setter;
import java.util.List;
@@ -16,8 +16,10 @@
public class PostRiskReqDTO {
@Schema(description = "주의 요소 목록", example = "[\"SLOPE\", \"CURB\"]")
- private List cautionTypes;
+ @NotNull
+ private List cautionFactors;
@Schema(description = "위험 요소 목록", example = "[\"CURB\", \"STAIRS\"]")
- private List dangerTypes;
+ @NotNull
+ private List dangerFactors;
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/BuildingRouteResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/BuildingRouteResDTO.java
new file mode 100644
index 0000000..446d04f
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/BuildingRouteResDTO.java
@@ -0,0 +1,27 @@
+package com.softeer5.uniro_backend.map.dto.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@Schema(name = "BuildingRouteResDTO", description = "빌딩 루트 정보 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class BuildingRouteResDTO {
+
+ @Schema(description = "코어 노드 1", example = "32")
+ private final Long coreNode1Id;
+
+ @Schema(description = "코어 노드 2", example = "13")
+ private final Long coreNode2Id;
+
+ @Schema(description = "간선 정보", example = "")
+ private final List routes;
+
+ public static BuildingRouteResDTO of(Long startNode, Long endNode, List routes){
+ return new BuildingRouteResDTO(startNode, endNode, routes);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/CoreRouteResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/CoreRouteResDTO.java
similarity index 89%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/CoreRouteResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/CoreRouteResDTO.java
index 452f909..e7d06a6 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/CoreRouteResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/CoreRouteResDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
@@ -16,7 +16,7 @@ public class CoreRouteResDTO {
private final Long coreNode1Id;
@Schema(description = "코어 노드 2", example = "13")
- private final Long cordNode2Id;
+ private final Long coreNode2Id;
@Schema(description = "간선 정보", example = "")
private final List routes;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/FastestRouteResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/FastestRouteResDTO.java
new file mode 100644
index 0000000..ee909b3
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/FastestRouteResDTO.java
@@ -0,0 +1,49 @@
+package com.softeer5.uniro_backend.map.dto.response;
+
+import com.softeer5.uniro_backend.map.entity.RoadExclusionPolicy;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@Schema(name = "FastestRouteResDTO", description = "빠른 경로 조회 DTO")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class FastestRouteResDTO {
+ @Schema(description = "길찾기 타입 (PEDES-도보, WHEEL_FAST-휠체어빠른, WHEEL_SAFE-휠체어안전)", example = "PEDES")
+ private final RoadExclusionPolicy routeType;
+ @Schema(description = "길 찾기 결과에 위험요소가 포함되어있는지 여부", example = "true")
+ private final boolean hasCaution;
+ @Schema(description = "총 이동거리", example = "150.3421234")
+ private final double totalDistance;
+ @Schema(description = "총 걸리는 시간(초) - 도보", example = "1050.32198432")
+ private final Double pedestrianTotalCost;
+ @Schema(description = "총 걸리는 시간(초) - 수동휠체어", example = "2253.51234432")
+ private final Double manualTotalCost;
+ @Schema(description = "총 걸리는 시간(초) - 전동휠체어", example = "935.3125632")
+ private final Double electricTotalCost;
+ @Schema(description = "길 찾기 결과에 포함된 모든 길", example = "")
+ private final List routes;
+ @Schema(description = "상세안내 관련 정보", example = "")
+ private final List routeDetails;
+
+ public static FastestRouteResDTO of(RoadExclusionPolicy routeType,
+ boolean hasCaution,
+ double totalDistance,
+ Double pedestrianTotalCost,
+ Double manualTotalCost,
+ Double electricTotalCost,
+ List routes,
+ List routeDetails) {
+ return new FastestRouteResDTO(routeType,
+ hasCaution,
+ totalDistance,
+ pedestrianTotalCost,
+ manualTotalCost,
+ electricTotalCost,
+ routes,
+ routeDetails);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetAllRoutesResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetAllRoutesResDTO.java
similarity index 62%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetAllRoutesResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetAllRoutesResDTO.java
index 9b0de0f..a4c3a99 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetAllRoutesResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetAllRoutesResDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
@@ -7,8 +7,6 @@
import java.util.List;
-import com.softeer5.uniro_backend.route.dto.response.CoreRouteResDTO;
-
@Getter
@Schema(name = "GetAllRoutesResDTO", description = "모든 노드,루트 조회 DTO")
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -18,8 +16,11 @@ public class GetAllRoutesResDTO {
private final List nodeInfos;
@Schema(description = "루트 정보 (id, startNodeId, endNodeId)", example = "")
private final List coreRoutes;
+ @Schema(description = "빌딩 루트 정보 (id, startNodeId, endNodeId)", example = "")
+ private final List buildingRoutes;
- public static GetAllRoutesResDTO of(List nodeInfos, List coreRoutes){
- return new GetAllRoutesResDTO(nodeInfos, coreRoutes);
+ public static GetAllRoutesResDTO of(List nodeInfos, List coreRoutes,
+ List buildingRoutes){
+ return new GetAllRoutesResDTO(nodeInfos, coreRoutes, buildingRoutes);
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetCautionResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetCautionResDTO.java
similarity index 71%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetCautionResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetCautionResDTO.java
index 8d44017..1d54807 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetCautionResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetCautionResDTO.java
@@ -1,10 +1,9 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import java.util.List;
import java.util.Map;
-import com.softeer5.uniro_backend.node.entity.Node;
-import com.softeer5.uniro_backend.route.entity.CautionType;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
@@ -27,9 +26,9 @@ public class GetCautionResDTO {
private final Long routeId;
@Schema(description = "위험 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
- private final List cautionTypes;
+ private final List cautionFactors;
- public static GetCautionResDTO of(Map node1, Map node2, Long routeId, List cautionTypes){
- return new GetCautionResDTO(node1, node2, routeId, cautionTypes);
+ public static GetCautionResDTO of(Map node1, Map node2, Long routeId, List cautionFactors){
+ return new GetCautionResDTO(node1, node2, routeId, cautionFactors);
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetDangerResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetDangerResDTO.java
similarity index 68%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetDangerResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetDangerResDTO.java
index caecb7f..3972e81 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetDangerResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetDangerResDTO.java
@@ -1,10 +1,9 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import java.util.List;
import java.util.Map;
-import com.softeer5.uniro_backend.node.entity.Node;
-import com.softeer5.uniro_backend.route.entity.DangerType;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
@@ -26,13 +25,13 @@ public class GetDangerResDTO {
private final Long routeId;
@Schema(description = "위험 요소 타입 리스트", example = "[\"CURB\", \"STAIRS\"]")
- private final List dangerTypes;
+ private final List dangerFactors;
- public static GetDangerResDTO of(Map node1, Map node2, Long routeId, List dangerTypes){
- return new GetDangerResDTO(node1, node2, routeId, dangerTypes);
+ public static GetDangerResDTO of(Map node1, Map node2, Long routeId, List dangerFactors){
+ return new GetDangerResDTO(node1, node2, routeId, dangerFactors);
}
- public List getDangerTypes() {
- return dangerTypes;
+ public List getDangerFactors() {
+ return dangerFactors;
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskResDTO.java
similarity index 71%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskResDTO.java
index fa980e8..26855c5 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskResDTO.java
@@ -1,8 +1,8 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
-import com.softeer5.uniro_backend.route.entity.CautionType;
-import com.softeer5.uniro_backend.route.entity.DangerType;
-import com.softeer5.uniro_backend.route.entity.Route;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
+import com.softeer5.uniro_backend.map.entity.Route;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Getter;
@@ -17,9 +17,9 @@ public class GetRiskResDTO {
@Schema(description = "route ID", example = "3")
private final Long routeId;
@Schema(description = "위험 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
- private final List cautionTypes;
+ private final List cautionFactors;
@Schema(description = "위험 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
- private final List dangerTypes;
+ private final List dangerFactors;
public static GetRiskResDTO of(Route route) {
return new GetRiskResDTO(route.getId(),route.getCautionFactorsByList(), route.getDangerFactorsByList());
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskRoutesResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskRoutesResDTO.java
similarity index 93%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskRoutesResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskRoutesResDTO.java
index ac096ca..f0b79ca 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/GetRiskRoutesResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/GetRiskRoutesResDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import java.util.List;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/NodeInfoResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/NodeInfoResDTO.java
similarity index 89%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/NodeInfoResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/NodeInfoResDTO.java
index 9fa9641..707b479 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/NodeInfoResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/NodeInfoResDTO.java
@@ -1,11 +1,9 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
-import java.util.Map;
-
@Getter
@Schema(name = "NodeInfoDTO", description = "노드 정보 DTO")
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteCoordinatesInfoResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteCoordinatesInfoResDTO.java
similarity index 92%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteCoordinatesInfoResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteCoordinatesInfoResDTO.java
index 2771147..5875648 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteCoordinatesInfoResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteCoordinatesInfoResDTO.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteDetailResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteDetailResDTO.java
similarity index 62%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteDetailResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteDetailResDTO.java
index 540274e..f100af1 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteDetailResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteDetailResDTO.java
@@ -1,11 +1,13 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
-import com.softeer5.uniro_backend.route.entity.DirectionType;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DirectionType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import java.util.List;
import java.util.Map;
@Getter
@@ -18,8 +20,12 @@ public class RouteDetailResDTO {
private final DirectionType directionType;
@Schema(description = "상세 경로의 좌표", example = "{\"lng\": 127.123456, \"lat\": 37.123456}")
private final Map coordinates;
+ @Schema(description = "주의 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
+ private final List cautionFactors;
- public static RouteDetailResDTO of(double dist, DirectionType directionType, Map coordinates) {
- return new RouteDetailResDTO(dist, directionType, coordinates);
+ public static RouteDetailResDTO of(double dist, DirectionType directionType,
+ Map coordinates,
+ List cautionFactors) {
+ return new RouteDetailResDTO(dist, directionType, coordinates, cautionFactors);
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteInfoResDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteInfoResDTO.java
similarity index 65%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteInfoResDTO.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteInfoResDTO.java
index 6a38995..3a18597 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/dto/response/RouteInfoResDTO.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/dto/response/RouteInfoResDTO.java
@@ -1,15 +1,13 @@
-package com.softeer5.uniro_backend.route.dto.response;
+package com.softeer5.uniro_backend.map.dto.response;
-import com.softeer5.uniro_backend.node.entity.Node;
-import com.softeer5.uniro_backend.route.entity.CautionType;
-import com.softeer5.uniro_backend.route.entity.Route;
+import com.softeer5.uniro_backend.map.entity.Node;
+import com.softeer5.uniro_backend.map.entity.Route;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Map;
-import java.util.Set;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -21,13 +19,10 @@ public class RouteInfoResDTO {
private final Map node1;
@Schema(description = "노드 2의 좌표", example = "{\"lng\": 127.123456, \"lat\": 37.123456}")
private final Map node2;
- @Schema(description = "위험 요소 타입 리스트", example = "[\"SLOPE\", \"STAIRS\"]")
- private final Set cautionFactors;
public static RouteInfoResDTO of(Route route, Node node1, Node node2) {
return new RouteInfoResDTO(route.getId(),
node1.getXY(),
- node2.getXY(),
- route.getCautionFactors());
+ node2.getXY());
}
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Node.java
similarity index 78%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Node.java
index f07c6ea..970f4c8 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Node.java
@@ -1,13 +1,17 @@
-package com.softeer5.uniro_backend.node.entity;
+package com.softeer5.uniro_backend.map.entity;
import static com.softeer5.uniro_backend.common.constant.UniroConst.*;
+import java.time.LocalDateTime;
import java.util.Map;
+import jakarta.persistence.EntityListeners;
import lombok.*;
import org.hibernate.envers.Audited;
import org.locationtech.jts.geom.Point;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -18,11 +22,10 @@
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
-@Builder
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
@Audited
+@EntityListeners(AuditingEntityListener.class)
public class Node {
@Id
@@ -30,7 +33,6 @@ public class Node {
private Long id;
@NotNull
- @Column(columnDefinition = "POINT SRID 4326")
private Point coordinates;
private double height;
@@ -42,6 +44,9 @@ public class Node {
@NotNull
private Long univId;
+ @CreatedDate
+ private LocalDateTime createdAt;
+
public Map getXY(){
return Map.of("lat", coordinates.getY(), "lng", coordinates.getX());
}
@@ -65,6 +70,9 @@ public void setCoordinates(Point coordinates) {
public void setCore(boolean isCore){
this.isCore = isCore;
}
+ public void updateFromRevision(boolean isCore){
+ this.isCore = isCore;
+ }
public String getNodeKey() {
return coordinates.getX() + NODE_KEY_DELIMITER + coordinates.getY();
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/RoadExclusionPolicy.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/RoadExclusionPolicy.java
new file mode 100644
index 0000000..e241799
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/RoadExclusionPolicy.java
@@ -0,0 +1,29 @@
+package com.softeer5.uniro_backend.map.entity;
+
+import static com.softeer5.uniro_backend.common.constant.UniroConst.PEDESTRIAN_SECONDS_PER_MITER;
+
+public enum RoadExclusionPolicy {
+
+ PEDES,
+ WHEEL_FAST,
+ WHEEL_SAFE;
+
+ public static boolean isAvailableRoute(RoadExclusionPolicy policy, Route route) {
+ boolean isCaution = !route.getCautionFactors().isEmpty();
+ boolean isDanger = !route.getDangerFactors().isEmpty();
+ return switch(policy){
+ case PEDES -> true;
+ case WHEEL_FAST -> !isDanger;
+ case WHEEL_SAFE -> !isCaution && !isDanger;
+ default -> false;
+ };
+ }
+
+ public static Double calculateCost(RoadExclusionPolicy policy, double type, double distance){
+ boolean isWheel = (type!=PEDESTRIAN_SECONDS_PER_MITER);
+ double totalCost = type * distance;
+ if(isWheel && policy == PEDES) return null;
+ if(!isWheel && policy != PEDES) return null;
+ return totalCost;
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Route.java
similarity index 52%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Route.java
index 56ab4e3..2ee456d 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/entity/Route.java
@@ -1,21 +1,26 @@
-package com.softeer5.uniro_backend.route.entity;
+package com.softeer5.uniro_backend.map.entity;
import static jakarta.persistence.FetchType.*;
+import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import com.softeer5.uniro_backend.resolver.CautionListConverter;
-import com.softeer5.uniro_backend.resolver.DangerListConverter;
-import com.softeer5.uniro_backend.node.entity.Node;
+import com.softeer5.uniro_backend.common.resolver.CautionListConverter;
+import com.softeer5.uniro_backend.common.resolver.DangerListConverter;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DangerFactor;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.locationtech.jts.geom.LineString;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -31,15 +36,15 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Audited
+@EntityListeners(AuditingEntityListener.class)
public class Route {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- private double cost;
+ private double distance;
- @Column(columnDefinition = "LINESTRING SRID 4326") // WGS84 좌표계
private LineString path;
@ManyToOne(fetch = LAZY)
@@ -57,46 +62,61 @@ public class Route {
@Column(name = "univ_id")
private Long univId;
- @Column(name = "core_route_id")
- private Long coreRouteId;
-
@Convert(converter = CautionListConverter.class)
@Column(name = "caution_factors")
@NotNull
- private Set cautionFactors = new HashSet<>();
+ private Set cautionFactors = new HashSet<>();
@Convert(converter = DangerListConverter.class)
@Column(name = "danger_factors")
@NotNull
- private Set dangerFactors = new HashSet<>();
+ private Set dangerFactors = new HashSet<>();
+
+ @CreatedDate
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
- public List getCautionFactorsByList(){
+ public List getCautionFactorsByList(){
return cautionFactors.stream().toList();
}
- public List getDangerFactorsByList(){
+ public List getDangerFactorsByList(){
return dangerFactors.stream().toList();
}
- public void setCautionFactors(List cautionFactors) {
+ public void setCautionFactorsByList(List cautionFactors) {
this.cautionFactors.clear();
this.cautionFactors.addAll(cautionFactors);
}
- public void setDangerFactors(List dangerFactors) {
+ public void setDangerFactorsByList(List dangerFactors) {
this.dangerFactors.clear();
this.dangerFactors.addAll(dangerFactors);
}
+ public void updateFromRevision(Route revRoute){
+ this.cautionFactors = revRoute.getCautionFactors();
+ this.dangerFactors = revRoute.getDangerFactors();
+ this.distance = revRoute.distance;
+ }
+
+ public boolean isEqualRoute(Route route) {
+ if(!route.getId().equals(this.getId())) return false;
+ if(route.getDistance() != this.getDistance())return false;
+ if(!route.getCautionFactors().equals(this.getCautionFactors())) return false;
+ if(!route.getDangerFactors().equals(this.getDangerFactors())) return false;
+
+ return true;
+ }
+
@Builder
- private Route(double cost, LineString path, Node node1, Node node2, Long univId, Long coreRouteId,
- Set cautionFactors, Set dangerFactors) {
- this.cost = cost;
+ private Route(double distance, LineString path, Node node1, Node node2, Long univId,
+ Set cautionFactors, Set dangerFactors) {
+ this.distance = distance;
this.path = path;
this.node1 = node1;
this.node2 = node2;
this.univId = univId;
- this.coreRouteId = coreRouteId;
this.cautionFactors = cautionFactors;
this.dangerFactors = dangerFactors;
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/CautionFactor.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/CautionFactor.java
new file mode 100644
index 0000000..f7963f3
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/CautionFactor.java
@@ -0,0 +1,8 @@
+package com.softeer5.uniro_backend.map.enums;
+
+public enum CautionFactor {
+ CURB, // 턱
+ CRACK, // 균열
+ SLOPE, // 경사
+ ETC // 기타
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DangerType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DangerFactor.java
similarity index 50%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DangerType.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DangerFactor.java
index 481bcc7..d23c8d7 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DangerType.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DangerFactor.java
@@ -1,6 +1,6 @@
-package com.softeer5.uniro_backend.route.entity;
+package com.softeer5.uniro_backend.map.enums;
-public enum DangerType {
+public enum DangerFactor {
CURB, // 턱
STAIRS, // 계단
SLOPE, // 경사
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DirectionType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DirectionType.java
similarity index 81%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DirectionType.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DirectionType.java
index d561b41..c4b90f6 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/DirectionType.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/enums/DirectionType.java
@@ -1,4 +1,4 @@
-package com.softeer5.uniro_backend.route.entity;
+package com.softeer5.uniro_backend.map.enums;
public enum DirectionType {
STRAIGHT, // 직진
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/NodeRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/NodeRepository.java
new file mode 100644
index 0000000..2172d6e
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/NodeRepository.java
@@ -0,0 +1,18 @@
+package com.softeer5.uniro_backend.map.repository;
+
+import com.softeer5.uniro_backend.map.entity.Node;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+
+public interface NodeRepository extends JpaRepository {
+
+ @Modifying(clearAutomatically = true)
+ @Transactional
+ @Query("DELETE FROM Node n WHERE n.univId = :univId AND n.createdAt > :versionTimeStamp")
+ void deleteAllByCreatedAt(Long univId, LocalDateTime versionTimeStamp);
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/repository/RouteRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/RouteRepository.java
similarity index 72%
rename from uniro_backend/src/main/java/com/softeer5/uniro_backend/route/repository/RouteRepository.java
rename to uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/RouteRepository.java
index 8925506..a979e1b 100644
--- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/repository/RouteRepository.java
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/repository/RouteRepository.java
@@ -1,17 +1,18 @@
-package com.softeer5.uniro_backend.route.repository;
+package com.softeer5.uniro_backend.map.repository;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
-import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
-import com.softeer5.uniro_backend.route.entity.Route;
+import com.softeer5.uniro_backend.map.entity.Route;
-@Repository
public interface RouteRepository extends JpaRepository {
@EntityGraph(attributePaths = {"node1", "node2"})
@@ -58,4 +59,20 @@ Optional findRouteByLineStringAndUnivId(@Param("univId") Long univId,
Optional findByIdAndUnivId (Long id, Long univId);
+ @Modifying(clearAutomatically = true)
+ @Transactional
+ @Query("DELETE FROM Route r WHERE r.univId =:univId AND r.createdAt > :versionTimeStamp")
+ void deleteAllByCreatedAt(@Param("univId") Long univId, @Param("versionTimeStamp") LocalDateTime versionTimeStamp);
+
+ @Query(value = """
+ SELECT COUNT(*) FROM Route r
+ WHERE r.univId = :univId
+ AND (
+ r.node1.id = :nodeId
+ OR
+ r.node2.id = :nodeId
+ )
+ """)
+ int countByUnivIdAndNodeId(Long univId, Long nodeId);
+
}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java
new file mode 100644
index 0000000..354a589
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java
@@ -0,0 +1,166 @@
+package com.softeer5.uniro_backend.map.service;
+
+import static com.softeer5.uniro_backend.common.constant.UniroConst.BUILDING_ROUTE_DISTANCE;
+import static com.softeer5.uniro_backend.common.constant.UniroConst.CORE_NODE_CONDITION;
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+import static com.softeer5.uniro_backend.common.utils.GeoUtils.getInstance;
+
+import java.util.*;
+
+import com.softeer5.uniro_backend.admin.annotation.RevisionOperation;
+import com.softeer5.uniro_backend.admin.enums.RevisionOperationType;
+import com.softeer5.uniro_backend.building.entity.Building;
+import com.softeer5.uniro_backend.common.error.ErrorCode;
+import com.softeer5.uniro_backend.common.exception.custom.BuildingException;
+import com.softeer5.uniro_backend.common.exception.custom.NodeException;
+import com.softeer5.uniro_backend.common.exception.custom.RouteCalculationException;
+import com.softeer5.uniro_backend.common.exception.custom.RouteException;
+import com.softeer5.uniro_backend.external.MapClient;
+import com.softeer5.uniro_backend.map.dto.request.CreateRoutesReqDTO;
+import com.softeer5.uniro_backend.map.entity.Node;
+
+import com.softeer5.uniro_backend.building.repository.BuildingRepository;
+import com.softeer5.uniro_backend.map.repository.NodeRepository;
+import com.softeer5.uniro_backend.map.dto.request.CreateBuildingRouteReqDTO;
+import com.softeer5.uniro_backend.map.dto.response.*;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.softeer5.uniro_backend.map.dto.request.PostRiskReqDTO;
+import com.softeer5.uniro_backend.map.entity.Route;
+import com.softeer5.uniro_backend.map.repository.RouteRepository;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class MapService {
+ private final RouteRepository routeRepository;
+ private final NodeRepository nodeRepository;
+ private final BuildingRepository buildingRepository;
+
+ private final RouteCalculator routeCalculator;
+
+ private final MapClient mapClient;
+
+ public GetAllRoutesResDTO getAllRoutes(Long univId) {
+ List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId);
+
+ // 맵이 존재하지 않을 경우 예외
+ if(routes.isEmpty()) {
+ throw new RouteException("Route Not Found", ROUTE_NOT_FOUND);
+ }
+
+ return routeCalculator.assembleRoutes(routes);
+ }
+
+ public List findFastestRoute(Long univId, Long startNodeId, Long endNodeId){
+
+ if(startNodeId.equals(endNodeId)){
+ throw new RouteCalculationException("Start and end nodes cannot be the same", SAME_START_AND_END_POINT);
+ }
+
+ List buildings = buildingRepository.findAllByNodeIdIn(List.of(startNodeId, endNodeId));
+
+ if(buildings.size() != 2
+ || buildings.get(0).getNodeId().equals(buildings.get(1).getNodeId())
+ || buildings.stream().anyMatch(building -> !Objects.equals(building.getUnivId(), univId))){
+
+ throw new BuildingException("Building not found", BUILDING_NOT_FOUND);
+ }
+
+ List routesWithNode = routeRepository.findAllRouteByUnivIdWithNodes(univId);
+
+ return routeCalculator.calculateFastestRoute(startNodeId, endNodeId, routesWithNode);
+ }
+
+ public GetRiskRoutesResDTO getRiskRoutes(Long univId) {
+ List riskRoutes = routeRepository.findRiskRouteByUnivId(univId);
+ return routeCalculator.mapRisks(riskRoutes);
+ }
+
+
+
+ public GetRiskResDTO getRisk(Long univId, Long routeId) {
+ Route route = routeRepository.findById(routeId)
+ .orElseThrow(() -> new RouteException("Route not found", ROUTE_NOT_FOUND));
+ return GetRiskResDTO.of(route);
+ }
+
+ @RevisionOperation(RevisionOperationType.UPDATE_RISK)
+ @Transactional
+ public void updateRisk(Long univId, Long routeId, PostRiskReqDTO postRiskReqDTO) {
+ Route route = routeRepository.findByIdAndUnivId(routeId, univId)
+ .orElseThrow(() -> new RouteException("Route not Found", ROUTE_NOT_FOUND));
+
+ if(!postRiskReqDTO.getCautionFactors().isEmpty() && !postRiskReqDTO.getDangerFactors().isEmpty()){
+ throw new RouteException("DangerFactors and CautionFactors can't exist simultaneously.",
+ ErrorCode.CAUTION_DANGER_CANT_EXIST_SIMULTANEOUSLY);
+ }
+
+ route.setCautionFactorsByList(postRiskReqDTO.getCautionFactors());
+ route.setDangerFactorsByList(postRiskReqDTO.getDangerFactors());
+ }
+
+ @RevisionOperation(RevisionOperationType.CREATE_BUILDING_ROUTE)
+ @Transactional
+ public void createBuildingRoute(Long univId, CreateBuildingRouteReqDTO createBuildingRouteReqDTO) {
+ GeometryFactory geometryFactory = getInstance();
+ Long buildingNodeId = createBuildingRouteReqDTO.getBuildingNodeId();
+ Long nodeId = createBuildingRouteReqDTO.getNodeId();
+
+ if(!buildingRepository.existsByNodeIdAndUnivId(buildingNodeId, univId)) {
+ throw new BuildingException("Not Building Node", NOT_BUILDING_NODE);
+ }
+
+ Node buildingNode = nodeRepository.findById(buildingNodeId)
+ .orElseThrow(()-> new NodeException("Node not found", NODE_NOT_FOUND));
+
+ Node connectedNode = nodeRepository.findById(nodeId)
+ .orElseThrow(()-> new NodeException("Node not found", NODE_NOT_FOUND));
+
+ int connectedRouteCount = routeRepository.countByUnivIdAndNodeId(univId, nodeId);
+ if(connectedRouteCount>= CORE_NODE_CONDITION - 1){
+ connectedNode.setCore(true);
+ }
+
+ int buildingRouteCount = routeRepository.countByUnivIdAndNodeId(univId, buildingNodeId);
+ if(buildingRouteCount>=CORE_NODE_CONDITION-1){
+ buildingNode.setCore(true);
+ }
+
+ Route route = Route.builder()
+ .distance(BUILDING_ROUTE_DISTANCE)
+ .path(geometryFactory.createLineString(
+ new Coordinate[] {buildingNode.getCoordinates().getCoordinate(),
+ connectedNode.getCoordinates().getCoordinate()}))
+ .node1(buildingNode)
+ .node2(connectedNode)
+ .cautionFactors(Collections.EMPTY_SET)
+ .dangerFactors(Collections.EMPTY_SET)
+ .univId(univId).build();
+
+ routeRepository.save(route);
+ }
+
+ @Transactional
+ @RevisionOperation(RevisionOperationType.CREATE_ROUTE)
+ public void createRoute(Long univId, CreateRoutesReqDTO requests){
+
+ List savedRoutes = routeRepository.findAllRouteByUnivIdWithNodes(univId);
+
+ List nodesForSave = routeCalculator.createValidRouteNodes(univId, requests.getStartNodeId(),
+ requests.getEndNodeId(),
+ requests.getCoordinates(), savedRoutes);
+
+ mapClient.fetchHeights(nodesForSave);
+
+ List routes = routeCalculator.createLinkedRouteAndSave(univId, nodesForSave);
+
+ nodeRepository.saveAll(nodesForSave);
+ routeRepository.saveAll(routes);
+ }
+}
diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/RouteCalculator.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/RouteCalculator.java
new file mode 100644
index 0000000..6e0e5d7
--- /dev/null
+++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/RouteCalculator.java
@@ -0,0 +1,748 @@
+package com.softeer5.uniro_backend.map.service;
+
+import static com.softeer5.uniro_backend.common.constant.UniroConst.*;
+import static com.softeer5.uniro_backend.common.error.ErrorCode.*;
+import static com.softeer5.uniro_backend.map.entity.RoadExclusionPolicy.calculateCost;
+import static com.softeer5.uniro_backend.map.entity.RoadExclusionPolicy.isAvailableRoute;
+
+import com.softeer5.uniro_backend.common.error.ErrorCode;
+import com.softeer5.uniro_backend.common.exception.custom.NodeException;
+import com.softeer5.uniro_backend.common.exception.custom.RouteCalculationException;
+import com.softeer5.uniro_backend.common.exception.custom.RouteException;
+import com.softeer5.uniro_backend.common.utils.GeoUtils;
+import com.softeer5.uniro_backend.map.dto.response.*;
+import com.softeer5.uniro_backend.map.entity.*;
+import com.softeer5.uniro_backend.map.dto.request.CreateRouteReqDTO;
+import com.softeer5.uniro_backend.map.enums.CautionFactor;
+import com.softeer5.uniro_backend.map.enums.DirectionType;
+import com.softeer5.uniro_backend.map.entity.Route;
+import lombok.AllArgsConstructor;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.index.strtree.STRtree;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+@Component
+public class RouteCalculator {
+ private final GeometryFactory geometryFactory = GeoUtils.getInstance();
+ private final List policies = List.of(
+ RoadExclusionPolicy.PEDES,
+ RoadExclusionPolicy.WHEEL_FAST,
+ RoadExclusionPolicy.WHEEL_SAFE
+ );
+
+ @AllArgsConstructor
+ private class CostToNextNode implements Comparable {
+ private double cost;
+ private Node nextNode;
+
+ @Override
+ public int compareTo(CostToNextNode o) {
+ return Double.compare(this.cost, o.cost);
+ }
+ }
+
+ public GetAllRoutesResDTO assembleRoutes(List routes) {
+ Map> adjMap = new HashMap<>();
+ Map nodeMap = new HashMap<>();
+ List buildingRoutes = new ArrayList<>();
+ Node startNode = null;
+
+ for (Route route : routes) {
+ nodeMap.put(route.getNode1().getId(), route.getNode1());
+ nodeMap.put(route.getNode2().getId(), route.getNode2());
+
+ if (isBuildingRoute(route)) {
+ List routeCoordinates = new ArrayList<>();
+ routeCoordinates.add(RouteCoordinatesInfoResDTO.of(route.getId(), route.getNode1().getId(), route.getNode2().getId()));
+ buildingRoutes.add(BuildingRouteResDTO.of(route.getNode1().getId(), route.getNode2().getId(), routeCoordinates));
+ continue;
+ }
+
+ adjMap.computeIfAbsent(route.getNode1().getId(), k -> new ArrayList<>()).add(route);
+ adjMap.computeIfAbsent(route.getNode2().getId(), k -> new ArrayList<>()).add(route);
+
+ if (startNode == null) {
+ if (route.getNode1().isCore()) startNode = route.getNode1();
+ else if (route.getNode2().isCore()) startNode = route.getNode2();
+ }
+ }
+
+ List nodeInfos = nodeMap.entrySet().stream()
+ .map(entry -> NodeInfoResDTO.of(entry.getKey(), entry.getValue().getX(), entry.getValue().getY()))
+ .toList();
+
+ startNode = determineStartNode(startNode, adjMap, nodeMap, routes);
+
+ return GetAllRoutesResDTO.of(nodeInfos, getCoreRoutes(adjMap, List.of(startNode)), buildingRoutes);
+ }
+
+ private Node determineStartNode(Node startNode, Map> adjMap, Map nodeMap, List routes) {
+ if (startNode != null) return startNode;
+
+ List endNodes = adjMap.entrySet().stream()
+ .filter(entry -> entry.getValue().size() == 1)
+ .map(Map.Entry::getKey)
+ .toList();
+
+ if (endNodes.size() == IS_SINGLE_ROUTE) {
+ return nodeMap.get(endNodes.get(0));
+ } else if (endNodes.isEmpty()) {
+ return routes.get(0).getNode1();
+ } else {
+ throw new RouteException("Invalid Map", ErrorCode.INVALID_MAP);
+ }
+ }
+
+
+ public List calculateFastestRoute(Long startNodeId, Long endNodeId, List routes){
+ List result = new ArrayList<>();
+
+ for (RoadExclusionPolicy policy : policies) {
+ //인접 리스트
+ Map> adjMap = new HashMap<>();
+ Map nodeMap = new HashMap<>();
+
+ for (Route route : routes) {
+ if (!isAvailableRoute(policy, route)) continue;
+ addRouteToGraph(route, adjMap, nodeMap);
+ }
+
+ Node startNode = nodeMap.get(startNodeId);
+ Node endNode = nodeMap.get(endNodeId);
+
+ if(startNode==null || endNode==null){
+ continue;
+ }
+
+ //길찾기 알고리즘 수행
+ Map prevRoute = findFastestRoute(startNode, endNode, adjMap);
+
+ //길찾기 결과가 null인 경우 continue
+ if(prevRoute == null) continue;
+
+ //길찾기 경로 결과 정리
+ List shortestRoutes = reorderRoute(startNode, endNode, prevRoute);
+ Route startRoute = shortestRoutes.get(0);
+ Route endRoute = shortestRoutes.get(shortestRoutes.size() - 1);
+
+ if (isBuildingRoute(startRoute)) {
+ if (startRoute.getId().equals(endRoute.getId())) {
+ //출발점과 도착점이 같은 경우
+ continue;
+ }
+ startNode = startNode.getId().equals(startRoute.getNode1().getId()) ? startRoute.getNode2() : startRoute.getNode1();
+ shortestRoutes.remove(0);
+ }
+ //만약 종료 route가 건물과 이어진 노드라면 해당 route는 결과에서 제외
+ if (isBuildingRoute(endRoute)) {
+ endNode = endNode.getId().equals(endRoute.getNode1().getId()) ? endRoute.getNode2() : endRoute.getNode1();
+ shortestRoutes.remove(shortestRoutes.size() - 1);
+ }
+
+ boolean hasCaution = false;
+ double totalDistance = 0.0;
+
+ // 결과를 DTO로 정리
+ List routeInfoDTOS = new ArrayList<>();
+ Node currentNode = startNode;
+ // 외부 변수를 수정해야하기 때문에 for-loop문 사용
+ for (Route route : shortestRoutes) {
+ totalDistance += route.getDistance();
+ if (!route.getCautionFactors().isEmpty()) {
+ hasCaution = true;
+ }
+
+ Node firstNode = route.getNode1();
+ Node secondNode = route.getNode2();
+ if (currentNode.getId().equals(secondNode.getId())) {
+ Node temp = firstNode;
+ firstNode = secondNode;
+ secondNode = temp;
+ }
+ currentNode = secondNode;
+
+ routeInfoDTOS.add(RouteInfoResDTO.of(route, firstNode, secondNode));
+ }
+
+ //처음과 마지막을 제외한 구간에서 빌딩노드를 거쳐왔다면, 이는 유효한 길이 없는 것이므로 예외처리
+ if (totalDistance > BUILDING_ROUTE_DISTANCE - 1) continue;
+
+ List details = getRouteDetail(startNode, endNode, shortestRoutes);
+
+ result.add(FastestRouteResDTO.of(policy, hasCaution, totalDistance,
+ calculateCost(policy, PEDESTRIAN_SECONDS_PER_MITER, totalDistance),
+ calculateCost(policy, MANUAL_WHEELCHAIR_SECONDS_PER_MITER,totalDistance),
+ calculateCost(policy, ELECTRIC_WHEELCHAIR_SECONDS_PER_MITER,totalDistance),
+ routeInfoDTOS, details));
+ }
+
+ if(result.isEmpty()) {
+ throw new RouteCalculationException("Unable to find a valid route", ErrorCode.FASTEST_ROUTE_NOT_FOUND);
+ }
+
+ return result;
+ }
+
+ private void addRouteToGraph(Route route, Map> adjMap, Map nodeMap) {
+ adjMap.computeIfAbsent(route.getNode1().getId(), k -> new ArrayList<>()).add(route);
+ adjMap.computeIfAbsent(route.getNode2().getId(), k -> new ArrayList<>()).add(route);
+ nodeMap.putIfAbsent(route.getNode1().getId(), route.getNode1());
+ nodeMap.putIfAbsent(route.getNode2().getId(), route.getNode2());
+ }
+
+ private Map findFastestRoute(Node startNode, Node endNode, Map> adjMap){
+ //key : nodeId, value : 최단거리 중 해당 노드를 향한 route
+ Map prevRoute = new HashMap<>();
+ // startNode로부터 각 노드까지 걸리는 cost를 저장하는 자료구조
+ Map costMap = new HashMap<>();
+ PriorityQueue pq = new PriorityQueue<>();
+ pq.add(new CostToNextNode(0.0, startNode));
+ costMap.put(startNode.getId(), 0.0);
+
+ // 길찾기 알고리즘
+ while(!pq.isEmpty()){
+ CostToNextNode costToNextNode = pq.poll();
+ double currentDistance = costToNextNode.cost;
+ Node currentNode = costToNextNode.nextNode;
+ if (currentNode.getId().equals(endNode.getId())) break;
+ if(costMap.containsKey(currentNode.getId())
+ && currentDistance > costMap.get(currentNode.getId())) continue;
+
+ for(Route route : adjMap.getOrDefault(currentNode.getId(), Collections.emptyList())){
+ double newDistance = currentDistance + route.getDistance();
+ Node nextNode = route.getNode1().getId().equals(currentNode.getId())?route.getNode2():route.getNode1();
+ if(!costMap.containsKey(nextNode.getId()) || costMap.get(nextNode.getId()) > newDistance){
+ costMap.put(nextNode.getId(), newDistance);
+ pq.add(new CostToNextNode(newDistance, nextNode));
+ prevRoute.put(nextNode.getId(), route);
+ }
+ }
+ }
+ //길 없는 경우
+ if(!costMap.containsKey(endNode.getId())){
+ return null;
+ }
+
+ return prevRoute;
+ }
+
+ // 길찾기 결과를 파싱하여 출발지 -> 도착지 형태로 재배열하는 메서드
+ private List reorderRoute(Node startNode, Node endNode, Map prevRoute){
+ List shortestRoutes = new ArrayList<>();
+ Node currentNode = endNode;
+
+ //endNode부터 역순회하여 배열에 저장
+ while(!currentNode.getId().equals(startNode.getId())){
+ Route previousRoute = prevRoute.get(currentNode.getId());
+ shortestRoutes.add(previousRoute);
+ currentNode = previousRoute.getNode1().getId().equals(currentNode.getId()) ? previousRoute.getNode2() : previousRoute.getNode1();
+ }
+
+ //이후 reverse를 통해 시작점 -> 도착점 방향으로 재정렬
+ Collections.reverse(shortestRoutes);
+
+ return shortestRoutes;
+ }
+
+ private boolean isBuildingRoute(Route route){
+ return route.getDistance() > BUILDING_ROUTE_DISTANCE - 1;
+ }
+
+ // 두 route 간의 각도를 통한 계산으로 방향성을 정하는 메서드
+ private DirectionType calculateDirection(Node secondNode, Route inBoundRoute, Route outBoundRoute) {
+ Node firstNode = inBoundRoute.getNode1().equals(secondNode) ? inBoundRoute.getNode2() : inBoundRoute.getNode1();
+ Node thirdNode = outBoundRoute.getNode1().equals(secondNode) ? outBoundRoute.getNode2() : outBoundRoute.getNode1();
+
+ Point p1 = firstNode.getCoordinates();
+ Point p2 = secondNode.getCoordinates();
+ Point p3 = thirdNode.getCoordinates();
+
+ double v1x = p2.getX() - p1.getX();
+ double v1y = p2.getY() - p1.getY();
+
+ double v2x = p3.getX() - p2.getX();
+ double v2y = p3.getY() - p2.getY();
+
+ double dotProduct = (v1x * v2x) + (v1y * v2y);
+ double magnitudeV1 = Math.sqrt(v1x * v1x + v1y * v1y);
+ double magnitudeV2 = Math.sqrt(v2x * v2x + v2y * v2y);
+
+ double cosTheta = dotProduct / (magnitudeV1 * magnitudeV2);
+ double angle = Math.toDegrees(Math.acos(cosTheta));
+
+ double crossProduct = (v1x * v2y) - (v1y * v2x);
+
+ if (angle < 30) {
+ return DirectionType.STRAIGHT;
+ } else if (angle >= 30 && angle < 150) {
+ return (crossProduct > 0) ? DirectionType.LEFT : DirectionType.RIGHT;
+ } else if (angle >= 150 && angle < 180) {
+ return (crossProduct > 0) ? DirectionType.SHARP_LEFT : DirectionType.SHARP_RIGHT;
+ } else {
+ return DirectionType.STRAIGHT;
+ }
+ }
+
+ private double calculateRouteDistance(Route route){
+ return calculateDistance(route.getNode1().getCoordinates().getX(),
+ route.getNode1().getCoordinates().getY(),
+ route.getNode2().getCoordinates().getX(),
+ route.getNode2().getCoordinates().getY());
+ }
+
+ // 하버사인 공식으로 길의 길이를 리턴하는 메서드
+ private double calculateDistance(double lng1, double lat1, double lng2, double lat2) {
+ double radLat1 = Math.toRadians(lat1);
+ double radLat2 = Math.toRadians(lat2);
+ double radLng1 = Math.toRadians(lng1);
+ double radLng2 = Math.toRadians(lng2);
+
+ double deltaLat = radLat2 - radLat1;
+ double deltaLng = radLng2 - radLng1;
+
+ double a = Math.pow(Math.sin(deltaLat / 2), 2)
+ + Math.cos(radLat1) * Math.cos(radLat2)
+ * Math.pow(Math.sin(deltaLng / 2), 2);
+ double c = 2 * Math.asin(Math.sqrt(a));
+
+ return EARTH_RADIUS * c;
+ }
+
+ // 길 상세정보를 추출하는 메서드
+ private List getRouteDetail(Node startNode, Node endNode, List shortestRoutes){
+ List details = new ArrayList<>();
+ double accumulatedDistance = 0.0;
+ Node now = startNode;
+ Map checkPointNodeCoordinates = startNode.getXY();
+ DirectionType checkPointType = DirectionType.STRAIGHT;
+ List checkPointCautionFactors = new ArrayList<>();
+
+ // 길찾기 결과 상세정보 정리
+ for(int i=0;i()));
+ break;
+ }
+ if(nxt.isCore()){
+ DirectionType directionType = calculateDirection(nxt, nowRoute, shortestRoutes.get(i+1));
+ if(directionType == DirectionType.STRAIGHT){
+ now = nxt;
+ continue;
+ }
+ details.add(RouteDetailResDTO.of(accumulatedDistance, checkPointType,
+ checkPointNodeCoordinates, checkPointCautionFactors));
+ checkPointNodeCoordinates = nxt.getXY();
+ checkPointType = directionType;
+ accumulatedDistance = 0.0;
+ checkPointCautionFactors = Collections.emptyList();
+ }
+
+ now = nxt;
+ }
+
+ return details;
+ }
+
+ private Map