Skip to content

Commit d926c6c

Browse files
committed
Add several repository tests and make existing tests pass
1 parent 6939d05 commit d926c6c

File tree

14 files changed

+300
-112
lines changed

14 files changed

+300
-112
lines changed

src/main/java/net/hackyourfuture/coursehub/SecurityConfig.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import net.hackyourfuture.coursehub.service.UserAuthenticationService;
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.http.HttpMethod;
67
import org.springframework.security.authentication.AuthenticationManager;
78
import org.springframework.security.config.Customizer;
89
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -20,7 +21,9 @@ public class SecurityConfig {
2021
@Bean
2122
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2223
return http.csrf(AbstractHttpConfigurer::disable)
23-
.authorizeHttpRequests(auth -> auth.requestMatchers("/", "/login", "/register")
24+
.authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.POST, "/login", "/register")
25+
.permitAll()
26+
.requestMatchers(HttpMethod.GET, "/courses")
2427
.permitAll()
2528
.anyRequest()
2629
.authenticated())
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
11
package net.hackyourfuture.coursehub.data;
22

3-
public record InstructorEntity(
4-
Integer instructorId,
5-
String firstName,
6-
String lastName,
7-
String email
8-
) {
9-
}
3+
public record InstructorEntity(Integer instructorId, String firstName, String lastName, String emailAddress) {}
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
11
package net.hackyourfuture.coursehub.data;
22

3-
public record StudentEntity(
4-
Integer studentId,
5-
String firstName,
6-
String lastName,
7-
String email
8-
) {
9-
}
3+
public record StudentEntity(Integer studentId, String firstName, String lastName, String email) {}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package net.hackyourfuture.coursehub.data;
22

3-
public record UserAccountEntity(Integer userId, String username, String passwordHash, String emailAddress, Role role) {}
3+
public record UserAccountEntity(Integer userId, String passwordHash, String emailAddress, Role role) {}
Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package net.hackyourfuture.coursehub.repository;
22

33
import net.hackyourfuture.coursehub.data.InstructorEntity;
4+
import org.springframework.dao.EmptyResultDataAccessException;
45
import org.springframework.jdbc.core.RowMapper;
56
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
67
import org.springframework.stereotype.Repository;
8+
import org.springframework.transaction.annotation.Transactional;
79

810
import java.util.Map;
911

@@ -13,33 +15,69 @@ public class InstructorRepository {
1315
rs.getInt("instructor_id"),
1416
rs.getString("first_name"),
1517
rs.getString("last_name"),
16-
rs.getString("email")
17-
);
18+
rs.getString("email_address"));
1819
private final NamedParameterJdbcTemplate jdbcTemplate;
20+
private final UserAccountRepository userAccountRepository;
1921

