Skip to content

Commit 4934cc8

Browse files
use custom query for permissions
1 parent 5bb0a82 commit 4934cc8

File tree

7 files changed

+120
-59
lines changed

7 files changed

+120
-59
lines changed

http/user-app.http

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
### Permissions
22

33
###
4-
GET {{host}}/permissions?page=0&size=10&sort=id&direction=ASC
4+
GET {{host}}/permissions?page=0&size=10&sort=id&direction=DESC&search=test&createdFrom=1759429679915&createdBy=78652bda-8e80-432b-ba1b-47b4f7ee98e6
5+
Authorization: Bearer {{oauthToken}}
6+
7+
###
8+
GET {{host}}/permissions/count?search=test&createdFrom=1759429679915&createdBy=78652bda-8e80-432b-ba1b-47b4f7ee98e6
59
Authorization: Bearer {{oauthToken}}
610

711
###
@@ -20,11 +24,11 @@ GET {{host}}/permissions/{{permissionId}}
2024
Authorization: Bearer {{oauthToken}}
2125

2226
###
23-
PUT {{host}}/permissions/1
27+
PUT {{host}}/permissions/{{permissionId}}
2428
Content-Type: application/json
2529
Authorization: Bearer {{oauthToken}}
2630

27-
{"name": "test-coroutine-changed 22", "description": "test description coroutine changed!", "version": 1}
31+
{"name": "test-coroutine-changed", "description": "test description coroutine changed!", "version": 1}
2832

2933
###
3034
DELETE {{host}}/permissions/{{permissionId}}

src/main/kotlin/com/softeno/template/app/config/security/ReactiveAuditorConfig.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import org.springframework.security.core.context.ReactiveSecurityContextHolder
1010
import org.springframework.security.core.context.SecurityContext
1111
import org.springframework.security.oauth2.jwt.Jwt
1212
import reactor.core.publisher.Mono
13-
import kotlin.jvm.javaClass
1413

1514

