diff --git a/backend/src/main/java/com/rit/portal/service/BusLocationService.java b/backend/src/main/java/com/rit/portal/service/BusLocationService.java index ebda89f..942082f 100644 --- a/backend/src/main/java/com/rit/portal/service/BusLocationService.java +++ b/backend/src/main/java/com/rit/portal/service/BusLocationService.java @@ -45,8 +45,17 @@ public class BusLocationService { public List getActiveLocations() { Instant threshold = Instant.now().minus(STALE_DURATION); - // Clean up stale entries on read + return locationCache.values().stream() + .filter(loc -> !loc.getLastUpdated().isBefore(threshold)) + .toList(); + } + + /** + * Periodic background eviction of stale GPS coordinates every 5 minutes + */ + @org.springframework.scheduling.annotation.Scheduled(fixedRate = 300000) + public void evictStaleLocations() { + Instant threshold = Instant.now().minus(STALE_DURATION); locationCache.entrySet().removeIf(entry -> entry.getValue().getLastUpdated().isBefore(threshold)); - return List.copyOf(locationCache.values()); } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 788959b..0287598 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -3,14 +3,24 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/rit_freshers_hub?sslmode= spring.datasource.username=postgres spring.datasource.password=${DB_PASSWORD:Amudiesh22@.} +# ─── HIKARICP CONNECTION POOL OPTIMIZATIONS ─── +spring.datasource.hikari.maximum-pool-size=30 +spring.datasource.hikari.minimum-idle=10 +spring.datasource.hikari.idle-timeout=300000 +spring.datasource.hikari.max-lifetime=1200000 +spring.datasource.hikari.connection-timeout=20000 + # ─── JPA / HIBERNATE SETTINGS ─── spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=false -# ─── SERVER PORT ─── +# ─── SERVER PORT & HTTP GZIP COMPRESSION ─── server.port=8085 +server.compression.enabled=true +server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css +server.compression.min-response-size=1024 # ─── GOOGLE OAUTH2 CONFIGURATION ─── google.client.id=${GOOGLE_CLIENT_ID:81935488244-vmab5t8fipfof1n6l7di7eg4fve6k8lf.apps.googleusercontent.com} @@ -23,10 +33,8 @@ admin.super.password=${SUPER_ADMIN_PASSWORD:RIT@2026} admin.username=${ADMIN_USERNAME:ritadmin} admin.password=${ADMIN_PASSWORD:ritadmin2026} - # ─── IMS INTEGRATION CONFIGURATION ─── # Options: "mock" (active now for testing & simulation) | "real" (for live college IMS) ims.client.type=mock ims.base-url=${IMS_BASE_URL:https://api.ims.ritchennai.edu.in} ims.api-key=${IMS_API_KEY:rit_nexus_vps_secret_key_2026} - diff --git a/src/App.tsx b/src/App.tsx index 46eae73..742d88d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,29 +1,35 @@ -import { useEffect } from 'react'; +import { useEffect, lazy, Suspense } from 'react'; import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthProvider } from '@/context/AuthContext'; import ProtectedRoute from '@/components/Auth/ProtectedRoute'; import MainLayout from '@/layouts/MainLayout'; -import Home from '@/pages/Home/Home'; -import Notes from '@/pages/Notes/Notes'; -import Toolkit from '@/pages/Toolkit/Toolkit'; -import AIAssistant from '@/pages/AIAssistant/AIAssistant'; -import Campus from '@/pages/Campus/Campus'; -import Events from '@/pages/Events/Events'; -import Community from '@/pages/Community/Community'; -import BusRoutes from '@/pages/BusRoutes/BusRoutes'; -import Faculty from '@/pages/Faculty/Faculty'; -import DevCollab from '@/pages/DevCollab/DevCollab'; -import LeetcodeLeaderboard from '@/pages/LeetcodeLeaderboard/LeetcodeLeaderboard'; -import AdminDashboard from '@/pages/Admin/AdminDashboard'; -import Raise from '@/pages/Raise/Raise'; -import StudentDashboard from '@/pages/StudentDashboard/StudentDashboard'; +import PageLoader from '@/components/PageLoader/PageLoader'; +import ErrorBoundary from '@/components/ErrorBoundary/ErrorBoundary'; + +// Code-split route components with dynamic imports for minimal initial bundle size +const Home = lazy(() => import('@/pages/Home/Home')); +const Notes = lazy(() => import('@/pages/Notes/Notes')); +const Toolkit = lazy(() => import('@/pages/Toolkit/Toolkit')); +const AIAssistant = lazy(() => import('@/pages/AIAssistant/AIAssistant')); +const Campus = lazy(() => import('@/pages/Campus/Campus')); +const Events = lazy(() => import('@/pages/Events/Events')); +const Community = lazy(() => import('@/pages/Community/Community')); +const BusRoutes = lazy(() => import('@/pages/BusRoutes/BusRoutes')); +const Faculty = lazy(() => import('@/pages/Faculty/Faculty')); +const DevCollab = lazy(() => import('@/pages/DevCollab/DevCollab')); +const LeetcodeLeaderboard = lazy(() => import('@/pages/LeetcodeLeaderboard/LeetcodeLeaderboard')); +const AdminDashboard = lazy(() => import('@/pages/Admin/AdminDashboard')); +const Raise = lazy(() => import('@/pages/Raise/Raise')); +const StudentDashboard = lazy(() => import('@/pages/StudentDashboard/StudentDashboard')); const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + retry: 1, }, }, }); @@ -40,72 +46,76 @@ function ScrollToTop() { function App() { return ( - - - - - - {/* Standalone Admin Route */} - } /> - } /> + + + + + + }> + + {/* Standalone Admin Route */} + } /> + } /> - {/* Standalone RAISE Route */} - } /> + {/* Standalone RAISE Route */} + } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - {/* Student Dashboard & Timetable — Real IMS Reg No & Password */} - } /> - } /> + {/* Student Dashboard & Timetable — Real IMS Reg No & Password */} + } /> + } /> - {/* Protected Routes — Require Google Sign-In */} - - - - } - /> - - - - } - /> - - - - } - /> - - - - - + {/* Protected Routes — Require Google Sign-In */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + + + + ); } diff --git a/src/components/ErrorBoundary/ErrorBoundary.tsx b/src/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 0000000..a2b81a6 --- /dev/null +++ b/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,91 @@ +import { Component, type ErrorInfo, type ReactNode } from 'react'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null, + }; + + public static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('[ErrorBoundary caught error]:', error, errorInfo); + } + + private handleReset = () => { + this.setState({ hasError: false, error: null }); + window.location.reload(); + }; + + private handleGoHome = () => { + this.setState({ hasError: false, error: null }); + window.location.href = '/'; + }; + + public render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+
+ +
+ +
+

+ Something went wrong +

+

+ An unexpected error occurred while rendering this section. You can try refreshing the page or navigating back home. +

+
+ + {this.state.error && ( +
+ {this.state.error.message} +
+ )} + +
+ + +
+
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/PageLoader/PageLoader.tsx b/src/components/PageLoader/PageLoader.tsx new file mode 100644 index 0000000..a1ab0a7 --- /dev/null +++ b/src/components/PageLoader/PageLoader.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +const PageLoader: React.FC = () => { + return ( +
+
+ {/* Outer glowing ripple */} +
+ {/* Spinning gradient ring */} +
+ {/* Center dot */} +
+
+
+

+ Loading Freshers Hub +

+

Preparing your campus experience...

+
+
+ ); +}; + +export default PageLoader; diff --git a/vite.config.ts b/vite.config.ts index 4de9b7f..545b3f9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -50,6 +50,33 @@ export default defineConfig({ } }) ], + build: { + target: 'esnext', + chunkSizeWarningLimit: 800, + rollupOptions: { + output: { + manualChunks(id: string) { + if (id.includes('node_modules')) { + if (id.includes('react') || id.includes('react-dom') || id.includes('react-router-dom')) { + return 'vendor-react'; + } + if (id.includes('framer-motion') || id.includes('lucide-react')) { + return 'vendor-ui'; + } + if (id.includes('@radix-ui')) { + return 'vendor-radix'; + } + if (id.includes('@tanstack')) { + return 'vendor-tanstack'; + } + if (id.includes('leaflet') || id.includes('react-leaflet')) { + return 'vendor-map'; + } + } + } + } + } + }, server: { host: true, proxy: {