Skip to content

Commit d24c44c

Browse files
committed
Several routing and user state fixes, show or hide My Courses only when logged-in
1 parent d3ade5b commit d24c44c

File tree

12 files changed

+186
-78
lines changed

12 files changed

+186
-78
lines changed

src/main/java/net/hackyourfuture/coursehub/repository/CourseRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public List<CourseEntity> findAll() {
3030
return jdbcTemplate.query(sql, COURSE_ROW_MAPPER);
3131
}
3232

33+
public List<CourseEntity> findAllByStudentId(Integer studentId) {
34+
String sql =
35+
"SELECT * FROM course c JOIN enrollment e ON c.course_id = e.course_id WHERE e.student_id = :studentId";
36+
return jdbcTemplate.query(sql, Map.of("studentId", studentId), COURSE_ROW_MAPPER);
37+
}
38+
3339
public CourseEntity findByCourseId(Long courseId) {
3440
String sql = "SELECT * FROM course WHERE course_id = :courseId";
3541
return jdbcTemplate.queryForObject(sql, Map.of("courseId", courseId), COURSE_ROW_MAPPER);
Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package net.hackyourfuture.coursehub.service;
22

3-
import net.hackyourfuture.coursehub.data.InstructorEntity;
43
import net.hackyourfuture.coursehub.repository.CourseRepository;
5-
import net.hackyourfuture.coursehub.repository.InstructorRepository;
64
import net.hackyourfuture.coursehub.web.model.CourseDto;
75
import org.springframework.stereotype.Service;
86

@@ -11,30 +9,37 @@
119
@Service
1210
public class CourseService {
1311
private final CourseRepository courseRepository;
14-
private final InstructorRepository instructorRepository;
12+
private final InstructorService instructorService;
1513

16-
public CourseService(CourseRepository courseRepository, InstructorRepository instructorRepository) {
14+
public CourseService(CourseRepository courseRepository, InstructorService instructorService) {
1715
this.courseRepository = courseRepository;
18-
this.instructorRepository = instructorRepository;
16+
this.instructorService = instructorService;
17+
}
18+
19+
public List<CourseDto> getCoursesForStudent(Integer studentId) {
20+
21+
return courseRepository.findAllByStudentId(studentId).stream()
22+
.map(c -> new CourseDto(
23+
c.courseId(),
24+
c.name(),
25+
c.description(),
26+
instructorService.getFullInstructorName(c.instructorId()),
27+
c.startDate(),
28+
c.endDate(),
29+
c.maxEnrollments()))
30+
.toList();
1931
}
2032

2133
public List<CourseDto> getAllCourses() {
2234
return courseRepository.findAll().stream()
23-
.map(c -> {
24-
InstructorEntity instructor = instructorRepository.findById(c.instructorId());
25-
if (instructor == null) {
26-
throw new IllegalStateException("Instructor with id " + c.instructorId() + " not found");
27-
}
28-
String instructorName = instructor.firstName() + " " + instructor.lastName();
29-
return new CourseDto(
30-
c.courseId(),
31-
c.name(),
32-
c.description(),
33-
instructorName,
34-
c.startDate(),
35-
c.endDate(),
36-
c.maxEnrollments());
37-
})
35+
.map(c -> new CourseDto(
36+
c.courseId(),
37+
c.name(),
38+
c.description(),
39+
instructorService.getFullInstructorName(c.instructorId()),
40+
c.startDate(),
41+
c.endDate(),
42+
c.maxEnrollments()))
3843
.toList();
3944
}
4045
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.hackyourfuture.coursehub.service;
2+
3+
import net.hackyourfuture.coursehub.data.InstructorEntity;
4+
import net.hackyourfuture.coursehub.repository.InstructorRepository;
5+
import org.springframework.stereotype.Service;
6+
7+
@Service
8+
public class InstructorService {
9+
private final InstructorRepository instructorRepository;
10+
11+
public InstructorService(InstructorRepository instructorRepository) {
12+
this.instructorRepository = instructorRepository;
13+
}
14+
15+
public String getFullInstructorName(Integer instructorId) {
16+
InstructorEntity instructor = instructorRepository.findById(instructorId);
17+
if (instructor == null) {
18+
throw new IllegalArgumentException("Instructor with id " + instructorId + " not found");
19+
}
20+
return instructor.firstName() + " " + instructor.lastName();
21+
}
22+
}

src/main/java/net/hackyourfuture/coursehub/web/CourseController.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import net.hackyourfuture.coursehub.service.CourseService;
44
import net.hackyourfuture.coursehub.web.model.CourseListResponse;
55
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
67
import org.springframework.web.bind.annotation.RestController;
78

89
@RestController
10+
@RequestMapping("/courses")
911
public class CourseController {
1012

1113
private final CourseService courseService;
@@ -14,7 +16,7 @@ public CourseController(CourseService courseService) {
1416
this.courseService = courseService;
1517
}
1618

17-
@GetMapping("/courses")
19+
@GetMapping
1820
public CourseListResponse getAllCourses() {
1921
return new CourseListResponse(courseService.getAllCourses());
2022
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package net.hackyourfuture.coursehub.web;
2+
3+
import net.hackyourfuture.coursehub.service.CourseService;
4+
import net.hackyourfuture.coursehub.web.model.CourseDto;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
import java.util.List;
10+
11+
@RestController
12+
@RequestMapping("students")
13+
public class StudentController {
14+
15+
private final CourseService courseService;
16+
17+
public StudentController(CourseService courseService) {
18+
this.courseService = courseService;
19+
}
20+
21+
@GetMapping("/{studentId}/courses")
22+
public List<CourseDto> getCoursesForStudent(Integer studentId) {
23+
return courseService.getCoursesForStudent(studentId);
24+
}
25+
}

src/main/java/net/hackyourfuture/coursehub/web/model/CourseDto.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.time.LocalDate;
44

55
public record CourseDto(
6-
Integer courseId,
6+
Integer id,
77
String name,
88
String description,
99
String instructor,

src/test/java/net/hackyourfuture/coursehub/service/CourseServiceTest.java

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.hackyourfuture.coursehub.service;
22

33
import net.hackyourfuture.coursehub.data.CourseEntity;
4-
import net.hackyourfuture.coursehub.data.InstructorEntity;
54
import net.hackyourfuture.coursehub.repository.CourseRepository;
65
import net.hackyourfuture.coursehub.repository.InstructorRepository;
76
import net.hackyourfuture.coursehub.web.model.CourseDto;
@@ -31,9 +30,12 @@ class CourseServiceTest {
3130
@Mock
3231
private InstructorRepository instructorRepository;
3332

33+
@Mock
34+
private InstructorService instructorService;
35+
3436
@Test
3537
void shouldReturnCoursesWithInstructorNames() {
36-
CourseService courseService = new CourseService(courseRepository, instructorRepository);
38+
CourseService courseService = new CourseService(courseRepository, instructorService);
3739

3840
when(courseRepository.findAll())
3941
.thenReturn(List.of(
@@ -53,22 +55,23 @@ void shouldReturnCoursesWithInstructorNames() {
5355
LocalDate.of(2026, Month.MAY, 1),
5456
LocalDate.of(2026, Month.SEPTEMBER, 1),
5557
50)));
56-
when(instructorRepository.findById(1))
57-
.thenReturn(new InstructorEntity(1, "Alice", "Smith", "[email protected]"));
58-
when(instructorRepository.findById(2)).thenReturn(new InstructorEntity(2, "Bob", "Johnson", "[email protected]"));
58+
when(instructorService.getFullInstructorName(1)).thenReturn("Alice Smith");
59+
when(instructorService.getFullInstructorName(2)).thenReturn("Bob Johnson");
5960

6061
var courses = courseService.getAllCourses();
6162

6263
assertThat(courses)
6364
.hasSize(2)
64-
.contains(new CourseDto(1,
65+
.contains(new CourseDto(
66+
1,
6567
"Testing course",
6668
"A course about testing",
6769
"Alice Smith",
6870
LocalDate.of(2026, Month.JANUARY, 15),
6971
LocalDate.of(2026, Month.MARCH, 1),
7072
30))
71-
.contains(new CourseDto(2,
73+
.contains(new CourseDto(
74+
2,
7275
"Spring course",
7376
"A course about using Spring Boot",
7477
"Bob Johnson",
@@ -77,27 +80,9 @@ void shouldReturnCoursesWithInstructorNames() {
7780
50));
7881
}
7982

80-
@Test
81-
void shouldFailIfCourseExistsWithoutAnInstructor() {
82-
CourseService courseService = new CourseService(courseRepository, instructorRepository);
83-
84-
when(courseRepository.findAll())
85-
.thenReturn(List.of(new CourseEntity(
86-
1,
87-
"Testing course",
88-
"A course about testing",
89-
1,
90-
LocalDate.of(2026, Month.JANUARY, 15),
91-
LocalDate.of(2026, Month.MARCH, 1),
92-
30)));
93-
when(instructorRepository.findById(1)).thenReturn(null);
94-
95-
assertThatThrownBy(courseService::getAllCourses).hasMessage("Instructor with id 1 not found");
96-
}
97-
9883
@Test
9984
void shouldFailIfRepositoryThrowsException() {
100-
CourseService courseService = new CourseService(courseRepository, instructorRepository);
85+
CourseService courseService = new CourseService(courseRepository, instructorService);
10186

10287
when(courseRepository.findAll()).thenThrow(new RuntimeException("Database is down"));
10388

ui/src/App.tsx

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,38 @@
11
import React, {useEffect, useState} from "react";
2-
import {createBrowserRouter, RouterProvider} from 'react-router'
3-
import HomePage from './pages/HomePage'
4-
import Login from './pages/Login'
2+
import {createBrowserRouter, RouterProvider} from 'react-router';
3+
import AllCourses from './pages/AllCourses';
4+
import Login from './pages/Login';
55
import type {User} from './types/User';
66
import Layout from "./components/PageLayout";
7+
import MyCourses from "./pages/MyCourses";
78

89
function App() {
9-
const [user, setUser] = useState<User | null>(null);
10-
11-
const router = createBrowserRouter([
12-
{
13-
element: <Layout user={user} setUser={setUser}/>,
14-
children: [
15-
{
16-
path: '/',
17-
element: <HomePage user={user} setUser={setUser}/>
18-
},
19-
{
20-
path: '/login',
21-
element: <Login user={user} setUser={setUser}/>
22-
}]
23-
}
24-
25-
])
26-
;
10+
const [user, setUser] = useState<User | null | undefined>(undefined);
2711

2812
useEffect(() => {
29-
// Try to load user from localStorage on app start
3013
const storedUser = localStorage.getItem("user");
3114
if (storedUser) {
3215
setUser(JSON.parse(storedUser));
16+
} else {
17+
setUser(null);
18+
localStorage.removeItem("user");
3319
}
3420
}, []);
3521

36-
return (
37-
<RouterProvider router={router}/>
38-
);
22+
if (user === undefined) {
23+
return null; // Wait for user to be loaded before rendering
24+
}
25+
26+
const router = createBrowserRouter([{
27+
element: <Layout user={user} setUser={setUser}/>,
28+
children: [
29+
{path: '/', element: <AllCourses/>},
30+
{path: '/my-courses', element: <MyCourses user={user}/>},
31+
{path: '/login', element: <Login setUser={setUser}/>}
32+
]
33+
}]);
34+
35+
return <RouterProvider router={router}/>;
3936
}
4037

4138
export default App;

ui/src/components/Header.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ function Header({user, setUser}: {
2626
className="text-2xl font-bold text-blue-600 hover:text-blue-800 transition">CourseHub</Link>
2727
<nav className="flex gap-6">
2828
<Link to="/" className="text-gray-700 font-medium hover:text-blue-600 transition">All Courses</Link>
29-
<Link to="/my-courses" className="text-gray-700 font-medium hover:text-blue-600 transition">My
30-
Courses</Link>
29+
{user && (
30+
<Link to="/my-courses" className="text-gray-700 font-medium hover:text-blue-600 transition">
31+
My Courses
32+
</Link>
33+
)}
3134
</nav>
3235
</div>
3336
<div>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import '../App.css'
33
import type {Course} from '../types/Course';
44
import {useConfig} from '../ConfigContext';
55

6-
function HomePage() {
6+
function AllCourses() {
77
const [courses, setCourses] = useState<Course[]>([]);
88
const {backendUrl} = useConfig();
99

@@ -52,6 +52,6 @@ function HomePage() {
5252
);
5353
}
5454

55-
export default HomePage
55+
export default AllCourses
5656

5757

0 commit comments

Comments
 (0)