From 24b3a02b00ead7bec62a13f235f11e5a9de96a3e Mon Sep 17 00:00:00 2001 From: Bennett Date: Sun, 29 Mar 2026 21:56:23 +0300 Subject: [PATCH 1/2] release: Remove roles on createdBy in records --- .../core/controllers/BaseController.java | 6 +- .../flextuma/core/entities/base/Owner.java | 2 + .../flextuma/core/services/BaseService.java | 103 +++++++++++++++++- .../core/controllers/BaseControllerTest.java | 4 +- 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/flexcodelabs/flextuma/core/controllers/BaseController.java b/src/main/java/com/flexcodelabs/flextuma/core/controllers/BaseController.java index ae965b7..b8558b2 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/controllers/BaseController.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/controllers/BaseController.java @@ -44,8 +44,10 @@ public ResponseEntity> getFields() { } @GetMapping("/{id}") - public ResponseEntity getById(@PathVariable UUID id) { - return service.findById(id) + public ResponseEntity getById( + @PathVariable UUID id, + @RequestParam(required = false) String fields) { + return service.findById(id, fields) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } diff --git a/src/main/java/com/flexcodelabs/flextuma/core/entities/base/Owner.java b/src/main/java/com/flexcodelabs/flextuma/core/entities/base/Owner.java index b636666..11f0b40 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/entities/base/Owner.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/entities/base/Owner.java @@ -24,10 +24,12 @@ public class Owner extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "creator", referencedColumnName = "id", nullable = false, updatable = false) @CreatedBy + @JsonIgnoreProperties({ "roles", "organisation", "createdBy", "updatedBy", "password", "salt" }) private User createdBy; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "updator", referencedColumnName = "id") @LastModifiedBy + @JsonIgnoreProperties({ "roles", "organisation", "createdBy", "updatedBy", "password", "salt" }) private User updatedBy; } diff --git a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java index 49c4176..71822e0 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java @@ -16,7 +16,11 @@ import jakarta.persistence.criteria.*; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.EntityType; +import jakarta.persistence.metamodel.ManagedType; +import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -178,6 +182,7 @@ private Specification buildTenantSpec() { } private Pagination buildPaginatedResponse(Page resultPage, Pageable pageable) { + resultPage.getContent().forEach(entity -> initializeAssociationsForResponse(entity, 1)); return Pagination.builder() .page(pageable.getPageNumber() + 1) .total(resultPage.getTotalElements()) @@ -190,7 +195,9 @@ private Pagination buildPaginatedResponse(Page resultPage, Pageable pageab public List findAll() { checkPermission(getReadPermission()); Specification spec = buildTenantSpec(); - return getRepositoryAsExecutor().findAll(spec); + List results = getRepositoryAsExecutor().findAll(spec); + results.forEach(entity -> initializeAssociationsForResponse(entity, 1)); + return results; } @Transactional(readOnly = true) @@ -215,13 +222,17 @@ private List doFindAll(String fields, List filter, String rootJoin) { if (fields != null && !fields.isBlank()) { spec = spec.and(buildFetchSpec(fields)); } - return getRepositoryAsExecutor().findAll(spec); + List results = getRepositoryAsExecutor().findAll(spec); + results.forEach(entity -> initializeAssociationsForResponse(entity, 1)); + return results; } @Transactional(readOnly = true) public Optional findById(UUID id) { checkPermission(getReadPermission()); - return getRepository().findById(id); + Optional result = getRepository().findById(id); + result.ifPresent(entity -> initializeAssociationsForResponse(entity, 1)); + return result; } @Transactional(readOnly = true) @@ -232,7 +243,9 @@ public Optional findById(UUID id, String fields) { if (fields != null && !fields.isBlank()) { spec = spec.and(buildFetchSpec(fields)); } - return getRepositoryAsExecutor().findOne(spec); + Optional result = getRepositoryAsExecutor().findOne(spec); + result.ifPresent(entity -> initializeAssociationsForResponse(entity, 1)); + return result; } @Transactional @@ -240,6 +253,7 @@ public T save(T entity) { checkPermission(getAddPermission()); onPreSave(entity); T saved = getRepository().save(entity); + initializeAssociationsForResponse(saved, 1); onPostSave(saved); eventPublisher.publishEvent(new EntityEvent<>(this, saved, EntityEvent.EntityEventType.CREATED)); return saved; @@ -255,6 +269,7 @@ public T update(UUID id, T entity) { String[] excludedFields = getNullPropertyNames(entity); org.springframework.beans.BeanUtils.copyProperties(entity, existing, excludedFields); T saved = getRepository().save(existing); + initializeAssociationsForResponse(saved, 1); eventPublisher.publishEvent(new EntityEvent<>(this, saved, EntityEvent.EntityEventType.UPDATED)); return saved; } @@ -430,6 +445,84 @@ protected void onPreSave(T entity) { protected void onPostSave(T entity) { } + protected void initializeAssociationsForResponse(Object entity, int depth) { + if (entity == null || depth < 0) { + return; + } + + Hibernate.initialize(entity); + ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); + if (managedType == null) { + return; + } + + BeanWrapper wrapper = new BeanWrapperImpl(entity); + for (Attribute attribute : managedType.getAttributes()) { + if (!attribute.isAssociation() || !wrapper.isReadableProperty(attribute.getName())) { + continue; + } + + Object value = wrapper.getPropertyValue(attribute.getName()); + if (value == null) { + continue; + } + + Hibernate.initialize(value); + if (depth == 0) { + continue; + } + + if (value instanceof Collection collection) { + for (Object item : collection) { + initializeSingularAssociations(item, depth - 1); + } + continue; + } + + initializeSingularAssociations(value, depth - 1); + } + } + + private void initializeSingularAssociations(Object entity, int depth) { + if (entity == null || depth < 0) { + return; + } + + Hibernate.initialize(entity); + ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); + if (managedType == null) { + return; + } + + BeanWrapper wrapper = new BeanWrapperImpl(entity); + for (Attribute attribute : managedType.getAttributes()) { + if (!attribute.isAssociation() || attribute.isCollection() || !wrapper.isReadableProperty(attribute.getName())) { + continue; + } + + Object value = wrapper.getPropertyValue(attribute.getName()); + if (value == null) { + continue; + } + + Hibernate.initialize(value); + if (depth > 0) { + initializeSingularAssociations(value, depth - 1); + } + } + } + + private ManagedType resolveManagedType(Class javaType) { + try { + if (entityManager == null || entityManager.getMetamodel() == null) { + return null; + } + return entityManager.getMetamodel().managedType(javaType); + } catch (IllegalArgumentException ex) { + return null; + } + } + private final ObjectMapper objectMapper = new ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -440,4 +533,4 @@ protected T onPreUpdate(T newEntity, T oldEntity) { return newEntity; } } -} \ No newline at end of file +} diff --git a/src/test/java/com/flexcodelabs/flextuma/core/controllers/BaseControllerTest.java b/src/test/java/com/flexcodelabs/flextuma/core/controllers/BaseControllerTest.java index a2a679f..d796873 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/controllers/BaseControllerTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/controllers/BaseControllerTest.java @@ -84,7 +84,7 @@ public void getById_shouldReturnEntity_whenFound() throws Exception { T entity = createEntity(); entity.setId(id); - when(getService().findById(id)).thenReturn(Optional.of(entity)); + when(getService().findById(eq(id), any())).thenReturn(Optional.of(entity)); mockMvc.perform(get(getBaseUrl() + "/" + id)) .andExpect(status().isOk()) @@ -94,7 +94,7 @@ public void getById_shouldReturnEntity_whenFound() throws Exception { @Test public void getById_shouldReturnNotFound_whenNotFound() throws Exception { UUID id = UUID.randomUUID(); - when(getService().findById(id)).thenReturn(Optional.empty()); + when(getService().findById(eq(id), any())).thenReturn(Optional.empty()); mockMvc.perform(get(getBaseUrl() + "/" + id)) .andExpect(status().isNotFound()); From 4f8d75ca5b5f9c5625caada585813b2637af31dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 29 Mar 2026 18:57:01 +0000 Subject: [PATCH 2/2] Release v0.0.31 [skip ci] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0c6286a..d8a933d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.30' +version = '0.0.31' description = 'Flextuma App' java {