20-
public InstructorRepository(NamedParameterJdbcTemplate jdbcTemplate) {
22+
public InstructorRepository(NamedParameterJdbcTemplate jdbcTemplate, UserAccountRepository userAccountRepository) {
2123
this.jdbcTemplate = jdbcTemplate;
24+
this.userAccountRepository = userAccountRepository;
2225
}
2326

2427
public InstructorEntity findById(Integer instructorId) {
25-
String sql = "SELECT * FROM instructor WHERE instructor_id = :instructorId";
26-
return jdbcTemplate.queryForObject(sql, Map.of("instructorId", instructorId), INSTRUCTOR_ROW_MAPPER);
28+
try {
29+
String sql = "SELECT i.instructor_id, i.first_name, i.last_name, ua.email_address "
30+
+ "FROM instructor i JOIN user_account ua ON i.instructor_id = ua.user_id "
31+
+ "WHERE i.instructor_id = :instructorId";
32+
return jdbcTemplate.queryForObject(sql, Map.of("instructorId", instructorId), INSTRUCTOR_ROW_MAPPER);
33+
} catch (EmptyResultDataAccessException e) {
34+
return null;
35+
}
2736
}
2837

29-
public InstructorEntity insertInstructor(InstructorEntity instructor) {
30-
String sql = """
31-
INSERT INTO instructor (first_name, last_name, email)
32-
VALUES (:firstName, :lastName, :email)
33-
RETURNING *;
34-
""";
35-
return jdbcTemplate.queryForObject(
36-
sql, Map.of(
37-
"firstName", instructor.firstName(),
38-
"lastName", instructor.lastName(),
39-
"email", instructor.email()
40-
),
41-
INSTRUCTOR_ROW_MAPPER
42-
);
43-
38+
@Transactional
39+
public InstructorEntity insertInstructor(
40+
String firstName, String lastName, String emailAddress, String passwordHash) {
41+
if (firstName == null || firstName.isBlank()) {
42+
throw new IllegalArgumentException("firstName must not be empty");
43+
}
44+
if (lastName == null || lastName.isBlank()) {
45+
throw new IllegalArgumentException("lastName must not be empty");
46+
}
47+
if (emailAddress == null || emailAddress.isBlank()) {
48+
throw new IllegalArgumentException("emailAddress must not be empty");
49+
}
50+
if (passwordHash == null || passwordHash.isBlank()) {
51+
throw new IllegalArgumentException("passwordHash must not be empty");
52+
}
53+
Integer userId = userAccountRepository.findUserIdByEmail(emailAddress);
54+
if (userId != null) {
55+
throw new IllegalArgumentException(
56+
"Another user with the email address '%s' already exists".formatted(emailAddress));
57+
}
58+
// Insert into user_account first
59+
String userSql =
60+
"INSERT INTO user_account (email_address, password_hash, role) VALUES (:emailAddress, :passwordHash, :role::role) RETURNING user_id";
61+
userId = jdbcTemplate.queryForObject(
62+
userSql,
63+
Map.of(
64+
"emailAddress", emailAddress,
65+
"passwordHash", passwordHash,
66+
"role", "instructor"),
67+
Integer.class);
68+
if (userId == null) {
69+
throw new IllegalStateException("Something went wrong inserting user account for a new instructor");
70+
}
71+
// Insert into the instructor table
72+
String instructorSql =
73+
"INSERT INTO instructor (instructor_id, first_name, last_name) VALUES (:instructorId, :firstName, :lastName)";
74+
jdbcTemplate.update(
75+
instructorSql,
76+
Map.of(
77+
"instructorId", userId,
78+
"firstName", firstName,
79+
"lastName", lastName));
80+
// Return the joined entity
81+
return findById(userId);
4482
}
4583
}
Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package net.hackyourfuture.coursehub.repository;
22

3+
import net.hackyourfuture.coursehub.data.Role;
34
import net.hackyourfuture.coursehub.data.StudentEntity;
5+
import net.hackyourfuture.coursehub.data.UserAccountEntity;
46
import org.springframework.jdbc.core.RowMapper;
57
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
68
import org.springframework.stereotype.Repository;
9+
import org.springframework.transaction.annotation.Transactional;
710

811
import java.util.Map;
912

@@ -13,30 +16,39 @@ public class StudentRepository {
1316
rs.getInt("student_id"),
1417
rs.getString("first_name"),
1518
rs.getString("last_name"),
16-
rs.getString("email")
17-
);
19+
rs.getString("email_address") // from user_account
20+
);
1821
private final NamedParameterJdbcTemplate jdbcTemplate;
22+
private final UserAccountRepository userAccountRepository;
1923

20-
public StudentRepository(NamedParameterJdbcTemplate jdbcTemplate) {
24+
public StudentRepository(NamedParameterJdbcTemplate jdbcTemplate, UserAccountRepository userAccountRepository) {
2125
this.jdbcTemplate = jdbcTemplate;
26+
this.userAccountRepository = userAccountRepository;
2227
}
2328

2429
public StudentEntity findById(Integer studentId) {
25-
String sql = "SELECT * FROM student WHERE student_id = :studentId";
30+
String sql = "SELECT s.student_id, s.first_name, s.last_name, ua.email_address "
31+
+ "FROM student s JOIN user_account ua ON s.student_id = ua.user_id "
32+
+ "WHERE s.student_id = :studentId";
2633
return jdbcTemplate.queryForObject(sql, Map.of("studentId", studentId), STUDENT_ROW_MAPPER);
2734
}
2835

29-
public void insertStudent(StudentEntity student) {
30-
String sql = """
31-
INSERT INTO student (first_name, last_name, email)
32-
VALUES (:firstName, :lastName, :email)
33-
""";
36+
@Transactional
37+
public StudentEntity insertStudent(String firstName, String lastName, String emailAddress, String passwordHash) {
38+
UserAccountEntity userAccountEntity =
39+
userAccountRepository.insertUserAccount(emailAddress, passwordHash, Role.student);
40+
if (userAccountEntity == null) {
41+
throw new IllegalStateException("Something went wrong inserting user account for a new instructor");
42+
}
43+
String sql =
44+
"INSERT INTO student (student_id, first_name, last_name) VALUES (:studentId, :firstName, :lastName)";
3445
jdbcTemplate.update(
35-
sql, Map.of(
36-
"firstName", student.firstName(),
37-
"lastName", student.lastName(),
38-
"email", student.email()
39-
)
40-
);
46+
sql,
47+
Map.of(
48+
"studentId", userAccountEntity.userId(),
49+
"firstName", firstName,
50+
"lastName", lastName));
51+
// Return the joined entity
52+
return findById(userAccountEntity.userId());
4153
}
4254
}
Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,70 @@
11
package net.hackyourfuture.coursehub.repository;
22

3+
import jakarta.annotation.Nullable;
34
import net.hackyourfuture.coursehub.data.Role;
45
import net.hackyourfuture.coursehub.data.UserAccountEntity;
6+
import org.springframework.dao.EmptyResultDataAccessException;
57
import org.springframework.jdbc.core.RowMapper;
68
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
79
import org.springframework.stereotype.Repository;
10+
import org.springframework.transaction.annotation.Transactional;
811

912
import java.util.Map;
1013

