diff --git a/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java b/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java index bcb01ecb5..064a4bad7 100644 --- a/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java +++ b/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java @@ -177,6 +177,11 @@ public class AuditableAction { public static final EventId TenantConfiguration_LookupByType = new EventId(270004, "TenantConfiguration_LookupByType"); public static final EventId Annotation_Created_Notify = new EventId(280000, "Annotation_Created_Notify"); + + public static final EventId UsageLimit_Query = new EventId(290000, "UsageLimit_Query"); + public static final EventId UsageLimit_Lookup = new EventId(290001, "UsageLimit_Lookup"); + public static final EventId UsageLimit_Persist = new EventId(290002, "UsageLimit_Persist"); + public static final EventId UsageLimit_Delete = new EventId(290003, "UsageLimit_Delete"); diff --git a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java index 0d1fa6add..07014bda6 100644 --- a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java +++ b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java @@ -200,6 +200,11 @@ public final class Permission { public static String EditPrefillingSource= "EditPrefillingSource"; public static String DeletePrefillingSource = "DeletePrefillingSource"; + //UsageLimit + public static String BrowseUsageLimit = "BrowseUsageLimit"; + public static String EditUsageLimit = "EditUsageLimit"; + public static String DeleteUsageLimit = "DeleteUsageLimit"; + //NotificationTemplate public static String BrowseStatus = "BrowseStatus"; public static String EditStatus = "EditStatus"; @@ -220,6 +225,7 @@ public final class Permission { public static String ViewReferenceTypePage = "ViewReferenceTypePage"; public static String ViewReferencePaPlge = "ViewReferencePage"; public static String ViewEntityLockPage = "ViewEntityLockPage"; + public static String ViewUsageLimitPage = "ViewUsageLimitPage"; public static String ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage"; public static String ViewPlanBlueprintPage = "ViewPlanBlueprintPage"; public static String ViewPublicDescriptionPage = "ViewPublicDescriptionPage"; diff --git a/backend/core/src/main/java/org/opencdmp/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java b/backend/core/src/main/java/org/opencdmp/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java index e2ff60441..4ab06886c 100644 --- a/backend/core/src/main/java/org/opencdmp/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java +++ b/backend/core/src/main/java/org/opencdmp/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java @@ -69,7 +69,7 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes if (idsToResolve.isEmpty()) return affiliatedResources; List planUsers; try { - this.tenantEntityManager.loadExplictTenantFilters(); + this.tenantEntityManager.loadExplicitTenantFilters(); planUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking().planIds(ids).sectionIsEmpty(true).userIds(userId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanUser._role).ensure(PlanUser._plan)); } catch (InvalidApplicationException e) { log.error(e.getMessage(), e); @@ -109,7 +109,7 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes List userDescriptionTemplates; try { - this.tenantEntityManager.loadExplictTenantFilters(); + this.tenantEntityManager.loadExplicitTenantFilters(); userDescriptionTemplates = this.queryFactory.query(UserDescriptionTemplateQuery.class).disableTracking().descriptionTemplateIds(ids).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(UserDescriptionTemplate._role).ensure(UserDescriptionTemplate._descriptionTemplate)); } catch (InvalidApplicationException e) { log.error(e.getMessage(), e); @@ -139,7 +139,7 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes //TODO: investigate if we want to use cache boolean hasAny; try { - this.tenantEntityManager.loadExplictTenantFilters(); + this.tenantEntityManager.loadExplicitTenantFilters(); hasAny = this.queryFactory.query(UserDescriptionTemplateQuery.class).disableTracking().userIds(userId).isActive(IsActive.Active).count() > 0; } catch (InvalidApplicationException e) { log.error(e.getMessage(), e); @@ -176,7 +176,7 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes List planUsers; List descriptionEntities; try { - this.tenantEntityManager.loadExplictTenantFilters(); + this.tenantEntityManager.loadExplicitTenantFilters(); descriptionEntities = this.queryFactory.query(DescriptionQuery.class).disableTracking().ids(ids).collectAs(new BaseFieldSet().ensure(Description._id).ensure(Description._planDescriptionTemplate).ensure(Description._plan)); planDescriptionTemplateEntities = this.queryFactory.query(PlanDescriptionTemplateQuery.class).disableTracking().ids(descriptionEntities.stream().map(DescriptionEntity::getPlanDescriptionTemplateId).distinct().toList()).collectAs(new BaseFieldSet().ensure(PlanDescriptionTemplate._id).ensure(PlanDescriptionTemplate._sectionId)); planUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking().descriptionIds(ids).userIds(userId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanUser._role).ensure(PlanUser._sectionId).ensure(PlanUser._plan)); @@ -232,7 +232,7 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes List planUsers; try { - this.tenantEntityManager.loadExplictTenantFilters(); + this.tenantEntityManager.loadExplicitTenantFilters(); planUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking().planIds(planId).userIds(userId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanUser._role).ensure(PlanUser._sectionId).ensure(PlanUser._plan)); } catch (InvalidApplicationException e) { log.error(e.getMessage(), e); diff --git a/backend/core/src/main/java/org/opencdmp/commons/enums/UsageLimitMetricValue.java b/backend/core/src/main/java/org/opencdmp/commons/enums/UsageLimitMetricValue.java new file mode 100644 index 000000000..8d7e7a171 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/enums/UsageLimitMetricValue.java @@ -0,0 +1,44 @@ +package org.opencdmp.commons.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.opencdmp.data.converters.enums.DatabaseEnum; + +import java.util.Map; + +public enum UsageLimitMetricValue implements DatabaseEnum { + USER_COUNT(MetricValues.UserCount), + PLAN_COUNT(MetricValues.PlanCount), + BLUEPRINT_COUNT(MetricValues.BlueprintCount), + DESCRIPTION_COUNT(MetricValues.DescriptionCount), + DESCRIPTION_TEMPLATE_COUNT(MetricValues.DescriptionTemplateCount), + DESCRIPTION_TEMPLATE_TYPE_COUNT(MetricValues.DescriptionTemplateTypeCount), + PREFILLING_SOURCES_COUNT(MetricValues.PrefillingSourcesCount), + REFERENCE_TYPE_COUNT(MetricValues.ReferenceTypeCount); + private final String value; + + public static class MetricValues { + public static final String UserCount = "user_count"; + public static final String PlanCount = "plan_count"; + public static final String BlueprintCount = "blueprint_count"; + public static final String DescriptionCount = "description_count"; + public static final String DescriptionTemplateCount = "description_template_count"; + public static final String DescriptionTemplateTypeCount = "description_template_type_count"; + public static final String PrefillingSourcesCount = "prefilling_sources_count"; + public static final String ReferenceTypeCount = "reference_type_count"; + } + + UsageLimitMetricValue(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return this.value; + } + + private static final Map map = EnumUtils.getEnumValueMap(UsageLimitMetricValue.class); + + public static UsageLimitMetricValue of(String i) { + return map.get(i); + } +} diff --git a/backend/core/src/main/java/org/opencdmp/data/TenantEntityManager.java b/backend/core/src/main/java/org/opencdmp/data/TenantEntityManager.java index 93d0979ff..8498d2f45 100644 --- a/backend/core/src/main/java/org/opencdmp/data/TenantEntityManager.java +++ b/backend/core/src/main/java/org/opencdmp/data/TenantEntityManager.java @@ -114,7 +114,7 @@ public class TenantEntityManager { this.tenantFiltersDisabled = false; } - public void loadExplictTenantFilters() throws InvalidApplicationException { + public void loadExplicitTenantFilters() throws InvalidApplicationException { if (!this.entityManager.isOpen()) return; this.disableTenantFilters(); diff --git a/backend/core/src/main/java/org/opencdmp/data/UsageLimitEntity.java b/backend/core/src/main/java/org/opencdmp/data/UsageLimitEntity.java new file mode 100644 index 000000000..3598ad8bd --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/data/UsageLimitEntity.java @@ -0,0 +1,111 @@ +package org.opencdmp.data; + +import jakarta.persistence.*; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.data.converters.enums.IsActiveConverter; +import org.opencdmp.data.tenant.TenantScopedBaseEntity; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UsageLimit\"") +public class UsageLimitEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "label", length = _labelLength, nullable = false) + private String label; + + public static final String _label = "label"; + + public static final int _labelLength = 250; + + + @Column(name = "metric_value", nullable = false) + private UsageLimitMetricValue metricValue; + + public static final String _metricValue = "metricValue"; + + @Column(name = "value", nullable = false) + private Long value; + + public static final String _value = "value"; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public UsageLimitMetricValue getMetricValue() { + return metricValue; + } + + public void setMetricValue(UsageLimitMetricValue metricValue) { + this.metricValue = metricValue; + } + + public Long getValue() { + return value; + } + + public void setValue(Long value) { + this.value = value; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/UsageLimit.java b/backend/core/src/main/java/org/opencdmp/model/UsageLimit.java new file mode 100644 index 000000000..5ddfff509 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/UsageLimit.java @@ -0,0 +1,109 @@ +package org.opencdmp.model; + +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.UsageLimitMetricValue; + +import java.time.Instant; +import java.util.UUID; + +public class UsageLimit { + + private UUID id; + public static final String _id = "id"; + + private String label; + public static final String _label = "label"; + + private UsageLimitMetricValue metricValue; + public static final String _metricValue = "metricValue"; + + private Long value; + public static final String _value = "value"; + + private IsActive isActive; + public static final String _isActive = "isActive"; + + private Instant createdAt; + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + public static final String _updatedAt = "updatedAt"; + + private String hash; + public static final String _hash = "hash"; + + private Boolean belongsToCurrentTenant; + public static final String _belongsToCurrentTenant = "belongsToCurrentTenant"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public UsageLimitMetricValue getMetricValue() { + return metricValue; + } + + public void setMetricValue(UsageLimitMetricValue metricValue) { + this.metricValue = metricValue; + } + + public Long getValue() { + return value; + } + + public void setValue(Long value) { + this.value = value; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public Boolean getBelongsToCurrentTenant() { + return belongsToCurrentTenant; + } + + public void setBelongsToCurrentTenant(Boolean belongsToCurrentTenant) { + this.belongsToCurrentTenant = belongsToCurrentTenant; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/UsageLimitBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/UsageLimitBuilder.java new file mode 100644 index 000000000..12d4e54da --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/UsageLimitBuilder.java @@ -0,0 +1,74 @@ +package org.opencdmp.model.builder; + +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.commons.scope.tenant.TenantScope; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.UsageLimitEntity; +import org.opencdmp.model.UsageLimit; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UsageLimitBuilder extends BaseBuilder { + + private final TenantScope tenantScope; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public UsageLimitBuilder( + ConventionService conventionService, + TenantScope tenantScope) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(UsageLimitBuilder.class))); + this.tenantScope = tenantScope; + } + + public UsageLimitBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + List models = new ArrayList<>(); + for (UsageLimitEntity d : data) { + UsageLimit m = new UsageLimit(); + if (fields.hasField(this.asIndexer(UsageLimit._id))) + m.setId(d.getId()); + if (fields.hasField(this.asIndexer(UsageLimit._label))) + m.setLabel(d.getLabel()); + if (fields.hasField(this.asIndexer(UsageLimit._metricValue))) + m.setMetricValue(d.getMetricValue()); + if (fields.hasField(this.asIndexer(UsageLimit._value))) + m.setValue(d.getValue()); + if (fields.hasField(this.asIndexer(UsageLimit._createdAt))) + m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(UsageLimit._updatedAt))) + m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(UsageLimit._isActive))) + m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(UsageLimit._hash))) + m.setHash(this.hashValue(d.getUpdatedAt())); + if (fields.hasField(this.asIndexer(UsageLimit._belongsToCurrentTenant))) m.setBelongsToCurrentTenant(this.getBelongsToCurrentTenant(d, this.tenantScope)); + + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/censorship/UsageLimitsCensor.java b/backend/core/src/main/java/org/opencdmp/model/censorship/UsageLimitsCensor.java new file mode 100644 index 000000000..b26d158f6 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/censorship/UsageLimitsCensor.java @@ -0,0 +1,40 @@ +package org.opencdmp.model.censorship; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.tools.data.censor.CensorFactory; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.authorization.Permission; +import org.opencdmp.convention.ConventionService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UsageLimitsCensor extends BaseCensor { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitsCensor.class)); + + protected final AuthorizationService authService; + protected final CensorFactory censorFactory; + + public UsageLimitsCensor(ConventionService conventionService, + AuthorizationService authService, + CensorFactory censorFactory) { + super(conventionService); + this.authService = authService; + this.censorFactory = censorFactory; + } + + public void censor(FieldSet fields) { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (fields == null || fields.isEmpty()) + return; + + this.authService.authorizeForce(Permission.BrowseUsageLimit); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/model/deleter/UsageLimitDeleter.java b/backend/core/src/main/java/org/opencdmp/model/deleter/UsageLimitDeleter.java new file mode 100644 index 000000000..12706f5b4 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/deleter/UsageLimitDeleter.java @@ -0,0 +1,79 @@ +package org.opencdmp.model.deleter; + +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.data.TenantEntityManager; +import org.opencdmp.data.UsageLimitEntity; +import org.opencdmp.query.UsageLimitQuery; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UsageLimitDeleter implements Deleter { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitDeleter.class)); + + private final TenantEntityManager entityManager; + + protected final QueryFactory queryFactory; + + protected final DeleterFactory deleterFactory; + + @Autowired + public UsageLimitDeleter( + TenantEntityManager entityManager, + QueryFactory queryFactory, + DeleterFactory deleterFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + this.deleterFactory = deleterFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(UsageLimitQuery.class).ids(ids).collect(); + logger.trace("retrieved {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.deleteAndSave(data); + } + + public void deleteAndSave(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.delete(data); + logger.trace("saving changes"); + this.entityManager.flush(); + logger.trace("changes saved"); + } + + public void delete(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + if (data == null || data.isEmpty()) + return; + + Instant now = Instant.now(); + + for (UsageLimitEntity item : data) { + logger.trace("deleting item {}", item.getId()); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/UsageLimitPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/UsageLimitPersist.java new file mode 100644 index 000000000..7b0bccb64 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/persist/UsageLimitPersist.java @@ -0,0 +1,122 @@ +package org.opencdmp.model.persist; + +import gr.cite.tools.validation.specification.Specification; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.commons.validation.BaseValidator; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.UsageLimitEntity; +import org.opencdmp.errorcode.ErrorThesaurusProperties; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class UsageLimitPersist { + + private UUID id; + public static final String _id = "id"; + + private String label; + public static final String _label = "label"; + + private UsageLimitMetricValue metricValue;; + public static final String _metricValue = "metricValue"; + + private Long value; + public static final String _value = "value"; + + private String hash; + public static final String _hash = "hash"; + + public UUID getId() { + return this.id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getLabel() { + return this.label; + } + + public void setLabel(String label) { + this.label = label; + } + + public UsageLimitMetricValue getMetricValue() { + return metricValue; + } + + public void setMetricValue(UsageLimitMetricValue metricValue) { + this.metricValue = metricValue; + } + + public Long getValue() { + return value; + } + + public void setValue(Long value) { + this.value = value; + } + + public String getHash() { + return this.hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + @Component(UsageLimitPersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class UsageLimitPersistValidator extends BaseValidator { + + public static final String ValidatorName = "UsageLimitPersistValidator"; + + private final MessageSource messageSource; + + protected UsageLimitPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return UsageLimitPersist.class; + } + + @Override + protected List specifications(UsageLimitPersist item) { + return Arrays.asList( + this.spec() + .iff(() -> this.isValidGuid(item.getId())) + .must(() -> this.isValidHash(item.getHash())) + .failOn(UsageLimitPersist._hash).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._hash}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isValidGuid(item.getId())) + .must(() -> !this.isValidHash(item.getHash())) + .failOn(UsageLimitPersist._hash).failWith(this.messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getLabel())) + .failOn(UsageLimitPersist._label).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._label}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getLabel())) + .must(() -> this.lessEqualLength(item.getLabel(), UsageLimitEntity._labelLength)) + .failOn(UsageLimitPersist._label).failWith(this.messageSource.getMessage("Validation_MaxLength", new Object[]{UsageLimitPersist._label}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getMetricValue())) + .failOn(UsageLimitPersist._metricValue).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._metricValue}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getValue())) + .failOn(UsageLimitPersist._value).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._value}, LocaleContextHolder.getLocale())) + ); + } + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/query/UsageLimitQuery.java b/backend/core/src/main/java/org/opencdmp/query/UsageLimitQuery.java new file mode 100644 index 000000000..74092c48a --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/query/UsageLimitQuery.java @@ -0,0 +1,227 @@ +package org.opencdmp.query; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.commons.scope.user.UserScope; +import org.opencdmp.data.*; +import org.opencdmp.model.UsageLimit; +import org.opencdmp.query.utils.QueryUtilsService; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UsageLimitQuery extends QueryBase { + + private String like; + + private Collection ids; + + private Collection isActives; + + private Collection usageLimitMetricValues; + + private Collection excludedIds; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + public UsageLimitQuery like(String value) { + this.like = value; + return this; + } + + public UsageLimitQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UsageLimitQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UsageLimitQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UsageLimitQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public UsageLimitQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public UsageLimitQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public UsageLimitQuery excludedIds(Collection values) { + this.excludedIds = values; + return this; + } + + public UsageLimitQuery excludedIds(UUID value) { + this.excludedIds = List.of(value); + return this; + } + + public UsageLimitQuery excludedIds(UUID... value) { + this.excludedIds = Arrays.asList(value); + return this; + } + + public UsageLimitQuery usageLimitMetricValues(UsageLimitMetricValue value) { + this.usageLimitMetricValues = List.of(value); + return this; + } + + public UsageLimitQuery usageLimitMetricValues(UsageLimitMetricValue... value) { + this.usageLimitMetricValues = Arrays.asList(value); + return this; + } + + public UsageLimitQuery usageLimitMetricValues(Collection values) { + this.usageLimitMetricValues = values; + return this; + } + + public UsageLimitQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + public UsageLimitQuery enableTracking() { + this.noTracking = false; + return this; + } + + public UsageLimitQuery disableTracking() { + this.noTracking = true; + return this; + } + + private final UserScope userScope; + + private final AuthorizationService authService; + + private final QueryUtilsService queryUtilsService; + private final TenantEntityManager tenantEntityManager; + + public UsageLimitQuery( + UserScope userScope, AuthorizationService authService, QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) { + this.userScope = userScope; + this.authService = authService; + this.queryUtilsService = queryUtilsService; + this.tenantEntityManager = tenantEntityManager; + } + + @Override + protected EntityManager entityManager(){ + return this.tenantEntityManager.getEntityManager(); + } + + @Override + protected Class entityClass() { + return UsageLimitEntity.class; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.excludedIds) || this.isEmpty(this.usageLimitMetricValues); + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause); + } + if (this.like != null && !this.like.isBlank()) { + predicates.add(this.queryUtilsService.ilike(queryContext.CriteriaBuilder, queryContext.Root.get(UsageLimitEntity._label), this.like)); + } + if (this.isActives != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._isActive)); + for (IsActive item : this.isActives) + inClause.value(item); + predicates.add(inClause); + } + if (this.usageLimitMetricValues != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._metricValue)); + for (UsageLimitMetricValue item : this.usageLimitMetricValues) + inClause.value(item); + predicates.add(inClause); + } + if (this.excludedIds != null) { + CriteriaBuilder.In notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(ReferenceEntity._id)); + for (UUID item : this.excludedIds) + notInClause.value(item); + predicates.add(notInClause.not()); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected UsageLimitEntity convert(Tuple tuple, Set columns) { + UsageLimitEntity item = new UsageLimitEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._id, UUID.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._tenantId, UUID.class)); + item.setLabel(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._label, String.class)); + item.setMetricValue(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._metricValue, UsageLimitMetricValue.class)); + item.setValue(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._value, Long.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._isActive, IsActive.class)); + return item; + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UsageLimit._id)) + return UsageLimitEntity._id; + else if (item.match(UsageLimit._label)) + return UsageLimitEntity._label; + else if (item.match(UsageLimit._metricValue)) + return UsageLimitEntity._metricValue; + else if (item.match(UsageLimit._value)) + return UsageLimitEntity._value; + else if (item.match(UsageLimit._createdAt)) + return UsageLimitEntity._createdAt; + else if (item.match(UsageLimit._updatedAt)) + return UsageLimitEntity._updatedAt; + else if (item.match(UsageLimit._hash)) + return UsageLimitEntity._updatedAt; + else if (item.match(UsageLimit._isActive)) + return UsageLimitEntity._isActive; + else if (item.prefix(UsageLimit._belongsToCurrentTenant)) + return UsageLimitEntity._tenantId; + else + return null; + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/query/lookup/UsageLimitLookup.java b/backend/core/src/main/java/org/opencdmp/query/lookup/UsageLimitLookup.java new file mode 100644 index 000000000..cb32432d8 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/query/lookup/UsageLimitLookup.java @@ -0,0 +1,70 @@ +package org.opencdmp.query.lookup; + +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.query.UsageLimitQuery; + +import java.util.List; +import java.util.UUID; + +public class UsageLimitLookup extends Lookup { + + private String like; + private List isActive; + private List ids; + private List usageLimitMetricValues; + private List excludedIds; + + public String getLike() { + return like; + } + + public void setLike(String like) { + this.like = like; + } + + public List getIsActive() { + return isActive; + } + + public void setIsActive(List isActive) { + this.isActive = isActive; + } + + public List getIds() { + return ids; + } + + public void setIds(List ids) { this.ids = ids; } + + public List getUsageLimitsMetricValues() { + return usageLimitMetricValues; + } + + public void setUsageLimitsMetricValues(List usageLimitMetricValues) { + this.usageLimitMetricValues = usageLimitMetricValues; + } + + public List getExcludedIds() { + return excludedIds; + } + + public void setExcludedIds(List excludeIds) { + this.excludedIds = excludeIds; + } + + public UsageLimitQuery enrich(QueryFactory queryFactory) { + UsageLimitQuery query = queryFactory.query(UsageLimitQuery.class); + if (this.like != null) query.like(this.like); + if (this.isActive != null) query.isActive(this.isActive); + if (this.ids != null) query.ids(this.ids); + if (this.usageLimitMetricValues != null) query.usageLimitMetricValues(this.usageLimitMetricValues); + if (this.excludedIds != null) query.excludedIds(this.excludedIds); + + this.enrichCommon(query); + + return query; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingService.java b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingService.java new file mode 100644 index 000000000..e1ad947e3 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingService.java @@ -0,0 +1,26 @@ +package org.opencdmp.service.accounting; + +public class AccountingService { + + private Integer getCurrentMetricValue(String metric) { + return 10; + } + + private void set(String metric) { + //Get/Calculate current metric value + //Find metric value from db + // compare these two and throw UsageLimitException when current > metric value + } + + private void increase(String metric) { + //Get/Calculate current metric value + //Find metric value from db + // compare these two and throw UsageLimitException when current > metric value + } + + private void decrease(String metric) { + //Get/Calculate current metric value + //Find metric value from db + // compare these two and throw UsageLimitException when current > metric value + } +} diff --git a/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java index 3e3075e95..9c3f68549 100644 --- a/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java @@ -222,6 +222,8 @@ public class DescriptionServiceImpl implements DescriptionService { if (!data.getPlanId().equals(model.getPlanId())) throw new MyValidationException(this.errors.getPlanCanNotChange().getCode(), this.errors.getPlanCanNotChange().getMessage()); if (!data.getPlanDescriptionTemplateId().equals(model.getPlanDescriptionTemplateId())) throw new MyValidationException(this.errors.getPlanDescriptionTemplateCanNotChange().getCode(), this.errors.getPlanDescriptionTemplateCanNotChange().getMessage()); } else { + //this.usageLimitService.checkIncrease("description_count"); + PlanEntity planEntity = this.entityManager.find(PlanEntity.class, model.getPlanId(), true); if (planEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getPlanId(), Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); diff --git a/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitService.java b/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitService.java new file mode 100644 index 000000000..07c74d2ee --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitService.java @@ -0,0 +1,23 @@ +package org.opencdmp.service.usagelimit; + +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.FieldSet; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.model.UsageLimit; +import org.opencdmp.model.persist.UsageLimitPersist; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +public interface UsageLimitService { + + UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException; + + void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + + void checkIncrease(UsageLimitMetricValue metric); + +} diff --git a/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitServiceImpl.java new file mode 100644 index 000000000..d14b54ec9 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/usagelimit/UsageLimitServiceImpl.java @@ -0,0 +1,126 @@ +package org.opencdmp.service.usagelimit; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.authorization.Permission; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.UsageLimitMetricValue; +import org.opencdmp.commons.scope.user.UserScope; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.TenantEntityManager; +import org.opencdmp.data.UsageLimitEntity; +import org.opencdmp.errorcode.ErrorThesaurusProperties; +import org.opencdmp.model.Tag; +import org.opencdmp.model.UsageLimit; +import org.opencdmp.model.builder.UsageLimitBuilder; +import org.opencdmp.model.deleter.UsageLimitDeleter; +import org.opencdmp.model.persist.UsageLimitPersist; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +@Service +public class UsageLimitServiceImpl implements UsageLimitService { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitServiceImpl.class)); + + private final TenantEntityManager entityManager; + + private final AuthorizationService authorizationService; + + private final DeleterFactory deleterFactory; + + private final BuilderFactory builderFactory; + + private final ConventionService conventionService; + + private final ErrorThesaurusProperties errors; + + private final MessageSource messageSource; + + private final UserScope userScope; + + + @Autowired + public UsageLimitServiceImpl( + TenantEntityManager entityManager, + AuthorizationService authorizationService, + DeleterFactory deleterFactory, + BuilderFactory builderFactory, + ConventionService conventionService, + ErrorThesaurusProperties errors, + MessageSource messageSource, + UserScope userScope) { + this.entityManager = entityManager; + this.authorizationService = authorizationService; + this.deleterFactory = deleterFactory; + this.builderFactory = builderFactory; + this.conventionService = conventionService; + this.errors = errors; + this.messageSource = messageSource; + this.userScope = userScope; + } + + public UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException { + logger.debug(new MapLogEntry("persisting data tag").And("model", model).And("fields", fields)); + + this.authorizationService.authorizeForce(Permission.EditUsageLimit); + + Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); + + UsageLimitEntity data; + if (isUpdate) { + data = this.entityManager.find(UsageLimitEntity.class, model.getId()); + if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Tag.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); + } else { + data = new UsageLimitEntity(); + data.setId(UUID.randomUUID()); + data.setIsActive(IsActive.Active); + data.setCreatedAt(Instant.now()); + } + + data.setLabel(model.getLabel()); + data.setMetricValue(model.getMetricValue()); + data.setValue(model.getValue()); + data.setUpdatedAt(Instant.now()); + if (isUpdate) + this.entityManager.merge(data); + else + this.entityManager.persist(data); + + this.entityManager.flush(); + return this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(BaseFieldSet.build(fields, UsageLimit._id), data); + } + + public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug("deleting UsageLimit: {}", id); + + this.authorizationService.authorizeForce(Permission.DeleteUsageLimit); + + this.deleterFactory.deleter(UsageLimitDeleter.class).deleteAndSaveByIds(List.of(id)); + } + + public void checkIncrease(UsageLimitMetricValue metric) { + //TODO + } + +} + diff --git a/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java index 7f1d4f648..b900245b2 100644 --- a/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java @@ -326,7 +326,9 @@ public class UserServiceImpl implements UserService { @Override public User patchRoles(UserRolePatchPersist model, FieldSet fields) throws InvalidApplicationException { logger.debug(new MapLogEntry("persisting data UserRole").And("model", model).And("fields", fields)); - this.authorizationService.authorizeAtLeastOneForce(this.userScope.getUserId() != null ? List.of(new OwnedResource(this.userScope.getUserId())) : null, Permission.EditUser, Permission.EditTenantUserRole); + + if (!model.getHasTenantAdminMode()) this.authorizationService.authorizeForce(Permission.EditUser); + else this.authorizationService.authorizeForce(Permission.EditTenantUserRole); UserEntity data = this.entityManager.find(UserEntity.class, model.getId(), true); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale())); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/UsageFilterController.java b/backend/web/src/main/java/org/opencdmp/controllers/UsageFilterController.java new file mode 100644 index 000000000..b6c6d1592 --- /dev/null +++ b/backend/web/src/main/java/org/opencdmp/controllers/UsageFilterController.java @@ -0,0 +1,142 @@ +package org.opencdmp.controllers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.censor.CensorFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import gr.cite.tools.validation.ValidationFilterAnnotation; +import jakarta.transaction.Transactional; +import jakarta.xml.bind.JAXBException; +import org.opencdmp.audit.AuditableAction; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.data.UsageLimitEntity; +import org.opencdmp.model.UsageLimit; +import org.opencdmp.model.builder.UsageLimitBuilder; +import org.opencdmp.model.censorship.UsageLimitsCensor; +import org.opencdmp.model.persist.UsageLimitPersist; +import org.opencdmp.model.result.QueryResult; +import org.opencdmp.query.UsageLimitQuery; +import org.opencdmp.query.lookup.UsageLimitLookup; +import org.opencdmp.service.usagelimit.UsageLimitService; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.web.bind.annotation.*; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.management.InvalidApplicationException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping(path = "api/usage-limit") +public class UsageFilterController { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageFilterController.class)); + + private final BuilderFactory builderFactory; + + private final AuditService auditService; + + private final UsageLimitService usageLimitService; + + private final CensorFactory censorFactory; + + private final QueryFactory queryFactory; + + private final MessageSource messageSource; + + public UsageFilterController( + BuilderFactory builderFactory, + AuditService auditService, + UsageLimitService usageLimitService, + CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource) { + this.builderFactory = builderFactory; + this.auditService = auditService; + this.usageLimitService = usageLimitService; + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.messageSource = messageSource; + } + + @PostMapping("query") + public QueryResult query(@RequestBody UsageLimitLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException { + logger.debug("querying {}", UsageLimit.class.getSimpleName()); + + this.censorFactory.censor(UsageLimitsCensor.class).censor(lookup.getProject()); + UsageLimitQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.AllExceptPublic); + + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.UsageLimit_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("{id}") + public UsageLimit get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidApplicationException { + logger.debug(new MapLogEntry("retrieving" + UsageLimit.class.getSimpleName()).And("id", id).And("fields", fieldSet)); + + this.censorFactory.censor(UsageLimitsCensor.class).censor(fieldSet); + + UsageLimitQuery query = this.queryFactory.query(UsageLimitQuery.class).disableTracking().authorize(AuthorizationFlags.AllExceptPublic).ids(id); + UsageLimit model = this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, UsageLimit.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.UsageLimit_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return model; + } + + @PostMapping("persist") + @Transactional + @ValidationFilterAnnotation(validator = UsageLimitPersist.UsageLimitPersistValidator.ValidatorName, argumentName = "model") + public UsageLimit persist(@RequestBody UsageLimitPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + logger.debug(new MapLogEntry("persisting" + UsageLimit.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); + this.censorFactory.censor(UsageLimitsCensor.class).censor(fieldSet); + + UsageLimit persisted = this.usageLimitService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.UsageLimit_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", model), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return persisted; + } + + @DeleteMapping("{id}") + @Transactional + public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug(new MapLogEntry("retrieving" + UsageLimit.class.getSimpleName()).And("id", id)); + + this.usageLimitService.deleteAndSave(id); + + this.auditService.track(AuditableAction.UsageLimit_Delete, "id", id); + } + +} diff --git a/backend/web/src/main/resources/config/permissions.yml b/backend/web/src/main/resources/config/permissions.yml index 8ee5609c2..19af56300 100644 --- a/backend/web/src/main/resources/config/permissions.yml +++ b/backend/web/src/main/resources/config/permissions.yml @@ -1063,6 +1063,28 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false + + # Tenant Permissions + BrowseUsageLimit: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditUsageLimit: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteUsageLimit: + roles: + - Admin + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + # Status BrowseStatus: roles: @@ -1179,6 +1201,12 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false + ViewUsageLimitPage: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false ViewDescriptionTemplatePage: roles: - Admin diff --git a/dmp-db-scema/updates/00.01.067_Add_UsageLimits.sql b/dmp-db-scema/updates/00.01.067_Add_UsageLimits.sql new file mode 100644 index 000000000..eaaf80488 --- /dev/null +++ b/dmp-db-scema/updates/00.01.067_Add_UsageLimits.sql @@ -0,0 +1,27 @@ +DO $$DECLARE + this_version CONSTANT varchar := '00.01.067'; +BEGIN + PERFORM * FROM "DBVersion" WHERE version = this_version; + IF FOUND THEN RETURN; END IF; + + CREATE TABLE public."UsageLimit" + ( + id uuid NOT NULL, + label character varying NOT NULL, + target_metric character varying NOT NULL, + value integer NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + is_active smallint NOT NULL, + tenant uuid, + PRIMARY KEY (id), + CONSTRAINT "UsageLimit_tenant_fkey" FOREIGN KEY (tenant) + REFERENCES public."Tenant" (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION + NOT VALID + ); + + INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.067', '2024-11-07 12:00:00.000000+02', now(), 'Add UsageLimit table.'); + +END$$; \ No newline at end of file diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index b8808fd64..4edf2e9aa 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -7,7 +7,6 @@ import { TranslateService } from '@ngx-translate/core'; import { filter, map, switchMap } from 'rxjs/operators'; import { AuthService, LoginStatus } from './core/services/auth/auth.service'; import { CultureService } from './core/services/culture/culture-service'; -// import { BreadCrumbResolverService } from './ui/misc/breadcrumb/service/breadcrumb.service'; import { DomSanitizer, Title } from '@angular/platform-browser'; import { CookieService } from "ngx-cookie-service"; import { NgcCookieConsentService, NgcStatusChangeEvent } from "ngx-cookieconsent"; @@ -34,7 +33,6 @@ declare var $: any; export class AppComponent implements OnInit, AfterViewInit { hasBreadCrumb = observableOf(false); - // sideNavOpen = false; private sideNavSubscription: Subscription; helpContentEnabled: boolean; private statusChangeSubscription: Subscription; @@ -48,7 +46,6 @@ export class AppComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private authentication: AuthService, private translate: TranslateService, - // private breadCrumbResolverService: BreadCrumbResolverService, private titleService: Title, private cultureService: CultureService, private timezoneService: TimezoneService, @@ -106,16 +103,13 @@ export class AppComponent implements OnInit, AfterViewInit { } onActivate(event: any) { - // this.breadCrumbResolverService.push(event); } onDeactivate(event: any) { - //this.breadCrumbResolverService.clear() } ngOnInit() { if (!this.cookieService.check("cookiesConsent")) { - // this.cookieService.set("cookiesConsent", "false", 356); this.cookieService.set("cookiesConsent", "false", 356, null, null, false, 'Lax'); } @@ -179,16 +173,10 @@ export class AppComponent implements OnInit, AfterViewInit { if (this.authentication.getSelectedTenantName() && this.authentication.getSelectedTenantName() !== '') this.breadcrumbService.addIdResolvedValue(this.authentication.selectedTenant(), this.authentication.getSelectedTenantName()); - - // const enrichedUrl = this.tenantHandlingService.getUrlEnrichedWithTenantCode(event.url, this.authentication.selectedTenant() ?? 'default'); - // if (event.url != enrichedUrl) { - // this.router.navigateByUrl(enrichedUrl); - // } }); this.statusChangeSubscription = this.ccService.statusChange$.subscribe((event: NgcStatusChangeEvent) => { if (event.status == "dismiss") { - // this.cookieService.set("cookiesConsent", "true", 365); this.cookieService.set("cookiesConsent", "true", 356, null, null, false, 'Lax'); } }); diff --git a/frontend/src/app/core/model/plan/plan-import.ts b/frontend/src/app/core/model/plan/plan-import.ts index 25feda261..8b711e24f 100644 --- a/frontend/src/app/core/model/plan/plan-import.ts +++ b/frontend/src/app/core/model/plan/plan-import.ts @@ -8,7 +8,7 @@ export interface PreprocessingPlanModel { preprocessingDescriptionModels: PreprocessingDescriptionModel[]; } -// rda config +// common config export interface PlanCommonModelConfig { fileId: Guid; label: string; diff --git a/frontend/src/app/core/model/plan/plan.ts b/frontend/src/app/core/model/plan/plan.ts index 49e126cc0..efc5c811b 100644 --- a/frontend/src/app/core/model/plan/plan.ts +++ b/frontend/src/app/core/model/plan/plan.ts @@ -66,8 +66,8 @@ export interface PlanUser extends BaseEntity { export interface PlanDescriptionTemplate extends BaseEntity { plan?: Plan; - currentDescriptionTemplate?: DescriptionTemplate; //TODO: what is this? - descriptionTemplates?: DescriptionTemplate[]; //TODO: why it is array? + currentDescriptionTemplate?: DescriptionTemplate; + descriptionTemplates?: DescriptionTemplate[]; descriptionTemplateGroupId?: Guid; sectionId?: Guid; } diff --git a/frontend/src/app/core/services/auth/auth.service.ts b/frontend/src/app/core/services/auth/auth.service.ts index e6458d6dd..b212e57c7 100644 --- a/frontend/src/app/core/services/auth/auth.service.ts +++ b/frontend/src/app/core/services/auth/auth.service.ts @@ -350,10 +350,7 @@ export class AuthService extends BaseService { onAuthenticateError(errorResponse: HttpErrorResponse) { this.zone.run(() => { - // const error: HttpError = - // this.httpErrorHandlingService.getError(errorResponse); this.uiNotificationService.snackBarNotification( - // error.getMessagesString(), errorResponse.message, SnackBarNotificationLevel.Warning ); diff --git a/frontend/src/app/core/services/culture/language-info-service.ts b/frontend/src/app/core/services/culture/language-info-service.ts index c6ef06823..7dfe8d609 100644 --- a/frontend/src/app/core/services/culture/language-info-service.ts +++ b/frontend/src/app/core/services/culture/language-info-service.ts @@ -65,7 +65,6 @@ export class LanguageInfoService { if (selectedLocale) { registerLocaleData(selectedLocale.default); } else { - // locale = newCulture.code.split('-')[0]; import(`/node_modules/@angular/common/locales/${locale}.mjs`).catch(reason => { this.logger.error('Could not load locale: ' + locale); }).then(selectedDefaultLocale => { diff --git a/frontend/src/app/core/services/file-transformer/file-transformer.http.service.ts b/frontend/src/app/core/services/file-transformer/file-transformer.http.service.ts index 0c6f1a3c6..bcbd719d7 100644 --- a/frontend/src/app/core/services/file-transformer/file-transformer.http.service.ts +++ b/frontend/src/app/core/services/file-transformer/file-transformer.http.service.ts @@ -26,13 +26,11 @@ export class FileTransformerHttpService extends BaseService { } exportPlan(planId: Guid, repositoryId: string, format: string): Observable { - //TODO: implement const url = `${this.apiBase}/export-plan`; return this.http.post(url, {id: planId, repositoryId: repositoryId, format: format}, {responseType: 'blob', observe: 'response'}).pipe(catchError((error: any) => throwError(error))); } exportDescription(id: Guid, repositoryId: string, format: string): Observable { - //TODO: implement const url = `${this.apiBase}/export-description`; return this.http.post(url, {id: id, repositoryId: repositoryId, format: format}, {responseType: 'blob', observe: 'response'}).pipe(catchError((error: any) => throwError(error))); } diff --git a/frontend/src/app/core/services/matomo/matomo-service.ts b/frontend/src/app/core/services/matomo/matomo-service.ts index 5921401e7..55178ffed 100644 --- a/frontend/src/app/core/services/matomo/matomo-service.ts +++ b/frontend/src/app/core/services/matomo/matomo-service.ts @@ -43,9 +43,6 @@ export class MatomoService { var principalid = this.authService.userId(); if (principalid != null) { this.matomoTracker.setUserId(principalid.toString()); } this.matomoTracker.trackLink(this.configurationService.server + category + "/" + type + "/" + id, "download"); - - // this.matomoTracker.trackLink(url, "download"); - // this.matomoTracker.trackEvent(category, "Downloaded", type); } } } diff --git a/frontend/src/app/core/services/user-settings/user-settings.service.ts b/frontend/src/app/core/services/user-settings/user-settings.service.ts index 1cf18e6d6..13280502f 100644 --- a/frontend/src/app/core/services/user-settings/user-settings.service.ts +++ b/frontend/src/app/core/services/user-settings/user-settings.service.ts @@ -149,11 +149,6 @@ export class UserSettingsService extends BaseService { } return this.userSettingsHttpService.getSingle(userSettingsInformation.key).pipe( - // catchError(() => { - // const result: UserSettings = this.defaultValue(userSettingsInformation); - // this.persistUserSettings(userSettingsInformation.key, result, userSettingsInformation, false); - // return observableOf(result); - // }), map(x => { const result: UserSettings = (x ? this.toTypedUserSettings(x as UserSettingsObject) : null); this.persistUserSettings(userSettingsInformation.key, result, userSettingsInformation, false); diff --git a/frontend/src/app/core/services/utilities/query-params.service.ts b/frontend/src/app/core/services/utilities/query-params.service.ts index 20260db4a..5ce4159ae 100644 --- a/frontend/src/app/core/services/utilities/query-params.service.ts +++ b/frontend/src/app/core/services/utilities/query-params.service.ts @@ -11,8 +11,6 @@ export class QueryParamsService { serializeLookup(lookup: Lookup): string { return JSON.stringify(lookup, (key: string, value: any) => { switch (key) { - // case nameof(x => x.page): - // case nameof(x => x.order): case nameof(x => x.metadata): case nameof(x => x.project): return undefined; @@ -24,8 +22,6 @@ export class QueryParamsService { deSerializeLookup(serializedLookup: string): any { const json = JSON.parse(serializedLookup); - // delete json[nameof(x => x.page)]; - // delete json[nameof(x => x.order)]; delete json[nameof(x => x.metadata)]; delete json[nameof(x => x.project)]; return json; diff --git a/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.html b/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.html index b817622f1..4d025718b 100644 --- a/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.html +++ b/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.html @@ -26,7 +26,6 @@ - @@ -59,7 +58,6 @@
- diff --git a/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.ts b/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.ts index 0ce1b82a4..ae91dc69b 100644 --- a/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.ts +++ b/frontend/src/app/library/auto-complete/multiple/multiple-auto-complete.component.ts @@ -67,8 +67,6 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp valueOnBlur = new BehaviorSubject(null); onSelectAutoCompleteValue = new BehaviorSubject(null); - // valueAssignSubscription: Subscription; - queryValue: string = ""; focused = false; @@ -406,16 +404,9 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp //Chip Functions _addItem(event: MatChipInputEvent): void { const input = event.input; - const value = event.value; - // Add our fruit - // if ((value || '').trim()) { - // this.selectedItems.push(value.trim()); - // } - // Reset the input value if (input) { this.inputValue = ''; } - //this.inputFormControl.setValue(null); } public reset(): void { this._inputSubject.unsubscribe(); diff --git a/frontend/src/app/library/auto-complete/single/single-auto-complete.component.html b/frontend/src/app/library/auto-complete/single/single-auto-complete.component.html index bb9cfcb5b..631b2bf9a 100644 --- a/frontend/src/app/library/auto-complete/single/single-auto-complete.component.html +++ b/frontend/src/app/library/auto-complete/single/single-auto-complete.component.html @@ -8,7 +8,6 @@ - @@ -29,7 +28,6 @@
- diff --git a/frontend/src/app/library/auto-complete/single/single-auto-complete.component.ts b/frontend/src/app/library/auto-complete/single/single-auto-complete.component.ts index 7e2295771..212f31722 100644 --- a/frontend/src/app/library/auto-complete/single/single-auto-complete.component.ts +++ b/frontend/src/app/library/auto-complete/single/single-auto-complete.component.ts @@ -375,7 +375,4 @@ export class SingleAutoCompleteComponent extends _CustomComponentMixinBase imple this.configuration.valueAssign != null ? this.configuration.valueAssign(item) : item ) } - // get forceFocus(): boolean { - // return this.configuration.forceFocus != null ? this.configuration.forceFocus : false; - // } } diff --git a/frontend/src/app/library/deactivate/can-deactivate.guard.ts b/frontend/src/app/library/deactivate/can-deactivate.guard.ts index 8e6df7ff9..d35a8308a 100644 --- a/frontend/src/app/library/deactivate/can-deactivate.guard.ts +++ b/frontend/src/app/library/deactivate/can-deactivate.guard.ts @@ -30,7 +30,6 @@ export class CanDeactivateGuard extends BaseComponent implements CanDeactivate x ? true : false)); diff --git a/frontend/src/app/library/guided-tour/guided-tour.component.ts b/frontend/src/app/library/guided-tour/guided-tour.component.ts index 651975b98..eb094e184 100644 --- a/frontend/src/app/library/guided-tour/guided-tour.component.ts +++ b/frontend/src/app/library/guided-tour/guided-tour.component.ts @@ -337,8 +337,6 @@ export class GuidedTourComponent implements AfterViewInit, OnDestroy { public get overlayTop(): number { if (this.selectedElementRect) { - // return this.selectedElementRect.top - this.getHighlightPadding(); - /*custom add*/ let customTopOffset = 0; if (this.currentTourStep.customTopOffset) { diff --git a/frontend/src/app/library/rich-text-editor/rich-text-editor.component.ts b/frontend/src/app/library/rich-text-editor/rich-text-editor.component.ts index b9c73c95f..ea37ca853 100644 --- a/frontend/src/app/library/rich-text-editor/rich-text-editor.component.ts +++ b/frontend/src/app/library/rich-text-editor/rich-text-editor.component.ts @@ -12,10 +12,6 @@ import { Subscription } from "rxjs"; placeholder="{{(placeholder? (placeholder | translate) : '') + (required ? ' *': '')}}" (paste)="pasteWithoutFormatting($event)"> close -
`, styleUrls: ['./rich-text-editor.component.scss'], @@ -68,10 +64,8 @@ export class RichTextEditorComponent implements OnInit, OnChanges, OnDestroy { [ 'fontSize', 'backgroundColor', - // 'customClasses', 'insertImage', 'insertVideo', - // 'removeFormat', 'toggleEditorMode' ], [ diff --git a/frontend/src/app/services/pagination.service.ts b/frontend/src/app/services/pagination.service.ts index 72f238384..edee64c3e 100644 --- a/frontend/src/app/services/pagination.service.ts +++ b/frontend/src/app/services/pagination.service.ts @@ -1,7 +1,6 @@ export class PaginationService { getPagination(groups, totalGroups: number, currentPage: number = 1, pageSize: number = 3) { // calculate total pages - //let totalPages = Math.ceil(totalGroups / pageSize); let totalPages = 0 ; //totalpages based on pages from xml, each group and section has each one page groups.forEach(group => { diff --git a/frontend/src/app/ui/admin/description-template/editor/components/default-value/description-template-editor-default-value.component.html b/frontend/src/app/ui/admin/description-template/editor/components/default-value/description-template-editor-default-value.component.html index 11c9ff54b..9ba23a1f5 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/default-value/description-template-editor-default-value.component.html +++ b/frontend/src/app/ui/admin/description-template/editor/components/default-value/description-template-editor-default-value.component.html @@ -75,7 +75,6 @@ - {{placeHolder}} diff --git a/frontend/src/app/ui/admin/description-template/editor/components/field-type/label-and-multiplicity-field/description-template-editor-label-and-multiplicity-field.component.html b/frontend/src/app/ui/admin/description-template/editor/components/field-type/label-and-multiplicity-field/description-template-editor-label-and-multiplicity-field.component.html index 505f3850b..3693c6132 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/field-type/label-and-multiplicity-field/description-template-editor-label-and-multiplicity-field.component.html +++ b/frontend/src/app/ui/admin/description-template/editor/components/field-type/label-and-multiplicity-field/description-template-editor-label-and-multiplicity-field.component.html @@ -1,5 +1,4 @@
- {{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-MULTIPLE-AUTOCOMPLETE' | translate}} diff --git a/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.html b/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.html index 8e3f2e5f3..fe5909e3d 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.html +++ b/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.html @@ -46,7 +46,6 @@ - upload {{enumUtils.toDescriptionTemplateFieldTypeString(descriptionTemplateFieldTypeEnum.UPLOAD)}} diff --git a/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.ts b/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.ts index b27793b86..3530d7c6a 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.ts +++ b/frontend/src/app/ui/admin/description-template/editor/components/field/description-template-editor-field.component.ts @@ -212,10 +212,6 @@ export class DescriptionTemplateEditorFieldComponent extends BaseComponent imple } this.clearVisibilityRulesValue(); - - // setTimeout(() => { //TODO - // this.showPreview = true; - // }); } diff --git a/frontend/src/app/ui/admin/description-template/editor/components/section-fieldset/description-template-editor-section-fieldset.component.ts b/frontend/src/app/ui/admin/description-template/editor/components/section-fieldset/description-template-editor-section-fieldset.component.ts index 9e204735f..a860836f1 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/section-fieldset/description-template-editor-section-fieldset.component.ts +++ b/frontend/src/app/ui/admin/description-template/editor/components/section-fieldset/description-template-editor-section-fieldset.component.ts @@ -138,8 +138,7 @@ export class DescriptionTemplateEditorSectionFieldSetComponent implements OnInit this._selectedFieldSetId = this.tocentry.id; this.scroller.next(this.tocentry.id); - } else {//scroll on top - // this._scrollOnTop(); + } else { this.scroller.next(null); } } diff --git a/frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts b/frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts index d03771546..62370cec1 100644 --- a/frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts +++ b/frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts @@ -70,10 +70,6 @@ export class DescriptionTemplateEditorRuleComponent implements OnInit { return type == DescriptionTemplateFieldType.VALIDATION || type == DescriptionTemplateFieldType.DATASET_IDENTIFIER;; } - targetValidation() { - //TODO - } - deleteRule(index) { this.form.removeAt(index); this.form.controls?.forEach( @@ -187,12 +183,10 @@ export class DescriptionTemplateEditorRuleComponent implements OnInit { const result: OptionItem[] = []; - // parentIds.push(form.get('id').value); const currentOptionItem: OptionItem = { id: form.get('id').value, type: type, label: type === ToCEntryType.Field ? form.get('data').get('label').value : form.get('title').value, - // parentsIds: [form.get('id').value] parentsIds: [...parentIds, form.get('id').value], form: form, hiddenBy: [] diff --git a/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html b/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html index 096e3e881..bc0abcb5d 100644 --- a/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html +++ b/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html @@ -32,9 +32,6 @@ - -
diff --git a/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts b/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts index b6181aa88..436312088 100644 --- a/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts +++ b/frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts @@ -382,8 +382,6 @@ export class DescriptionTemplateEditorComponent extends BaseEditor(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.title)].join('.'), [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.description)].join('.'), [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.ordinal)].join('.'), - [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.sections)].join('.'), // TODO: it is recursive here + [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.sections)].join('.'), [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.id)].join('.'), [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.ordinal)].join('.'), diff --git a/frontend/src/app/ui/admin/entity-locks/filters/lock-listing-filters.component.ts b/frontend/src/app/ui/admin/entity-locks/filters/lock-listing-filters.component.ts index d2b9a971e..15e1f30bf 100644 --- a/frontend/src/app/ui/admin/entity-locks/filters/lock-listing-filters.component.ts +++ b/frontend/src/app/ui/admin/entity-locks/filters/lock-listing-filters.component.ts @@ -1,6 +1,5 @@ import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; -import { IsActive } from '@app/core/common/enum/is-active.enum'; import { LockTargetType } from '@app/core/common/enum/lock-target-type'; import { LockFilter } from '@app/core/query/lock.lookup'; import { UserService } from '@app/core/services/user/user.service'; @@ -90,9 +89,6 @@ export class LockListingFiltersComponent extends BaseComponent implements OnInit private _computeAppliedFilters(filters: LockListingFilters): number { let count = 0; - // if (filters?.isActive) { - // count++ - // } return count; } diff --git a/frontend/src/app/ui/admin/language/listing/language-listing.component.ts b/frontend/src/app/ui/admin/language/listing/language-listing.component.ts index 250beb84d..2b819039c 100644 --- a/frontend/src/app/ui/admin/language/listing/language-listing.component.ts +++ b/frontend/src/app/ui/admin/language/listing/language-listing.component.ts @@ -33,7 +33,6 @@ export class LanguageListingComponent extends BaseListingComponent; @ViewChild('actions', { static: true }) actions?: TemplateRef; @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; diff --git a/frontend/src/app/ui/admin/plan-blueprint/editor/plan-blueprint-editor.component.ts b/frontend/src/app/ui/admin/plan-blueprint/editor/plan-blueprint-editor.component.ts index b7b5f8ca7..c627108ce 100644 --- a/frontend/src/app/ui/admin/plan-blueprint/editor/plan-blueprint-editor.component.ts +++ b/frontend/src/app/ui/admin/plan-blueprint/editor/plan-blueprint-editor.component.ts @@ -536,22 +536,21 @@ export class PlanBlueprintEditorComponent extends BaseEditor 0) { this.showValidationErrorsDialog(undefined, errorMessages); diff --git a/frontend/src/app/ui/admin/prefilling-source/listing/prefilling-source-listing.component.ts b/frontend/src/app/ui/admin/prefilling-source/listing/prefilling-source-listing.component.ts index 282a37c0d..3195c4a97 100644 --- a/frontend/src/app/ui/admin/prefilling-source/listing/prefilling-source-listing.component.ts +++ b/frontend/src/app/ui/admin/prefilling-source/listing/prefilling-source-listing.component.ts @@ -33,7 +33,6 @@ export class PrefillingSourceListingComponent extends BaseListingComponent; @ViewChild('actions', { static: true }) actions?: TemplateRef; @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; diff --git a/frontend/src/app/ui/admin/reference-type/listing/reference-type-listing.component.ts b/frontend/src/app/ui/admin/reference-type/listing/reference-type-listing.component.ts index b76619c73..da50ea322 100644 --- a/frontend/src/app/ui/admin/reference-type/listing/reference-type-listing.component.ts +++ b/frontend/src/app/ui/admin/reference-type/listing/reference-type-listing.component.ts @@ -32,9 +32,7 @@ export class ReferenceTypeListingComponent extends BaseListingComponent; @ViewChild('actions', { static: true }) actions?: TemplateRef; @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; diff --git a/frontend/src/app/ui/admin/tenant/listing/tenant-listing.component.ts b/frontend/src/app/ui/admin/tenant/listing/tenant-listing.component.ts index 1a1e48f77..e0e03b55b 100644 --- a/frontend/src/app/ui/admin/tenant/listing/tenant-listing.component.ts +++ b/frontend/src/app/ui/admin/tenant/listing/tenant-listing.component.ts @@ -33,7 +33,6 @@ export class TenantListingComponent extends BaseListingComponent; @ViewChild('actions', { static: true }) actions?: TemplateRef; @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; diff --git a/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts b/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts index 027d0b183..52ae223c2 100644 --- a/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts +++ b/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts @@ -113,7 +113,6 @@ export class AnnotationDialogComponent extends BaseComponent { } replyThread(threadId: Guid) { - // if (!this.threadReplyTexts[threadId.toString()] || this.threadReplyTexts[threadId.toString()].length === 0) { return; } this.formService.removeAllBackEndErrors(this.threadReplyTextsFG[threadId.toString()]); this.formService.touchAllFormFields(this.threadReplyTextsFG[threadId.toString()]); if (!this.isFormValid(this.threadReplyTextsFG[threadId.toString()])) { @@ -178,7 +177,6 @@ export class AnnotationDialogComponent extends BaseComponent { this.resetFormGroup(); this.comments = data.items.sort((a1, a2) => new Date(a2.timeStamp).getTime() - new Date(a1.timeStamp).getTime()); this.comments.forEach(element => { - // this.threadReplyTextsFG.addControl(element.id.toString(), new FormControl(null)); this.threadReplyTextsFG[element.threadId.toString()] = this.formBuilder.group({ replyText: new FormControl(null, [Validators.required]) }); this.annotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id !== element.id).sort((a1, a2) => new Date(a1.timeStamp).getTime() - new Date(a2.timeStamp).getTime()); this.parentAnnotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id === element.id)[0]; @@ -257,11 +255,6 @@ export class AnnotationDialogComponent extends BaseComponent { this.dialogRef.close(this.changesMade); } - startWizard() { // not used - this.router.navigate([this.routerUtils.generateUrl('/plans/new')]); - this.close(); - } - showReplies(threadId: string): void { this.showRepliesPerThread[threadId] = true; } diff --git a/frontend/src/app/ui/auth/login/login.routing.ts b/frontend/src/app/ui/auth/login/login.routing.ts index ef810dd29..05ca35964 100644 --- a/frontend/src/app/ui/auth/login/login.routing.ts +++ b/frontend/src/app/ui/auth/login/login.routing.ts @@ -5,7 +5,6 @@ import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-c import { UnlinkEmailConfirmation } from './unlink-email-confirmation/unlink-email-confirmation.component'; import { AuthGuard } from '@app/core/auth-guard.service'; import { UserInviteConfirmation } from './user-invite-confirmation/user-invite-confirmation.component'; -// import { PostLoginComponent } from './post-login/post-login.component'; const routes: Routes = [ { path: '', component: LoginComponent }, @@ -14,10 +13,6 @@ const routes: Routes = [ component: MergeEmailConfirmation, canActivate: [AuthGuard] }, - // { - // path: 'post', - // component: PostLoginComponent - // }, { path: 'unlink/confirmation/:token', component: UnlinkEmailConfirmation }, { path: 'invitation/confirmation/:token', component: UserInviteConfirmation }, diff --git a/frontend/src/app/ui/auth/logout/logout.component.ts b/frontend/src/app/ui/auth/logout/logout.component.ts index 483613234..02cc0fb4d 100644 --- a/frontend/src/app/ui/auth/logout/logout.component.ts +++ b/frontend/src/app/ui/auth/logout/logout.component.ts @@ -16,7 +16,6 @@ export class LogoutComponent implements OnInit { this.authService.clear(); this.keycloak.logout(location.origin).then(() => { localStorage.clear(); - // this.router.navigate(['./'], { replaceUrl: true }); }); } } diff --git a/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts index 0e81ada00..dd9b5d597 100644 --- a/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts +++ b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts @@ -31,7 +31,7 @@ export class DescriptionCopyDialogComponent { sections: PlanBlueprintDefinitionSection[] = []; descriptionDescriptionTemplateLabel: String; - planAutoCompleteConfiguration: SingleAutoCompleteConfiguration = { //TODO: add filter to only get plans that have connection with the same Description Template group. + planAutoCompleteConfiguration: SingleAutoCompleteConfiguration = { initialItems: (data?: any) => this.planService.query(this.buildPlanLookup(null,null,null, this.planDescriptionTemplateLookup)).pipe(map(x => x.items)), filterFn: (searchQuery: string, data?: any) => this.planService.query(this.buildPlanLookup(searchQuery, null, null, this.planDescriptionTemplateLookup)).pipe(map(x => x.items)), getSelectedItem: (selectedItem: any) => this.planService.query(this.buildPlanLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), diff --git a/frontend/src/app/ui/description/editor/description-editor.component.ts b/frontend/src/app/ui/description/editor/description-editor.component.ts index 319eaf744..3700ef61a 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-editor.component.ts @@ -501,7 +501,6 @@ export class DescriptionEditorComponent extends BaseEditor { if (key === 'required') { - // errors.push(this.language.instant(name + ": " + this.language.instant('GENERAL.FORM-VALIDATION-DISPLAY-DIALOG.REQUIRED'))); if (name == 'label') errors.push(this.language.instant(this.language.instant('DESCRIPTION-EDITOR.BASE-INFO.FIELDS.TITLE') + ": " + this.language.instant('GENERAL.FORM-VALIDATION-DISPLAY-DIALOG.REQUIRED'))); else if (name == 'descriptionTemplateId') errors.push(this.language.instant(this.language.instant('DESCRIPTION-EDITOR.BASE-INFO.FIELDS.DESCRIPTION-TEMPLATE') + ": " + this.language.instant('GENERAL.FORM-VALIDATION-DISPLAY-DIALOG.REQUIRED'))); } @@ -539,7 +538,7 @@ export class DescriptionEditorComponent extends BaseEditor this.getNestedSectionIdsByField(subsection, fieldSetId)) if (subNestedSectionIds.length > 0) return [section.id, ...subNestedSectionIds]; - // return [section.id, ...section.sections.flatMap((subsection: DescriptionTemplateSection) => this.getNestedSectionIdsByField(subsection, fieldSetId))]; } else if (section.fieldSets.find(fieldSet => fieldSet.id == fieldSetId)) return [section.id]; diff --git a/frontend/src/app/ui/description/editor/description-form/components/form-field/form-field.component.ts b/frontend/src/app/ui/description/editor/description-form/components/form-field/form-field.component.ts index 29f0dcd41..bee984270 100644 --- a/frontend/src/app/ui/description/editor/description-form/components/form-field/form-field.component.ts +++ b/frontend/src/app/ui/description/editor/description-form/components/form-field/form-field.component.ts @@ -241,7 +241,7 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn let messages: string[] = []; if (this.filesToUpload.length == 0) { - messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.NO-FILES-SELECTED')); // fix + messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.NO-FILES-SELECTED')); return; } else { let fileToUpload = this.filesToUpload[0]; @@ -253,9 +253,9 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn this.upload(); } else { this.filesToUpload = null; - messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.LARGE-FILE-OR-UNACCEPTED-TYPE')); // fix - messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-MAX-FILE-SIZE', { 'maxfilesize': data.maxFileSizeInMB })); // fix FIELD-UPLOAD-MAX-FILE-SIZE - messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.ACCEPTED-FILE-TRANSFOMER') + data.types.map(type => type.value).join(", ")); // fix + messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.LARGE-FILE-OR-UNACCEPTED-TYPE')); + messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-UPLOAD-MAX-FILE-SIZE', { 'maxfilesize': data.maxFileSizeInMB })); + messages.push(this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.ACCEPTED-FILE-TRANSFOMER') + data.types.map(type => type.value).join(", ")); } if (messages && messages.length > 0) { diff --git a/frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts b/frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts index bbbe82951..e2b43f9e3 100644 --- a/frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts +++ b/frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts @@ -27,10 +27,6 @@ export class DescriptionFormSectionComponent extends BaseComponent implements On @Input() path: string; @Input() descriptionId: Guid; @Input() planUsers: PlanUser[] = []; - - - // @Input() descriptionTemplateId: String; - // @Input() form: UntypedFormGroup; @Input() tocentry: ToCEntry; @Input() pathName: string; @Input() linkToScroll: LinkToScroll; diff --git a/frontend/src/app/ui/description/editor/table-of-contents/models/toc-entry.ts b/frontend/src/app/ui/description/editor/table-of-contents/models/toc-entry.ts index 8fe8adb48..86b69761f 100644 --- a/frontend/src/app/ui/description/editor/table-of-contents/models/toc-entry.ts +++ b/frontend/src/app/ui/description/editor/table-of-contents/models/toc-entry.ts @@ -7,7 +7,6 @@ export interface ToCEntry { subEntriesType: ToCEntryType; subEntries: ToCEntry[]; type: ToCEntryType; - // form: AbstractControl; numbering: string; ordinal: number; hidden?: boolean; diff --git a/frontend/src/app/ui/description/editor/table-of-contents/table-of-contents.component.ts b/frontend/src/app/ui/description/editor/table-of-contents/table-of-contents.component.ts index 83c0bb3b9..b5be59ceb 100644 --- a/frontend/src/app/ui/description/editor/table-of-contents/table-of-contents.component.ts +++ b/frontend/src/app/ui/description/editor/table-of-contents/table-of-contents.component.ts @@ -222,7 +222,6 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O }); } return { - // form: form, id: item.id, label: item.title, numbering: '', @@ -239,7 +238,6 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O if (!item) return null; return { - // form: form, id: item.id, label: item.title, numbering: 's', diff --git a/frontend/src/app/ui/description/listing/description-listing.component.html b/frontend/src/app/ui/description/listing/description-listing.component.html index 176374f61..426d36565 100644 --- a/frontend/src/app/ui/description/listing/description-listing.component.html +++ b/frontend/src/app/ui/description/listing/description-listing.component.html @@ -27,7 +27,7 @@
-
diff --git a/frontend/src/app/ui/plan/listing/plan-listing.component.ts b/frontend/src/app/ui/plan/listing/plan-listing.component.ts index f4e63082a..d80c85c6e 100644 --- a/frontend/src/app/ui/plan/listing/plan-listing.component.ts +++ b/frontend/src/app/ui/plan/listing/plan-listing.component.ts @@ -152,9 +152,11 @@ export class PlanListingComponent extends BaseListingComponent { - this.tenants = tenants; - }); + if (!this.isPublic){ + this._loadUserTenants().pipe(takeUntil(this._destroyed)).subscribe( tenants => { + this.tenants = tenants; + }); + } this.formGroup.get('like').valueChanges .pipe(takeUntil(this._destroyed), debounceTime(500)) @@ -273,10 +275,6 @@ export class PlanListingComponent extends BaseListingComponent
- +