Skip to content

Commit f7366f3

Browse files
authored
Fixed an issue in GitHub Workflow. (#12)
* Fixed an issue in GitHub Workflow. * Refactored and enhanced annotations for better clarity and readability in entity models. * Implemented repository, service and controller for managing AttributeValue entities. * Changed entity response model. * Clean up code. * Changed EntityAttributeId model and repository, service, dtos and controllers related to EntityAttributeId. * Included authentication scheme definition in the OpenAPI specification. * Configure Spring Security for specific paths and roles. * Changed AttributePermission entity and created repository, services. Created cache scheduler service, and implement AttributeSecurityExpressionRoot. * Changed attribute permission entity. Implemented repository and service. Code clean up.
1 parent 44e1545 commit f7366f3

Some content is hidden

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

41 files changed

+668
-178
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
run: chmod +x ./gradlew
5353

5454
- name: Run build with Gradle Wrapper
55-
run: ./gradlew build -x test
55+
run: ./gradlew build
5656

5757
- name: Run code coverage verification
5858
run: ./gradlew testCoverage

build.gradle.kts

+4-10
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,13 @@ dependencies {
2323
implementation("org.springframework.boot:spring-boot-starter-web:3.2.4")
2424
implementation("org.springframework.boot:spring-boot-starter-validation:3.2.4")
2525
implementation("org.springframework.boot:spring-boot-starter-mail:3.2.4")
26+
implementation("org.springframework.boot:spring-boot-starter-cache:3.2.4")
2627
implementation("io.jsonwebtoken:jjwt-api:0.12.5")
2728
implementation("io.jsonwebtoken:jjwt-impl:0.12.5")
2829
implementation("io.jsonwebtoken:jjwt-jackson:0.12.5")
2930
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-
35-
36-
//implementation("jakarta.validation:jakarta.validation-api:3.0.2")
3731
implementation("org.modelmapper:modelmapper:3.2.0")
38-
3932
runtimeOnly("org.postgresql:postgresql:42.7.3")
40-
4133
testImplementation("org.springframework.boot:spring-boot-starter-test:3.2.4")
4234
testImplementation("com.h2database:h2:2.2.224")
4335

@@ -48,9 +40,11 @@ tasks.test {
4840
finalizedBy(tasks.jacocoTestReport)
4941
}
5042

51-
5243
tasks.jacocoTestReport {
5344
dependsOn(tasks.test)
45+
reports {
46+
csv.required = true
47+
}
5448
}
5549

5650
tasks.jacocoTestCoverageVerification {

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

+33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package com.github.nenadjakic.eav.configuration
22

33
import com.github.nenadjakic.eav.dto.converter.*
4+
import io.swagger.v3.oas.models.Components
5+
import io.swagger.v3.oas.models.OpenAPI
6+
import io.swagger.v3.oas.models.info.Info
7+
import io.swagger.v3.oas.models.security.SecurityRequirement
8+
import io.swagger.v3.oas.models.security.SecurityScheme
49
import org.modelmapper.ModelMapper
10+
import org.springframework.beans.factory.annotation.Value
11+
import org.springframework.cache.CacheManager
12+
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
513
import org.springframework.context.annotation.Bean
614
import org.springframework.context.annotation.Configuration
715
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
@@ -14,6 +22,7 @@ import org.springframework.security.core.userdetails.UserDetailsService
1422
import org.springframework.security.crypto.password.PasswordEncoder
1523
import java.util.*
1624

25+
1726
@Configuration
1827
@EnableJpaRepositories(basePackages = ["com.github.nenadjakic.eav.repository"])
1928
@EnableAsync
@@ -22,6 +31,29 @@ open class MainConfiguration(
2231
private val passwordEncoder: PasswordEncoder
2332
) {
2433

34+
private fun createAPIKeyScheme(): SecurityScheme {
35+
return SecurityScheme().type(SecurityScheme.Type.HTTP)
36+
.bearerFormat("JWT")
37+
.scheme("bearer")
38+
}
39+
40+
@Bean
41+
fun openAPI(@Value("\${application.name}") name: String?, @Value("\${application.version}") version: String?): OpenAPI {
42+
return OpenAPI()
43+
.info(
44+
Info()
45+
.title(name)
46+
.version(version)
47+
)
48+
.addSecurityItem(SecurityRequirement().addList("Bearer_Authentication"))
49+
.components(Components().addSecuritySchemes("Bearer_Authentication", createAPIKeyScheme()))
50+
}
51+
52+
@Bean
53+
open fun cacheManager() : CacheManager {
54+
return ConcurrentMapCacheManager("roles", "attributePermissions")
55+
}
56+
2557
@Bean
2658
open fun modelMapper(): ModelMapper {
2759
val modelMapper = ModelMapper()
@@ -32,6 +64,7 @@ open class MainConfiguration(
3264
modelMapper.addConverter(AttributeUpdateRequestToAttributeConverter())
3365
modelMapper.addConverter(AttributeValueToAttributeValueResponseConverter())
3466
modelMapper.addConverter(EntityTypeToEntityTypeResponseConverter())
67+
modelMapper.addConverter(AttributeValueAddRequestToAttributeValueConverter())
3568

3669
return modelMapper
3770
}

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

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,44 @@ package com.github.nenadjakic.eav.configuration
33
import com.github.nenadjakic.eav.security.filter.JwtAuthenticationFilter
44
import org.springframework.context.annotation.Bean
55
import org.springframework.context.annotation.Configuration
6+
import org.springframework.security.access.vote.RoleVoter
67
import org.springframework.security.authentication.AuthenticationProvider
8+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
79
import org.springframework.security.config.annotation.web.builders.HttpSecurity
810
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
11+
import org.springframework.security.config.core.GrantedAuthorityDefaults
912
import org.springframework.security.config.http.SessionCreationPolicy
1013
import org.springframework.security.web.SecurityFilterChain
1114
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
1215

16+
1317
@Configuration
1418
@EnableWebSecurity
19+
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
1520
open class SecurityConfiguration(
1621
private val authenticationProvider: AuthenticationProvider,
1722
private val jwtAuthenticationFilter: JwtAuthenticationFilter
1823
) {
1924

25+
@Bean
26+
open fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
27+
return GrantedAuthorityDefaults("");
28+
}
29+
2030
@Bean
2131
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
2232
http.csrf { it.disable() }
2333
http.httpBasic { it.disable() }
2434
http.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
2535

2636
http.authorizeHttpRequests {
37+
it.requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**").permitAll()
2738
it.requestMatchers("/auth/**").permitAll()
28-
it.anyRequest().permitAll()
39+
it.anyRequest().authenticated()
2940
}
3041

3142
http.authenticationProvider(authenticationProvider)
3243
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
3344
return http.build()
3445
}
35-
3646
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
66

77
@Configuration
88
@EnableWebMvc
9-
open class WebConfiguration : WebMvcConfigurer {
10-
}
9+
open class WebConfiguration : WebMvcConfigurer
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,130 @@
11
package com.github.nenadjakic.eav.controller
22

3+
import com.github.nenadjakic.eav.dto.AttributeValueAddRequest
34
import com.github.nenadjakic.eav.dto.AttributeValueResponse
5+
import com.github.nenadjakic.eav.entity.AttributeValue
46
import com.github.nenadjakic.eav.extension.collectionMap
57
import com.github.nenadjakic.eav.service.AttributeValueService
8+
import io.swagger.v3.oas.annotations.Operation
9+
import io.swagger.v3.oas.annotations.responses.ApiResponse
10+
import io.swagger.v3.oas.annotations.responses.ApiResponses
611
import io.swagger.v3.oas.annotations.tags.Tag
12+
import jakarta.validation.Valid
713
import org.modelmapper.ModelMapper
814
import org.springframework.http.ResponseEntity
15+
import org.springframework.validation.annotation.Validated
16+
import org.springframework.web.bind.annotation.DeleteMapping
917
import org.springframework.web.bind.annotation.GetMapping
1018
import org.springframework.web.bind.annotation.PathVariable
19+
import org.springframework.web.bind.annotation.PostMapping
20+
import org.springframework.web.bind.annotation.PutMapping
21+
import org.springframework.web.bind.annotation.RequestBody
1122
import org.springframework.web.bind.annotation.RequestMapping
1223
import org.springframework.web.bind.annotation.RestController
24+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
1325

1426
@Tag(name = "Attribute-value controller", description = "API endpoints for managing values of attributes.")
1527
@RestController
1628
@RequestMapping("/attribute-value")
29+
@Validated
1730
open class AttributeValueController(
1831
val modelMapper: ModelMapper,
1932
val attributeValueService: AttributeValueService
2033
) {
2134

35+
@Operation(
36+
operationId = "findAttributeValueById",
37+
summary = "Get attribute-value by id.",
38+
description = "Returns an attribute-value with the specified id (entityId-attributeId)."
39+
)
40+
@ApiResponses(
41+
value = [
42+
ApiResponse(responseCode = "200", description = "Successfully retrieved attribute-value."),
43+
ApiResponse(responseCode = "404", description = "Attribute-value not found.")
44+
]
45+
)
46+
@GetMapping("/{entityId}-{attributeId}")
47+
open fun findById(@PathVariable entityId: Long, @PathVariable attributeId: Long): ResponseEntity<AttributeValueResponse> {
48+
val attributeValue = attributeValueService.findById(entityId, attributeId)
49+
val response = attributeValue?.let { modelMapper.map(attributeValue, AttributeValueResponse::class.java) }
50+
51+
return ResponseEntity.ofNullable(response)
52+
}
53+
54+
@Operation(
55+
operationId = "findAttributeValueByEntityId",
56+
summary = "Get all attribute-value by entitiyId.",
57+
description = "Returns all attribute-values with the specified entityId."
58+
)
59+
@ApiResponses(
60+
value = [
61+
ApiResponse(responseCode = "200", description = "Successfully retrieved attribute-value."),
62+
ApiResponse(responseCode = "404", description = "Attribute-value not found.")
63+
]
64+
)
2265
@GetMapping("entity/{entityId}")
2366
open fun findByEntityId(@PathVariable entityId: Long): ResponseEntity<List<AttributeValueResponse>> {
2467
val attributeValues = attributeValueService.findByEntityId(entityId)
2568
val response = modelMapper.collectionMap(attributeValues, AttributeValueResponse::class.java)
2669
return ResponseEntity.ok(response)
2770
}
2871

72+
@Operation(
73+
operationId = "createAttributeValue",
74+
summary = "Create attribute-vale.",
75+
description = "Creates a new attribute-value based on the provided model."
76+
)
77+
@ApiResponses(
78+
value = [
79+
ApiResponse(responseCode = "201", description = "Attribute-value created successfully."),
80+
ApiResponse(responseCode = "400", description = "Invalid request data.")
81+
]
82+
)
83+
@PostMapping
84+
open fun create(@RequestBody @Valid attributeValueAddRequest: AttributeValueAddRequest): ResponseEntity<Void> {
85+
val attributeValue = modelMapper.map(attributeValueAddRequest, AttributeValue::class.java)
86+
val createdAttributeValue = attributeValueService.create(attributeValue)
87+
88+
val location = ServletUriComponentsBuilder
89+
.fromCurrentRequest()
90+
.path("/{entityId}-{attributeId}")
91+
.buildAndExpand(createdAttributeValue.entity.id, createdAttributeValue.attribute.id)
92+
.toUri()
93+
94+
return ResponseEntity.created(location).build()
95+
}
96+
97+
@Operation(
98+
operationId = "updateAttributeValue",
99+
summary = "Update attribute-value.",
100+
description = "Updates an existing attribute-value based on the provided model."
101+
)
102+
@ApiResponses(
103+
value = [
104+
ApiResponse(responseCode = "204", description = "Attribute-value updated successfully."),
105+
ApiResponse(responseCode = "400", description = "Invalid request data.")
106+
]
107+
)
108+
@PutMapping
109+
open fun update(@RequestBody @Valid attributeValueAddRequest: AttributeValueAddRequest): ResponseEntity<Void> {
110+
val attributeValue = modelMapper.map(attributeValueAddRequest, AttributeValue::class.java)
111+
attributeValueService.update(attributeValue)
112+
return ResponseEntity.noContent().build()
113+
}
114+
115+
@Operation(
116+
operationId = "deleteAttributeValueById",
117+
summary = "Delete attribute-value by id.",
118+
description = "Deletes an attribute-value with the specified id."
119+
)
120+
@ApiResponses(
121+
value = [
122+
ApiResponse(responseCode = "204", description = "Attribute-value deleted successfully")
123+
]
124+
)
125+
@DeleteMapping
126+
open fun deleteById(@PathVariable entityId: Long, @PathVariable attributeId: Long): ResponseEntity<Void> {
127+
attributeValueService.deleteById(entityId, attributeId)
128+
return ResponseEntity.noContent().build()
129+
}
29130
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.nenadjakic.eav.controller
33
import org.springframework.data.domain.Page
44
import org.springframework.http.MediaType
55
import org.springframework.http.ResponseEntity
6+
import org.springframework.security.access.prepost.PreAuthorize
67
import org.springframework.web.bind.annotation.GetMapping
78
import org.springframework.web.bind.annotation.PathVariable
89
import org.springframework.web.bind.annotation.RequestParam
@@ -15,12 +16,15 @@ import org.springframework.web.bind.annotation.RequestParam
1516
*/
1617
interface EavReadController<RE> {
1718

19+
@PreAuthorize("hasRole('READER')")
1820
@GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
1921
fun findAll(): ResponseEntity<List<RE>>
2022

23+
@PreAuthorize("hasRole('READER')")
2124
@GetMapping(value = ["/page"], produces = [MediaType.APPLICATION_JSON_VALUE])
2225
fun findPage(@RequestParam pageNumber: Int, @RequestParam(required = false) pageSize: Int?): ResponseEntity<Page<RE>>
2326

27+
@PreAuthorize("hasRole('READER')")
2428
@GetMapping(value = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
2529
fun findById(@PathVariable id: Long): ResponseEntity<RE>
2630
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.nenadjakic.eav.controller
33
import jakarta.validation.Valid
44
import org.springframework.http.MediaType
55
import org.springframework.http.ResponseEntity
6+
import org.springframework.security.access.prepost.PreAuthorize
67
import org.springframework.validation.annotation.Validated
78
import org.springframework.web.bind.annotation.DeleteMapping
89
import org.springframework.web.bind.annotation.PathVariable
@@ -20,13 +21,16 @@ import org.springframework.web.bind.annotation.RequestBody
2021
@Validated
2122
interface EavWriteController<CR, UR> {
2223

24+
@PreAuthorize("hasRole('WRITER')")
2325
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE],
2426
produces = [MediaType.APPLICATION_JSON_VALUE])
2527
fun create(@Valid @RequestBody model:CR): ResponseEntity<Void>
2628

29+
@PreAuthorize("hasRole('WRITER')")
2730
@PutMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
2831
fun update(@Valid @RequestBody model:UR): ResponseEntity<Void>
2932

33+
@PreAuthorize("hasRole('WRITER')")
3034
@DeleteMapping("/{id}")
3135
fun deleteById(@PathVariable id: Long): ResponseEntity<Void>
3236
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.github.nenadjakic.eav.dto
2+
3+
import jakarta.validation.constraints.NotNull
4+
5+
class AttributeValueAddRequest {
6+
@NotNull
7+
var entityId: Long? = null
8+
9+
@NotNull
10+
var attributeId: Long? = null
11+
12+
var value: String? = null
13+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
package com.github.nenadjakic.eav.dto
22

3-
data class AttributeValueResponse(
3+
data class EntitySimpleResponse(
44
val entityId: Long,
5-
val entityName: String,
5+
)
6+
7+
data class AttributeSimpleResponse(
68
val attributeId: Long,
79
val attributeName: String,
10+
)
11+
12+
data class AttributeValueSimpleResponse(
13+
val attribute : AttributeSimpleResponse,
14+
val value: Any?
15+
)
16+
17+
data class AttributeValueResponse(
18+
val entitiy: EntitySimpleResponse,
19+
//TODO. Should be list
20+
val attributes: AttributeSimpleResponse,
821
val value: Any
922
)

src/main/kotlin/com/github/nenadjakic/eav/dto/EntityResponse.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ package com.github.nenadjakic.eav.dto
22

33
data class EntityResponse(
44
val id: Long,
5-
val entityType: EntityTypeSimpleResponse
5+
val entityType: EntityTypeSimpleResponse,
6+
val attributeValues: List<AttributeValueSimpleResponse>
67
)

0 commit comments

Comments
 (0)