Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions backend/src/main/kotlin/backend/controller/UserController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ class UserController(private val userManager: UserManager) {
@PostMapping
fun createUser(@RequestBody @Valid request: UserRequest): ResponseEntity<Any> {
return try {
val user = userManager.createUser(request.username, request.email, request.password)
val user = userManager.createUser(
request.username,
request.email,
request.password,
request.firstName,
request.lastName)

ResponseEntity.status(HttpStatus.CREATED).body(user)
} catch (ex: Exception) {
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(mapOf("error" to "Username or email is already in use."))
Expand All @@ -39,13 +45,15 @@ class UserController(private val userManager: UserManager) {
@PutMapping("/{id}")
fun updateUser(@PathVariable id: UUID, @RequestBody @Valid request: UserRequest): ResponseEntity<User> {
return try {
val updatedUser = User(username = request.username, email = request.email, password = request.password)
val updatedUser = User(
username = request.username,
email = request.email,
password = request.password,
firstName = request.firstName,
lastName = request.lastName)

val user = userManager.updateUser(id, updatedUser)
if (user != null) {
ResponseEntity.ok(user)
} else {
ResponseEntity.status(HttpStatus.NOT_FOUND).build()
}
ResponseEntity.ok(user)
} catch (ex: Exception) {
ResponseEntity.status(HttpStatus.NOT_FOUND).build()
}
Expand Down Expand Up @@ -100,7 +108,10 @@ data class UserRequest(
val email: String,

@field:Size(min = 8)
val password: String
val password: String,

val firstName: String? = null,
val lastName: String? = null
)

// Data class for adding and removing friends
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package backend.exception

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

@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
val errors = ex.bindingResult.fieldErrors.associate { it.field to it.defaultMessage!! }
return ResponseEntity(errors, HttpStatus.BAD_REQUEST)
}
}
16 changes: 13 additions & 3 deletions backend/src/main/kotlin/backend/manager/UserManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class UserManager (private val userRepository: UserRepository) {
// Hashes and salts the password, for safe managing
private val passwordEncoder = BCryptPasswordEncoder()

fun createUser(username: String, email: String, password: String): User {
fun createUser(username: String, email: String, password: String, firstName: String? = null, lastName: String? = null): User {
if (userRepository.existsByUsername(username)) {
throw IllegalArgumentException("Username already exists")
}
Expand All @@ -23,7 +23,12 @@ class UserManager (private val userRepository: UserRepository) {
// Salt and hash password before storing it
val safePassword = passwordEncoder.encode(password)

val user = User(username = username, email = email, password = safePassword)
val user = User(
username = username,
email = email,
password = safePassword,
firstName = firstName,
lastName = lastName)
return userRepository.save(user)
}

Expand All @@ -37,7 +42,12 @@ class UserManager (private val userRepository: UserRepository) {
// Salt and hash password before updating it
val safePassword = passwordEncoder.encode(updatedUser.password)

val userToUpdate = existingUser.copy(username = updatedUser.username, email = updatedUser.email, password = safePassword)
val userToUpdate = existingUser.copy(
username = updatedUser.username,
email = updatedUser.email,
password = safePassword,
firstName = updatedUser.firstName,
lastName = updatedUser.lastName)

return userRepository.save(userToUpdate)
}
Expand Down
6 changes: 6 additions & 0 deletions backend/src/main/kotlin/backend/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ data class User (
@field:Size(min = 8)
val password: String,

@Column(nullable = true)
val firstName: String? = null,

@Column(nullable = true)
val lastName: String? = null,

@ManyToMany
@JoinTable(
name = "user_friends",
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Configure Jackson to always include non-null properties in JSON responses
spring.jackson.default-property-inclusion=ALWAYS

# JSON Web Token (JWT) secret key (for authentication using Spring Security)
# jwt.secret.key=secret-key-must-be-very-long-and-secure

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,25 @@ class UserControllerTest {
@Test
fun `should create a user successfully`() {
val userId = UUID.randomUUID()
val user = User(id = userId, username = "testuser", email = "test@example.com", password = "SecurePass123")
val requestBody = objectMapper.writeValueAsString(mapOf("username" to "testuser", "email" to "test@example.com", "password" to "SecurePass123"))
val user = User(
id = userId,
username = "testuser",
email = "test@example.com",
password = "SecurePass123",
firstName = "TestFirst",
lastName = "TestLast"
)
val requestBody = objectMapper.writeValueAsString(
mapOf(
"username" to "testuser",
"email" to "test@example.com",
"password" to "SecurePass123",
"firstName" to "TestFirst",
"lastName" to "TestLast"
)
)

whenever(userManager.createUser(any(), any(), any())).thenReturn(user)
whenever(userManager.createUser(any(), any(), any(), any(), any())).thenReturn(user)

mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
Expand All @@ -57,6 +72,8 @@ class UserControllerTest {
.andExpect(jsonPath("$.id").value(userId.toString()))
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"))
.andExpect(jsonPath("$.firstName").value("TestFirst"))
.andExpect(jsonPath("$.lastName").value("TestLast"))
}

@Test
Expand Down Expand Up @@ -96,8 +113,23 @@ class UserControllerTest {
@Test
fun `should update user successfully`() {
val userId = UUID.randomUUID()
val updatedUser = User(id = userId, username = "updatedUser", email = "updated@example.com", password = "NewSecurePass123")
val requestBody = objectMapper.writeValueAsString(mapOf("username" to "updatedUser", "email" to "updated@example.com", "password" to "NewSecurePass123"))
val updatedUser = User(
id = userId,
username = "updatedUser",
email = "updated@example.com",
password = "NewSecurePass123",
firstName = "UpdatedFirst",
lastName = "UpdatedLast"
)
val requestBody = objectMapper.writeValueAsString(
mapOf(
"username" to "updatedUser",
"email" to "updated@example.com",
"password" to "NewSecurePass123",
"firstName" to "UpdatedFirst",
"lastName" to "UpdatedLast"
)
)

whenever(userManager.updateUser(eq(userId), any<User>())).thenReturn(updatedUser)

Expand All @@ -107,14 +139,22 @@ class UserControllerTest {
.andExpect(status().isOk)
.andExpect(jsonPath("$.username").value("updatedUser"))
.andExpect(jsonPath("$.email").value("updated@example.com"))
.andExpect(jsonPath("$.firstName").value("UpdatedFirst"))
.andExpect(jsonPath("$.lastName").value("UpdatedLast"))
}

@Test
fun `should return 404 when updating non-existent user`() {
val userId = UUID.randomUUID()
val requestBody = objectMapper.writeValueAsString(mapOf("username" to "updatedUser", "email" to "updated@example.com", "password" to "NewSecurePass123"))
val requestBody = objectMapper.writeValueAsString(
mapOf(
"username" to "updatedUser",
"email" to "updated@example.com",
"password" to "NewSecurePass123"
)
)

whenever(userManager.updateUser(eq(userId), any<User>())).thenReturn(null)
whenever(userManager.updateUser(eq(userId), any<User>())).thenThrow(NoSuchElementException("User not found"))

mockMvc.perform(put("/api/users/$userId")
.contentType(MediaType.APPLICATION_JSON)
Expand Down
26 changes: 23 additions & 3 deletions backend/src/test/kotlin/backend/unit/manager/UserManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ class UserManagerTest {
val username = "testuser"
val email = "test@example.com"
val password = "example123"
val firstName = "FirstName"
val lastName = "LastName"

`when`(userRepository.save(any())).thenAnswer { invocation -> invocation.arguments[0] as User }

val createdUser = userManager.createUser(username, email, password)
val createdUser = userManager.createUser(username, email, password, firstName, lastName)
val userCaptor = ArgumentCaptor.forClass(User::class.java)
verify(userRepository).save(userCaptor.capture())
val savedUser = userCaptor.value

assertEquals(username, savedUser.username)
assertEquals(email, savedUser.email)
assertEquals(firstName, savedUser.firstName)
assertEquals(lastName, savedUser.lastName)
//assertEquals(password, savedUser.password)
assertTrue(encoder.matches(password, savedUser.password))
assertNull(savedUser.id, "ID skal ikke være generert før lagring")
Expand Down Expand Up @@ -89,8 +93,22 @@ class UserManagerTest {
@Test
fun `updateUser should update existing user and return updated user`() {
val userId = UUID.randomUUID()
val existingUser = User(id = userId, username = "oldUser", email = "old@example.com", password = "oldPassword")
val updatedUser = User(id = userId, username = "newUser", email = "new@example.com", password = "newPassword")
val existingUser = User(
id = userId,
username = "oldUser",
email = "old@example.com",
password = "oldPassword",
firstName = null,
lastName = null,
)
val updatedUser = User(
id = userId,
username = "newUser",
email = "new@example.com",
password = "newPassword",
firstName = "NewFirst",
lastName = "NewLast",
)

`when`(userRepository.findById(userId)).thenReturn(Optional.of(existingUser))
`when`(userRepository.save(any())).thenAnswer { it.arguments[0] }
Expand All @@ -99,6 +117,8 @@ class UserManagerTest {

assertEquals(updatedUser.username, result.username)
assertEquals(updatedUser.email, result.email)
assertEquals(updatedUser.firstName, result.firstName)
assertEquals(updatedUser.lastName, result.lastName)
assertTrue(encoder.matches(updatedUser.password, result.password))
assertEquals(userId, result.id)
verify(userRepository).findById(userId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,24 @@ class UserRepositoryTest {
@Autowired
private lateinit var userRepository: UserRepository

@Test
@Transactional
fun `save should persist user with firstName and lastName`() {
val user = User(
username = "testUser",
email = "test@example.com",
password = "securepassword",
firstName = "John",
lastName = "Doe"
)
val savedUser = userRepository.save(user)

assertNotNull(savedUser.id)
assertEquals(user.username, savedUser.username)
assertEquals(user.email, savedUser.email)
assertEquals("John", savedUser.firstName)
assertEquals("Doe", savedUser.lastName)
}

@Test
@Transactional
Expand Down
6 changes: 6 additions & 0 deletions frontend/node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions frontend/node_modules/.vite/deps/@astrojs_vue_client__js.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading