diff --git a/dbrepo-metadata-service/pom.xml b/dbrepo-metadata-service/pom.xml index 8d29d42c862d34bb6a129bde5348ef82057c8190..325514d848603f3984aeab03d7a5a3d784d5a5f5 100644 --- a/dbrepo-metadata-service/pom.xml +++ b/dbrepo-metadata-service/pom.xml @@ -63,7 +63,8 @@ <mapstruct.version>1.5.5.Final</mapstruct.version> <rabbitmq.version>5.20.0</rabbitmq.version> <jackson-datatype.version>2.15.0</jackson-datatype.version> - <commons.version>2.11.0</commons.version> + <commons-io.version>2.15.0</commons-io.version> + <commons-validator.version>1.8.0</commons-validator.version> <guava.version>33.0.0-jre</guava.version> <jacoco.version>0.8.11</jacoco.version> <jwt.version>4.3.0</jwt.version> @@ -238,7 +239,12 @@ <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>${commons.version}</version> + <version>${commons-io.version}</version> + </dependency> + <dependency> + <groupId>commons-validator</groupId> + <artifactId>commons-validator</artifactId> + <version>${commons-validator.version}</version> </dependency> <!-- AMPQ --> <dependency> diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java index aac1e1c4ef266fed4be72521518fb5821acb084e..a6d9f56143ea208b62444e7366d6e473e17d5e2d 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java @@ -37,6 +37,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.security.Principal; +import java.time.Instant; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -163,6 +164,12 @@ public class IdentifierEndpoint { QueryStoreException, QueryNotFoundException, ImageNotSupportedException, UserNotFoundException, DatabaseConnectionException, RemoteUnavailableException { log.debug("endpoint create identifier, data={}, {}", data, PrincipalUtil.formatForDebug(principal)); + /* check data */ + if (!endpointValidator.validatePublicationDate(data)) { + log.error("Failed to create identifier: publication date is invalid"); + throw new IdentifierRequestException("Failed to create identifier: publication date is invalid"); + } + /* check access */ DatabaseAccess access = null; try { access = accessService.find(data.getDatabaseId(), UserUtil.getId(principal)); @@ -172,6 +179,7 @@ public class IdentifierEndpoint { throw new NotAllowedException("Failed to create identifier: insufficient role"); } } + /* create identifier */ final Database database = databaseService.find(data.getDatabaseId()); switch (data.getType()) { case VIEW -> { diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java index 5fa79263ba6230f2a1dd54483325a28bf66b9faa..6d874dbc9c9849f05ce7b90b2450550078a34c45 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java @@ -5,19 +5,19 @@ import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; +import at.tuwien.api.identifier.IdentifierSaveDto; import at.tuwien.config.QueryConfig; import at.tuwien.entities.database.AccessType; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.database.table.Table; -import at.tuwien.entities.user.User; import at.tuwien.exception.*; -import at.tuwien.repository.mdb.IdentifierRepository; import at.tuwien.service.AccessService; import at.tuwien.service.DatabaseService; import at.tuwien.service.TableService; import at.tuwien.utils.UserUtil; import lombok.extern.log4j.Log4j2; +import org.apache.commons.validator.GenericValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -321,4 +321,34 @@ public class EndpointValidator { throw new NotAllowedException("Access not allowed: no write access for table with id " + tableId); } + /** + * Precondition: identifier.getPublicationYear() is not null + * + * @param identifier The identifier that will be created. + * @return True if the publication date is valid, false otherwise. + */ + public boolean validatePublicationDate(IdentifierSaveDto identifier) { + if (identifier.getPublicationMonth() != null && (identifier.getPublicationMonth() < 1 || identifier.getPublicationMonth() > 12)) { + log.trace("publication month {} needs to fulfill: 1 >= publicationMonth <= 12", identifier.getPublicationMonth()); + return false; + } + if (identifier.getPublicationDay() != null && (identifier.getPublicationDay() < 1 || identifier.getPublicationDay() > 31)) { + log.trace("publication day {} needs to fulfill: 1 >= publicationDay <= 31", identifier.getPublicationDay()); + return false; + } + if (identifier.getPublicationMonth() != null && identifier.getPublicationDay() != null) { + final String paddedMonth = identifier.getPublicationMonth() < 9 ? "0" + identifier.getPublicationMonth() : "" + identifier.getPublicationMonth(); + final boolean result = GenericValidator.isDate(identifier.getPublicationYear() + "-" + paddedMonth + "-" + + identifier.getPublicationDay(), "yyyy-MM-dd", true); + if (!result) { + log.trace("publication date {}-{}-{} needs to be valid", identifier.getPublicationYear(), paddedMonth, + identifier.getPublicationDay()); + return false; + } + return true; + } + log.trace("publication date is valid"); + return true; + } + } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java index 162b05a4e02eac8f2e15e412ab86fd75ca22ad8e..a937cf531c34b7bbb281972bbaf018c2592a7608 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java @@ -7,6 +7,7 @@ import at.tuwien.annotations.MockOpensearch; import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; +import at.tuwien.api.identifier.IdentifierSaveDto; import at.tuwien.entities.database.table.Table; import at.tuwien.exception.*; import at.tuwien.repository.mdb.IdentifierRepository; @@ -29,7 +30,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.List; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -46,9 +47,6 @@ public class EndpointValidatorUnitTest extends BaseUnitTest { @MockBean private AccessService accessService; - @MockBean - private IdentifierRepository identifierRepository; - @MockBean private TableService tableService; @@ -463,4 +461,136 @@ public class EndpointValidatorUnitTest extends BaseUnitTest { }); } + @Test + public void validatePublicationDate_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationMonth(1) + .publicationDay(25) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_missingDay_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationMonth(1) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_missingMonth_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationDay(1) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_missingDayMonth_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_yearEarly_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(1300) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_yearLate_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(12345) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_monthInvalid1_fails() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationMonth(0) + .build(); + + /* test */ + assertFalse(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_monthInvalid2_fails() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationMonth(13) + .build(); + + /* test */ + assertFalse(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_dayInvalid1_fails() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationDay(0) + .build(); + + /* test */ + assertFalse(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_dayInvalid2_fails() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationDay(32) + .build(); + + /* test */ + assertFalse(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_february29Invalid_fails() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2023) + .publicationMonth(2) + .publicationDay(29) + .build(); + + /* test */ + assertFalse(endpointValidator.validatePublicationDate(request)); + } + + @Test + public void validatePublicationDate_february29_succeeds() { + final IdentifierSaveDto request = IdentifierSaveDto.builder() + .publicationYear(2024) + .publicationMonth(2) + .publicationDay(29) + .build(); + + /* test */ + assertTrue(endpointValidator.validatePublicationDate(request)); + } + } diff --git a/dbrepo-metadata-service/rest-service/src/test/resources/application.properties b/dbrepo-metadata-service/rest-service/src/test/resources/application.properties index 0c0fab40c34a920f03a7c48022a4b0bfe4c8de0c..70e4aa0fa0d92a2d9b4ea17aff55a2f104ef50ea 100644 --- a/dbrepo-metadata-service/rest-service/src/test/resources/application.properties +++ b/dbrepo-metadata-service/rest-service/src/test/resources/application.properties @@ -17,6 +17,7 @@ spring.jpa.hibernate.ddl-auto=create # logging logging.level.root=error logging.level.at.tuwien.=debug +logging.level.at.tuwien.validation.=trace # rabbitmq spring.rabbitmq.host=localhost diff --git a/dbrepo-ui/components/identifier/Persist.vue b/dbrepo-ui/components/identifier/Persist.vue index e95f644bde687eebe789c19652f5873f2dd946ed..869ac3c5fc5550585693d1b198b4d3e67244b554 100644 --- a/dbrepo-ui/components/identifier/Persist.vue +++ b/dbrepo-ui/components/identifier/Persist.vue @@ -13,7 +13,7 @@ v-if="!isUpdate" class="mb-1" :loading="loading" - :disabled="!formValid || loading" + :disabled="!formValid || !validPublicationMonth || !validPublicationDay || loading" color="primary" @click="save"> <v-icon left>mdi-content-save-outline</v-icon> Create PID @@ -286,6 +286,7 @@ v-model.number="identifier.publication_day" type="number" label="Publication day" + :rules="[validPublicationDay || $t('Invalid day')]" clearable /> </v-col> <v-col cols="2"> @@ -294,6 +295,7 @@ v-model.number="identifier.publication_month" type="number" label="Publication month" + :rules="[validPublicationMonth || $t('Invalid month')]" clearable /> </v-col> <v-col cols="2"> @@ -649,6 +651,20 @@ export default { return 'Table' } return '' + }, + validPublicationDay () { + const day = this.identifier.publication_day + if (day === null) { + return true + } + return day >= 1 && day <= 31 + }, + validPublicationMonth () { + const month = this.identifier.publication_month + if (month === null) { + return true + } + return month >= 1 && month <= 12 } }, watch: { diff --git a/dbrepo-ui/components/identifier/Summary.vue b/dbrepo-ui/components/identifier/Summary.vue index e4fe690432a41cd59f84b4f3880907839787214e..a9f16bd347cc5889067dc274369868520d5fdfcd 100644 --- a/dbrepo-ui/components/identifier/Summary.vue +++ b/dbrepo-ui/components/identifier/Summary.vue @@ -97,7 +97,7 @@ import IsniIcon from '@/components/icons/IsniIcon.vue' import OrcidIcon from '@/components/icons/OrcidIcon.vue' import RorIcon from '@/components/icons/RorIcon.vue' import Banner from '@/components/identifier/Banner.vue' -import { formatDateUTC, formatLanguage } from '@/utils' +import { formatLanguage } from '@/utils' export default { components: { @@ -167,7 +167,9 @@ export default { if (this.identifier.publication_year && !this.identifier.publication_month && !this.identifier.publication_day) { return this.identifier.publication_year } else if (this.identifier.publication_year && this.identifier.publication_month && this.identifier.publication_day) { - return formatDateUTC(this.identifier.publication_year + '-' + this.identifier.publication_month + '-' + this.identifier.publication_day) + const month = this.identifier.publication_month + const day = this.identifier.publication_day + return `${this.identifier.publication_year}-${month < 9 ? '0' + month : month}-${day < 9 ? '0' + day : day}` } else { return null } diff --git a/dbrepo-ui/dbrepo.config.json b/dbrepo-ui/dbrepo.config.json index 24ddb9e90f225cc0f8091747c7130d622c4ef3e5..3ec6309002a75fe1bc2a424d5185175db12109b1 100644 --- a/dbrepo-ui/dbrepo.config.json +++ b/dbrepo-ui/dbrepo.config.json @@ -1,6 +1,6 @@ { "title": "Database Repository", - "version": "1.4.0", + "version": "1.4.1", "ssl": { "force": false },