diff --git a/MtdrSpring/backend/src/main/frontend/src/pages/TasksPage.jsx b/MtdrSpring/backend/src/main/frontend/src/pages/TasksPage.jsx index 6baecbc56..34da9f17f 100644 --- a/MtdrSpring/backend/src/main/frontend/src/pages/TasksPage.jsx +++ b/MtdrSpring/backend/src/main/frontend/src/pages/TasksPage.jsx @@ -44,7 +44,7 @@ export default function TasksPage() { const [showForm, setShowForm] = useState(false); const [editTask, setEditTask] = useState(null); const [saving, setSaving] = useState(false); - const [filters, setFilters] = useState({ status: '', sprint: '', priority: '' }); + const [filters, setFilters] = useState({ status: '', sprint: undefined, priority: '' }); const [showClosed, setShowClosed] = useState(false); const [donePrompt, setDonePrompt] = useState(null); const [actualHoursInput, setActualHoursInput] = useState(''); @@ -55,6 +55,7 @@ export default function TasksPage() { const isManager = userRole === 'MANAGER'; const load = useCallback(() => { + if (filters.sprint === undefined) return; setLoading(true); taskService.list(project.id, { ...filters, size: 500 }) .then((data) => setTasks(data.content || [])) @@ -68,10 +69,12 @@ export default function TasksPage() { sprintService.list(project.id).then((data) => { setSprints(data); const active = data.find((s) => s.status === 'ACTIVE'); - const fallback = data.filter((s) => s.status === 'PLANNING').sort((a, b) => a.startDate.localeCompare(b.startDate))[0]; - const defaultSprint = active || fallback; - if (defaultSprint) setFilters((f) => ({ ...f, sprint: String(defaultSprint.id) })); - }).catch(() => {}); + const mostRecent = [...data].sort((a, b) => b.startDate.localeCompare(a.startDate))[0]; + const defaultSprint = active || mostRecent; + setFilters((f) => ({ ...f, sprint: defaultSprint ? String(defaultSprint.id) : '' })); + }).catch(() => { + setFilters((f) => ({ ...f, sprint: '' })); + }); }, [project.id]); const handleCreate = async (form) => { diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/JwtAuthFilter.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/JwtAuthFilter.java index fe2a517db..b574be6c1 100644 --- a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/JwtAuthFilter.java +++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/JwtAuthFilter.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -27,14 +28,22 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractToken(request); - if (token != null && jwtUtil.validateToken(token)) { - String userId = jwtUtil.extractUserId(token); - UserDetails userDetails = userDetailsService.loadUserByUsername(userId); - if (userDetails.isEnabled()) { - UsernamePasswordAuthenticationToken auth = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(auth); + if (token != null) { + if (jwtUtil.validateToken(token)) { + String userId = jwtUtil.extractUserId(token); + UserDetails userDetails = userDetailsService.loadUserByUsername(userId); + if (userDetails.isEnabled()) { + UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(auth); + } + } else { + // Token present but expired/invalid → 401 so the frontend redirects to login + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write("{\"error\":\"Token expired or invalid\"}"); + return; } } filterChain.doFilter(request, response); diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SecurityConfig.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SecurityConfig.java index 5f2ff10b1..975422239 100644 --- a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SecurityConfig.java +++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SecurityConfig.java @@ -34,16 +34,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/api/v1/auth/**", "/swagger-ui/**", "/swagger-ui.html", - "/v3/api-docs/**", - "/", - "/index.html", - "/static/**", - "/*.js", - "/*.css", - "/*.ico", - "/*.json" + "/v3/api-docs/**" ).permitAll() - .anyRequest().authenticated() + .requestMatchers("/api/**").authenticated() + .anyRequest().permitAll() ) .authenticationProvider(authenticationProvider()) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SpaController.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SpaController.java new file mode 100644 index 000000000..2e2fab604 --- /dev/null +++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/config/SpaController.java @@ -0,0 +1,17 @@ +package com.springboot.MyTodoList.config; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class SpaController { + + // Forward all non-file, non-API routes to index.html so React Router handles them + @RequestMapping(value = { + "/{path:[^\\.]*}", + "/{path:[^\\.]*}/**" + }) + public String forward() { + return "forward:/index.html"; + } +}