1114
@Repository
1215
public class UserAccountRepository {
1316
public static final RowMapper<UserAccountEntity> USER_ACCOUNT_ROW_MAPPER = (rs, rowNum) -> new UserAccountEntity(
1417
rs.getInt("user_id"),
15-
rs.getString("username"),
16-
rs.getString("password_hash"),
1718
rs.getString("email_address"),
18-
rs.getObject("role", Role.class));
19+
rs.getString("password_hash"),
20+
Role.valueOf(rs.getString("role")));
1921
private final NamedParameterJdbcTemplate jdbcTemplate;
2022

2123
public UserAccountRepository(NamedParameterJdbcTemplate jdbcTemplate) {
2224
this.jdbcTemplate = jdbcTemplate;
2325
}
2426

2527
public UserAccountEntity findByUserId(Integer userId) {
26-
String sql = "SELECT * FROM user_account WHERE user_id = :useId";
28+
String sql = "SELECT * FROM user_account WHERE user_id = :userId";
2729
return jdbcTemplate.queryForObject(sql, Map.of("userId", userId), USER_ACCOUNT_ROW_MAPPER);
2830
}
2931

32+
@Nullable
3033
public UserAccountEntity findByEmailAddress(String emailAddress) {
3134
String sql = "SELECT * FROM user_account WHERE email_address = :emailAddress";
32-
return jdbcTemplate.queryForObject(sql, Map.of("emailAddress", emailAddress), USER_ACCOUNT_ROW_MAPPER);
35+
try {
36+
return jdbcTemplate.queryForObject(sql, Map.of("emailAddress", emailAddress), USER_ACCOUNT_ROW_MAPPER);
37+
} catch (EmptyResultDataAccessException e) {
38+
return null;
39+
}
3340
}
3441

35-
public void save(UserAccountEntity user) {
36-
String sql =
37-
"INSERT INTO user_account (password_hash, email_address, role) VALUES (:passwordHash, :emailAddress, :role)";
38-
jdbcTemplate.update(
39-
sql,
42+
@Transactional
43+
public UserAccountEntity insertUserAccount(String emailAddress, String passwordHash, Role role) {
44+
Integer userId = findUserIdByEmail(emailAddress);
45+
if (userId != null) {
46+
throw new IllegalArgumentException(
47+
"Another user with the email address '%s' already exists".formatted(emailAddress));
48+
}
49+
String userSql = "INSERT INTO user_account (email_address, password_hash, role) "
50+
+ "VALUES (:emailAddress, :passwordHash, :role::role) "
51+
+ "RETURNING user_id, email_address, password_hash, role";
52+
return jdbcTemplate.queryForObject(
53+
userSql,
4054
Map.of(
41-
"passwordHash", user.passwordHash(),
42-
"emailAddress", user.emailAddress(),
43-
"role", user.role()));
55+
"emailAddress", emailAddress,
56+
"passwordHash", passwordHash,
57+
"role", role.name()),
58+
USER_ACCOUNT_ROW_MAPPER);
59+
}
60+
61+
@Nullable
62+
public Integer findUserIdByEmail(String emailAddress) {
63+
String sql = "SELECT user_id FROM user_account WHERE email_address = :emailAddress";
64+
try {
65+
return jdbcTemplate.queryForObject(sql, Map.of("emailAddress", emailAddress), Integer.class);
66+
} catch (EmptyResultDataAccessException e) {
67+
return null;
68+
}
4469
}
4570
}

src/main/java/net/hackyourfuture/coursehub/service/UserAuthenticationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public UserAuthenticationService(UserAccountRepository userAccountRepository) {
2121
public UserDetails loadUserByUsername(String emailAddress) throws UsernameNotFoundException {
2222
UserAccountEntity user = userAccountRepository.findByEmailAddress(emailAddress);
2323
if (user == null) {
24-
throw new UsernameNotFoundException("No user found for provided email address");
24+
throw new UsernameNotFoundException("No user found for provided emailAddress address");
2525
}
2626
return new org.springframework.security.core.userdetails.User(
2727
user.emailAddress(),

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public LoginResponse login(@RequestBody LoginRequest request) {
3232
Authentication authentication = authenticationManager.authenticate(
3333
new UsernamePasswordAuthenticationToken(request.emailAddress(), request.password()));
3434
UserAccountEntity user = userAccountRepository.findByEmailAddress(request.emailAddress());
35+
if (user == null) {
36+
return new LoginResponse(null, false, "No user found for provided email address");
37+
}
3538
return new LoginResponse(user.userId(), authentication.isAuthenticated(), null);
3639
} catch (AuthenticationException e) {
3740
if (e instanceof BadCredentialsException) {

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

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

55
public record CourseDto(
6-
String name, String description, String instructor, LocalDate startDate, LocalDate endDate, Integer maxEnrollments) {}
6+
String name,
7+
String description,
8+
String instructor,
9+
LocalDate startDate,
10+
LocalDate endDate,
11+
Integer maxEnrollments) {}

0 commit comments

Comments
 (0)