Skip to content

Commit 44e1545

Browse files
authored
Improvement/entity type validators entity fix (#11)
* Changed DTO name. Updated OpenAPI documentation. * Expanded controller advice and added min-max request dto with validator. * Updated entity models. Added script to create schema. * Updated DTO models with validation annotations and added converters. * Updated unit tests. * Fixed unit tests. * Updated REST annotations. * Added new entity EntityType, repository, service, dtos, converters and REST controller. * Changed Entity model (added new field) and changed dtos. * Optimized data retrieval using JPA. * Changed entity type entity and response (included attributes). Changed attribute entity, dto, controller. Changed entity entity and dtos. Code clean up. * Fixed unit test problems. * Updated workflow file.
1 parent 37eb496 commit 44e1545

File tree

61 files changed

+1160
-95
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1160
-95
lines changed

.github/workflows/main.yml

+15-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,20 @@ jobs:
2525
POSTGRES_PASSWORD: postgres
2626
ports:
2727
- 6432:5432
28-
28+
options: >-
29+
--health-cmd pg_isready
30+
--health-interval 10s
31+
--health-timeout 5s
32+
--health-retries 5
2933
steps:
34+
- name: Set up PostgeSQL database
35+
run: |
36+
PGPASSWORD=postgres psql -U postgres -h 127.0.0.1 -p 6432 -c "CREATE DATABASE \"eav-test\";"
37+
38+
- name: Set up PostgeSQL schemas
39+
run: |
40+
PGPASSWORD=postgres psql -U postgres -h 127.0.0.1 -p 6432 -d eav-test -c "CREATE SCHEMA IF NOT EXISTS public;" -c "CREATE SCHEMA IF NOT EXISTS security;"
41+
3042
- name: Checkout source
3143
uses: actions/checkout@v4
3244

@@ -42,5 +54,5 @@ jobs:
4254
- name: Run build with Gradle Wrapper
4355
run: ./gradlew build -x test
4456

45-
#- name: Run code coverage verification
46-
# run: ./gradlew testCoverage
57+
- name: Run code coverage verification
58+
run: ./gradlew testCoverage

build.gradle.kts

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ dependencies {
2727
implementation("io.jsonwebtoken:jjwt-impl:0.12.5")
2828
implementation("io.jsonwebtoken:jjwt-jackson:0.12.5")
2929
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
30+
// implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2")
31+
// implementation("org.glassfish.jaxb:jaxb-runtime:4.0.5")
32+
// implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0")
33+
34+
3035

3136
//implementation("jakarta.validation:jakarta.validation-api:3.0.2")
3237
implementation("org.modelmapper:modelmapper:3.2.0")

src/main/kotlin/com/github/nenadjakic/eav/configuration/MainConfiguration.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.github.nenadjakic.eav.configuration
22

3-
4-
import com.github.nenadjakic.eav.dto.converter.EntityToEntityResponseConverter
5-
import com.github.nenadjakic.eav.dto.converter.RegisterRequestToUserConverter
3+
import com.github.nenadjakic.eav.dto.converter.*
64
import org.modelmapper.ModelMapper
75
import org.springframework.context.annotation.Bean
86
import org.springframework.context.annotation.Configuration
@@ -16,7 +14,6 @@ import org.springframework.security.core.userdetails.UserDetailsService
1614
import org.springframework.security.crypto.password.PasswordEncoder
1715
import java.util.*
1816

19-
2017
@Configuration
2118
@EnableJpaRepositories(basePackages = ["com.github.nenadjakic.eav.repository"])
2219
@EnableAsync
@@ -30,6 +27,11 @@ open class MainConfiguration(
3027
val modelMapper = ModelMapper()
3128
modelMapper.addConverter(RegisterRequestToUserConverter(passwordEncoder))
3229
modelMapper.addConverter(EntityToEntityResponseConverter())
30+
modelMapper.addConverter(AttributeAddRequestToAttributeConverter())
31+
modelMapper.addConverter(AttributeToAttributeResponseConverter())
32+
modelMapper.addConverter(AttributeUpdateRequestToAttributeConverter())
33+
modelMapper.addConverter(AttributeValueToAttributeValueResponseConverter())
34+
modelMapper.addConverter(EntityTypeToEntityTypeResponseConverter())
3335

3436
return modelMapper
3537
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.github.nenadjakic.eav.configuration
2+
3+
import org.springframework.context.annotation.Configuration
4+
import org.springframework.web.servlet.config.annotation.EnableWebMvc
5+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
6+
7+
@Configuration
8+
@EnableWebMvc
9+
open class WebConfiguration : WebMvcConfigurer {
10+
}

src/main/kotlin/com/github/nenadjakic/eav/controller/AttributeController.kt

+76-11
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,121 @@
11
package com.github.nenadjakic.eav.controller
22

3-
import collectionMap
4-
import com.github.nenadjakic.eav.util.RestUtil
5-
import com.github.nenadjakic.eav.dto.AttributeAddResponse
63
import com.github.nenadjakic.eav.dto.AttributeAddRequest
4+
import com.github.nenadjakic.eav.dto.AttributeResponse
75
import com.github.nenadjakic.eav.dto.AttributeUpdateRequest
86
import com.github.nenadjakic.eav.entity.Attribute
7+
import com.github.nenadjakic.eav.extension.collectionMap
98
import com.github.nenadjakic.eav.service.AttributeService
9+
import com.github.nenadjakic.eav.util.RestUtil
10+
import io.swagger.v3.oas.annotations.Operation
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse
12+
import io.swagger.v3.oas.annotations.responses.ApiResponses
13+
import io.swagger.v3.oas.annotations.tags.Tag
1014
import org.modelmapper.ModelMapper
1115
import org.springframework.data.domain.Page
1216
import org.springframework.http.ResponseEntity
1317
import org.springframework.web.bind.annotation.RequestMapping
1418
import org.springframework.web.bind.annotation.RestController
1519
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
1620

21+
@Tag(name = "Attribute controller", description = "API endpoints for managing attributes.")
1722
@RestController
1823
@RequestMapping("/attribute")
1924
open class AttributeController(
2025
private val modelMapper: ModelMapper,
2126
private val attributeService: AttributeService
22-
) : EavReadController<AttributeAddResponse>, EavWriteController<AttributeAddRequest, AttributeUpdateRequest> {
27+
) : EavReadController<AttributeResponse>, EavWriteController<AttributeAddRequest, AttributeUpdateRequest> {
2328

24-
open override fun findAll(): ResponseEntity<List<AttributeAddResponse>> {
29+
@Operation(
30+
operationId = "findAllAttributes",
31+
summary = "Get all attributes",
32+
description = "Returns a list of all attributes.")
33+
@ApiResponses(
34+
value = [
35+
ApiResponse(responseCode = "200", description = "Successful operation")
36+
]
37+
)
38+
open override fun findAll(): ResponseEntity<List<AttributeResponse>> {
2539
val attributes = attributeService.findAll()
26-
val response = modelMapper.collectionMap(attributes, AttributeAddResponse::class.java)
40+
val response = modelMapper.collectionMap(attributes, AttributeResponse::class.java)
2741
return ResponseEntity.ok(response)
2842
}
2943

30-
open override fun findPage(pageNumber: Int, pageSize: Int?): ResponseEntity<Page<AttributeAddResponse>> {
44+
@Operation(
45+
operationId = "findPageWithAttributes",
46+
summary = "Get attributes by page.",
47+
description = "Returns a page of attributes based on page number and page size.")
48+
@ApiResponses(
49+
value = [
50+
ApiResponse(responseCode = "200", description = "Successfully retrieved page of attributes.")
51+
]
52+
)
53+
open override fun findPage(pageNumber: Int, pageSize: Int?): ResponseEntity<Page<AttributeResponse>> {
3154
val page = attributeService.findPage(RestUtil.getPager(pageNumber, pageSize))
32-
val response = page.map { modelMapper.map(it, AttributeAddResponse::class.java) }
55+
val response = page.map { modelMapper.map(it, AttributeResponse::class.java) }
3356
return ResponseEntity.ok(response)
3457
}
3558

36-
open override fun findById(id: Long): ResponseEntity<AttributeAddResponse> {
59+
@Operation(
60+
operationId = "findAttributeById",
61+
summary = "Get attribute by id.",
62+
description = "Returns an attribute with the specified id."
63+
)
64+
@ApiResponses(
65+
value = [
66+
ApiResponse(responseCode = "200", description = "Successfully retrieved attribute."),
67+
ApiResponse(responseCode = "404", description = "Attribute not found.")
68+
]
69+
)
70+
open override fun findById(id: Long): ResponseEntity<AttributeResponse> {
3771
val attribute = attributeService.findById(id)
38-
val response: AttributeAddResponse? = attribute?.let { modelMapper.map(attribute, AttributeAddResponse::class.java) }
72+
val response: AttributeResponse? = attribute?.let { modelMapper.map(attribute, AttributeResponse::class.java) }
3973
return ResponseEntity.ofNullable(response)
40-
4174
}
4275

76+
@Operation(
77+
operationId = "deleteAttributeById",
78+
summary = "Delete attribute by id.",
79+
description = "Deletes an attribute with the specified id."
80+
)
81+
@ApiResponses(
82+
value = [
83+
ApiResponse(responseCode = "204", description = "Entity deleted successfully")
84+
]
85+
)
4386
open override fun deleteById(id: Long): ResponseEntity<Void> {
4487
attributeService.deleteById(id)
4588
return ResponseEntity.noContent().build()
4689
}
4790

91+
@Operation(
92+
operationId = "updateAttribute",
93+
summary = "Update attribute.",
94+
description = "Updates an existing attribute based on the provided model."
95+
)
96+
@ApiResponses(
97+
value = [
98+
ApiResponse(responseCode = "204", description = "Attribute updated successfully."),
99+
ApiResponse(responseCode = "400", description = "Invalid request data.")
100+
]
101+
)
48102
open override fun update(model: AttributeUpdateRequest): ResponseEntity<Void> {
49103
val entity = modelMapper.map(model, Attribute::class.java)
50104
attributeService.update(entity)
51105
return ResponseEntity.noContent().build()
52106
}
53107

108+
@Operation(
109+
operationId = "createAttribute",
110+
summary = "Create attribute.",
111+
description = "Creates a new attribute based on the provided model."
112+
)
113+
@ApiResponses(
114+
value = [
115+
ApiResponse(responseCode = "201", description = "Attribute created successfully."),
116+
ApiResponse(responseCode = "400", description = "Invalid request data.")
117+
]
118+
)
54119
open override fun create(model: AttributeAddRequest): ResponseEntity<Void> {
55120
val entity = modelMapper.map(model, Attribute::class.java)
56121
val createdEntity = attributeService.create(entity)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.github.nenadjakic.eav.controller
2+
3+
import com.github.nenadjakic.eav.dto.AttributeValueResponse
4+
import com.github.nenadjakic.eav.extension.collectionMap
5+
import com.github.nenadjakic.eav.service.AttributeValueService
6+
import io.swagger.v3.oas.annotations.tags.Tag
7+
import org.modelmapper.ModelMapper
8+
import org.springframework.http.ResponseEntity
9+
import org.springframework.web.bind.annotation.GetMapping
10+
import org.springframework.web.bind.annotation.PathVariable
11+
import org.springframework.web.bind.annotation.RequestMapping
12+
import org.springframework.web.bind.annotation.RestController
13+
14+
@Tag(name = "Attribute-value controller", description = "API endpoints for managing values of attributes.")
15+
@RestController
16+
@RequestMapping("/attribute-value")
17+
open class AttributeValueController(
18+
val modelMapper: ModelMapper,
19+
val attributeValueService: AttributeValueService
20+
) {
21+
22+
@GetMapping("entity/{entityId}")
23+
open fun findByEntityId(@PathVariable entityId: Long): ResponseEntity<List<AttributeValueResponse>> {
24+
val attributeValues = attributeValueService.findByEntityId(entityId)
25+
val response = modelMapper.collectionMap(attributeValues, AttributeValueResponse::class.java)
26+
return ResponseEntity.ok(response)
27+
}
28+
29+
}

src/main/kotlin/com/github/nenadjakic/eav/controller/AuthController.kt

+32-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import com.github.nenadjakic.eav.security.JwtService
88
import com.github.nenadjakic.eav.security.model.SecurityUser
99
import com.github.nenadjakic.eav.service.security.RefreshTokenService
1010
import com.github.nenadjakic.eav.service.security.UserService
11+
import io.swagger.v3.oas.annotations.Operation
12+
import io.swagger.v3.oas.annotations.responses.ApiResponse
13+
import io.swagger.v3.oas.annotations.responses.ApiResponses
14+
import io.swagger.v3.oas.annotations.tags.Tag
1115
import jakarta.validation.Valid
1216
import org.modelmapper.ModelMapper
1317
import org.springframework.http.HttpStatus
@@ -18,7 +22,7 @@ import org.springframework.security.core.Authentication
1822
import org.springframework.validation.annotation.Validated
1923
import org.springframework.web.bind.annotation.*
2024

21-
25+
@Tag(name = "Authentication controller", description = "API endpoints for user authentication.")
2226
@RestController
2327
@RequestMapping("/auth")
2428
@Validated
@@ -43,6 +47,15 @@ open class AuthController(
4347
* Returns ResponseEntity.created() if the registration is successful, otherwise returns an
4448
* appropriate error response.
4549
*/
50+
@Operation(
51+
summary = "Register a new user.",
52+
description = "Creates a new user account based on the provided registration request."
53+
)
54+
@ApiResponses(
55+
value = [
56+
ApiResponse(responseCode = "201", description = "User registered successfully.")
57+
]
58+
)
4659
@PostMapping("/register")
4760
open fun register(@Valid @RequestBody registerRequest: RegisterRequest): ResponseEntity<Void> {
4861
val user = modelMapper.map(registerRequest, User::class.java)
@@ -62,6 +75,15 @@ open class AuthController(
6275
* Returns ResponseEntity.ok() if the email is successfully confirmed,
6376
* otherwise returns an appropriate error response.
6477
*/
78+
@Operation(
79+
summary = "Confirm email.",
80+
description = "Confirms the user's email address based on the provided confirmation token."
81+
)
82+
@ApiResponses(
83+
value = [
84+
ApiResponse(responseCode = "200", description = "Email confirmed successfully")
85+
]
86+
)
6587
@GetMapping("/confirm-email")
6688
open fun confirmEmail(@RequestParam(name = "token") token: String): ResponseEntity<String> {
6789
userService.confirmEmail(token)
@@ -80,6 +102,15 @@ open class AuthController(
80102
* Returns ResponseEntity.ok() with a TokenResponse if authentication is successful,
81103
* otherwise returns an appropriate error response.
82104
*/
105+
@Operation(
106+
summary = "Sign in user.",
107+
description = "Signs in a user based on the provided credentials."
108+
)
109+
@ApiResponses(
110+
value = [
111+
ApiResponse(responseCode = "200", description = "User signed in successfully.")
112+
]
113+
)
83114
@PostMapping("/signin")
84115
open fun signIn(
85116
@Valid @RequestBody signInRequest: SignInRequest

src/main/kotlin/com/github/nenadjakic/eav/controller/EavReadController.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.github.nenadjakic.eav.controller
22

33
import org.springframework.data.domain.Page
4+
import org.springframework.http.MediaType
45
import org.springframework.http.ResponseEntity
56
import org.springframework.web.bind.annotation.GetMapping
67
import org.springframework.web.bind.annotation.PathVariable
@@ -14,12 +15,12 @@ import org.springframework.web.bind.annotation.RequestParam
1415
*/
1516
interface EavReadController<RE> {
1617

17-
@GetMapping
18+
@GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
1819
fun findAll(): ResponseEntity<List<RE>>
1920

20-
@GetMapping("/page")
21+
@GetMapping(value = ["/page"], produces = [MediaType.APPLICATION_JSON_VALUE])
2122
fun findPage(@RequestParam pageNumber: Int, @RequestParam(required = false) pageSize: Int?): ResponseEntity<Page<RE>>
2223

23-
@GetMapping("/{id}")
24+
@GetMapping(value = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
2425
fun findById(@PathVariable id: Long): ResponseEntity<RE>
2526
}

src/main/kotlin/com/github/nenadjakic/eav/controller/EavWriteController.kt

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.github.nenadjakic.eav.controller
22

3+
import jakarta.validation.Valid
4+
import org.springframework.http.MediaType
35
import org.springframework.http.ResponseEntity
6+
import org.springframework.validation.annotation.Validated
47
import org.springframework.web.bind.annotation.DeleteMapping
58
import org.springframework.web.bind.annotation.PathVariable
69
import org.springframework.web.bind.annotation.PostMapping
@@ -14,13 +17,15 @@ import org.springframework.web.bind.annotation.RequestBody
1417
* @param <CR> The type representing the create request.
1518
* @param <UR> The type representing the update request.
1619
*/
20+
@Validated
1721
interface EavWriteController<CR, UR> {
1822

19-
@PostMapping
20-
fun create(@RequestBody model:CR): ResponseEntity<Void>
23+
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE],
24+
produces = [MediaType.APPLICATION_JSON_VALUE])
25+
fun create(@Valid @RequestBody model:CR): ResponseEntity<Void>
2126

22-
@PutMapping
23-
fun update(@RequestBody model:UR): ResponseEntity<Void>
27+
@PutMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
28+
fun update(@Valid @RequestBody model:UR): ResponseEntity<Void>
2429

2530
@DeleteMapping("/{id}")
2631
fun deleteById(@PathVariable id: Long): ResponseEntity<Void>

0 commit comments

Comments
 (0)