Skip to content

Commit

Permalink
Feature/44 vytvořit na BE i FE uživatelské lokace (#58)
Browse files Browse the repository at this point in the history
* added backend and frontend for user weather locations

---------

Co-authored-by: Štěpán Moc <[email protected]>
  • Loading branch information
MocStepan and MocStepan authored Apr 28, 2024
1 parent d4fa718 commit 632a0e6
Show file tree
Hide file tree
Showing 38 changed files with 827 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package com.tul.backend.auth.base.dto
import com.tul.backend.auth.base.valueobject.AuthUserRole
import com.tul.backend.auth.base.valueobject.EmailAddress
import com.tul.backend.auth.entity.AuthUser
import org.springframework.security.core.AuthenticatedPrincipal

data class AccessTokenClaims(
val authUserId: Long,
val authUserRole: AuthUserRole,
val email: EmailAddress
) : AuthenticatedPrincipal {
override val authUserId: Long,
override val authUserRole: AuthUserRole,
override val email: EmailAddress
) : AuthJwtClaims {

constructor(authUser: AuthUser) : this(
authUserId = authUser.id,
Expand All @@ -18,4 +17,4 @@ data class AccessTokenClaims(
)

override fun getName(): String = email.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tul.backend.auth.base.dto

import com.tul.backend.auth.base.valueobject.AuthUserRole
import com.tul.backend.auth.base.valueobject.EmailAddress
import org.springframework.security.core.AuthenticatedPrincipal

interface AuthJwtClaims : AuthenticatedPrincipal {
val authUserId: Long
val authUserRole: AuthUserRole
val email: EmailAddress
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ data class RegisterDTO(
) {
fun isValid(): Boolean =
username.isNotBlank() &&
password.isNotBlank() &&
email.isValid() &&
password == passwordConfirmation
password.isNotBlank() &&
email.isValid() &&
password == passwordConfirmation
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import com.tul.backend.auth.base.valueobject.AuthUserRole
import com.tul.backend.auth.base.valueobject.AuthUserRole.USER
import com.tul.backend.auth.base.valueobject.EmailAddress
import com.tul.backend.auth.dto.RegisterDTO
import com.tul.backend.weather.entity.UserWeatherLocation
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.OneToMany

@Entity
class AuthUser(
Expand All @@ -20,7 +22,9 @@ class AuthUser(
val email: EmailAddress,
var password: String,
@Enumerated(EnumType.STRING)
val role: AuthUserRole = USER
val role: AuthUserRole = USER,
@OneToMany(mappedBy = "user")
val locations: List<UserWeatherLocation> = mutableListOf()
) {
companion object {
fun from(registerDTO: RegisterDTO): AuthUser {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.tul.backend.weather.controller

import com.tul.backend.auth.base.dto.AuthJwtClaims
import com.tul.backend.weather.dto.CurrentWeatherDTO
import com.tul.backend.weather.dto.ForecastWeatherDTO
import com.tul.backend.weather.dto.UserWeatherLocationDTO
import com.tul.backend.weather.service.WeatherService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

Expand All @@ -32,4 +38,36 @@ class WeatherController(
val status = if (response != null) HttpStatus.OK else HttpStatus.NOT_FOUND
return ResponseEntity(response, status)
}

@PostMapping("/v1/weather/location")
fun saveUserWeatherLocation(
@RequestBody location: String,
authentication: Authentication
): ResponseEntity<Boolean> {
val claims = authentication.principal as AuthJwtClaims
val response = weatherService.saveUserWeatherLocation(location, claims)
val status = if (response) HttpStatus.OK else HttpStatus.BAD_REQUEST
return ResponseEntity(response, status)
}

@GetMapping("/v1/weather/locations")
fun getUserWeatherLocations(
authentication: Authentication
): ResponseEntity<List<UserWeatherLocationDTO>?> {
val claims = authentication.principal as AuthJwtClaims
val response = weatherService.getUserWeatherLocations(claims)
val status = if (response != null) HttpStatus.OK else HttpStatus.NOT_FOUND
return ResponseEntity(response, status)
}

@DeleteMapping("/v1/weather/location/{id}")
fun deleteUserWeatherLocation(
@PathVariable id: Long,
authentication: Authentication
): ResponseEntity<Boolean> {
val claims = authentication.principal as AuthJwtClaims
val response = weatherService.deleteUserWeatherLocation(id, claims)
val status = if (response) HttpStatus.OK else HttpStatus.NOT_FOUND
return ResponseEntity(response, status)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ data class CurrentWeatherDTO(
val temperature: Double,
val cloudCover: Int,
val windSpeed: Double,
val isDay: Boolean
val isDay: Boolean,
var location: LocationDTO? = null
) {

companion object {
Expand All @@ -22,4 +23,4 @@ data class CurrentWeatherDTO(
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.tul.backend.weather.dto
import com.tul.backend.weather.valueobject.LocationOutcomeJson

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

companion object {
Expand All @@ -15,4 +15,4 @@ data class LocationDTO(
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tul.backend.weather.dto

import com.tul.backend.weather.entity.UserWeatherLocation

data class UserWeatherLocationDTO(
val id: Long,
val location: String
) {
companion object {
fun from(userWeatherLocation: UserWeatherLocation): UserWeatherLocationDTO {
return UserWeatherLocationDTO(
id = userWeatherLocation.id,
location = userWeatherLocation.location
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.tul.backend.weather.entity

import com.tul.backend.auth.entity.AuthUser
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.ManyToOne

@Entity
class UserWeatherLocation(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
val location: String,
@ManyToOne(optional = false)
val user: AuthUser
) {

companion object {
fun from(location: String, user: AuthUser): UserWeatherLocation {
return UserWeatherLocation(
location = location,
user = user
)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tul.backend.weather.repository

import com.tul.backend.weather.entity.UserWeatherLocation
import org.springframework.data.jpa.repository.JpaRepository

interface UserWeatherLocationRepository : JpaRepository<UserWeatherLocation, Long> {

fun findByUser_Id(id: Long): List<UserWeatherLocation>

fun existsByUser_IdAndLocation(id: Long, location: String): Boolean

fun existsByUser_IdAndId(id: Long, id1: Long): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package com.tul.backend.weather.service

import com.tul.backend.auth.base.dto.AuthJwtClaims
import com.tul.backend.auth.repository.AuthUserRepository
import com.tul.backend.weather.dto.CurrentWeatherDTO
import com.tul.backend.weather.dto.ForecastWeatherDTO
import com.tul.backend.weather.dto.UserWeatherLocationDTO
import com.tul.backend.weather.entity.UserWeatherLocation
import com.tul.backend.weather.repository.UserWeatherLocationRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

private val log = KotlinLogging.logger {}

@Service
@Transactional
class WeatherService(
private val weatherHandler: WeatherHandler
private val weatherHandler: WeatherHandler,
private val authUserRepository: AuthUserRepository,
private val userWeatherLocationRepository: UserWeatherLocationRepository
) {
fun getCurrentWeather(location: String): CurrentWeatherDTO? {
val currentWeatherDTO = weatherHandler.getCurrentWeather(location)
Expand All @@ -28,4 +38,44 @@ class WeatherService(
}
return forecastWeatherDTO
}

fun saveUserWeatherLocation(location: String, claims: AuthJwtClaims): Boolean {
val authUser = authUserRepository.findByIdOrNull(claims.authUserId)
if (authUser == null) {
log.warn { "No user found for ${claims.authUserId}" }
return false
}

val exists = userWeatherLocationRepository.existsByUser_IdAndLocation(authUser.id, location)
if (exists) {
log.warn { "Location $location already exists for user ${authUser.id}" }
return false
}

userWeatherLocationRepository.save(UserWeatherLocation.from(location, authUser))
return true
}

fun getUserWeatherLocations(claims: AuthJwtClaims): List<UserWeatherLocationDTO>? {
val authUser = authUserRepository.findByIdOrNull(claims.authUserId)
if (authUser == null) {
log.warn { "No user found for ${claims.authUserId}" }
return null
}

return userWeatherLocationRepository.findByUser_Id(authUser.id).map {
UserWeatherLocationDTO.from(it)
}
}

fun deleteUserWeatherLocation(id: Long, claims: AuthJwtClaims): Boolean {
val exists = userWeatherLocationRepository.existsByUser_IdAndId(claims.authUserId, id)
if (!exists) {
log.warn { "Location $id not found for user ${claims.authUserId}" }
return false
}

userWeatherLocationRepository.deleteById(id)
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">


<changeSet id="1714045336927-1" author="stepan.moc">
<createTable tableName="user_weather_location">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_userweatherlocation"/>
</column>
<column name="location" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="user_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="user_id" baseTableName="user_weather_location"
constraintName="FK_USERWEATHERLOCATION_ON_USER" referencedColumnNames="id"
referencedTableName="auth_user"/>
</changeSet>
</databaseChangeLog>
22 changes: 18 additions & 4 deletions backend/src/test/kotlin/com/tul/backend/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.tul.backend.auth.base.dto.AccessTokenClaims
import com.tul.backend.auth.base.dto.AuthJwtClaims
import com.tul.backend.auth.base.valueobject.AuthUserRole
import com.tul.backend.auth.base.valueobject.EmailAddress
import com.tul.backend.auth.entity.AuthUser
Expand All @@ -16,10 +18,10 @@ val objectMapper: ObjectMapper = jacksonObjectMapper()

fun createAuthUser(
id: Long = 0L,
username: String = "admin",
email: EmailAddress = EmailAddress("admin@admin.cz"),
password: String = "admin",
role: AuthUserRole = AuthUserRole.ADMIN
username: String = "test",
email: EmailAddress = EmailAddress("test@test.cz"),
password: String = "test",
role: AuthUserRole = AuthUserRole.USER
): AuthUser {
return AuthUser(
id = id,
Expand All @@ -29,3 +31,15 @@ fun createAuthUser(
role = role
)
}

fun createAccessTokenClaims(
authUserId: Long = 0L,
role: AuthUserRole = AuthUserRole.USER,
email: String = "[email protected]"
) = AccessTokenClaims(
authUserId = authUserId,
authUserRole = role,
email = EmailAddress(email)
)

fun createUserClaims(): AuthJwtClaims = createAccessTokenClaims()
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.tul.backend.auth.base.dto.AccessTokenClaims
import com.tul.backend.createAuthUser
import com.tul.backend.objectMapper
import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.core.spec.style.FeatureSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
Expand Down
Loading

0 comments on commit 632a0e6

Please sign in to comment.