diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java index b623ace3b..b2c430509 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java @@ -59,6 +59,7 @@ import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.OWNER_ASSOCIATION_MANAGE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.OWNER_CREATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.OWNER_DELETE; +import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.OWNER_RELATION_MANAGE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.OWNER_UPDATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.POLICY_CREATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.POLICY_DELETE; @@ -149,6 +150,14 @@ NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/owner_associatio NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher( "/api/owner_association_request/{owner_association_request_id}", PUT), OWNER_ASSOCIATION_MANAGE), + new SecurityRule( + NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher( + "/api/owners/mapping", POST), + OWNER_RELATION_MANAGE), + new SecurityRule( + NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher( + "/api/owners/mapping/{owner_id}", DELETE), + OWNER_RELATION_MANAGE), new SecurityRule(NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/policies", POST), POLICY_CREATE), new SecurityRule(NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/policies/{policy_id}", PUT), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/OwnerAssociationRequestController.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/OwnerAssociationRequestController.java index 270a9b380..3319437ce 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/OwnerAssociationRequestController.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/OwnerAssociationRequestController.java @@ -2,10 +2,16 @@ import lombok.RequiredArgsConstructor; import org.opendatadiscovery.oddplatform.api.contract.api.OwnerAssociationRequestApi; +import org.opendatadiscovery.oddplatform.api.contract.model.Owner; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequest; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestActivityList; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestList; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusFormData; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerFormData; +import org.opendatadiscovery.oddplatform.api.contract.model.ProviderList; +import org.opendatadiscovery.oddplatform.api.contract.model.UserOwnerMappingFormData; +import org.opendatadiscovery.oddplatform.service.OwnerAssociationRequestActivityService; import org.opendatadiscovery.oddplatform.service.OwnerAssociationRequestService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -16,6 +22,7 @@ @RequiredArgsConstructor public class OwnerAssociationRequestController implements OwnerAssociationRequestApi { private final OwnerAssociationRequestService ownerAssociationRequestService; + private final OwnerAssociationRequestActivityService activityService; @Override public Mono> createOwnerAssociationRequest( @@ -27,12 +34,21 @@ public Mono> createOwnerAssociationReque } @Override - public Mono> getOwnerAssociationRequestList(final Integer page, - final Integer size, - final Boolean active, - final String query, - final ServerWebExchange e) { - return ownerAssociationRequestService.getOwnerAssociationRequestList(page, size, query, active) + public Mono> + getOwnerAssociationRequestList(final Integer page, + final Integer size, + final OwnerAssociationRequestStatusParam status, + final String query, + final ServerWebExchange e) { + return ownerAssociationRequestService.getOwnerAssociationRequestList(page, size, query, status) + .map(ResponseEntity::ok); + } + + @Override + public Mono> getOwnerAssociationRequestActivityList( + final Integer page, final Integer size, final OwnerAssociationRequestStatusParam status, final String query, + final ServerWebExchange exchange) { + return activityService.getOwnerAssociationRequestList(page, size, query, status) .map(ResponseEntity::ok); } @@ -45,4 +61,26 @@ public Mono> updateOwnerAssociationReque .flatMap(fd -> ownerAssociationRequestService.updateOwnerAssociationRequest(id, fd.getStatus())) .map(ResponseEntity::ok); } + + @Override + public Mono> + createUserOwnerMapping(final Mono userOwnerMappingFormData, + final ServerWebExchange exchange) { + return userOwnerMappingFormData + .flatMap(ownerAssociationRequestService::createUserOwnerMapping) + .map(ResponseEntity::ok); + } + + @Override + public Mono> deleteActiveUserOwnerMapping(final Long ownerId, + final ServerWebExchange exchange) { + return ownerAssociationRequestService.deleteActiveUserOwnerMapping(ownerId) + .thenReturn(ResponseEntity.noContent().build()); + } + + @Override + public Mono> getAuthProviders(final ServerWebExchange exchange) { + return ownerAssociationRequestService.getAuthProviders() + .map(ResponseEntity::ok); + } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityDto.java new file mode 100644 index 000000000..1ef064112 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityDto.java @@ -0,0 +1,7 @@ +package org.opendatadiscovery.oddplatform.dto; + +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestActivityPojo; + +public record OwnerAssociationRequestActivityDto(OwnerAssociationRequestActivityPojo activityPojo, + OwnerAssociationRequestDto associationRequestDto) { +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityType.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityType.java new file mode 100644 index 000000000..4df346cc5 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestActivityType.java @@ -0,0 +1,9 @@ +package org.opendatadiscovery.oddplatform.dto; + +public enum OwnerAssociationRequestActivityType { + REQUEST_CREATED, + REQUEST_DECLINED, + REQUEST_APPROVED, + REQUEST_MANUALLY_APPROVED, + REQUEST_MANUALLY_DECLINED +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestDto.java index 128d86dbb..06fd701af 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerAssociationRequestDto.java @@ -1,8 +1,12 @@ package org.opendatadiscovery.oddplatform.dto; +import java.util.Collection; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; public record OwnerAssociationRequestDto(OwnerAssociationRequestPojo pojo, String ownerName, + Long ownerId, + Collection roles, AssociatedOwnerDto statusUpdatedUser) { } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerDto.java index 9f4d0049a..df6bfc72a 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/OwnerDto.java @@ -3,6 +3,9 @@ import java.util.Collection; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.UserOwnerMappingPojo; -public record OwnerDto(OwnerPojo ownerPojo, Collection roles) { +public record OwnerDto(OwnerPojo ownerPojo, + Collection roles, + UserOwnerMappingPojo associatedUser) { } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java index d7cbca446..95736c990 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java @@ -64,6 +64,7 @@ public enum PolicyPermissionDto { OWNER_UPDATE(MANAGEMENT), OWNER_DELETE(MANAGEMENT), OWNER_ASSOCIATION_MANAGE(MANAGEMENT), + OWNER_RELATION_MANAGE(MANAGEMENT), DIRECT_OWNER_SYNC(MANAGEMENT), POLICY_CREATE(MANAGEMENT), POLICY_UPDATE(MANAGEMENT), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/AssociatedOwnerMapperImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/AssociatedOwnerMapperImpl.java index d3e11a9f1..4db6326d8 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/AssociatedOwnerMapperImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/AssociatedOwnerMapperImpl.java @@ -1,14 +1,19 @@ package org.opendatadiscovery.oddplatform.mapper; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.opendatadiscovery.oddplatform.api.contract.model.AssociatedOwner; import org.opendatadiscovery.oddplatform.api.contract.model.Identity; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequest; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; import org.opendatadiscovery.oddplatform.api.contract.model.Permission; +import org.opendatadiscovery.oddplatform.api.contract.model.Role; import org.opendatadiscovery.oddplatform.dto.AssociatedOwnerDto; import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; +import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; import org.springframework.stereotype.Component; @Component @@ -46,6 +51,17 @@ private OwnerAssociationRequest mapAssociationRequest(final OwnerAssociationRequ return new OwnerAssociationRequest() .username(dto.pojo().getUsername()) .ownerName(dto.ownerName()) + .ownerId(dto.ownerId()) + .roles(rolePojoCollectionToRoleList(dto.roles())) .status(OwnerAssociationRequestStatus.valueOf(dto.pojo().getStatus())); } + + private List rolePojoCollectionToRoleList(final Collection collection) { + if (collection == null || collection.isEmpty()) { + return null; + } + + return collection.stream().map(rolePojo -> new Role().id(rolePojo.getId()).name(rolePojo.getName())) + .collect(Collectors.toCollection(() -> new ArrayList<>(collection.size()))); + } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestActivityMapper.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestActivityMapper.java new file mode 100644 index 000000000..a49387095 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestActivityMapper.java @@ -0,0 +1,44 @@ +package org.opendatadiscovery.oddplatform.mapper; + +import java.util.Collection; +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestActivity; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestActivityList; +import org.opendatadiscovery.oddplatform.api.contract.model.PageInfo; +import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityDto; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestActivityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; +import org.opendatadiscovery.oddplatform.utils.Page; + +@Mapper(config = MapperConfig.class, uses = {DateTimeMapper.class, AssociatedOwnerMapper.class}) +public interface OwnerAssociationRequestActivityMapper { + + @Mapping(target = "activityId", source = "dto.activityPojo.id") + @Mapping(target = ".", source = "dto.associationRequestDto.pojo") + @Mapping(target = "statusUpdatedBy", source = "dto.associationRequestDto.statusUpdatedUser") + @Mapping(target = "status", source = "dto.activityPojo.status") + @Mapping(target = "roles", source = "dto.associationRequestDto.roles") + @Mapping(target = "ownerName", source = "dto.associationRequestDto.ownerName") + @Mapping(target = "eventType", source = "dto.activityPojo.eventType") + @Mapping(target = "statusUpdatedAt", source = "dto.activityPojo.createdAt") + OwnerAssociationRequestActivity mapToOwnerAssociationRequestActivity(final OwnerAssociationRequestActivityDto dto); + + List mapList(final Collection dtos); + + default OwnerAssociationRequestActivityList mapPage(final Page page) { + final List items = mapList(page.getData()); + final PageInfo pageInfo = new PageInfo(page.getTotal(), page.isHasNext()); + return new OwnerAssociationRequestActivityList(items, pageInfo); + } + + @Mapping(target = "ownerAssociationRequestId", source = "pojo.id") + @Mapping(target = "status", source = "pojo.status") + @Mapping(target = "statusUpdatedBy", source = "statusUpdatedBy") + @Mapping(target = "id", ignore = true) + @Mapping(target = "createdAt", ignore = true) + OwnerAssociationRequestActivityPojo mapToPojo(final OwnerAssociationRequestPojo pojo, + final String statusUpdatedBy, + final String eventType); +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveActivityRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveActivityRepositoryImpl.java index 65542afc6..798c3bd59 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveActivityRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveActivityRepositoryImpl.java @@ -153,7 +153,9 @@ public Mono getTotalActivitiesCount(final OffsetDateTime beginDate, final var countQuery = DSL.selectCount() .from(ACTIVITY) .join(DATA_ENTITY).on(DATA_ENTITY.ID.eq(ACTIVITY.DATA_ENTITY_ID)) - .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY)); + .leftJoin(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())); addJoins(countQuery, datasourceId, namespaceId, tagIds, ownerIds); final List conditions = getCommonConditions(beginDate, endDate, datasourceId, namespaceId, tagIds, ownerIds, userIds, eventType); @@ -172,7 +174,9 @@ public Mono getMyObjectsActivitiesCount(final OffsetDateTime beginDate, final var countQuery = DSL.selectCount() .from(ACTIVITY) .join(DATA_ENTITY).on(DATA_ENTITY.ID.eq(ACTIVITY.DATA_ENTITY_ID)) - .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY)); + .leftJoin(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())); addJoins(countQuery, datasourceId, namespaceId, tagIds, List.of(currentOwnerId)); final List conditions = getCommonConditions(beginDate, endDate, datasourceId, namespaceId, tagIds, List.of(currentOwnerId), userIds, eventType); @@ -191,7 +195,9 @@ public Mono getDependentActivitiesCount(final OffsetDateTime beginDate, final var countQuery = DSL.selectCount() .from(ACTIVITY) .join(DATA_ENTITY).on(DATA_ENTITY.ID.eq(ACTIVITY.DATA_ENTITY_ID)) - .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY)); + .leftJoin(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())); addJoins(countQuery, datasourceId, namespaceId, tagIds, List.of()); final List conditions = getCommonConditions(beginDate, endDate, datasourceId, namespaceId, tagIds, List.of(), userIds, eventType); @@ -211,7 +217,9 @@ private SelectJoinStep buildBaseQuery(final Long datasourceId, final Long nam final var query = DSL.select(selectFields) .from(ACTIVITY) .join(DATA_ENTITY).on(DATA_ENTITY.ID.eq(ACTIVITY.DATA_ENTITY_ID)) - .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY)) + .leftJoin(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(ACTIVITY.CREATED_BY) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .leftJoin(OWNER).on(OWNER.ID.eq(USER_OWNER_MAPPING.OWNER_ID)); return addJoins(query, datasourceId, namespaceId, tagIds, ownerIds); } @@ -263,6 +271,7 @@ private List getCommonConditions(final OffsetDateTime beginDate, } if (CollectionUtils.isNotEmpty(userIds)) { conditions.add(USER_OWNER_MAPPING.OWNER_ID.in(userIds)); + conditions.add(USER_OWNER_MAPPING.DELETED_AT.isNull()); } return conditions; } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveAlertRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveAlertRepositoryImpl.java index b1d4d6974..b94b04cfd 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveAlertRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveAlertRepositoryImpl.java @@ -80,7 +80,9 @@ public Mono get(final long id) { .select(jsonArrayAgg(field(ALERT_CHUNK.asterisk().toString())).as(ALERT_CHUNK_FIELD)) .from(ALERT) .join(DATA_ENTITY).on(DATA_ENTITY.ODDRN.eq(ALERT.DATA_ENTITY_ODDRN)) - .leftJoin(USER_OWNER_MAPPING).on(ALERT.STATUS_UPDATED_BY.eq(USER_OWNER_MAPPING.OIDC_USERNAME)) + .leftJoin(USER_OWNER_MAPPING) + .on(ALERT.STATUS_UPDATED_BY.eq(USER_OWNER_MAPPING.OIDC_USERNAME) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .leftJoin(OWNER).on(USER_OWNER_MAPPING.OWNER_ID.eq(OWNER.ID)) .join(ALERT_CHUNK).on(ALERT_CHUNK.ALERT_ID.eq(ALERT.ID)) .where(ALERT.ID.eq(id)) @@ -100,7 +102,9 @@ public Mono> get(final List ids) { .select(jsonArrayAgg(field(ALERT_CHUNK.asterisk().toString())).as(ALERT_CHUNK_FIELD)) .from(ALERT) .join(DATA_ENTITY).on(DATA_ENTITY.ODDRN.eq(ALERT.DATA_ENTITY_ODDRN)) - .leftJoin(USER_OWNER_MAPPING).on(ALERT.STATUS_UPDATED_BY.eq(USER_OWNER_MAPPING.OIDC_USERNAME)) + .leftJoin(USER_OWNER_MAPPING) + .on(ALERT.STATUS_UPDATED_BY.eq(USER_OWNER_MAPPING.OIDC_USERNAME) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .leftJoin(OWNER).on(USER_OWNER_MAPPING.OWNER_ID.eq(OWNER.ID)) .join(ALERT_CHUNK).on(ALERT_CHUNK.ALERT_ID.eq(ALERT.ID)) .where(ALERT.ID.in(ids)) @@ -493,7 +497,8 @@ private SelectSeekStepN createAlertOuterSelect(final Select { + Mono> getDtoList(final Integer page, final Integer size, + final String query, + final OwnerAssociationRequestStatusParam status); +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestActivityRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestActivityRepositoryImpl.java new file mode 100644 index 000000000..5e22c3fe0 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestActivityRepositoryImpl.java @@ -0,0 +1,178 @@ +package org.opendatadiscovery.oddplatform.repository.reactive; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.jooq.Condition; +import org.jooq.Field; +import org.jooq.Record; +import org.jooq.ResultQuery; +import org.jooq.Select; +import org.jooq.SortOrder; +import org.jooq.Table; +import org.jooq.impl.DSL; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; +import org.opendatadiscovery.oddplatform.dto.AssociatedOwnerDto; +import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityDto; +import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; +import org.opendatadiscovery.oddplatform.model.tables.Owner; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestActivityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; +import org.opendatadiscovery.oddplatform.model.tables.records.OwnerAssociationRequestActivityRecord; +import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; +import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; +import org.opendatadiscovery.oddplatform.repository.util.JooqRecordHelper; +import org.opendatadiscovery.oddplatform.repository.util.OrderByField; +import org.opendatadiscovery.oddplatform.utils.Page; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.jsonArrayAgg; +import static org.opendatadiscovery.oddplatform.model.Tables.OWNER; +import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_ASSOCIATION_REQUEST; +import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_ASSOCIATION_REQUEST_ACTIVITY; +import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_TO_ROLE; +import static org.opendatadiscovery.oddplatform.model.Tables.ROLE; +import static org.opendatadiscovery.oddplatform.model.Tables.USER_OWNER_MAPPING; + +@Repository +public class ReactiveOwnerAssociationRequestActivityRepositoryImpl + extends ReactiveAbstractCRUDRepository + implements ReactiveOwnerAssociationRequestActivityRepository { + + private static final String REQUEST_OWNER_ALIAS = "request_owner"; + private static final String STATUS_UPDATED_OWNER_ALIAS = "status_updated_owner"; + private static final String AGG_ROLE_FIELD = "agg_role_field"; + private final JooqRecordHelper jooqRecordHelper; + + public ReactiveOwnerAssociationRequestActivityRepositoryImpl(final JooqReactiveOperations jooqReactiveOperations, + final JooqQueryHelper jooqQueryHelper, + final JooqRecordHelper jooqRecordHelper) { + super(jooqReactiveOperations, jooqQueryHelper, OWNER_ASSOCIATION_REQUEST_ACTIVITY, + OwnerAssociationRequestActivityPojo.class, + null, OWNER_ASSOCIATION_REQUEST_ACTIVITY.ID, OWNER_ASSOCIATION_REQUEST_ACTIVITY.CREATED_AT, + null); + this.jooqRecordHelper = jooqRecordHelper; + } + + @Override + public Mono> getDtoList(final Integer page, + final Integer size, + final String query, + final OwnerAssociationRequestStatusParam status) { + final List conditions = getConditions(query, status); + + final Select homogeneousQuery = DSL.select(OWNER_ASSOCIATION_REQUEST_ACTIVITY.fields()) + .from(OWNER_ASSOCIATION_REQUEST_ACTIVITY) + .join(OWNER_ASSOCIATION_REQUEST) + .on(OWNER_ASSOCIATION_REQUEST.ID.eq(OWNER_ASSOCIATION_REQUEST_ACTIVITY.OWNER_ASSOCIATION_REQUEST_ID)) + .join(OWNER).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(OWNER.ID)) + .where(conditions); + + final Select select = paginate(homogeneousQuery, + List.of(new OrderByField(OWNER_ASSOCIATION_REQUEST_ACTIVITY.ID, SortOrder.DESC)), + (page - 1) * size, size); + + final Table cte = select.asTable("oara_cte"); + + final Owner requestOwner = OWNER.as(REQUEST_OWNER_ALIAS); + final Owner statusUpdatedOwner = OWNER.as(STATUS_UPDATED_OWNER_ALIAS); + + final List> groupByFields = Stream.of(cte.fields(), requestOwner.fields(), + statusUpdatedOwner.fields(), OWNER_ASSOCIATION_REQUEST.fields()) + .flatMap(Arrays::stream) + .toList(); + + final ResultQuery selectQuery = DSL.with(cte.getName()) + .as(select) + .select(groupByFields) + .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) + .from(cte.getName()) + .join(OWNER_ASSOCIATION_REQUEST) + .on(OWNER_ASSOCIATION_REQUEST.ID.eq( + cte.field(OWNER_ASSOCIATION_REQUEST_ACTIVITY.OWNER_ASSOCIATION_REQUEST_ID))) + .join(requestOwner).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(OWNER_TO_ROLE).on(OWNER_TO_ROLE.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) + .leftJoin(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(cte.field(OWNER_ASSOCIATION_REQUEST_ACTIVITY.STATUS_UPDATED_BY)) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) + .leftJoin(statusUpdatedOwner) + .on(USER_OWNER_MAPPING.OWNER_ID.eq(statusUpdatedOwner.ID)) + .groupBy(groupByFields) + .orderBy(cte.field(OWNER_ASSOCIATION_REQUEST_ACTIVITY.ID).desc()); + + return jooqReactiveOperations.flux(selectQuery) + .collectList() + .flatMap(records -> jooqQueryHelper.pageifyResult( + records, + r -> mapRecordToDto(r, cte.getName()), + fetchCount(query, status) + )); + } + + private List getConditions(final String query, + final OwnerAssociationRequestStatusParam status) { + final List conditions = new ArrayList<>(); + if (StringUtils.isNotEmpty(query)) { + conditions.add(OWNER_ASSOCIATION_REQUEST.USERNAME.containsIgnoreCase(query) + .or(OWNER.NAME.containsIgnoreCase(query))); + } + if (status != null) { + switch (status) { + case PENDING -> conditions.add( + OWNER_ASSOCIATION_REQUEST_ACTIVITY.STATUS.eq(OwnerAssociationRequestStatus.PENDING.getValue())); + case APPROVED -> conditions.add( + OWNER_ASSOCIATION_REQUEST_ACTIVITY.STATUS.eq(OwnerAssociationRequestStatus.APPROVED.getValue())); + case DECLINED -> conditions.add( + OWNER_ASSOCIATION_REQUEST_ACTIVITY.STATUS.eq(OwnerAssociationRequestStatus.DECLINED.getValue())); + default -> conditions.add( + OWNER_ASSOCIATION_REQUEST_ACTIVITY.STATUS.ne(OwnerAssociationRequestStatus.PENDING.getValue())); + } + } + return conditions; + } + + private OwnerAssociationRequestActivityDto mapRecordToDto(final Record r, final String mainTableName) { + final OwnerAssociationRequestActivityPojo activityPojo = jooqRecordHelper + .remapCte(r, mainTableName, OWNER_ASSOCIATION_REQUEST_ACTIVITY) + .into(OwnerAssociationRequestActivityPojo.class); + + final OwnerAssociationRequestPojo pojo = + jooqRecordHelper.extractRelation(r, OWNER_ASSOCIATION_REQUEST, OwnerAssociationRequestPojo.class); + final Record requestOwnerRecord = jooqRecordHelper.remapCte(r, REQUEST_OWNER_ALIAS, OWNER); + final OwnerPojo requestOwner = jooqRecordHelper.extractRelation(requestOwnerRecord, OWNER, OwnerPojo.class); + + final Set rolePojos = jooqRecordHelper.extractAggRelation(r, AGG_ROLE_FIELD, RolePojo.class); + + final Record statusUpdatedRecord = jooqRecordHelper.remapCte(r, STATUS_UPDATED_OWNER_ALIAS, OWNER); + final OwnerPojo statusOwner = jooqRecordHelper.extractRelation(statusUpdatedRecord, OWNER, OwnerPojo.class); + + final AssociatedOwnerDto associatedOwnerDto = pojo.getStatusUpdatedBy() != null + ? new AssociatedOwnerDto(pojo.getStatusUpdatedBy(), statusOwner, null) : null; + + final OwnerAssociationRequestDto ownerAssociationRequestDto = + new OwnerAssociationRequestDto(pojo, requestOwner.getName(), requestOwner.getId(), rolePojos, + associatedOwnerDto); + return new OwnerAssociationRequestActivityDto(activityPojo, ownerAssociationRequestDto); + } + + private Mono fetchCount(final String query, + final OwnerAssociationRequestStatusParam status) { + final List conditions = getConditions(query, status); + return jooqReactiveOperations + .mono(DSL.selectCount().from(OWNER_ASSOCIATION_REQUEST_ACTIVITY) + .join(OWNER_ASSOCIATION_REQUEST) + .on(OWNER_ASSOCIATION_REQUEST.ID.eq(OWNER_ASSOCIATION_REQUEST_ACTIVITY.OWNER_ASSOCIATION_REQUEST_ID)) + .join(OWNER).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(OWNER.ID)) + .where(conditions)) + .map(r -> r.component1().longValue()); + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepository.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepository.java index 32344b754..c6f221b65 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepository.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepository.java @@ -1,8 +1,10 @@ package org.opendatadiscovery.oddplatform.repository.reactive; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; import org.opendatadiscovery.oddplatform.utils.Page; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface ReactiveOwnerAssociationRequestRepository extends ReactiveCRUDRepository { @@ -12,7 +14,13 @@ public interface ReactiveOwnerAssociationRequestRepository extends ReactiveCRUDR Mono> getDtoList(final int page, final int size, final String query, - final Boolean active); + final OwnerAssociationRequestStatusParam status); Mono getLastRequestForUsername(final String username); + + Flux cancelAssociationByOwnerId(final long id, final String updateBy); + + Flux cancelAssociationByUsername(final String username, final String updateBy); + + Flux cancelCollisionAssociationById(final OwnerAssociationRequestPojo pojo); } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepositoryImpl.java index 36dc714fc..601ed30e3 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerAssociationRequestRepositoryImpl.java @@ -1,34 +1,45 @@ package org.opendatadiscovery.oddplatform.repository.reactive; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; +import org.jooq.Field; import org.jooq.Record; +import org.jooq.ResultQuery; import org.jooq.Select; -import org.jooq.SelectConditionStep; -import org.jooq.SelectOnConditionStep; import org.jooq.SortOrder; import org.jooq.Table; import org.jooq.impl.DSL; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; import org.opendatadiscovery.oddplatform.dto.AssociatedOwnerDto; import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; import org.opendatadiscovery.oddplatform.model.tables.Owner; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; import org.opendatadiscovery.oddplatform.model.tables.records.OwnerAssociationRequestRecord; import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; import org.opendatadiscovery.oddplatform.repository.util.JooqRecordHelper; import org.opendatadiscovery.oddplatform.repository.util.OrderByField; +import org.opendatadiscovery.oddplatform.service.ingestion.util.DateTimeUtil; import org.opendatadiscovery.oddplatform.utils.Page; import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.jsonArrayAgg; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_ASSOCIATION_REQUEST; +import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_TO_ROLE; +import static org.opendatadiscovery.oddplatform.model.Tables.ROLE; import static org.opendatadiscovery.oddplatform.model.Tables.USER_OWNER_MAPPING; @Repository @@ -38,6 +49,7 @@ public class ReactiveOwnerAssociationRequestRepositoryImpl private static final String REQUEST_OWNER_ALIAS = "request_owner"; private static final String STATUS_UPDATED_OWNER_ALIAS = "status_updated_owner"; + private static final String AGG_ROLE_FIELD = "agg_role_field"; private final JooqRecordHelper jooqRecordHelper; public ReactiveOwnerAssociationRequestRepositoryImpl(final JooqReactiveOperations jooqReactiveOperations, @@ -53,16 +65,25 @@ public ReactiveOwnerAssociationRequestRepositoryImpl(final JooqReactiveOperation public Mono getDto(final long id) { final Owner requestOwner = OWNER.as(REQUEST_OWNER_ALIAS); final Owner statusUpdatedOwner = OWNER.as(STATUS_UPDATED_OWNER_ALIAS); - final SelectConditionStep query = DSL.select(OWNER_ASSOCIATION_REQUEST.fields()) - .select(requestOwner.fields()) - .select(statusUpdatedOwner.fields()) + + final List> groupByFields = Stream.of(OWNER_ASSOCIATION_REQUEST.fields(), requestOwner.fields(), + statusUpdatedOwner.fields()) + .flatMap(Arrays::stream) + .toList(); + + final ResultQuery query = DSL.select(groupByFields) + .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) .from(OWNER_ASSOCIATION_REQUEST) .join(requestOwner).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(OWNER_TO_ROLE).on(OWNER_TO_ROLE.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) .leftJoin(USER_OWNER_MAPPING) - .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY)) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .leftJoin(statusUpdatedOwner) .on(USER_OWNER_MAPPING.OWNER_ID.eq(statusUpdatedOwner.ID)) - .where(OWNER_ASSOCIATION_REQUEST.ID.eq(id)); + .where(OWNER_ASSOCIATION_REQUEST.ID.eq(id)) + .groupBy(groupByFields); return jooqReactiveOperations.mono(query) .map(r -> mapRecordToDto(r, OWNER_ASSOCIATION_REQUEST.getName())); } @@ -71,8 +92,8 @@ public Mono getDto(final long id) { public Mono> getDtoList(final int page, final int size, final String query, - final Boolean active) { - final List conditions = getConditions(query, active); + final OwnerAssociationRequestStatusParam status) { + final List conditions = getConditions(query, status); final Select homogeneousQuery = DSL.select(OWNER_ASSOCIATION_REQUEST.fields()) .from(OWNER_ASSOCIATION_REQUEST) @@ -80,51 +101,109 @@ public Mono> getDtoList(final int page, .where(conditions); final Select select = paginate(homogeneousQuery, - List.of(new OrderByField(OWNER_ASSOCIATION_REQUEST.ID, SortOrder.ASC)), (page - 1) * size, size); + List.of(new OrderByField(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_AT, SortOrder.DESC)), + (page - 1) * size, size); final Table cte = select.asTable("oar_cte"); final Owner requestOwner = OWNER.as(REQUEST_OWNER_ALIAS); final Owner statusUpdatedOwner = OWNER.as(STATUS_UPDATED_OWNER_ALIAS); - final SelectOnConditionStep selectQuery = DSL.with(cte.getName()) + final List> groupByFields = Stream.of(cte.fields(), requestOwner.fields(), + statusUpdatedOwner.fields()) + .flatMap(Arrays::stream) + .toList(); + + final ResultQuery selectQuery = DSL.with(cte.getName()) .as(select) - .select(cte.fields()) - .select(requestOwner.fields()) - .select(statusUpdatedOwner.fields()) + .select(groupByFields) + .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) .from(cte.getName()) .join(requestOwner).on(cte.field(OWNER_ASSOCIATION_REQUEST.OWNER_ID).eq(requestOwner.ID)) + .leftJoin(OWNER_TO_ROLE).on(OWNER_TO_ROLE.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) .leftJoin(USER_OWNER_MAPPING) - .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(cte.field(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY))) + .on(USER_OWNER_MAPPING.OIDC_USERNAME.eq(cte.field(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY)) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .leftJoin(statusUpdatedOwner) - .on(USER_OWNER_MAPPING.OWNER_ID.eq(statusUpdatedOwner.ID)); + .on(USER_OWNER_MAPPING.OWNER_ID.eq(statusUpdatedOwner.ID)) + .groupBy(groupByFields) + .orderBy(cte.field(OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_AT).desc()); return jooqReactiveOperations.flux(selectQuery) .collectList() .flatMap(records -> jooqQueryHelper.pageifyResult( records, r -> mapRecordToDto(r, cte.getName()), - fetchCount(query, active) + fetchCount(query, status) )); } @Override public Mono getLastRequestForUsername(final String username) { final Owner requestOwner = OWNER.as(REQUEST_OWNER_ALIAS); - final var query = DSL.select(OWNER_ASSOCIATION_REQUEST.fields()) - .select(requestOwner.fields()) + final List> groupByFields = Stream.of(OWNER_ASSOCIATION_REQUEST.fields(), requestOwner.fields()) + .flatMap(Arrays::stream) + .toList(); + + final var query = DSL.select(groupByFields) + .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) .from(OWNER_ASSOCIATION_REQUEST) .join(requestOwner).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(OWNER_TO_ROLE).on(OWNER_TO_ROLE.OWNER_ID.eq(requestOwner.ID)) + .leftJoin(ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) .where(OWNER_ASSOCIATION_REQUEST.USERNAME.eq(username)) + .groupBy(groupByFields) .orderBy(OWNER_ASSOCIATION_REQUEST.CREATED_AT.desc()) .limit(1); return jooqReactiveOperations.mono(query) .map(r -> mapRecordToDto(r, OWNER_ASSOCIATION_REQUEST.getName())); } + @Override + public Flux cancelAssociationByOwnerId(final long id, final String updateBy) { + final var query = DSL.update(OWNER_ASSOCIATION_REQUEST) + .set(Map.of(OWNER_ASSOCIATION_REQUEST.STATUS, OwnerAssociationRequestStatus.DECLINED.getValue(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_AT, DateTimeUtil.generateNow(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY, updateBy)) + .where(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(id)) + .and(OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.APPROVED.getValue())) + .returning(); + return jooqReactiveOperations.flux(query) + .map(r -> r.into(OwnerAssociationRequestPojo.class)); + } + + @Override + public Flux cancelAssociationByUsername(final String username, final String updateBy) { + final var query = DSL.update(OWNER_ASSOCIATION_REQUEST) + .set(Map.of(OWNER_ASSOCIATION_REQUEST.STATUS, OwnerAssociationRequestStatus.DECLINED.getValue(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_AT, DateTimeUtil.generateNow(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY, updateBy)) + .where(OWNER_ASSOCIATION_REQUEST.USERNAME.eq(username)) + .and(OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.APPROVED.getValue())) + .returning(); + return jooqReactiveOperations.flux(query) + .map(r -> r.into(OwnerAssociationRequestPojo.class)); + } + + @Override + public Flux cancelCollisionAssociationById(final OwnerAssociationRequestPojo pojo) { + final var query = DSL.update(OWNER_ASSOCIATION_REQUEST) + .set(Map.of(OWNER_ASSOCIATION_REQUEST.STATUS, OwnerAssociationRequestStatus.DECLINED.getValue(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_AT, DateTimeUtil.generateNow(), + OWNER_ASSOCIATION_REQUEST.STATUS_UPDATED_BY, pojo.getStatusUpdatedBy())) + .where(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(pojo.getOwnerId()) + .or(OWNER_ASSOCIATION_REQUEST.USERNAME.eq(pojo.getUsername()))) + .and(OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.APPROVED.getValue())) + .and(OWNER_ASSOCIATION_REQUEST.ID.ne(pojo.getId())) + .returning(); + return jooqReactiveOperations.flux(query) + .map(r -> r.into(OwnerAssociationRequestPojo.class)); + } + private Mono fetchCount(final String query, - final Boolean active) { - final List conditions = getConditions(query, active); + final OwnerAssociationRequestStatusParam status) { + final List conditions = getConditions(query, status); return jooqReactiveOperations .mono(DSL.selectCount().from(OWNER_ASSOCIATION_REQUEST) .join(OWNER).on(OWNER_ASSOCIATION_REQUEST.OWNER_ID.eq(OWNER.ID)) @@ -133,17 +212,22 @@ private Mono fetchCount(final String query, } private List getConditions(final String query, - final Boolean active) { + final OwnerAssociationRequestStatusParam status) { final List conditions = new ArrayList<>(); if (StringUtils.isNotEmpty(query)) { conditions.add(OWNER_ASSOCIATION_REQUEST.USERNAME.containsIgnoreCase(query) .or(OWNER.NAME.containsIgnoreCase(query))); } - if (active != null) { - if (active) { - conditions.add(OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.PENDING.getValue())); - } else { - conditions.add(OWNER_ASSOCIATION_REQUEST.STATUS.ne(OwnerAssociationRequestStatus.PENDING.getValue())); + if (status != null) { + switch (status) { + case PENDING -> conditions.add( + OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.PENDING.getValue())); + case APPROVED -> conditions.add( + OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.APPROVED.getValue())); + case DECLINED -> conditions.add( + OWNER_ASSOCIATION_REQUEST.STATUS.eq(OwnerAssociationRequestStatus.DECLINED.getValue())); + default -> conditions.add( + OWNER_ASSOCIATION_REQUEST.STATUS.ne(OwnerAssociationRequestStatus.PENDING.getValue())); } } return conditions; @@ -156,11 +240,14 @@ private OwnerAssociationRequestDto mapRecordToDto(final Record r, final String m final Record requestOwnerRecord = jooqRecordHelper.remapCte(r, REQUEST_OWNER_ALIAS, OWNER); final OwnerPojo requestOwner = jooqRecordHelper.extractRelation(requestOwnerRecord, OWNER, OwnerPojo.class); + final Set rolePojos = jooqRecordHelper.extractAggRelation(r, AGG_ROLE_FIELD, RolePojo.class); + final Record statusUpdatedRecord = jooqRecordHelper.remapCte(r, STATUS_UPDATED_OWNER_ALIAS, OWNER); final OwnerPojo statusOwner = jooqRecordHelper.extractRelation(statusUpdatedRecord, OWNER, OwnerPojo.class); final AssociatedOwnerDto associatedOwnerDto = pojo.getStatusUpdatedBy() != null ? new AssociatedOwnerDto(pojo.getStatusUpdatedBy(), statusOwner, null) : null; - return new OwnerAssociationRequestDto(pojo, requestOwner.getName(), associatedOwnerDto); + return new OwnerAssociationRequestDto(pojo, requestOwner.getName(), requestOwner.getId(), rolePojos, + associatedOwnerDto); } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerRepositoryImpl.java index 546fd8b88..1dce14922 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveOwnerRepositoryImpl.java @@ -1,10 +1,13 @@ package org.opendatadiscovery.oddplatform.repository.reactive; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Stream; import org.jooq.Condition; +import org.jooq.Field; import org.jooq.Record; import org.jooq.Select; import org.jooq.SelectConditionStep; @@ -15,6 +18,7 @@ import org.opendatadiscovery.oddplatform.dto.OwnerDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.RolePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.UserOwnerMappingPojo; import org.opendatadiscovery.oddplatform.model.tables.records.OwnerRecord; import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; @@ -33,6 +37,7 @@ import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_ASSOCIATION_REQUEST; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_TO_ROLE; import static org.opendatadiscovery.oddplatform.model.Tables.ROLE; +import static org.opendatadiscovery.oddplatform.model.Tables.USER_OWNER_MAPPING; @Repository public class ReactiveOwnerRepositoryImpl extends ReactiveAbstractSoftDeleteCRUDRepository @@ -59,13 +64,20 @@ public Flux get(final Collection ownerIds) { @Override public Mono getDto(final Long id) { + final List> groupByFields = Stream.of(OWNER.fields(), USER_OWNER_MAPPING.fields()) + .flatMap(Arrays::stream) + .toList(); + final var dtoQuery = DSL.select(OWNER.fields()) .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) + .select(USER_OWNER_MAPPING.asterisk()) .from(OWNER) .leftJoin(OWNER_TO_ROLE).on(OWNER.ID.eq(OWNER_TO_ROLE.OWNER_ID)) .leftJoin(ROLE).on(OWNER_TO_ROLE.ROLE_ID.eq(ROLE.ID)) + .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OWNER_ID.eq(OWNER.ID) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .where(OWNER.ID.eq(id)) - .groupBy(OWNER.fields()); + .groupBy(groupByFields); return jooqReactiveOperations.mono(dtoQuery) .map(this::mapRecordToDto); } @@ -83,14 +95,21 @@ public Mono> list(final int page, List.of(new OrderByField(idField, SortOrder.ASC)), (page - 1) * size, size); final Table ownerCTE = ownerSelect.asTable("owner_cte"); + final List> groupByFields = Stream.of(ownerCTE.fields(), USER_OWNER_MAPPING.fields()) + .flatMap(Arrays::stream) + .toList(); + final var query = DSL.with(ownerCTE.getName()) .as(ownerSelect) .select(ownerCTE.fields()) .select(jsonArrayAgg(field(ROLE.asterisk().toString())).as(AGG_ROLE_FIELD)) + .select(USER_OWNER_MAPPING.asterisk()) .from(ownerCTE) .leftJoin(OWNER_TO_ROLE).on(OWNER_TO_ROLE.OWNER_ID.eq(ownerCTE.field(idField))) .leftJoin(ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) - .groupBy(ownerCTE.fields()); + .leftJoin(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OWNER_ID.eq(ownerCTE.field(idField)) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) + .groupBy(groupByFields); return jooqReactiveOperations.flux(query) .collectList() @@ -145,12 +164,14 @@ private Select enrichSelect(final SelectJoinStep select private OwnerDto mapRecordToDto(final Record r) { final OwnerPojo pojo = r.into(OWNER).into(OwnerPojo.class); final Set roles = jooqRecordHelper.extractAggRelation(r, AGG_ROLE_FIELD, RolePojo.class); - return new OwnerDto(pojo, roles); + final UserOwnerMappingPojo associatedUsers = r.into(USER_OWNER_MAPPING).into(UserOwnerMappingPojo.class); + return new OwnerDto(pojo, roles, associatedUsers); } private OwnerDto mapRecordToDto(final Record r, final String cteName) { final OwnerPojo ownerPojo = jooqRecordHelper.remapCte(r, cteName, OWNER).into(OwnerPojo.class); final Set roles = jooqRecordHelper.extractAggRelation(r, AGG_ROLE_FIELD, RolePojo.class); - return new OwnerDto(ownerPojo, roles); + final UserOwnerMappingPojo associatedUsers = r.into(USER_OWNER_MAPPING).into(UserOwnerMappingPojo.class); + return new OwnerDto(ownerPojo, roles, associatedUsers); } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepository.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepository.java index ae6eec862..0ef35aa53 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepository.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepository.java @@ -14,6 +14,8 @@ Mono createRelation(final String oidcUsername, Mono deleteRelation(final String oidcUsername, final String provider); + Mono deleteActiveRelationByOwner(final Long ownerId); + Mono getAssociatedOwner(final String oidcUsername, final String provider); diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepositoryImpl.java index 19b3a94e1..7aad0a2e1 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveUserOwnerMappingRepositoryImpl.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; @@ -14,11 +15,13 @@ import org.opendatadiscovery.oddplatform.repository.mapper.RoleRecordMapper; import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; +import org.opendatadiscovery.oddplatform.service.ingestion.util.DateTimeUtil; import org.springframework.stereotype.Repository; import reactor.core.publisher.Mono; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.jsonArrayAgg; +import static org.jooq.impl.SQLDataType.DATE; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER_TO_ROLE; import static org.opendatadiscovery.oddplatform.model.Tables.POLICY; @@ -39,27 +42,37 @@ public Mono createRelation(final String oidcUsername, final String provider, final Long ownerId) { final var query = DSL.insertInto(USER_OWNER_MAPPING) - .values(ownerId, oidcUsername, provider) - .onConflict(USER_OWNER_MAPPING.OWNER_ID).doUpdate() - .set(USER_OWNER_MAPPING.OWNER_ID, ownerId) - .set(USER_OWNER_MAPPING.OIDC_USERNAME, oidcUsername) - .set(USER_OWNER_MAPPING.PROVIDER, provider) + .values(ownerId, oidcUsername, provider, DSL.val(null, DATE)) .returning(); - return jooqReactiveOperations.mono(query) - .map(r -> r.into(UserOwnerMappingPojo.class)); + return deleteActiveRelationByOwner(ownerId) + .then(jooqReactiveOperations.mono(query) + .map(r -> r.into(UserOwnerMappingPojo.class))); } @Override public Mono deleteRelation(final String oidcUsername, final String provider) { - final var query = DSL.deleteFrom(USER_OWNER_MAPPING) + final var query = DSL.update(USER_OWNER_MAPPING) + .set(Map.of(USER_OWNER_MAPPING.DELETED_AT, DateTimeUtil.generateNow())) .where(getConditions(oidcUsername, provider)) .returning(); return jooqReactiveOperations.mono(query) .map(r -> r.into(UserOwnerMappingPojo.class)); } + @Override + public Mono deleteActiveRelationByOwner(final Long ownerId) { + final var query = DSL.update(USER_OWNER_MAPPING) + .set(Map.of(USER_OWNER_MAPPING.DELETED_AT, DateTimeUtil.generateNow())) + .where(USER_OWNER_MAPPING.OWNER_ID.eq(ownerId)) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull()) + .returning(); + + return jooqReactiveOperations.mono(query) + .map(r -> r.into(UserOwnerMappingPojo.class)); + } + @Override public Mono getAssociatedOwner(final String oidcUsername, final String provider) { @@ -77,6 +90,7 @@ public Mono isOwnerAssociated(final Long ownerId) { DSL.select() .from(USER_OWNER_MAPPING) .where(USER_OWNER_MAPPING.OWNER_ID.eq(ownerId)) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull()) ); return jooqReactiveOperations.mono(query).map(Record1::component1); } @@ -89,7 +103,9 @@ public Mono> getUserRolesByOwner(final String username, final Stri .leftJoin(ROLE_TO_POLICY).on(ROLE.ID.eq(ROLE_TO_POLICY.ROLE_ID)) .leftJoin(POLICY).on(ROLE_TO_POLICY.POLICY_ID.eq(POLICY.ID)) .join(OWNER_TO_ROLE).on(ROLE.ID.eq(OWNER_TO_ROLE.ROLE_ID)) - .join(USER_OWNER_MAPPING).on(USER_OWNER_MAPPING.OWNER_ID.eq(OWNER_TO_ROLE.OWNER_ID)) + .join(USER_OWNER_MAPPING) + .on(USER_OWNER_MAPPING.OWNER_ID.eq(OWNER_TO_ROLE.OWNER_ID) + .and(USER_OWNER_MAPPING.DELETED_AT.isNull())) .where(getConditions(username, provider)) .groupBy(ROLE.fields()); return jooqReactiveOperations.flux(query) @@ -101,6 +117,7 @@ private List getConditions(final String oidcUsername, final String provider) { final List conditions = new ArrayList<>(); conditions.add(USER_OWNER_MAPPING.OIDC_USERNAME.eq(oidcUsername)); + conditions.add(USER_OWNER_MAPPING.DELETED_AT.isNull()); if (StringUtils.isNotEmpty(provider)) { conditions.add(USER_OWNER_MAPPING.PROVIDER.eq(provider)); } else { diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/IdentityServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/IdentityServiceImpl.java index 769cfb1ca..df31bcc1f 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/IdentityServiceImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/IdentityServiceImpl.java @@ -38,7 +38,7 @@ private Mono getAssociatedOwner(final UserDto userDto) { userOwnerMappingRepository.getAssociatedOwner(userDto.username(), userDto.provider()) .defaultIfEmpty(new OwnerPojo()), ownerAssociationRequestRepository.getLastRequestForUsername(userDto.username()) - .defaultIfEmpty(new OwnerAssociationRequestDto(null, null, null)), + .defaultIfEmpty(new OwnerAssociationRequestDto(null, null, null, null, null)), permissionService.getNonContextualPermissionsForCurrentUser(PermissionResourceType.MANAGEMENT) .collectList()) .map(function((owner, request, permissions) -> { diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityService.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityService.java new file mode 100644 index 000000000..f18d8a53e --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityService.java @@ -0,0 +1,21 @@ +package org.opendatadiscovery.oddplatform.service; + +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestActivityList; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; +import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestActivityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; +import reactor.core.publisher.Mono; + +public interface OwnerAssociationRequestActivityService { + Mono + getOwnerAssociationRequestList(final Integer page, + final Integer size, + final String query, + final OwnerAssociationRequestStatusParam status); + + Mono + createOwnerAssociationRequestActivity(final OwnerAssociationRequestPojo pojo, + final String updateBy, + final OwnerAssociationRequestActivityType eventType); +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityServiceImpl.java new file mode 100644 index 000000000..9990573e6 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestActivityServiceImpl.java @@ -0,0 +1,37 @@ +package org.opendatadiscovery.oddplatform.service; + +import lombok.RequiredArgsConstructor; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestActivityList; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; +import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType; +import org.opendatadiscovery.oddplatform.mapper.OwnerAssociationRequestActivityMapper; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestActivityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveOwnerAssociationRequestActivityRepository; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class OwnerAssociationRequestActivityServiceImpl implements OwnerAssociationRequestActivityService { + private final ReactiveOwnerAssociationRequestActivityRepository activityRepository; + private final OwnerAssociationRequestActivityMapper mapper; + + @Override + public Mono + getOwnerAssociationRequestList(final Integer page, + final Integer size, + final String query, + final OwnerAssociationRequestStatusParam status) { + return activityRepository.getDtoList(page, size, query, status) + .map(mapper::mapPage); + } + + @Override + public Mono createOwnerAssociationRequestActivity( + final OwnerAssociationRequestPojo pojo, + final String updateBy, + final OwnerAssociationRequestActivityType eventType) { + return activityRepository.create(mapper.mapToPojo(pojo, updateBy, eventType.name())); + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestService.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestService.java index 4eedb7905..c6d8f9ac1 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestService.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestService.java @@ -1,8 +1,12 @@ package org.opendatadiscovery.oddplatform.service; +import org.opendatadiscovery.oddplatform.api.contract.model.Owner; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequest; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestList; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; +import org.opendatadiscovery.oddplatform.api.contract.model.ProviderList; +import org.opendatadiscovery.oddplatform.api.contract.model.UserOwnerMappingFormData; import reactor.core.publisher.Mono; public interface OwnerAssociationRequestService { @@ -11,8 +15,14 @@ public interface OwnerAssociationRequestService { Mono getOwnerAssociationRequestList(final int page, final int size, final String query, - final Boolean active); + final OwnerAssociationRequestStatusParam status); Mono updateOwnerAssociationRequest(final long id, final OwnerAssociationRequestStatus status); + + Mono createUserOwnerMapping(final UserOwnerMappingFormData form); + + Mono deleteActiveUserOwnerMapping(final Long ownerId); + + Mono getAuthProviders(); } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestServiceImpl.java index 21508b054..bcfa3a0d9 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestServiceImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerAssociationRequestServiceImpl.java @@ -1,68 +1,87 @@ package org.opendatadiscovery.oddplatform.service; +import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; import org.opendatadiscovery.oddplatform.annotation.ReactiveTransactional; +import org.opendatadiscovery.oddplatform.api.contract.model.Owner; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequest; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestList; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; import org.opendatadiscovery.oddplatform.api.contract.model.Permission; +import org.opendatadiscovery.oddplatform.api.contract.model.ProviderList; +import org.opendatadiscovery.oddplatform.api.contract.model.UserOwnerMappingFormData; import org.opendatadiscovery.oddplatform.auth.AuthIdentityProvider; +import org.opendatadiscovery.oddplatform.auth.Provider; import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; +import org.opendatadiscovery.oddplatform.dto.OwnerDto; import org.opendatadiscovery.oddplatform.dto.security.UserDto; import org.opendatadiscovery.oddplatform.exception.NotFoundException; import org.opendatadiscovery.oddplatform.mapper.OwnerAssociationRequestMapper; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; -import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; -import org.opendatadiscovery.oddplatform.model.tables.pojos.UserOwnerMappingPojo; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveOwnerAssociationRequestRepository; -import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveUserOwnerMappingRepository; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveOwnerRepository; import org.opendatadiscovery.oddplatform.service.ingestion.util.DateTimeUtil; import org.opendatadiscovery.oddplatform.service.permission.PermissionService; +import org.opendatadiscovery.oddplatform.utils.ProviderUtils; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import static org.opendatadiscovery.oddplatform.api.contract.model.PermissionResourceType.MANAGEMENT; +import static org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType.REQUEST_APPROVED; +import static org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType.REQUEST_CREATED; +import static org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType.REQUEST_DECLINED; +import static org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType.REQUEST_MANUALLY_APPROVED; +import static org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestActivityType.REQUEST_MANUALLY_DECLINED; import static reactor.function.TupleUtils.function; @Service @RequiredArgsConstructor public class OwnerAssociationRequestServiceImpl implements OwnerAssociationRequestService { private final OwnerService ownerService; + private final ReactiveOwnerRepository ownerRepository; private final ReactiveOwnerAssociationRequestRepository ownerAssociationRequestRepository; - private final OwnerAssociationRequestMapper mapper; private final AuthIdentityProvider authIdentityProvider; - private final ReactiveUserOwnerMappingRepository userOwnerMappingRepository; + private final UserOwnerMappingService userOwnerMappingService; + private final OwnerAssociationRequestActivityService activityService; private final PermissionService permissionService; + private final OwnerAssociationRequestMapper mapper; + private final ProviderUtils providerUtils; @Override @ReactiveTransactional public Mono createOwnerAssociationRequest(final String ownerName) { final Mono currentUserMono = authIdentityProvider.getCurrentUser() .switchIfEmpty(Mono.error(() -> new RuntimeException("There is no current authorization"))); - final Mono ownerMono = ownerService.getOrCreate(ownerName); + final Mono ownerMono = ownerService.getOrCreate(ownerName) + .flatMap(item -> ownerRepository.getDto(item.getId())); + final Mono> permissionsMono = permissionService .getNonContextualPermissionsForCurrentUser(MANAGEMENT).collectList(); return Mono.zip(currentUserMono, ownerMono, permissionsMono) - .flatMap(function((user, ownerPojo, permissions) -> { + .flatMap(function((user, owner, permissions) -> { if (permissions.contains(Permission.DIRECT_OWNER_SYNC)) { - return createRelation(user.username(), user.provider(), ownerPojo.getId()) - .thenReturn(mapper.mapToApprovedRequest(user.username(), ownerPojo.getName())); + return userOwnerMappingService.createRelation(user.username(), user.provider(), + owner.ownerPojo().getId()) + .thenReturn(mapper.mapToApprovedRequest(user.username(), owner.ownerPojo().getName())); } else { - return Mono.just(mapper.mapToPojo(user.username(), user.provider(), ownerPojo.getId())) - .flatMap(ownerAssociationRequestRepository::create) + return Mono.just(mapper.mapToPojo(user.username(), user.provider(), owner.ownerPojo().getId())) + .flatMap(item -> createOwnerAssociationRequestWithActivity(item, false, user.username())) .map(pojo -> mapper.mapToOwnerAssociationRequest( - new OwnerAssociationRequestDto(pojo, ownerName, null))); + new OwnerAssociationRequestDto(pojo, ownerName, owner.ownerPojo().getId(), owner.roles(), + null))); } })); } @Override - public Mono getOwnerAssociationRequestList(final int page, - final int size, - final String query, - final Boolean active) { - return ownerAssociationRequestRepository.getDtoList(page, size, query, active) + public Mono + getOwnerAssociationRequestList(final int page, + final int size, + final String query, + final OwnerAssociationRequestStatusParam status) { + return ownerAssociationRequestRepository.getDtoList(page, size, query, status) .map(mapper::mapPage); } @@ -79,21 +98,125 @@ public Mono updateOwnerAssociationRequest(final long id (dto, user) -> mapper.applyToPojo(dto.pojo(), status, user.username(), DateTimeUtil.generateNow()))) .flatMap(ownerAssociationRequestRepository::update) .flatMap(this::createMappingForApprovedRequest) + .flatMap(item -> createActivity(item, false, item.getStatusUpdatedBy())) + .flatMap(this::cancelCollisionAssociationByIdForApprovedRequest) .flatMap(pojo -> ownerAssociationRequestRepository.getDto(pojo.getId())) .map(mapper::mapToOwnerAssociationRequest); } + @Override + @ReactiveTransactional + public Mono createUserOwnerMapping(final UserOwnerMappingFormData form) { + return createManualAssociationRequest(form.getOidcUsername(), form.getProvider(), + form.getOwnerId()) + .then(userOwnerMappingService.createRelation(form.getOidcUsername(), form.getProvider(), form.getOwnerId())) + .then(ownerService.getOwnerDtoById(form.getOwnerId())); + } + + @Override + @ReactiveTransactional + public Mono deleteActiveUserOwnerMapping(final Long ownerId) { + return cancelAssociationByOwnerId(ownerId) + .then(userOwnerMappingService.deleteActiveUserRelation(ownerId)) + .then(ownerService.getOwnerDtoById(ownerId)); + } + + @Override + public Mono getAuthProviders() { + return Mono.just(new ProviderList() + .defaultProviders(Arrays.stream(Provider.values()).map(value -> value.name().toLowerCase()).toList()) + .activeProviders(providerUtils.getRegisteredProviders())); + } + + private Mono createManualAssociationRequest(final String oidcUsername, + final String provider, + final Long ownerId) { + final Mono currentUserMono = authIdentityProvider.getCurrentUser() + .switchIfEmpty(Mono.error(() -> new RuntimeException("There is no current authorization"))); + + return currentUserMono.flatMap(user -> { + final OwnerAssociationRequestPojo association = mapper.mapToPojo(oidcUsername, provider, ownerId); + + association.setStatus(OwnerAssociationRequestStatus.APPROVED.getValue()); + association.setStatusUpdatedBy(user.username()); + association.setStatusUpdatedAt(DateTimeUtil.generateNow()); + + return cancelAssociationByOwnerId(ownerId, user.username()) + .then(cancelAssociationByUsername(oidcUsername, user.username())) + .then(createOwnerAssociationRequestWithActivity(association, true, user.username())); + }); + } + + private Mono createOwnerAssociationRequestWithActivity( + final OwnerAssociationRequestPojo pojoToCreate, final boolean isManual, final String createdBy) { + return ownerAssociationRequestRepository.create(pojoToCreate) + .flatMap(item -> createActivity(item, isManual, createdBy) + .thenReturn(item)); + } + + private Mono> cancelAssociationByOwnerId(final Long ownerId) { + final Mono currentUserMono = authIdentityProvider.getCurrentUser() + .switchIfEmpty(Mono.error(() -> new RuntimeException("There is no current authorization"))); + + return currentUserMono.flatMap(user -> cancelAssociationByOwnerId(ownerId, user.username())); + } + + private Mono> cancelAssociationByOwnerId(final long ownerId, + final String updateBy) { + return ownerAssociationRequestRepository.cancelAssociationByOwnerId(ownerId, updateBy) + .flatMap(item -> + activityService.createOwnerAssociationRequestActivity(item, item.getStatusUpdatedBy(), + REQUEST_MANUALLY_DECLINED) + .thenReturn(item)) + .collectList(); + } + + private Mono> cancelAssociationByUsername(final String oidcUsername, + final String updateBy) { + return ownerAssociationRequestRepository.cancelAssociationByUsername(oidcUsername, updateBy) + .flatMap(item -> + activityService.createOwnerAssociationRequestActivity(item, item.getStatusUpdatedBy(), + REQUEST_MANUALLY_DECLINED) + .thenReturn(item)) + .collectList(); + } + private Mono createMappingForApprovedRequest(final OwnerAssociationRequestPojo pojo) { if (!pojo.getStatus().equals(OwnerAssociationRequestStatus.APPROVED.getValue())) { return Mono.just(pojo); } - return createRelation(pojo.getUsername(), pojo.getProvider(), pojo.getOwnerId()).thenReturn(pojo); + return userOwnerMappingService.createRelation(pojo.getUsername(), pojo.getProvider(), pojo.getOwnerId()) + .thenReturn(pojo); } - private Mono createRelation(final String username, - final String provider, - final Long ownerId) { - return userOwnerMappingRepository.deleteRelation(username, provider) - .then(userOwnerMappingRepository.createRelation(username, provider, ownerId)); + private Mono cancelCollisionAssociationByIdForApprovedRequest( + final OwnerAssociationRequestPojo pojo) { + if (!pojo.getStatus().equals(OwnerAssociationRequestStatus.APPROVED.getValue())) { + return Mono.just(pojo); + } + + return ownerAssociationRequestRepository.cancelCollisionAssociationById(pojo) + .flatMap(item -> activityService.createOwnerAssociationRequestActivity(item, item.getStatusUpdatedBy(), + REQUEST_MANUALLY_DECLINED)) + .collectList() + .thenReturn(pojo); + } + + private Mono createActivity(final OwnerAssociationRequestPojo pojo, + final boolean isManual, + final String createdBy) { + if (pojo.getStatus().equals(OwnerAssociationRequestStatus.APPROVED.getValue())) { + return activityService.createOwnerAssociationRequestActivity(pojo, createdBy, + isManual ? REQUEST_MANUALLY_APPROVED : REQUEST_APPROVED) + .thenReturn(pojo); + } else if (pojo.getStatus().equals(OwnerAssociationRequestStatus.DECLINED.getValue())) { + return activityService.createOwnerAssociationRequestActivity(pojo, createdBy, + isManual ? REQUEST_MANUALLY_DECLINED : REQUEST_DECLINED) + .thenReturn(pojo); + } else { + return activityService.createOwnerAssociationRequestActivity(pojo, createdBy, + REQUEST_CREATED) + .thenReturn(pojo); + } } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerService.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerService.java index ff8b9ae34..fe8c5eaee 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerService.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerService.java @@ -21,4 +21,6 @@ Mono list(final int page, Mono update(final long id, final OwnerFormData updateEntityForm); Mono delete(final long id); + + Mono getOwnerDtoById(final long ownerId); } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerServiceImpl.java index 339f2f08d..ac0eced5f 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerServiceImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/OwnerServiceImpl.java @@ -20,7 +20,6 @@ import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveTermOwnershipRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveTermSearchEntrypointRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveUserOwnerMappingRepository; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -29,12 +28,12 @@ public class OwnerServiceImpl implements OwnerService { private final ReactiveOwnerRepository ownerRepository; private final ReactiveUserOwnerMappingRepository userOwnerMappingRepository; - private final OwnerMapper ownerMapper; private final ReactiveSearchEntrypointRepository searchEntrypointRepository; private final ReactiveTermSearchEntrypointRepository termSearchEntrypointRepository; private final ReactiveTermOwnershipRepository termOwnershipRepository; private final ReactiveOwnershipRepository ownershipRepository; private final ReactiveOwnerToRoleRepository ownerToRoleRepository; + private final OwnerMapper ownerMapper; @Override public Mono getOrCreate(final String name) { @@ -100,6 +99,13 @@ public Mono delete(final long id) { .then(); } + @Override + public Mono getOwnerDtoById(final long ownerId) { + return ownerRepository.getDto(ownerId) + .switchIfEmpty(Mono.error(new NotFoundException("Owner", ownerId))) + .map(ownerMapper::mapFromDto); + } + private Mono updateSearchVectors(final OwnerPojo owner) { return Mono.zip( searchEntrypointRepository.updateChangedOwnerVectors(owner.getId()), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingService.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingService.java new file mode 100644 index 000000000..58fcca73b --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingService.java @@ -0,0 +1,13 @@ +package org.opendatadiscovery.oddplatform.service; + +import org.opendatadiscovery.oddplatform.model.tables.pojos.UserOwnerMappingPojo; +import reactor.core.publisher.Mono; + +public interface UserOwnerMappingService { + + Mono createRelation(final String username, + final String provider, + final Long ownerId); + + Mono deleteActiveUserRelation(final Long ownerId); +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingServiceImpl.java new file mode 100644 index 000000000..756ee36c1 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/UserOwnerMappingServiceImpl.java @@ -0,0 +1,24 @@ +package org.opendatadiscovery.oddplatform.service; + +import lombok.RequiredArgsConstructor; +import org.opendatadiscovery.oddplatform.model.tables.pojos.UserOwnerMappingPojo; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveUserOwnerMappingRepository; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class UserOwnerMappingServiceImpl implements UserOwnerMappingService { + private final ReactiveUserOwnerMappingRepository userOwnerMappingRepository; + + @Override + public Mono createRelation(final String username, final String provider, final Long ownerId) { + return userOwnerMappingRepository.deleteRelation(username, provider) + .then(userOwnerMappingRepository.createRelation(username, provider, ownerId)); + } + + @Override + public Mono deleteActiveUserRelation(final Long ownerId) { + return userOwnerMappingRepository.deleteActiveRelationByOwner(ownerId); + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/utils/ProviderUtils.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/utils/ProviderUtils.java new file mode 100644 index 000000000..98d2d627c --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/utils/ProviderUtils.java @@ -0,0 +1,29 @@ +package org.opendatadiscovery.oddplatform.utils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.opendatadiscovery.oddplatform.auth.ODDOAuth2Properties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +public class ProviderUtils { + @Autowired + Environment environment; + + private static final Bindable> OAUTH2_PROPERTIES = Bindable + .mapOf(String.class, ODDOAuth2Properties.OAuth2Provider.class); + + public List getRegisteredProviders() { + final Map properties = Binder.get(environment) + .bind("auth.oauth2.client", OAUTH2_PROPERTIES) + .orElse(Map.of()); + return properties.values().stream() + .map(ODDOAuth2Properties.OAuth2Provider::getProvider) + .collect(Collectors.toList()); + } +} diff --git a/odd-platform-api/src/main/resources/db/migration/V0_0_89__update_user_owner.sql b/odd-platform-api/src/main/resources/db/migration/V0_0_89__update_user_owner.sql new file mode 100644 index 000000000..d266722c1 --- /dev/null +++ b/odd-platform-api/src/main/resources/db/migration/V0_0_89__update_user_owner.sql @@ -0,0 +1,28 @@ +ALTER TABLE IF EXISTS user_owner_mapping + ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL; + +DROP INDEX IF EXISTS user_owner_mapping_oidc_username_provider_key; + +ALTER TABLE IF EXISTS user_owner_mapping +DROP CONSTRAINT IF EXISTS user_owner_mapping_owner_id_key; + +CREATE UNIQUE INDEX IF NOT EXISTS unique_deleted_at_per_owner +ON user_owner_mapping (owner_id) +WHERE deleted_at IS NULL; + +CREATE UNIQUE INDEX IF NOT EXISTS user_owner_mapping_oidc_username_provider_deleted_key +ON user_owner_mapping (oidc_username, provider) +WHERE deleted_at IS NULL; + + +CREATE TABLE IF NOT EXISTS owner_association_request_activity +( + id bigserial PRIMARY KEY, + owner_association_request_id bigint, + event_type varchar(64) NOT NULL, + status varchar(64) NOT NULL, + created_at timestamp without time zone NOT NULL DEFAULT NOW(), + status_updated_by varchar(512), + + CONSTRAINT owner_association_request_activity_fk_owner_association_request FOREIGN KEY (owner_association_request_id) REFERENCES owner_association_request (id) +); diff --git a/odd-platform-api/src/main/resources/schema/policy_schema.json b/odd-platform-api/src/main/resources/schema/policy_schema.json index 6ff2eeb3c..29ad2f4d4 100644 --- a/odd-platform-api/src/main/resources/schema/policy_schema.json +++ b/odd-platform-api/src/main/resources/schema/policy_schema.json @@ -616,6 +616,7 @@ "OWNER_UPDATE", "OWNER_DELETE", "OWNER_ASSOCIATION_MANAGE", + "OWNER_RELATION_MANAGE", "DIRECT_OWNER_SYNC", "POLICY_CREATE", "POLICY_UPDATE", diff --git a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestMapperTest.java b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestMapperTest.java index 586ebe2c3..cf3769aaa 100644 --- a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestMapperTest.java +++ b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/mapper/OwnerAssociationRequestMapperTest.java @@ -4,6 +4,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.UUID; +import org.apache.commons.collections4.CollectionUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -71,7 +72,8 @@ public void applyToPojo() { public void mapToPendingOwnerAssociationRequest() { final OwnerAssociationRequestPojo pojo = createPendingPojo(); final String ownerName = UUID.randomUUID().toString(); - final OwnerAssociationRequestDto dto = new OwnerAssociationRequestDto(pojo, ownerName, null); + final OwnerAssociationRequestDto dto = + new OwnerAssociationRequestDto(pojo, ownerName, 1L, CollectionUtils.emptyCollection(), null); when(associatedOwnerMapper.mapAssociatedOwner(null)).thenReturn(null); when(dateTimeMapper.mapUTCDateTime(null)).thenReturn(null); @@ -92,7 +94,8 @@ public void mapToApprovedOwnerAssociationRequest() { final OffsetDateTime offsetDateTime = pojo.getStatusUpdatedAt().atOffset(ZoneOffset.UTC); final AssociatedOwnerDto ownerDto = new AssociatedOwnerDto(pojo.getUsername(), new OwnerPojo().setName(updatedUserOwnerName), null); - final OwnerAssociationRequestDto dto = new OwnerAssociationRequestDto(pojo, ownerName, ownerDto); + final OwnerAssociationRequestDto dto = + new OwnerAssociationRequestDto(pojo, ownerName, 1L, CollectionUtils.emptyCollection(), ownerDto); final AssociatedOwner identity = new AssociatedOwner() .identity(new Identity().username(pojo.getUsername())) diff --git a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/repository/ReactiveOwnerAssociationRequestRepositoryImplTest.java b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/repository/ReactiveOwnerAssociationRequestRepositoryImplTest.java index 6a8ea5058..d11ac8af0 100644 --- a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/repository/ReactiveOwnerAssociationRequestRepositoryImplTest.java +++ b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/repository/ReactiveOwnerAssociationRequestRepositoryImplTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.opendatadiscovery.oddplatform.BaseIntegrationTest; import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatus; +import org.opendatadiscovery.oddplatform.api.contract.model.OwnerAssociationRequestStatusParam; import org.opendatadiscovery.oddplatform.dto.AssociatedOwnerDto; import org.opendatadiscovery.oddplatform.dto.OwnerAssociationRequestDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerAssociationRequestPojo; @@ -91,7 +92,8 @@ public void getActiveDtoListTest() { final List pendingCreated = repository.bulkCreate(pendingPojos).collectList().block(); repository.bulkCreate(approvedPojos).collectList().block(); - StepVerifier.create(repository.getDtoList(1, 30, null, true)) + StepVerifier.create(repository.getDtoList(1, 30, null, + OwnerAssociationRequestStatusParam.PENDING)) .assertNext(page -> { assertThat(page.getTotal()).isEqualTo(pendingPojos.size()); assertThat(page.isHasNext()).isFalse(); @@ -121,7 +123,8 @@ public void getNotActiveDtoListTest() { repository.bulkCreate(pendingPojos).collectList().block(); final List approvedCreated = repository.bulkCreate(approvedPojos).collectList().block(); - StepVerifier.create(repository.getDtoList(1, 30, null, false)) + StepVerifier.create(repository.getDtoList(1, 30, null, + OwnerAssociationRequestStatusParam.RESOLVED)) .assertNext(page -> { assertThat(page.getTotal()).isEqualTo(approvedCreated.size()); assertThat(page.isHasNext()).isFalse(); diff --git a/odd-platform-specification/components.yaml b/odd-platform-specification/components.yaml index 7e9c52577..3e6ad5b7b 100644 --- a/odd-platform-specification/components.yaml +++ b/odd-platform-specification/components.yaml @@ -223,6 +223,7 @@ components: - OWNER_UPDATE - OWNER_DELETE - OWNER_ASSOCIATION_MANAGE + - OWNER_RELATION_MANAGE - DIRECT_OWNER_SYNC - POLICY_CREATE - POLICY_UPDATE @@ -358,10 +359,43 @@ components: type: array items: $ref: '#/components/schemas/Role' + associated_user: + $ref: '#/components/schemas/AssociatedUser' required: - id - name + AssociatedUser: + type: object + properties: + oidc_username: + type: string + provider: + type: string + + UserOwnerMappingFormData: + type: object + properties: + owner_id: + type: integer + format: int64 + oidc_username: + type: string + provider: + type: string + + ProviderList: + type: object + properties: + default_providers: + type: array + items: + type: string + active_providers: + type: array + items: + type: string + OwnerList: type: object properties: @@ -3124,6 +3158,19 @@ components: - items - page_info + OwnerAssociationRequestActivityList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/OwnerAssociationRequestActivity' + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - items + - page_info + OwnerAssociationRequest: type: object properties: @@ -3134,8 +3181,15 @@ components: type: string provider: type: string + owner_id: + type: integer + format: int64 owner_name: type: string + roles: + type: array + items: + $ref: '#/components/schemas/Role' status: $ref: '#/components/schemas/OwnerAssociationRequestStatus' status_updated_by: @@ -3155,6 +3209,25 @@ components: - APPROVED - DECLINED + OwnerAssociationRequestActivity: + allOf: + - $ref: '#/components/schemas/OwnerAssociationRequest' + - type: object + properties: + activity_id: + type: integer + format: int64 + event_type: + type: string + + OwnerAssociationRequestStatusParam: + type: string + enum: + - PENDING + - APPROVED + - DECLINED + - RESOLVED + OwnerAssociationRequestStatusFormData: type: object properties: diff --git a/odd-platform-specification/openapi.yaml b/odd-platform-specification/openapi.yaml index acef5f0e8..ed08d377a 100644 --- a/odd-platform-specification/openapi.yaml +++ b/odd-platform-specification/openapi.yaml @@ -3231,10 +3231,10 @@ paths: - $ref: './components.yaml/#/components/parameters/PageParam' - $ref: './components.yaml/#/components/parameters/SizeParam' - $ref: './components.yaml/#/components/parameters/SearchParam' - - name: active + - name: status in: query schema: - type: boolean + $ref: './components.yaml/#/components/schemas/OwnerAssociationRequestStatusParam' required: true responses: '200': @@ -3265,6 +3265,85 @@ paths: tags: - ownerAssociationRequest + /api/owner_association_request/activity: + get: + summary: List of owner association requests activity + description: Gets the list of activity for owner association requests + operationId: getOwnerAssociationRequestActivityList + parameters: + - $ref: './components.yaml/#/components/parameters/PageParam' + - $ref: './components.yaml/#/components/parameters/SizeParam' + - $ref: './components.yaml/#/components/parameters/SearchParam' + - name: status + in: query + schema: + $ref: './components.yaml/#/components/schemas/OwnerAssociationRequestStatusParam' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/OwnerAssociationRequestActivityList' + tags: + - ownerAssociationRequest + + /api/owners/mapping: + post: + summary: Create user owner mapping + operationId: createUserOwnerMapping + requestBody: + required: true + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/UserOwnerMappingFormData' + responses: + '201': + description: The resource has been successfully created + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/Owner' + '403': + $ref: './components.yaml/#/components/responses/Forbidden' + tags: + - ownerAssociationRequest + + /api/owners/mapping/{owner_id}: + delete: + summary: Delete active user owner + description: Deletes active user owner + operationId: deleteActiveUserOwnerMapping + parameters: + - name: owner_id + in: path + schema: + type: integer + format: int64 + required: true + responses: + '204': + $ref: './components.yaml/#/components/responses/Deleted' + tags: + - ownerAssociationRequest + + /api/owners/providers: + get: + summary: Get list of auth providers + description: Returns Get list of auth providers + operationId: getAuthProviders + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/ProviderList' + tags: + - ownerAssociationRequest + /api/owner_association_request/{owner_association_request_id}: put: summary: Update owner association request diff --git a/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociations.tsx b/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociations.tsx index 23780bb68..e80a2fb96 100644 --- a/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociations.tsx +++ b/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociations.tsx @@ -1,16 +1,11 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Grid } from '@mui/material'; -import { Navigate, Route, Routes, useLocation, useMatch } from 'react-router-dom'; -import { useAtom } from 'jotai'; -import { fetchOwnerAssociationRequestList } from 'redux/thunks'; -import { - getNewOwnerAssociationRequestsPageInfo, - getResolvedOwnerAssociationRequestsPageInfo, -} from 'redux/selectors'; -import { useAppDispatch, useAppSelector } from 'redux/lib/hooks'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { useGetOwnerAssociationRequestList } from 'lib/hooks/api/ownerAssociationRequest'; +import { OwnerAssociationRequestStatusParam, Permission } from 'generated-sources'; +import { WithPermissionsProvider } from 'components/shared/contexts'; import OwnerAssociationsTabs from './OwnerAssociationsTabs/OwnerAssociationsTabs'; import OwnerAssociationsHeader from './OwnerAssociationsHeader/OwnerAssociationsHeader'; -import { queryAtom } from './OwnerAssociationsStore/OwnerAssociationsAtoms'; import OwnerAssociationsAtomProvider from './OwnerAssociationsStore/OwnerAssociationsProvider'; const OwnerAssociationsNew = React.lazy( @@ -20,53 +15,53 @@ const OwnerAssociationsResolved = React.lazy( () => import('./OwnerAssociationsList/OwnerAssociationsResolved/OwnerAssociationsResolved') ); +const OwnerAssociationsActive = React.lazy( + () => import('./OwnerAssociationsList/OwnerAssociationsActive/OwnerAssociationsActive') +); const OwnerAssociations: React.FC = () => { - const dispatch = useAppDispatch(); - const match = useMatch(useLocation().pathname); - - const [query] = useAtom(queryAtom); - const size = 30; - const active = React.useMemo(() => { - if (match) { - return match.pathname.includes('new'); - } - return false; - }, [match?.pathname]); + const { data: activeRequests } = useGetOwnerAssociationRequestList({ + query: '', + status: OwnerAssociationRequestStatusParam.APPROVED, + size, + }); + const { data: newRequests } = useGetOwnerAssociationRequestList({ + query: '', + status: OwnerAssociationRequestStatusParam.PENDING, + size, + }); - const newRequestsPageInfo = useAppSelector(getNewOwnerAssociationRequestsPageInfo); - const resolvedRequestsPageInfo = useAppSelector( - getResolvedOwnerAssociationRequestsPageInfo + const totalActive = useMemo( + () => activeRequests?.pages[0].pageInfo.total, + [activeRequests?.pages] ); - - const total = React.useMemo( - () => newRequestsPageInfo.total + resolvedRequestsPageInfo.total, - [newRequestsPageInfo.total + resolvedRequestsPageInfo.total] + const totalNew = useMemo( + () => newRequests?.pages[0].pageInfo.total, + [newRequests?.pages] ); - React.useEffect(() => { - if (!query) { - dispatch(fetchOwnerAssociationRequestList({ page: 1, size, active: true })); - dispatch(fetchOwnerAssociationRequestList({ page: 1, size, active: false })); - } - }, []); - return ( - + } /> + } /> } /> - } /> + } /> diff --git a/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociationsHeader/OwnerAssociationForm/OwnerAssociationForm.tsx b/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociationsHeader/OwnerAssociationForm/OwnerAssociationForm.tsx new file mode 100644 index 000000000..e6b6c575e --- /dev/null +++ b/odd-platform-ui/src/components/Management/OwnerAssociations/OwnerAssociationsHeader/OwnerAssociationForm/OwnerAssociationForm.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import type { UserOwnerMappingFormData } from 'generated-sources'; +import { + Button, + DialogWrapper, + Input, + OwnerIdAutocomplete, + ProviderAutocomplete, +} from 'components/shared/elements'; +import { useCreateUserOwnerMapping } from 'lib/hooks'; + +interface OwnerAssociationFormProps { + btnCreateEl: React.JSX.Element; +} + +const OwnerAssociationForm: React.FC = ({ btnCreateEl }) => { + const { t } = useTranslation(); + + const { + mutateAsync: createUserOwnerMapping, + isPending: isAssociationCreating, + isSuccess, + } = useCreateUserOwnerMapping(); + + const { + handleSubmit, + control, + reset, + formState: { isValid }, + } = useForm({ + mode: 'all', + reValidateMode: 'onChange', + }); + + const clearState = () => { + reset(); + }; + + const onSubmit = async (data: UserOwnerMappingFormData) => { + await createUserOwnerMapping({ + userOwnerMappingFormData: data, + }); + }; + + const ownerAssociationFormTitle = ( + + Create association + + ); + + const ownerAssociationFormContent = () => ( +
+ } + /> + ( + + )} + /> + } + /> + + ); + + const ownerAssociationFormActionButtons = () => ( +