Adds login ability, error handling, and logging
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.controller
|
||||||
|
|
||||||
|
import jakarta.servlet.http.Cookie
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.converter.toDomain
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.converter.toLoginResponse
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.request.LoginRequest
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.response.LoginResponse
|
||||||
|
import net.halfbinary.scavengerhuntapi.service.LoginService
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class LoginController(private val loginService: LoginService) {
|
||||||
|
@PostMapping("/login")
|
||||||
|
fun login(@RequestBody body: LoginRequest, response: HttpServletResponse): ResponseEntity<LoginResponse> {
|
||||||
|
val result = loginService.login(body.toDomain())
|
||||||
|
val creds = "${result.email}|${result.name}"
|
||||||
|
val encodedCreds = URLEncoder.encode(creds, "UTF-8")
|
||||||
|
response.addCookie(Cookie("creds", encodedCreds))
|
||||||
|
return ResponseEntity.ok(result.toLoginResponse())
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logout")
|
||||||
|
fun logout(response: HttpServletResponse): ResponseEntity<String> {
|
||||||
|
val cookie = Cookie("creds", null)
|
||||||
|
cookie.maxAge = 0
|
||||||
|
response.addCookie(cookie)
|
||||||
|
return ResponseEntity.ok("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,7 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
class SignupController(private val signupService: SignupService) {
|
class SignupController(private val signupService: SignupService) {
|
||||||
@PostMapping("/signup")
|
@PostMapping("/signup")
|
||||||
fun hunterSignup(@RequestBody body: HunterSignupRequest): ResponseEntity<Any> {
|
fun hunterSignup(@RequestBody body: HunterSignupRequest): ResponseEntity<Any> {
|
||||||
try {
|
|
||||||
signupService.createNewHunter(body.toDomain())
|
signupService.createNewHunter(body.toDomain())
|
||||||
return ResponseEntity.ok().build()
|
return ResponseEntity.ok().build()
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
return ResponseEntity.badRequest().body(e.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.error
|
||||||
|
|
||||||
|
import net.halfbinary.scavengerhuntapi.error.exception.InvalidEmailException
|
||||||
|
import net.halfbinary.scavengerhuntapi.error.exception.LoginFailedException
|
||||||
|
import net.halfbinary.scavengerhuntapi.error.exception.PreexistingAccountException
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
class ExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(PreexistingAccountException::class)
|
||||||
|
@ResponseStatus(HttpStatus.CONFLICT)
|
||||||
|
fun preexistingAccountException(e: PreexistingAccountException): String? {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(LoginFailedException::class)
|
||||||
|
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||||
|
fun loginFailedException(e: LoginFailedException): String? {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(InvalidEmailException::class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
fun invalidEmailException(e: InvalidEmailException): String? {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.error.exception
|
||||||
|
|
||||||
|
class LoginFailedException(): RuntimeException("The email and password combination is not correct.")
|
||||||
@@ -3,6 +3,7 @@ package net.halfbinary.scavengerhuntapi.model.converter
|
|||||||
import net.halfbinary.scavengerhuntapi.model.domain.Hunter
|
import net.halfbinary.scavengerhuntapi.model.domain.Hunter
|
||||||
import net.halfbinary.scavengerhuntapi.model.record.HunterRecord
|
import net.halfbinary.scavengerhuntapi.model.record.HunterRecord
|
||||||
import net.halfbinary.scavengerhuntapi.model.request.HunterSignupRequest
|
import net.halfbinary.scavengerhuntapi.model.request.HunterSignupRequest
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.response.LoginResponse
|
||||||
|
|
||||||
fun HunterSignupRequest.toDomain(): Hunter {
|
fun HunterSignupRequest.toDomain(): Hunter {
|
||||||
return Hunter(
|
return Hunter(
|
||||||
@@ -16,3 +17,11 @@ fun HunterSignupRequest.toDomain(): Hunter {
|
|||||||
fun Hunter.toRecord(): HunterRecord {
|
fun Hunter.toRecord(): HunterRecord {
|
||||||
return HunterRecord(id, email, name, password, isAdmin)
|
return HunterRecord(id, email, name, password, isAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun HunterRecord.toDomain(): Hunter {
|
||||||
|
return Hunter(id, email, name, password, isAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Hunter.toLoginResponse(): LoginResponse {
|
||||||
|
return LoginResponse(email, name)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.model.converter
|
||||||
|
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.domain.Login
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.request.LoginRequest
|
||||||
|
|
||||||
|
fun LoginRequest.toDomain(): Login {
|
||||||
|
return Login(email, password)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.model.domain
|
||||||
|
|
||||||
|
data class Login(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.model.request
|
||||||
|
|
||||||
|
data class LoginRequest(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.model.response
|
||||||
|
|
||||||
|
data class LoginResponse(
|
||||||
|
val email: String,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
@@ -3,10 +3,18 @@ package net.halfbinary.scavengerhuntapi.repository
|
|||||||
import net.halfbinary.scavengerhuntapi.model.HunterId
|
import net.halfbinary.scavengerhuntapi.model.HunterId
|
||||||
import net.halfbinary.scavengerhuntapi.model.record.HunterRecord
|
import net.halfbinary.scavengerhuntapi.model.record.HunterRecord
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface HunterRepository : JpaRepository<HunterRecord, HunterId>
|
|
||||||
interface HunterRepository : JpaRepository<HunterRecord, HunterId> {
|
interface HunterRepository : JpaRepository<HunterRecord, HunterId> {
|
||||||
fun findByEmail(email: String): HunterRecord?
|
fun findByEmail(email: String): HunterRecord?
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT h.*
|
||||||
|
FROM hunter h
|
||||||
|
WHERE h.email = :email
|
||||||
|
AND h.password = :password
|
||||||
|
""", nativeQuery = true)
|
||||||
|
fun login(email: String, password: String): HunterRecord?
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package net.halfbinary.scavengerhuntapi.service
|
||||||
|
|
||||||
|
import net.halfbinary.scavengerhuntapi.error.exception.LoginFailedException
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.converter.toDomain
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.domain.Hunter
|
||||||
|
import net.halfbinary.scavengerhuntapi.model.domain.Login
|
||||||
|
import net.halfbinary.scavengerhuntapi.repository.HunterRepository
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class LoginService(private val hunterRepository: HunterRepository) {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(LoginService::class.java)
|
||||||
|
}
|
||||||
|
fun login(login: Login): Hunter {
|
||||||
|
log.info("Logging in with email: ${login.email}")
|
||||||
|
return hunterRepository.login(login.email, login.password)?.toDomain()?:throw LoginFailedException()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,17 +6,26 @@ import net.halfbinary.scavengerhuntapi.model.converter.toRecord
|
|||||||
import net.halfbinary.scavengerhuntapi.model.domain.Hunter
|
import net.halfbinary.scavengerhuntapi.model.domain.Hunter
|
||||||
import net.halfbinary.scavengerhuntapi.repository.HunterRepository
|
import net.halfbinary.scavengerhuntapi.repository.HunterRepository
|
||||||
import org.apache.commons.validator.routines.EmailValidator
|
import org.apache.commons.validator.routines.EmailValidator
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class SignupService(private val hunterRepository: HunterRepository) {
|
class SignupService(private val hunterRepository: HunterRepository) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(SignupService::class.java)
|
||||||
|
}
|
||||||
fun createNewHunter(hunter: Hunter) {
|
fun createNewHunter(hunter: Hunter) {
|
||||||
|
log.info("Creating new Hunter with email: ${hunter.email}...")
|
||||||
if (!EmailValidator.getInstance().isValid(hunter.email)) {
|
if (!EmailValidator.getInstance().isValid(hunter.email)) {
|
||||||
|
log.error("Invalid email ${hunter.email}")
|
||||||
throw InvalidEmailException(hunter.email)
|
throw InvalidEmailException(hunter.email)
|
||||||
}
|
}
|
||||||
if (hunterRepository.findByEmail(hunter.email) != null) {
|
if (hunterRepository.findByEmail(hunter.email) != null) {
|
||||||
|
log.error("Hunter ${hunter.email} already exists")
|
||||||
throw PreexistingAccountException()
|
throw PreexistingAccountException()
|
||||||
}
|
}
|
||||||
hunterRepository.save(hunter.toRecord())
|
hunterRepository.save(hunter.toRecord())
|
||||||
|
log.info("...Created new Hunter with email: ${hunter.email}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
11
src/main/resources/logback.xml
Normal file
11
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<logger name="net.halfbinary.scavengerhuntapi" level="debug" />
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
Reference in New Issue
Block a user