1615
class AuditorAwareImpl : ReactiveAuditorAware<String> {
Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
package com.softeno.template.app.permission.api
22

33
import com.softeno.template.app.common.PrincipalHandler
4-
import com.softeno.template.app.permission.db.getPageRequest
54
import com.softeno.template.app.permission.mapper.PermissionDto
65
import com.softeno.template.app.permission.service.PermissionService
76
import io.micrometer.tracing.Tracer
87
import kotlinx.coroutines.flow.Flow
98
import kotlinx.coroutines.reactor.awaitSingle
10-
import kotlinx.coroutines.reactor.awaitSingleOrNull
119
import org.apache.commons.logging.LogFactory
1210
import org.slf4j.MDC
11+
import org.springframework.data.domain.PageRequest
12+
import org.springframework.data.domain.Sort
1313
import org.springframework.http.ResponseEntity
1414
import org.springframework.validation.annotation.Validated
15-
import org.springframework.web.bind.annotation.DeleteMapping
16-
import org.springframework.web.bind.annotation.GetMapping
17-
import org.springframework.web.bind.annotation.PathVariable
18-
import org.springframework.web.bind.annotation.PostMapping
19-
import org.springframework.web.bind.annotation.PutMapping
20-
import org.springframework.web.bind.annotation.RequestBody
21-
import org.springframework.web.bind.annotation.RequestParam
22-
import org.springframework.web.bind.annotation.RestController
15+
import org.springframework.web.bind.annotation.*
2316
import reactor.core.publisher.Mono
2417
import java.security.Principal
2518

@@ -31,11 +24,24 @@ class PermissionController(
3124
) : PrincipalHandler {
3225
private val log = LogFactory.getLog(javaClass)
3326

27+
data class PermissionSearch(
28+
val search: String?,
29+
val createdBy: String?,
30+
val createdFrom: Long?,
31+
val createdTo: Long?
32+
)
33+
3434
@GetMapping("/permissions")
35-
suspend fun getPermissions(@RequestParam(required = false, defaultValue = "0") page: Int,
36-
@RequestParam(required = false, defaultValue = "10") size: Int,
37-
@RequestParam(required = false, defaultValue = "id") sort: String,
38-
@RequestParam(required = false, defaultValue = "ASC") direction: String, monoPrincipal: Mono<Principal>
35+
suspend fun getPermissions(
36+
@RequestParam(required = false, defaultValue = "0") page: Int,
37+
@RequestParam(required = false, defaultValue = "10") size: Int,
38+
@RequestParam(required = false, defaultValue = "id") sort: String,
39+
@RequestParam(required = false, defaultValue = "ASC") direction: String,
40+
@RequestParam(required = false) search: String? = null,
41+
@RequestParam(required = false) createdBy: String? = null,
42+
@RequestParam(required = false) createdFrom: Long? = null,
43+
@RequestParam(required = false) createdTo: Long? = null,
44+
monoPrincipal: Mono<Principal>
3945
): Flow<PermissionDto> {
4046
showPrincipal(log, monoPrincipal)
4147

@@ -45,7 +51,25 @@ class PermissionController(
4551
val mdcSpan = MDC.get("spanId")
4652
log.debug("Show traceId=$traceId, mdcTraceId=$mdc and mdcSpanId=$mdcSpan")
4753

48-
return permissionService.getAllPermissions(getPageRequest(page, size, sort, direction))
54+
log.debug("Show request params: " +
55+
"page=$page, size=$size, sort=$sort, direction=$direction, " +
56+
"search=$search, createdBy=$createdBy, createdFrom=$createdFrom, createdTo=$createdTo"
57+
)
58+
59+
return permissionService.getAllPermissions(
60+
getPageRequest(page, size, sort, direction),
61+
PermissionSearch(search, createdBy, createdFrom, createdTo)
62+
)
63+
}
64+
65+
@GetMapping("/permissions/count")
66+
suspend fun getPermissionsCount(
67+
@RequestParam(required = false) search: String? = null,
68+
@RequestParam(required = false) createdBy: String? = null,
69+
@RequestParam(required = false) createdFrom: Long? = null,
70+
@RequestParam(required = false) createdTo: Long? = null,
71+
): Long {
72+
return permissionService.countPermissions(PermissionSearch(search, createdBy, createdFrom, createdTo))
4973
}
5074

5175
@GetMapping("/permissions/{id}")
@@ -62,7 +86,11 @@ class PermissionController(
6286
}
6387

6488
@PutMapping("/permissions/{id}")
65-
suspend fun updatePermission(@PathVariable id: Long, @RequestBody permissionDto: PermissionDto, monoPrincipal: Mono<Principal>): ResponseEntity<PermissionDto> {
89+
suspend fun updatePermission(
90+
@PathVariable id: Long,
91+
@RequestBody permissionDto: PermissionDto,
92+
monoPrincipal: Mono<Principal>
93+
): ResponseEntity<PermissionDto> {
6694
val result = permissionService.updatePermission(id, permissionDto, principal = monoPrincipal.awaitSingle())
6795
return ResponseEntity.ok(result)
6896
}
@@ -71,4 +99,8 @@ class PermissionController(
7199
suspend fun deletePermission(@PathVariable id: Long) {
72100
permissionService.deletePermission(id)
73101
}
74-
}
102+
}
103+
104+
fun getPageRequest(page: Int, size: Int, sort: String, direction: String) =
105+
Sort.by(Sort.Order(if (direction == "ASC") Sort.Direction.ASC else Sort.Direction.DESC, sort))
106+
.let { PageRequest.of(page, size, it) }

src/main/kotlin/com/softeno/template/app/permission/db/PermissionRepository.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ package com.softeno.template.app.permission.db
22

33
import com.softeno.template.app.permission.BaseEntity
44
import com.softeno.template.app.permission.Permission
5+
import com.softeno.template.app.permission.api.PermissionController
6+
import kotlinx.coroutines.flow.Flow
57
import kotlinx.coroutines.reactor.awaitSingle
6-
import org.springframework.data.domain.PageRequest
78
import org.springframework.data.domain.Pageable
8-
import org.springframework.data.domain.Sort
99
import org.springframework.data.r2dbc.repository.Query
10-
import org.springframework.data.r2dbc.repository.R2dbcRepository
1110
import org.springframework.data.relational.core.mapping.Column
1211
import org.springframework.data.relational.core.mapping.Table
12+
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
1313
import org.springframework.data.repository.query.Param
1414
import org.springframework.r2dbc.core.DatabaseClient
1515
import org.springframework.r2dbc.core.bind
1616
import org.springframework.stereotype.Component
1717
import org.springframework.stereotype.Repository
18-
import reactor.core.publisher.Flux
1918
import reactor.core.publisher.Mono
2019
import java.util.*
2120
import kotlin.reflect.KProperty1
@@ -24,8 +23,36 @@ import kotlin.reflect.full.memberProperties
2423
import kotlin.reflect.jvm.javaField
2524

2625
@Repository
27-
interface PermissionRepository : R2dbcRepository<Permission, Long>, BatchPermissionRepository<Permission> {
28-
fun findBy(pageable: Pageable): Flux<Permission>
26+
interface PermissionRepository : CoroutineCrudRepository<Permission, Long>, BatchPermissionRepository<Permission> {
27+
28+
@Query("""
29+
SELECT p.* FROM permissions p
30+
WHERE (:#{#search.search} IS NULL OR (
31+
p.name LIKE CONCAT('%', :#{#search.search}, '%') OR
32+
p.description LIKE CONCAT('%', :#{#search.search}, '%')
33+
))
34+
AND (:#{#search.createdFrom} IS NULL OR p.created_date >= :#{#search.createdFrom})
35+
AND (:#{#search.createdTo} IS NULL OR p.created_date <= :#{#search.createdTo})
36+
AND (:#{#search.createdBy} IS NULL OR p.created_by = :#{#search.createdBy})
37+
ORDER BY
38+
CASE WHEN :#{#pageable.sort.toString()} = 'id: ASC' THEN p.id END ASC,
39+
CASE WHEN :#{#pageable.sort.toString()} = 'id: DESC' THEN p.id END DESC
40+
LIMIT :#{#pageable.pageSize}
41+
OFFSET :#{#pageable.offset}
42+
""")
43+
suspend fun findBy(search: PermissionController.PermissionSearch, pageable: Pageable): Flow<Permission>
44+
45+
@Query("""
46+
SELECT COUNT(*) FROM permissions p
47+
WHERE (:#{#search.search} IS NULL OR (
48+
p.name LIKE CONCAT('%', :#{#search.search}, '%') OR
49+
p.description LIKE CONCAT('%', :#{#search.search}, '%')
50+
))
51+
AND (:#{#search.createdFrom} IS NULL OR p.created_date >= :#{#search.createdFrom})
52+
AND (:#{#search.createdTo} IS NULL OR p.created_date <= :#{#search.createdTo})
53+
AND (:#{#search.createdBy} IS NULL OR p.created_by = :#{#search.createdBy})
54+
""")
55+
suspend fun countBy(search: PermissionController.PermissionSearch): Long
2956

3057
@Query("SELECT p.version FROM permissions as p WHERE p.id = :id")
3158
fun findVersionById(@Param("id") id: Long): Mono<Long>
@@ -159,7 +186,3 @@ class BatchPermissionRepositoryImpl<T : BaseEntity>(private val databaseClient:
159186
}
160187

161188
class OperationNotPermittedException(message: String) : RuntimeException(message)
162-
163-
fun getPageRequest(page: Int, size: Int, sort: String, direction: String) =
164-
Sort.by(Sort.Order(if (direction == "ASC") Sort.Direction.ASC else Sort.Direction.DESC, sort))
165-
.let { PageRequest.of(page, size, it) }

src/main/kotlin/com/softeno/template/app/permission/service/PermissionService.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package com.softeno.template.app.permission.service
33
import com.softeno.template.app.kafka.ReactiveKafkaSampleProducer
44
import com.softeno.template.app.kafka.dto.KafkaMessage
55
import com.softeno.template.app.permission.Permission
6+
import com.softeno.template.app.permission.api.PermissionController
67
import com.softeno.template.app.permission.db.PermissionRepository
78
import com.softeno.template.app.permission.mapper.PermissionDto
89
import com.softeno.template.app.permission.mapper.toDto
910
import kotlinx.coroutines.flow.Flow
10-
import kotlinx.coroutines.reactive.asFlow
11-
import kotlinx.coroutines.reactive.awaitFirstOrElse
11+
import kotlinx.coroutines.flow.map
1212
import kotlinx.coroutines.reactive.awaitSingle
1313
import kotlinx.coroutines.reactor.awaitSingleOrNull
1414
import kotlinx.coroutines.slf4j.MDCContext
@@ -33,20 +33,26 @@ class PermissionService(
3333
) {
3434
private val log = LogFactory.getLog(javaClass)
3535

36-
suspend fun getAllPermissions(pageable: Pageable): Flow<PermissionDto> =
36+
suspend fun getAllPermissions(pageable: Pageable, search: PermissionController.PermissionSearch): Flow<PermissionDto> =
3737
withContext(MDCContext()) {
38-
return@withContext permissionRepository.findBy(pageable).map { it.toDto() }.asFlow()
38+
return@withContext permissionRepository.findBy(search, pageable).map { it.toDto() }
39+
}
40+
41+
suspend fun countPermissions(search: PermissionController.PermissionSearch): Long =
42+
withContext(MDCContext()) {
43+
return@withContext permissionRepository.countBy(search)
3944
}
4045

4146
suspend fun getPermission(id: Long): PermissionDto =
4247
withContext(MDCContext()) {
43-
return@withContext permissionRepository.findById(id).awaitFirstOrElse { throw Exception("Not Found: $id") }.toDto()
48+
val result = permissionRepository.findById(id) ?: throw Exception("Not Found: $id")
49+
return@withContext result.toDto()
4450
}
4551

4652
@Transactional
4753
suspend fun createPermission(permissionDto: PermissionDto): PermissionDto =
4854
withContext(MDCContext()) {
49-
val created = permissionRepository.save(Permission(name = permissionDto.name, description = permissionDto.description)).awaitSingle().toDto()
55+
val created = permissionRepository.save(Permission(name = permissionDto.name, description = permissionDto.description)).toDto()
5056
kafkaPublisher.send(KafkaMessage(content = "CREATED_PREMISSION: ${created.id}", traceId = MDC.get("traceId"), spanId = MDC.get("spanId")))
5157
return@withContext created
5258
}
@@ -75,6 +81,6 @@ class PermissionService(
7581

7682
suspend fun deletePermission(id: Long) =
7783
withContext(MDCContext()) {
78-
permissionRepository.deleteById(id).awaitSingleOrNull()
84+
permissionRepository.deleteById(id)
7985
}
8086
}

src/test/kotlin/com/softeno/template/app/IntegrationTest.kt

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@ package com.softeno.template.app
22

33
import com.github.tomakehurst.wiremock.WireMockServer
44
import com.github.tomakehurst.wiremock.client.WireMock.*
5-
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
6-
import com.github.tomakehurst.wiremock.client.WireMock.urlMatching
75
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
86
import com.ninjasquad.springmockk.MockkBean
97
import com.softeno.template.SoftenoMvcJpaApp
108
import com.softeno.template.app.permission.PermissionFixture
119
import com.softeno.template.app.permission.PermissionFixture.Companion.aPermission
1210
import com.softeno.template.app.permission.PermissionFixture.Companion.aPermissionDto
11+
import com.softeno.template.app.permission.api.PermissionController
1312
import com.softeno.template.app.permission.db.OperationNotPermittedException
1413
import com.softeno.template.app.permission.db.PermissionRepository
1514
import com.softeno.template.sample.http.api.SampleResponseDto
16-
import io.mockk.every
15+
import io.mockk.coEvery
16+
import kotlinx.coroutines.flow.asFlow
1717
import kotlinx.coroutines.flow.count
1818
import kotlinx.coroutines.flow.first
1919
import kotlinx.coroutines.flow.toList
20-
import kotlinx.coroutines.reactive.asFlow
21-
import kotlinx.coroutines.reactive.awaitFirstOrNull
22-
import kotlinx.coroutines.reactive.awaitSingle
2320
import kotlinx.coroutines.runBlocking
2421
import kotlinx.coroutines.test.runTest
2522
import org.junit.jupiter.api.AfterEach
2623
import org.junit.jupiter.api.Assertions.assertEquals
27-
import org.junit.jupiter.api.Assertions.assertThrows
2824
import org.junit.jupiter.api.Assertions.assertTrue
2925
import org.junit.jupiter.api.BeforeEach
3026
import org.junit.jupiter.api.Test
@@ -46,8 +42,6 @@ import org.springframework.web.reactive.function.BodyInserters
4642
import org.springframework.web.reactive.function.client.WebClient
4743
import org.testcontainers.containers.PostgreSQLContainer
4844
import org.testcontainers.junit.jupiter.Container
49-
import reactor.core.publisher.Mono
50-
import reactor.kotlin.core.publisher.toFlux
5145

5246
@SpringBootTest(
5347
classes = [SoftenoMvcJpaApp::class],
@@ -104,7 +98,7 @@ abstract class BaseIntegrationTest {
10498
@AfterEach
10599
fun cleanup() {
106100
runBlocking {
107-
permissionRepository.deleteAll().awaitFirstOrNull()
101+
permissionRepository.deleteAll()
108102
}
109103
}
110104

@@ -158,8 +152,8 @@ class BatchPermissionRepositoryImplTest : BaseIntegrationTest(), PermissionFixtu
158152
// then
159153
assertEquals(returned.size, 2)
160154

161-
assertEquals(permissionRepository.findAll().asFlow().count(), 2)
162-
val saved = permissionRepository.findAll().asFlow().toList()
155+
assertEquals(permissionRepository.findAll().count(), 2)
156+
val saved = permissionRepository.findAll().toList()
163157

164158
assertEquals(saved[0].name, aPermission.name)
165159
assertEquals(saved[0].description, aPermission.description)
@@ -186,7 +180,7 @@ class BatchPermissionRepositoryImplTest : BaseIntegrationTest(), PermissionFixtu
186180
// then
187181
assertEquals(returned.size, 2)
188182

189-
val saved = permissionRepository.findAll().asFlow().toList()
183+
val saved = permissionRepository.findAll().toList()
190184
assertEquals(saved.size, 2)
191185

192186
assertEquals(saved[0].uuid, aPermission.uuid)
@@ -222,7 +216,7 @@ class BatchPermissionRepositoryImplTest : BaseIntegrationTest(), PermissionFixtu
222216

223217
assertEquals(changedReturned.size, 2)
224218

225-
val changedSaved = permissionRepository.findAll().asFlow().toList()
219+
val changedSaved = permissionRepository.findAll().toList()
226220
assertEquals(changedSaved.size, 2)
227221

228222
assertEquals(changedSaved[0].id, changedPermission.id)
@@ -260,7 +254,7 @@ class PermissionTest : BaseIntegrationTest(), PermissionFixture {
260254
@Test
261255
fun shouldRetrievePermission() = runTest {
262256
val aPermission = aPermission()
263-
permissionRepository.save(aPermission).awaitSingle()
257+
permissionRepository.save(aPermission)
264258

265259
webTestClient.get().uri("/permissions")
266260
.exchange()
@@ -279,9 +273,9 @@ class PermissionTest : BaseIntegrationTest(), PermissionFixture {
279273
.exchange()
280274
.expectStatus().isOk
281275

282-
assertEquals(permissionRepository.findAll().asFlow().count(), 1)
283-
assertEquals(permissionRepository.findAll().asFlow().first().name, aPermissionDto.name)
284-
assertEquals(permissionRepository.findAll().asFlow().first().description, aPermissionDto.description)
276+
assertEquals(permissionRepository.findAll().count(), 1)
277+
assertEquals(permissionRepository.findAll().first().name, aPermissionDto.name)
278+
assertEquals(permissionRepository.findAll().first().description, aPermissionDto.description)
285279
}
286280
}
287281

@@ -293,14 +287,17 @@ class PermissionTestMockk : BaseIntegrationTest() {
293287

294288
@BeforeEach
295289
fun initMockkRepository() {
296-
every { permissionRepositoryMock.deleteAll() }.answers { Mono.empty<Void>() }
290+
coEvery { permissionRepositoryMock.deleteAll() }.answers { }
297291
}
298292

299293
@Test
300294
fun shouldPersistAndRetrievePermission() = runTest {
301295
val aPermission = aPermission()
302296

303-
every { permissionRepositoryMock.findBy(any<Pageable>()) }.answers { listOf(aPermission).toFlux() }
297+
coEvery { permissionRepositoryMock.findBy(
298+
search = any<PermissionController.PermissionSearch>(),
299+
pageable = any<Pageable>()
300+
) }.answers { listOf(aPermission).asFlow() }
304301

305302
webTestClient.get().uri("/permissions")
306303
.exchange()

0 commit comments

Comments
 (0)