Skip to content

Commit

Permalink
add weather and geo webclient for current and forecast weathe (#43)
Browse files Browse the repository at this point in the history
* add weather and geo webclient for current and forecast weather, also add tests.
  • Loading branch information
MocStepan authored Apr 18, 2024
1 parent 3c7db21 commit d3a879a
Show file tree
Hide file tree
Showing 71 changed files with 1,830 additions and 150 deletions.
23 changes: 14 additions & 9 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ java {
}

configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
testImplementation {
exclude(group = "org.mockito") // it is shipped with spring and there is no need, since we use mockk
}
Expand All @@ -35,41 +38,43 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux")

implementation("io.github.oshai:kotlin-logging-jvm:5.1.0")

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")

implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")
implementation("io.github.oshai:kotlin-logging-jvm:5.1.0")

implementation("org.liquibase:liquibase-core:4.26.0")
implementation("org.postgresql:postgresql")

implementation ("org.postgresql:postgresql")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

val jjwtVersion = "0.12.5"
implementation("io.jsonwebtoken:jjwt-api:$jjwtVersion")
runtimeOnly("io.jsonwebtoken:jjwt-impl:$jjwtVersion")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:$jjwtVersion")

val kotestVersion = "5.8.0"
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")

val kotestVersion = "5.8.0"
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
testImplementation("io.kotest:kotest-property:$kotestVersion")
testImplementation("com.ninja-squad:springmockk:4.0.2")
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
testImplementation("com.ninja-squad:springmockk:4.0.2")
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
jvmTarget = "17"
verbose = true
}
}

tasks.withType<Test> {
useJUnitPlatform()
}

