Skip to content

Commit c9df19d

Browse files
authored
feat/qg-268: реализована подгрузка записей на странице журнала (#269)
2 parents 72b8c2a + ab12738 commit c9df19d

37 files changed

Lines changed: 787 additions & 174 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package pro.azhidkov.platform.errors
2+
3+
import kotlin.reflect.KProperty1
4+
import kotlin.reflect.jvm.jvmErasure
5+
6+
7+
class ResourceNotFoundException(
8+
val type: String,
9+
val keys: List<Pair<String, Any?>>,
10+
override val message: String = "Resource $type not found by ${keys.format()}",
11+
errorCode: String = "resource-not-found",
12+
cause: Throwable? = null
13+
) : DomainError(message, cause, errorCode = errorCode) {
14+
15+
init {
16+
require(keys.isNotEmpty())
17+
}
18+
19+
constructor(prop: KProperty1<*, Any?>, key: Any) : this(
20+
prop.parameters[0].type.jvmErasure.simpleName!!,
21+
listOf(prop.name to key)
22+
)
23+
24+
}
25+
26+
private fun List<Pair<String, Any?>>.format(): String =
27+
when (size) {
28+
1 -> first().let(::formatAttrValue)
29+
else -> this.joinToString(", ", prefix = "[", postfix = "]", transform = ::formatAttrValue)
30+
}
31+
32+
private fun formatAttrValue(attrValue: Pair<String, Any?>): String = "${attrValue.first}=${attrValue.second}"

app/src/main/kotlin/pro/azhidkov/platform/spring/mvc/ModelAndViewExt.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ fun modelAndView(viewName: String, model: Map<String, Any?>): ModelAndView {
2828
return modelAndView
2929
}
3030

31-
fun viewId(viewName: String, fragment: String?) = viewName + (fragment?.let { " :: $it" } ?: "")
31+
fun viewId(viewName: String, fragment: String? = null) = viewName + (fragment?.let { " :: $it" } ?: "")

app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoRepository.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import pro.azhidkov.platform.spring.sdj.findOneBy
2424
import pro.azhidkov.platform.spring.sdj.mapContent
2525
import pro.azhidkov.platform.spring.sdj.query.QueryBuilder
2626
import pro.azhidkov.platform.spring.sdj.query.query
27+
import kotlin.math.min
2728
import kotlin.reflect.KClass
2829
import kotlin.reflect.KProperty1
2930

@@ -176,18 +177,23 @@ class ErgoRepository<T : Any, ID : Any>(
176177
queryBuilder: QueryBuilder.() -> Unit = {}
177178
): Slice<T> {
178179
val query = query(queryBuilder)
180+
.with(
181+
PageRequest.of(
182+
pageRequest.pageNumber,
183+
min(pageRequest.pageSize, Int.MAX_VALUE - 1) + 1,
184+
pageRequest.sort
185+
)
186+
)
179187
val res = jdbcAggregateTemplate.findAll(
180188
query,
181189
relationalPersistentEntity.type,
182-
PageRequest.of(pageRequest.pageNumber, pageRequest.pageSize + 1, pageRequest.sort)
183190
)
184191

185-
val hydratedPage = res.content
192+
val hydratedPage = res
186193
.take(pageRequest.pageSize)
187194
.let { jdbcAggregateTemplate.hydrate(it, FetchSpec(fetch)) }
188-
val hasMore = res.totalElements > pageRequest.pageSize
189195

190-
return SliceImpl(hydratedPage, pageRequest, hasMore)
196+
return SliceImpl(hydratedPage, Pageable.unpaged(), hydratedPage.size < res.size)
191197
}
192198

193199
fun findPage(
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package pro.qyoga.app.infra
2+
3+
import org.slf4j.LoggerFactory
4+
import org.springframework.http.HttpStatus
5+
import org.springframework.web.bind.annotation.ControllerAdvice
6+
import org.springframework.web.bind.annotation.ExceptionHandler
7+
import org.springframework.web.bind.annotation.ResponseStatus
8+
import org.springframework.web.servlet.ModelAndView
9+
import pro.azhidkov.platform.errors.ResourceNotFoundException
10+
import pro.qyoga.app.platform.notFound
11+
12+
13+
@ControllerAdvice
14+
class GlobalErrorHandler {
15+
16+
private val log = LoggerFactory.getLogger(javaClass)
17+
18+
@ResponseStatus(HttpStatus.CONFLICT)
19+
@ExceptionHandler(value = [ResourceNotFoundException::class])
20+
fun handleResourceNotFound(ex: ResourceNotFoundException): ModelAndView {
21+
log.info("Resource not found", ex)
22+
return notFound
23+
}
24+
25+
}

app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class WebSecurityConfig(
6464
)
6565
.permitAll()
6666
.requestMatchers(HttpMethod.POST, "/login", "/register", "/surveys", "/error-p").permitAll()
67-
.requestMatchers("/error").permitAll()
67+
.requestMatchers("/error/**").permitAll()
6868

6969
.requestMatchers("/**").denyAll()
7070

app/src/main/kotlin/pro/qyoga/app/platform/Responses.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package pro.qyoga.app.platform
22

33
import org.springframework.core.io.InputStreamResource
4+
import org.springframework.http.HttpStatus
45
import org.springframework.http.MediaType
56
import org.springframework.http.ResponseEntity
67
import org.springframework.web.servlet.ModelAndView
78
import org.springframework.web.servlet.view.RedirectView
89
import pro.azhidkov.platform.file_storage.api.StoredFileInputStream
910

1011

11-
val notFound = ModelAndView("forward:error/404")
12+
val notFound = ModelAndView("error/404", HttpStatus.NOT_FOUND)
1213

1314
fun seeOther(url: String) = ModelAndView(RedirectView(url, true, false))
1415

app/src/main/kotlin/pro/qyoga/app/publc/register/RegisterTherapistOp.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,13 @@ class RegisterTherapistOp(
7575
}
7676

7777
override fun invoke(registerTherapistRequest: RegisterTherapistRequest): Therapist {
78-
log.info("Registering new therapist: {}", registerTherapistRequest)
79-
8078
if (captchaService.isInvalid(registerTherapistRequest.captchaAnswer)) {
79+
log.info("Register therapist with invalid captcha request terminated")
8180
throw RegistrationException.invalidCaptcha(newCaptcha = captchaService.generateCaptcha())
8281
}
8382

83+
log.info("Registering new therapist: {}", registerTherapistRequest)
84+
8485
val password = generateRandomPassword()
8586
val therapist = runCatching { createTherapistUser(registerTherapistRequest, password) }
8687
.mapFailure<DuplicatedEmailException, _> { ex ->

app/src/main/kotlin/pro/qyoga/app/therapist/clients/ClientPageModel.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package pro.qyoga.app.therapist.clients
22

3+
import org.springframework.ui.ModelMap
34
import org.springframework.web.servlet.ModelAndView
4-
import pro.azhidkov.platform.spring.mvc.modelAndView
5+
import pro.azhidkov.platform.spring.mvc.viewId
56
import pro.qyoga.core.clients.cards.model.Client
67
import pro.qyoga.core.clients.cards.toDto
78

@@ -13,16 +14,17 @@ enum class ClientPageTab {
1314
ADD_JOURNAL_ENTRY
1415
}
1516

16-
fun clientPageModel(
17-
client: Client,
18-
activeTab: ClientPageTab,
19-
fragmentModel: Map<String, Any?>
20-
): ModelAndView {
21-
val modelAndView = modelAndView(
22-
"therapist/clients/client-edit", mapOf(
17+
interface ClientPageFragmentModel {
18+
val model: ModelMap
19+
}
20+
21+
data class ClientPageModel<T : ClientPageFragmentModel>(
22+
private val client: Client,
23+
private val activeTab: ClientPageTab,
24+
val fragmentModel: T
25+
) : ModelAndView(
26+
viewId("therapist/clients/client-edit"), mapOf(
2327
"client" to client.toDto(),
2428
"activeTab" to activeTab,
25-
) + fragmentModel
29+
) + fragmentModel.model
2630
)
27-
return modelAndView
28-
}

app/src/main/kotlin/pro/qyoga/app/therapist/clients/cards/EditClientCardPageController.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,38 @@ package pro.qyoga.app.therapist.clients.cards
22

33
import org.springframework.data.repository.findByIdOrNull
44
import org.springframework.stereotype.Controller
5+
import org.springframework.ui.ModelMap
56
import org.springframework.web.bind.annotation.GetMapping
67
import org.springframework.web.bind.annotation.PathVariable
78
import org.springframework.web.bind.annotation.PostMapping
89
import org.springframework.web.servlet.ModelAndView
910
import pro.azhidkov.platform.kotlin.mapNull
1011
import pro.azhidkov.platform.kotlin.mapSuccessOrNull
1112
import pro.azhidkov.platform.kotlin.recoverFailure
13+
import pro.azhidkov.platform.spring.mvc.viewId
1214
import pro.qyoga.app.platform.notFound
15+
import pro.qyoga.app.therapist.clients.ClientPageFragmentModel
16+
import pro.qyoga.app.therapist.clients.ClientPageModel
1317
import pro.qyoga.app.therapist.clients.ClientPageTab
14-
import pro.qyoga.app.therapist.clients.clientPageModel
1518
import pro.qyoga.core.clients.cards.ClientsRepo
1619
import pro.qyoga.core.clients.cards.dtos.ClientCardDto
1720
import pro.qyoga.core.clients.cards.errors.DuplicatedPhoneException
1821
import pro.qyoga.core.clients.cards.patchedBy
1922
import java.util.*
2023

24+
data class EditClientCardPageModel(
25+
private val formAction: String
26+
) : ClientPageFragmentModel,
27+
ModelAndView(
28+
viewId("/therapist/clients/client-edit"),
29+
mapOf(
30+
"formAction" to formAction
31+
)
32+
) {
33+
34+
override val model: ModelMap = super<ModelAndView>.modelMap
35+
36+
}
2137

2238
@Controller
2339
class EditClientCardPageController(
@@ -31,9 +47,11 @@ class EditClientCardPageController(
3147
val client = clientsRepo.findByIdOrNull(clientId)
3248
?: return notFound
3349

34-
return clientPageModel(
35-
client, ClientPageTab.CARD, mapOf(
36-
"formAction" to "/therapist/clients/${client.id}/card"
50+
return ClientPageModel(
51+
client,
52+
ClientPageTab.CARD,
53+
EditClientCardPageModel(
54+
"/therapist/clients/${client.id}/card"
3755
)
3856
)
3957
}

app/src/main/kotlin/pro/qyoga/app/therapist/clients/files/ClientFilesPageController.kt

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,36 @@ package pro.qyoga.app.therapist.clients.files
33
import org.springframework.data.repository.findByIdOrNull
44
import org.springframework.http.ResponseEntity
55
import org.springframework.stereotype.Controller
6+
import org.springframework.ui.ModelMap
67
import org.springframework.web.bind.annotation.*
78
import org.springframework.web.multipart.MultipartFile
89
import org.springframework.web.servlet.ModelAndView
910
import pro.azhidkov.platform.spring.mvc.modelAndView
11+
import pro.azhidkov.platform.spring.mvc.viewId
1012
import pro.qyoga.app.platform.ResponseEntityExt
1113
import pro.qyoga.app.platform.notFound
1214
import pro.qyoga.app.platform.toStoredFile
15+
import pro.qyoga.app.therapist.clients.ClientPageFragmentModel
16+
import pro.qyoga.app.therapist.clients.ClientPageModel
1317
import pro.qyoga.app.therapist.clients.ClientPageTab
14-
import pro.qyoga.app.therapist.clients.clientPageModel
1518
import pro.qyoga.core.clients.cards.ClientsRepo
1619
import pro.qyoga.core.clients.cards.model.ClientRef
1720
import pro.qyoga.core.clients.files.ClientFilesService
1821
import pro.qyoga.core.clients.files.impl.ClientFilesRepo
22+
import pro.qyoga.core.clients.files.model.ClientFile
1923
import java.util.*
2024

25+
data class ClientFilesPageModel(
26+
private val clientFiles: List<ClientFile>,
27+
) : ClientPageFragmentModel,
28+
ModelAndView(
29+
viewId("client-files-fragment"),
30+
mapOf("clientFiles" to clientFiles)
31+
) {
32+
33+
override val model: ModelMap = super<ModelAndView>.modelMap
34+
35+
}
2136

2237
@Controller
2338
@RequestMapping("/therapist/clients/{clientId}/files")
@@ -33,10 +48,10 @@ class ClientFilesPageController(
3348

3449
val files = clientFilesService.findFilesPage(clientId, ClientFilesRepo.Page.tenNewest)
3550

36-
return clientPageModel(
37-
client, ClientPageTab.FILES, mapOf(
38-
"clientFiles" to files.content
39-
)
51+
return ClientPageModel(
52+
client,
53+
ClientPageTab.FILES,
54+
ClientFilesPageModel(files.content)
4055
)
4156
}
4257

0 commit comments

Comments
 (0)