tasks.test {
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class SecurityConfiguration(
private val userUnsecuredEndpoints =
arrayOf(
"/api/auth/login",
"/api/auth/register"
"/api/auth/register",
"/api/weather/current/*",
"/api/weather/forecast/*",
)

private val adminUnsecuredEndpoints =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package com.tul.backend.auth.base.service

import com.tul.backend.auth.entity.AuthUser
import com.tul.backend.auth.repository.AuthUserRepository
import jakarta.transaction.Transactional
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.tul.backend.auth.controller

import com.tul.backend.auth.dto.AuthUserDTO
import com.tul.backend.auth.dto.LoginDTO
import com.tul.backend.auth.dto.RegisterDTO
import com.tul.backend.auth.service.AuthUserService
Expand Down Expand Up @@ -34,9 +33,9 @@ class AuthUserController(
@PostMapping("/auth/register")
fun register(
@RequestBody registerDTO: RegisterDTO,
): ResponseEntity<AuthUserDTO?> {
): ResponseEntity<Boolean> {
val response = authUserService.register(registerDTO)
val status = if (response != null) HttpStatus.OK else HttpStatus.BAD_REQUEST
val status = if (response) HttpStatus.OK else HttpStatus.BAD_REQUEST
return ResponseEntity(response, status)
}
}

This file was deleted.

20 changes: 0 additions & 20 deletions backend/src/main/kotlin/com/tul/backend/auth/dto/AuthUserDTO.kt

This file was deleted.

6 changes: 3 additions & 3 deletions backend/src/main/kotlin/com/tul/backend/auth/dto/LoginDTO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package com.tul.backend.auth.dto
import com.tul.backend.auth.base.valueobject.EmailAddress

data class LoginDTO(
override val email: EmailAddress,
override val password: String,
) : AuthUserBase {
val email: EmailAddress,
val password: String,
) {
fun isValid(): Boolean {
return email.isValid() && password.isNotEmpty()
}
Expand Down
5 changes: 0 additions & 5 deletions backend/src/main/kotlin/com/tul/backend/auth/dto/TokenDTO.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.tul.backend.auth.entity

import com.tul.backend.auth.base.valueobject.AuthUserRole
import com.tul.backend.auth.base.valueobject.EmailAddress
import com.tul.backend.auth.dto.AuthUserBase
import com.tul.backend.auth.dto.RegisterDTO
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
Expand All @@ -17,11 +16,11 @@ class AuthUser(
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
val username: String,
override val email: EmailAddress,
override var password: String,
val email: EmailAddress,
var password: String,
@Enumerated(EnumType.STRING)
val role: AuthUserRole,
) : AuthUserBase {
) {
companion object {
fun from(registerDTO: RegisterDTO): AuthUser {
return AuthUser(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.tul.backend.auth.service

import com.tul.backend.auth.dto.AuthUserDTO
import com.tul.backend.auth.dto.LoginDTO
import com.tul.backend.auth.dto.RegisterDTO
import com.tul.backend.auth.repository.AuthUserRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

private val log = KotlinLogging.logger {}

Expand All @@ -27,21 +26,21 @@ class AuthUserService(
return authenticationHandler.authenticate(loginDTO, request, response)
}

fun register(registerDTO: RegisterDTO): AuthUserDTO? {
fun register(registerDTO: RegisterDTO): Boolean {
if (!registerDTO.isValid()) {
log.warn { "RegisterDTO: $registerDTO is invalid" }
return null
return false
}

val exists = authUserRepository.existsByEmail(registerDTO.email.value)
if (exists) {
log.warn { "User with email: ${registerDTO.email} already exists" }
return null
return false
}

val authUser = authUserRepository.save(
authUserRepository.save(
authenticationHandler.hashRegistrationPassword(registerDTO)
)
return AuthUserDTO.from(authUser)
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import com.tul.backend.auth.dto.RegisterDTO
import com.tul.backend.auth.entity.AuthUser
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import jakarta.transaction.Transactional
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tul.backend.configuration.jackson
package com.tul.backend.shared.jackson

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.tul.backend.weather.controller

import com.tul.backend.weather.dto.CurrentWeatherDTO
import com.tul.backend.weather.dto.ForecastWeatherDTO
import com.tul.backend.weather.service.WeatherService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

Expand All @@ -12,9 +15,20 @@ import org.springframework.web.bind.annotation.RestController
class WeatherController(
private val weatherService: WeatherService,
) {
@GetMapping("/auth/weather/actual-weather")
fun getActualWeather(): ResponseEntity<String?> {
val response = weatherService.getActualWeather()
@GetMapping("/weather/current/{location}")
fun getCurrentWeather(
@PathVariable location: String
): ResponseEntity<CurrentWeatherDTO?> {
val response = weatherService.getCurrentWeather(location)
val status = if (response != null) HttpStatus.OK else HttpStatus.NOT_FOUND
return ResponseEntity(response, status)
}

@GetMapping("/weather/forecast/{location}")
fun getForecastWeather(
@PathVariable location: String
): ResponseEntity<ForecastWeatherDTO?> {
val response = weatherService.getForecastWeather(location)
val status = if (response != null) HttpStatus.OK else HttpStatus.NOT_FOUND
return ResponseEntity(response, status)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.tul.backend.weather.dto

import com.tul.backend.weather.valueobject.CurrentWeatherJson
import java.time.LocalDateTime

data class CurrentWeatherDTO(
val time: LocalDateTime,
val temperature: Double,
val cloudCover: Int,
val windSpeed: Double,
val isDay: Boolean
) {

companion object {
fun from(currentWeatherJson: CurrentWeatherJson): CurrentWeatherDTO {
return CurrentWeatherDTO(
currentWeatherJson.current.time,
currentWeatherJson.current.temperature,
currentWeatherJson.current.cloudCover,
currentWeatherJson.current.windSpeed,
currentWeatherJson.current.isDay == 1
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.tul.backend.weather.dto

import com.tul.backend.weather.valueobject.ForecastWeatherJson
import java.time.LocalDate

data class ForecastWeatherDTO(
val time: List<LocalDate>,
val maxTemperature: List<Double>,
val minTemperature: List<Double>,
val maxWindSpeed: List<Double>
) {

companion object {
fun from(forecastWeatherJson: ForecastWeatherJson): ForecastWeatherDTO {
return ForecastWeatherDTO(
forecastWeatherJson.daily.time,
forecastWeatherJson.daily.maxTemperature,
forecastWeatherJson.daily.minTemperature,
forecastWeatherJson.daily.maxWindSpeed
)
}
}
}
18 changes: 18 additions & 0 deletions backend/src/main/kotlin/com/tul/backend/weather/dto/LocationDTO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.tul.backend.weather.dto

import com.tul.backend.weather.valueobject.LocationOutcomeJson

data class LocationDTO(
var latitude: Double,
var longitude: Double
) {

companion object {
fun from(locationOutcomeJson: LocationOutcomeJson): LocationDTO {
return LocationDTO(
latitude = locationOutcomeJson.latitude,
longitude = locationOutcomeJson.longitude
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.tul.backend.weather.service

import com.tul.backend.weather.dto.CurrentWeatherDTO
import com.tul.backend.weather.dto.ForecastWeatherDTO
import com.tul.backend.weather.service.extractor.WeatherExtractor
import com.tul.backend.weather.service.parser.WebClientParser
import com.tul.backend.weather.valueobject.WeatherStatus.CURRENT
import com.tul.backend.weather.valueobject.WeatherStatus.FORECAST
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.stereotype.Service

private val log = KotlinLogging.logger {}

@Service
class WeatherHandler(
weatherExtractors: List<WeatherExtractor>,
private val webClientParser: WebClientParser
) {

private val weatherExtractorList = weatherExtractors.associateBy { it.weatherStatus }

fun getCurrentWeather(location: String): CurrentWeatherDTO? {
val weatherExtractor = weatherExtractorList[CURRENT]
if (weatherExtractor == null) {
log.warn { "No weather extractor found for $CURRENT" }
return null
}
val weatherJson = weatherExtractor.getWeatherJson(location) ?: return null
return webClientParser.parseCurrentWeatherJson(weatherJson)
}

fun getForecastWeather(location: String): ForecastWeatherDTO? {
val weatherExtractor = weatherExtractorList[FORECAST]
if (weatherExtractor == null) {
log.warn { "No weather extractor found for $FORECAST" }
return null
}
val weatherJson = weatherExtractor.getWeatherJson(location) ?: return null
return webClientParser.parseForecastWeatherJson(weatherJson)
}
}
Loading

0 comments on commit d3a879a

Please sign in to comment.