diff --git a/.jupyter/api_query/api/table_data_endpoint_api.py b/.jupyter/api_query/api/table_data_endpoint_api.py index 632bf0023c0c979119e43647dec551cf4bc744ad..8f34b37dacac4db3543dd2def4951d0d24501b87 100644 --- a/.jupyter/api_query/api/table_data_endpoint_api.py +++ b/.jupyter/api_query/api/table_data_endpoint_api.py @@ -259,7 +259,7 @@ class TableDataEndpointApi(object): auth_settings = ['bearerAuth'] # noqa: E501 return self.api_client.call_api( - '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD', + '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET', path_params, query_params, header_params, @@ -380,7 +380,7 @@ class TableDataEndpointApi(object): auth_settings = ['bearerAuth'] # noqa: E501 return self.api_client.call_api( - '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET', + '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD', path_params, query_params, header_params, diff --git a/.jupyter/api_query/api/table_history_endpoint_api.py b/.jupyter/api_query/api/table_history_endpoint_api.py index f013bf0c6aede1359efe4f36e48a29c1d18215c9..024816773f5d4328cc00116d9c298b48cd860fc9 100644 --- a/.jupyter/api_query/api/table_history_endpoint_api.py +++ b/.jupyter/api_query/api/table_history_endpoint_api.py @@ -126,7 +126,7 @@ class TableHistoryEndpointApi(object): auth_settings = ['bearerAuth'] # noqa: E501 return self.api_client.call_api( - '/api/container/{id}/database/{databaseId}/table/{tableId}/history', 'HEAD', + '/api/container/{id}/database/{databaseId}/table/{tableId}/history', 'GET', path_params, query_params, header_params, @@ -235,7 +235,7 @@ class TableHistoryEndpointApi(object): auth_settings = ['bearerAuth'] # noqa: E501 return self.api_client.call_api( - '/api/container/{id}/database/{databaseId}/table/{tableId}/history', 'GET', + '/api/container/{id}/database/{databaseId}/table/{tableId}/history', 'HEAD', path_params, query_params, header_params, diff --git a/fda-database-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java b/fda-database-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java index 629d38c73ef5fa89b4ed3e1bc9ca9e5ba621980a..267052d4a8ea53c27460b7a88add00cb844c8e0c 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java +++ b/fda-database-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java @@ -4,6 +4,6 @@ import at.tuwien.entities.database.Database; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; -@Repository(value = "ElasticDatabaseRepository") +@Repository public interface DatabaseidxRepository extends ElasticsearchRepository<Database, Long> { } diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java b/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java index 53236b3dc77c1340540e94c1b7456ae971c461d2..b0d931e66eee841098a0301d021bc80380d6e3ff 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java +++ b/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java @@ -17,20 +17,14 @@ import at.tuwien.service.LicenseService; import at.tuwien.service.UserService; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.log4j.Log4j2; -import org.hibernate.Session; -import org.hibernate.Transaction; -import org.hibernate.query.NativeQuery; -import org.hibernate.service.spi.ServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.persistence.PersistenceException; import java.security.Principal; import java.sql.*; import java.time.Instant; -import java.time.temporal.ChronoField; import java.util.Calendar; import java.util.List; import java.util.Optional; diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifierIdxRepository.java b/fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifieridxRepository.java similarity index 69% rename from fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifierIdxRepository.java rename to fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifieridxRepository.java index a74613b7a267e443f5ac55172d3826086cbea5fc..cffa3d3cfd19c0e006e827426eb6ce4be74be228 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifierIdxRepository.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/repository/elastic/IdentifieridxRepository.java @@ -4,6 +4,6 @@ import at.tuwien.entities.identifier.Identifier; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; -@Repository(value = "ElasticIdentifierService") -public interface IdentifierIdxRepository extends ElasticsearchRepository<Identifier, Long> { +@Repository +public interface IdentifieridxRepository extends ElasticsearchRepository<Identifier, Long> { } \ No newline at end of file diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java index 009deb09de9372d9eb7bd68beb645f7ac8e5bb67..6058d35080688b094666b947385dd2a986ac3b7d 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java @@ -15,7 +15,6 @@ import at.tuwien.exception.*; import at.tuwien.gateway.QueryServiceGateway; import at.tuwien.mapper.DocumentMapper; import at.tuwien.mapper.IdentifierMapper; -import at.tuwien.repository.elastic.IdentifierIdxRepository; import at.tuwien.repository.jpa.IdentifierRepository; import at.tuwien.repository.jpa.RelatedIdentifierRepository; import at.tuwien.service.DatabaseService; @@ -41,13 +40,11 @@ public class IdentifierServiceImpl implements IdentifierService { private final IdentifierMapper identifierMapper; private final QueryServiceGateway queryServiceGateway; private final IdentifierRepository identifierRepository; - private final IdentifierIdxRepository identifierIdxRepository; private final RelatedIdentifierRepository relatedIdentifierRepository; public IdentifierServiceImpl(UserService userService, DocumentMapper documentMapper, DatabaseService databaseService, IdentifierMapper identifierMapper, QueryServiceGateway queryServiceGateway, IdentifierRepository identifierRepository, - IdentifierIdxRepository identifierIdxRepository, RelatedIdentifierRepository relatedIdentifierRepository) { this.userService = userService; this.documentMapper = documentMapper; @@ -55,7 +52,6 @@ public class IdentifierServiceImpl implements IdentifierService { this.identifierMapper = identifierMapper; this.queryServiceGateway = queryServiceGateway; this.identifierRepository = identifierRepository; - this.identifierIdxRepository = identifierIdxRepository; this.relatedIdentifierRepository = relatedIdentifierRepository; } @@ -134,10 +130,6 @@ public class IdentifierServiceImpl implements IdentifierService { final Identifier identifier = identifierRepository.save(entity); log.info("Created identifier with id {}", identifier.getId()); log.debug("created identifier {}", identifier); - /* save in identifier_index - elastic search */ - final Identifier eId = identifierIdxRepository.save(identifier); - log.info("Saved identifier in elastic search with id {}", eId.getId()); - log.debug("saved identifier in elastic search {}", eId); return identifier; } diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java index 76d29bbde92feb6fcebcdaef580ca5b6c5b3af81..b0f03a60a6ed5f8c151452031f8d7af84669deee 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java @@ -41,10 +41,6 @@ public class ContainerBriefDto { @Parameter(name = "container internal name", example = "weather-world") private String internalName; - @JsonProperty("is_public") - @Parameter(name = "container public", example = "true") - private Boolean isPublic; - @Parameter(name = "container created") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") private Instant created; diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java index 4cf8f13ec2fcc48353d759ef4ef8378c6e394cdc..cacac095d0f90dbd89103f8ff2599a5c2fb18dd8 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java @@ -49,10 +49,6 @@ public class ContainerDto { @JsonProperty("ip_address") private String ipAddress; - @JsonProperty("is_public") - @Parameter(name = "container public", example = "true") - private Boolean isPublic; - @Parameter(name = "container image") private ImageBriefDto image; diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java index 513b7e9458e604df91f96b144a7676c9453b79da..40dfad902f98a6e2aacf5ee1d4308eae8b8c6b46 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java @@ -3,8 +3,6 @@ package at.tuwien.api.database; import at.tuwien.api.container.ContainerDto; import at.tuwien.api.container.image.ImageDto; import at.tuwien.api.database.table.TableBriefDto; -import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.identifier.CreatorDto; import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import com.fasterxml.jackson.annotation.JsonFormat; diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/Database.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/Database.java index 60f95dcc6ce8b92c4a34620fbd241e9cc714a735..e01646a9d7f438ea2d78049bb47da4c2a73603b1 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/Database.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/Database.java @@ -48,7 +48,7 @@ public class Database { private User creator; @ToString.Exclude - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumns({ @JoinColumn(name = "id", referencedColumnName = "id", insertable = false, updatable = false) }) diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java index 475e675e0ef71733638a2e7484ac23fce3fa123f..c19555325c5f5baa2564deb10b048aba0f33b9d7 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java @@ -4,14 +4,11 @@ import at.tuwien.entities.database.Database; import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.user.User; import lombok.*; -import lombok.extern.log4j.Log4j2; import net.sf.jsqlparser.statement.select.FromItem; import org.hibernate.annotations.GenericGenerator; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.annotations.Field; -import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; @@ -21,14 +18,12 @@ import java.util.List; @Data @Entity @Builder +@ToString @AllArgsConstructor @NoArgsConstructor @Document(indexName = "tableindex", createIndex = false) -@IdClass(TableKey.class) -@ToString -@Log4j2 @EntityListeners(AuditingEntityListener.class) -@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@IdClass(TableKey.class) @javax.persistence.Table(name = "mdb_tables", uniqueConstraints = { @UniqueConstraint(columnNames = {"tdbid", "internalName"}) }) @@ -89,9 +84,16 @@ public class Table { this.database = null; } + /** + * KEEP THIS FUNCTION HERE! IT WILL BREAK CODE! + * Custom equality function implementation. + * + * @param other The other table + * @return True if tables are equal, false otherwise + */ public boolean equals(FromItem other) { final String name = other.toString() - .replace("`",""); + .replace("`", ""); if (other.getAlias() != null) { final int idx = name.indexOf(' '); return this.getInternalName() diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java index 0b2db1feed032fb52fc61a6243e8f53a09c9a718..18412a2792a1f9f9a37c560a77fc00015de5b437 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java @@ -2,15 +2,11 @@ package at.tuwien.entities.database.table.columns; import at.tuwien.entities.container.image.ContainerImageDate; import at.tuwien.entities.database.table.Table; -import at.tuwien.entities.database.table.columns.concepts.ColumnConcept; import at.tuwien.entities.database.table.columns.concepts.Concept; import at.tuwien.entities.user.User; import lombok.*; -import lombok.extern.log4j.Log4j2; import net.sf.jsqlparser.statement.select.SelectItem; import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.NotFound; -import org.hibernate.annotations.NotFoundAction; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.elasticsearch.annotations.Document; @@ -23,14 +19,12 @@ import java.util.List; @Data @Entity @Builder +@ToString @AllArgsConstructor @NoArgsConstructor @Document(indexName = "columnindex", createIndex = false) @IdClass(TableColumnKey.class) -@ToString -@Log4j2 @EntityListeners(AuditingEntityListener.class) -@EqualsAndHashCode(onlyExplicitlyIncluded = true) @javax.persistence.Table(name = "mdb_columns", uniqueConstraints = { @UniqueConstraint(columnNames = {"tid", "internalName"}) }) @@ -142,15 +136,20 @@ public class TableColumn implements Comparable<TableColumn> { return Integer.compare(this.ordinalPosition, tableColumn.getOrdinalPosition()); } + /** + * KEEP THIS FUNCTION HERE! IT WILL BREAK CODE! + * Custom equality function implementation. + * + * @param other The other column. + * @return True if columns are equal, false otherwise + */ public boolean equals(SelectItem other) { final String name = other.toString() - .replace("`",""); + .replace("`", ""); final int idx = name.indexOf('.'); if (idx == -1) { - log.trace("internal name {} =?= name {}", this.internalName, name); return name.equals(this.internalName); } - log.trace("internal name {} =?= name {}", this.internalName, name.substring(idx + 1)); return name.substring(idx + 1) .equals(this.internalName); } diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java index df563062011965a8c61c4df64807808a8df7e9c3..d60289f8c4e5c0423947dc7a03125f13d4d25333 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java @@ -21,7 +21,6 @@ import java.util.List; @ToString @AllArgsConstructor @NoArgsConstructor -@Document(indexName = "identifierindex", createIndex = false) @Where(clause = "deleted is null") @EntityListeners(AuditingEntityListener.class) @SQLDelete(sql = "update mdb_identifiers set deleted = NOW() where id = ?") diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index 16601ad35d7131d19a491fb231ba0fc3f2173e48..7aa1bb93bcc9baf21763f2635a0ac1d705a9073a 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -40,7 +40,9 @@ import java.time.DateTimeException; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; @Log4j2 @Service @@ -413,7 +415,9 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService i = true; } if (i) { - log.error("Table {} does not exist", queryMapper.stringToEscapedString(fromItem.toString())); + final String tableName = queryMapper.stringToEscapedString(fromItem.toString()); + log.error("Table {} does not exist", tableName); + log.debug("table {} does not exist, available tables are {}", tableName, database.getTables().stream().map(Table::getInternalName).collect(Collectors.toList())); throw new JSQLParserException("Table does not exist"); } } @@ -436,6 +440,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService } if (i) { log.error("Column {} does not exist", item); + log.debug("column {} does not exist, available columns are {}", item, allColumns.stream().map(TableColumn::getInternalName).collect(Collectors.toList())); throw new JSQLParserException("Column does not exist"); } } diff --git a/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java index 629d38c73ef5fa89b4ed3e1bc9ca9e5ba621980a..267052d4a8ea53c27460b7a88add00cb844c8e0c 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java +++ b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/DatabaseidxRepository.java @@ -4,6 +4,6 @@ import at.tuwien.entities.database.Database; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; -@Repository(value = "ElasticDatabaseRepository") +@Repository public interface DatabaseidxRepository extends ElasticsearchRepository<Database, Long> { } diff --git a/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableColumnidxRepository.java b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableColumnidxRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..96526e3a3c0313026b575023904b43c7ffd3671a --- /dev/null +++ b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableColumnidxRepository.java @@ -0,0 +1,9 @@ +package at.tuwien.repository.elastic; + +import at.tuwien.entities.database.table.columns.TableColumn; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TableColumnidxRepository extends ElasticsearchRepository<TableColumn, Long> { +} \ No newline at end of file diff --git a/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableidxRepository.java b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableidxRepository.java index fb60eb355aaaf4e050471da84b444d91368aca38..4dfc8eec357353ebd1cf6f9eb026e7adbcd00a9b 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableidxRepository.java +++ b/fda-table-service/services/src/main/java/at/tuwien/repository/elastic/TableidxRepository.java @@ -4,6 +4,6 @@ import at.tuwien.entities.database.table.Table; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; -@Repository(value = "ElasticDatabaseService") +@Repository public interface TableidxRepository extends ElasticsearchRepository<Table, Long> { } \ No newline at end of file diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index aff6857f43f39cb9974d91e21ee143a042f3640d..5fbf8a127d163f1aeeec03b1c7855eba9252fdcb 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -7,6 +7,7 @@ import at.tuwien.entities.database.table.Table; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.TableMapper; +import at.tuwien.repository.elastic.TableColumnidxRepository; import at.tuwien.repository.elastic.TableidxRepository; import at.tuwien.repository.jpa.TableRepository; import at.tuwien.service.DatabaseService; @@ -25,8 +26,6 @@ import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; -import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; - @Log4j2 @Service public class TableServiceImpl extends HibernateConnector implements TableService { @@ -36,15 +35,18 @@ public class TableServiceImpl extends HibernateConnector implements TableService private final TableRepository tableRepository; private final DatabaseService databaseService; private final TableidxRepository tableidxRepository; + private final TableColumnidxRepository tableColumnidxRepository; @Autowired public TableServiceImpl(TableMapper tableMapper, UserService userService, TableRepository tableRepository, - DatabaseService databaseService, TableidxRepository tableidxRepository) { + DatabaseService databaseService, TableidxRepository tableidxRepository, + TableColumnidxRepository tableColumnidxRepository) { this.tableMapper = tableMapper; this.userService = userService; this.tableRepository = tableRepository; this.databaseService = databaseService; this.tableidxRepository = tableidxRepository; + this.tableColumnidxRepository = tableColumnidxRepository; } @Override @@ -78,6 +80,12 @@ public class TableServiceImpl extends HibernateConnector implements TableService } log.info("Deleted table with id {}", table.getId()); log.debug("deleted table {}", table); + /* delete in database_index - elastic search */ + tableidxRepository.delete(table); + /* delete in column_index - elastic search */ + tableColumnidxRepository.deleteAll(table.getColumns()); + log.info("Deleted columns in elastic search with id {}", databaseId); + log.debug("deleted columns in elastic search {}", database); } @Override @@ -167,10 +175,12 @@ public class TableServiceImpl extends HibernateConnector implements TableService final Table table = tableRepository.save(entity); log.info("Created table with id {}", table.getId()); log.debug("created table {}", table); - /* save in elastic search */ - tableidxRepository.save(entity); - log.info("Added table with id {} to search index", table.getId()); - log.debug("added table {} to search index", table); + /* save in database_index - elastic search */ + final Table eTbl = tableidxRepository.save(entity); + /* save in column_index - elastic search */ + tableColumnidxRepository.saveAll(eTbl.getColumns()); + log.info("Saved table with id {} in elastic search", eTbl.getId()); + log.debug("saved database in elastic search {}", eTbl); return table; } diff --git a/fda-ui/components/DBToolbar.vue b/fda-ui/components/DBToolbar.vue index 229c09bfd25ed4f0a0c728864328e1aefb538a2b..d02d0b45032e0f60ca73081d157f48a35582a224 100644 --- a/fda-ui/components/DBToolbar.vue +++ b/fda-ui/components/DBToolbar.vue @@ -8,13 +8,13 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> - <v-btn v-if="token" class="mr-2 mb-1" :to="`/container/${$route.params.container_id}/database/${databaseId}/table/import`"> + <v-btn v-if="token && canModify" class="mr-2 mb-1" :to="`/container/${$route.params.container_id}/database/${databaseId}/table/import`"> <v-icon left>mdi-cloud-upload</v-icon> Import CSV </v-btn> <v-btn v-if="token" color="secondary" class="mr-2 mb-1 white--text" :to="`/container/${$route.params.container_id}/database/${databaseId}/query/create`"> <v-icon left>mdi-wrench</v-icon> Create Subset </v-btn> - <v-btn v-if="token" color="primary" class="mb-1" :to="`/container/${$route.params.container_id}/database/${databaseId}/table/create`"> + <v-btn v-if="token && canModify" color="primary" class="mb-1" :to="`/container/${$route.params.container_id}/database/${databaseId}/table/create`"> <v-icon left>mdi-table-large-plus</v-icon> Create Table </v-btn> </v-toolbar-title> @@ -36,15 +36,24 @@ </template> <script> +import { decodeJwt } from 'jose' + export default { data () { return { tab: null, loading: false, error: false, + user: { + username: null + }, database: { id: null, - is_public: null + is_public: null, + creator: { + id: null, + username: null + } } } }, @@ -61,6 +70,13 @@ export default { token () { return this.$store.state.token }, + canModify () { + if (!this.user.username) { + /* not yet loaded */ + return false + } + return this.database.creator.username === this.user.username + }, config () { if (this.token === null) { return {} @@ -85,6 +101,7 @@ export default { return } this.loadDatabase() + this.loadUser() }, methods: { async loadDatabase () { @@ -94,11 +111,17 @@ export default { this.database = res.data console.debug('database', res.data) this.$store.commit('SET_DATABASE', res.data) - this.loading = false } catch (err) { + console.error('Could not load database', err) this.$toast.error('Could not load database.') - this.loading = false } + this.loading = false + }, + loadUser () { + if (!this.token) { + return + } + this.user.username = decodeJwt(this.token).sub } } } diff --git a/fda-ui/components/TableList.vue b/fda-ui/components/TableList.vue index fd31415efee9785f990e764afc6ecb1e68e659cf..3e2065cb22faaa49ff8cec6b5926ec91b3711555 100644 --- a/fda-ui/components/TableList.vue +++ b/fda-ui/components/TableList.vue @@ -7,7 +7,7 @@ </v-card-title> </v-card> <v-expansion-panels v-if="!loading && tables.length > 0" v-model="panel" accordion> - <v-expansion-panel v-for="(item,i) in tables" :key="i" @click="details(item.id)"> + <v-expansion-panel v-for="(item,i) in tables" :key="i" @click="details(item)"> <v-expansion-panel-header> {{ item.name }} </v-expansion-panel-header> @@ -62,6 +62,7 @@ </v-list-item-title> <v-list-item-content class="amqp-consumer"> <v-badge + v-if="attemptedLoadingConsumers" class="ml-1" :color="consumersState" :content="`${consumersUp}/${consumersTotal} up`" /> @@ -71,20 +72,20 @@ <v-list-item> <v-list-item-content> <v-list-item-title> - Table Creation + Description </v-list-item-title> <v-list-item-content> - {{ createdUTC }} + {{ tableDetails.description }} </v-list-item-content> </v-list-item-content> </v-list-item> <v-list-item> <v-list-item-content> <v-list-item-title> - Description + Table Creation </v-list-item-title> <v-list-item-content> - {{ tableDetails.description }} + {{ createdUTC }} </v-list-item-content> </v-list-item-content> </v-list-item> @@ -99,7 +100,7 @@ <v-btn color="secondary" class="ml-2" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/query/create?tid=${item.id}`"> Create Subset </v-btn> - <v-btn class="ml-2" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${item.id}/import`"> + <v-btn v-if="canModify" class="ml-2" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${item.id}/import`"> Import csv </v-btn> </v-col> @@ -177,6 +178,8 @@ <script> import { formatTimestampUTCLabel } from '@/utils' +import { decodeJwt } from 'jose' + export default { data () { return { @@ -189,9 +192,16 @@ export default { column: null, unitDialog: false, consumers: [], + attemptedLoadingConsumers: false, + user: { + username: null + }, database: { exchange: null, - tables: [] + tables: [], + creator: { + username: null + } }, tableDetails: { id: null, @@ -261,11 +271,19 @@ export default { }, consumersUp () { return this.consumers.filter(c => c.active).length + }, + canModify () { + if (!this.user.username) { + /* not yet loaded */ + return false + } + return this.database.creator.username === this.user.username } }, mounted () { this.$root.$on('table-create', this.refresh) this.loadDatabase() + this.loadUser() }, methods: { pickUnit (item) { @@ -273,6 +291,12 @@ export default { this.unitDialog = true console.debug('select', this.unit) }, + loadUser () { + if (!this.token) { + return + } + this.user.username = decodeJwt(this.token).sub + }, async loadDatabase () { try { this.loading = true @@ -294,18 +318,25 @@ export default { } return column.column_type }, - async details (tableId) { - if (tableId === this.tableDetails.id) { + async details (table) { + if (table.id === this.tableDetails.id) { /* prevent weird glitch of opening and collapsing simultaneously */ return } + this.attemptedLoadingConsumers = false + /* use cache */ + this.tableDetails.id = table.id + this.tableDetails.name = table.name + this.tableDetails.internal_name = table.internal_name + this.tableDetails.topic = table.topic + /* load remaining info */ try { this.loadingDetails = true - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${tableId}`, this.config) + const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${table.id}`, this.config) this.tableDetails = res.data console.debug('table details', this.tableDetails) - if (tableId) { - this.openPanelByTableId(tableId) + if (table.id) { + this.openPanelByTableId(table.id) await this.consumerDetails(this.tableDetails.topic) } } catch (err) { @@ -347,6 +378,7 @@ export default { }, async consumerDetails (topic) { try { + this.attemptedLoadingConsumers = true this.loadingConsumers = true const res = await this.$axios.get('/api/broker/consumers/%2F', this.brokerConfig) const consumers = res.data.filter(c => c.queue.name === topic) diff --git a/fda-ui/components/dialogs/EditDB.vue b/fda-ui/components/dialogs/EditDB.vue index 1c9bdc2d975bc5148f095ae43783a9d6ba7b79f4..0eb935e0489bd6202a08765090a65f68575be302 100644 --- a/fda-ui/components/dialogs/EditDB.vue +++ b/fda-ui/components/dialogs/EditDB.vue @@ -4,7 +4,7 @@ <v-card> <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" /> <v-card-title> - Modify Database + Database Metadata </v-card-title> <v-card-text> <v-checkbox @@ -40,10 +40,11 @@ required /> <v-text-field id="publication-year" - v-model="modify.publication" + v-model.number="modify.publication_year" name="publication" - label="Publication Date *" - hint="e.g. 2022-07-16" + label="Publication Year *" + hint="e.g. 2022" + type="number" :rules="[v => !!v || $t('Required')]" required /> <v-select @@ -72,7 +73,7 @@ color="primary" type="submit" @click="updateDatabase"> - Create + Update </v-btn> </v-card-actions> </v-card> @@ -100,7 +101,7 @@ export default { publisher: null, description: null, language: null, - publication: null, + publication_year: null, license: null }, licenses: [], @@ -313,7 +314,7 @@ export default { this.modify.is_public = this.database.is_public this.modify.publisher = this.database.publisher this.modify.description = this.database.description - this.modify.publication = this.database.publication + this.modify.publication_year = this.database.publication_year this.modify.language = this.database.language this.modify.license = this.database.license }, diff --git a/fda-ui/layouts/default.vue b/fda-ui/layouts/default.vue index a07765bd97f0e33005150cd8424190806d2f2e5d..1c80b1f70d6496d5afe90a46099ee75a72e756ef 100644 --- a/fda-ui/layouts/default.vue +++ b/fda-ui/layouts/default.vue @@ -48,6 +48,7 @@ <v-app-bar-nav-icon @click.stop="drawer = !drawer" /> <v-autocomplete v-model="model" + class="ml-1" :items="searchResults" :loading="loadingSearch" :search-input.sync="query" @@ -59,16 +60,17 @@ solo flat single-line + clearable label="Search ..." return-object> <template v-slot:item="data"> <template> <v-list-item-icon> - <v-icon :title="icon(data).text">{{ icon(data).icon }}</v-icon> + <v-icon :title="metadata(data).text">{{ metadata(data).icon }}</v-icon> </v-list-item-icon> - <v-list-item-content> - <v-list-item-title>{{ title(data) }}</v-list-item-title> - <v-list-item-subtitle>{{ subtitle(data) }}</v-list-item-subtitle> + <v-list-item-content @click="navigate(data)"> + <v-list-item-title>{{ metadata(data).title }}</v-list-item-title> + <v-list-item-subtitle>{{ metadata(data).subtitle }}</v-list-item-subtitle> </v-list-item-content> </template> </template> @@ -147,11 +149,13 @@ export default { model: null, query: null, searchResults: [], + databases: [], user: { theme_dark: null }, loadingUser: true, - loadingSearch: false + loadingSearch: false, + loadingDatabases: false } }, computed: { @@ -214,33 +218,54 @@ export default { }, mounted () { this.loadDB() + this.loadContainers() + .then(() => this.loadDatabases()) this.loadUser() .then(() => this.setTheme()) }, methods: { - icon (item) { + metadata (item) { if (item.item.exchange !== undefined) { + /* is database */ return { icon: 'mdi-database', - text: 'Database' + text: 'Database', + link: `/container/${item.item.id}/database/${item.item.id}`, + title: item.item.name, + subtitle: item.item.description } } if (item.item.topic !== undefined) { + /* is table */ return { icon: 'mdi-table', - text: 'Table' + text: 'Table', + link: `/container/${item.item.tdbid}/database/${item.item.tdbid}/table/${item.item.id}`, + title: item.item.name, + subtitle: item.item.description } } + if (item.item.columnType !== undefined) { + /* is column */ + return { + icon: 'mdi-view-column-outline', + text: 'Column', + link: `/container/${item.item.cdbid}/database/${item.item.cdbid}/table/${item.item.tid}`, + title: item.item.name, + subtitle: item.item.columnType + } + } + /* is identifier */ return { - icon: 'mdi-view-column-outline', - text: 'Column' + icon: 'mdi-lock-clock', + text: 'Identifier', + link: `/pid/${item.item.id}`, + title: item.item.name, + subtitle: item.item.description } }, - title (item) { - return item.item.name - }, - subtitle (item) { - return item.item.description + navigate (item) { + this.$router.push(this.metadata(item).link) }, logout () { this.$store.commit('SET_TOKEN', null) @@ -252,9 +277,10 @@ export default { async queryDatabases (v) { this.loadingSearch = true try { - const res = await this.$axios.get(`/search/databaseindex/_search?q=*${v}*&_source_includes=id,name,description,exchange&terminate_after=10`) + const res = await this.$axios.get(`/search/databaseindex/_search?q=isPublic:true%20AND%20*${v}*&_source_includes=id,name,description,exchange&terminate_after=10`) + console.info('search databases results', res.data.hits.total.value) + console.debug('search databases results', res.data.hits.hits) const databases = res.data.hits.hits.map(h => h._source) - console.debug('search databases results', databases) databases.forEach(d => this.searchResults.push(d)) } catch (err) { console.error('Failed to load search results', err) @@ -264,9 +290,10 @@ export default { async queryTables (v) { this.loadingSearch = true try { - const res = await this.$axios.get(`/search/tableindex/_search?q=*${v}*&_source_includes=id,name,description,topic&terminate_after=10`) + const res = await this.$axios.get(`/search/tableindex/_search?q=*${v}*&_source_includes=id,tdbid,name,description,topic&terminate_after=10`) + console.info('search tables results', res.data.hits.total.value) + console.debug('search tables results', res.data.hits.hits) const tables = res.data.hits.hits.map(h => h._source) - console.debug('search tables results', tables) tables.forEach(t => this.searchResults.push(t)) } catch (err) { console.error('Failed to load search results', err) @@ -276,15 +303,52 @@ export default { async queryColumns (v) { this.loadingSearch = true try { - const res = await this.$axios.get(`/search/tableindex/_search?q=*${v}*&_source_includes=id,name,description&terminate_after=10`) - const columns = res.data.hits.hits.map(h => h._source) - console.debug('search column results', columns) + const res = await this.$axios.get(`/search/columnindex/_search?q=*${v}*&_source_includes=id,cdbid,tid,name,columnType&terminate_after=10`) + console.info('search column results', res.data.hits.total.value) + console.debug('search column results', res.data.hits.hits) + const dbpubids = this.databases.filter(d => d.is_public).map(d => d.id) + const columns = res.data.hits.hits.map(h => h._source).filter(c => dbpubids.includes(c.cdbid)) columns.forEach(c => this.searchResults.push(c)) } catch (err) { console.error('Failed to load search results', err) } this.loadingSearch = false }, + async loadContainers () { + this.createDbDialog = false + try { + this.loadingContainers = true + const res = await this.$axios.get('/api/container/') + this.containers = res.data + console.debug('containers', this.containers) + this.error = false + } catch (err) { + console.error('containers', err) + this.error = true + } + this.loadingContainers = false + }, + async loadDatabases () { + if (this.containers.length === 0) { + return + } + this.loading = true + for (const container of this.containers) { + try { + const res = await this.$axios.get(`/api/container/${container.id}/database`, this.config) + for (const info of res.data) { + info.container_id = container.id + this.databases.push(info) + } + } catch (err) { + if (err.response === undefined || err.response.status === undefined || err.response.status !== 401) { + console.error('Failed to load databases for container', err) + } + } + } + this.loading = false + console.debug('databases', this.databases) + }, async loadDB () { if (this.$route.params.db_id && !this.db) { try { diff --git a/fda-ui/package.json b/fda-ui/package.json index eb1f4721017c226e1172944448c9e6e1aadb2f72..7b45691ec32268d2a3cdadbbb93d87d8376d1ef5 100644 --- a/fda-ui/package.json +++ b/fda-ui/package.json @@ -32,6 +32,7 @@ "eslint": "^7.27.0", "express": "^4.17.1", "is-docker": "^2.2.1", + "jose": "^4.9.1", "knex": "^0.95.6", "lodash": "^4.17.21", "moment": "^2.29.1", diff --git a/fda-ui/pages/container/_container_id/database/_database_id/info.vue b/fda-ui/pages/container/_container_id/database/_database_id/info.vue index d7b586ac145832845219a90836569c0fc7377282..e7bfc00ba3e042c3d4e4fc39ebf9d9b862d8330e 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/info.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/info.vue @@ -81,7 +81,7 @@ </v-list-item-content> </v-list-item> </v-list> - <v-btn color="secondary" @click="editDbDialog = true">Edit</v-btn> + <v-btn color="secondary" @click="editDbDialog = true">Update Metadata</v-btn> <v-dialog v-model="editDbDialog" persistent diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue index 2f2c1baba41c483c0d436df80c983c4837f1d107..c1e4a4437d71899f803b8958c0240beba1881907 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue @@ -8,10 +8,10 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> - <v-btn v-if="token && !identifier.id && !loadingIdentifier" color="secondary" class="mr-2" :disabled="error || erroneous || !executionUTC" @click.stop="openDialog()"> + <v-btn v-if="token && !identifier.id && !loadingIdentifier && is_owner" class="mb-1 mr-2" color="secondary" :disabled="error || erroneous || !executionUTC" @click.stop="openDialog()"> <v-icon left>mdi-content-save-outline</v-icon> Save </v-btn> - <v-btn v-if="result_visibility" :disabled="error" color="primary" :loading="downloadLoading" @click.stop="download"> + <v-btn v-if="result_visibility" class="mb-1" :disabled="error" :loading="downloadLoading" @click.stop="download"> <v-icon left>mdi-download</v-icon> Data .csv </v-btn> <v-btn @@ -25,7 +25,7 @@ </v-btn> </v-toolbar-title> </v-toolbar> - <v-card flat> + <v-card flat tile> <v-card-title> Subset Information </v-card-title> @@ -237,6 +237,7 @@ import PersistQuery from '@/components/dialogs/PersistQuery' import OrcidIcon from '@/components/icons/OrcidIcon' import { formatTimestampUTCLabel } from '@/utils' +import { decodeJwt } from 'jose' export default { name: 'QueryShow', @@ -267,6 +268,9 @@ export default { lastname: null } }, + user: { + username: null + }, identifier: { id: null, dbid: null, @@ -345,6 +349,9 @@ export default { database_visibility () { return this.database.is_public }, + is_owner () { + return this.token && this.query.creator.username === this.user.username + }, result_visibility () { if (this.erroneous) { return false @@ -488,6 +495,12 @@ export default { if (event.action === 'persisted') { this.loadMetadata() } + }, + loadUser () { + if (!this.token) { + return + } + this.user.username = decodeJwt(this.token).sub } } } diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue index d00ccf387e9675940242917706d4da34ed988cf7..8538b1c71eef7fd98727559285e0d664819b6d40 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue @@ -18,13 +18,13 @@ <v-btn v-if="is_owner && canEdit" color="warning" class="mr-2 mb-1" @click="editTupleDialog = true"> <v-icon left>mdi-pencil</v-icon> Edit </v-btn> - <v-btn v-if="is_owner && canDelete" color="error" class="mr-2 mb-1" @click="deleteItems"> + <v-btn v-if="is_owner && canDelete" color="error" class="mb-1" @click="deleteItems"> <v-icon left>mdi-delete</v-icon> Delete<span v-if="selection.length > 1"> {{ selection.length }}</span> </v-btn> - <v-btn v-if="token" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/query/create?tid=${$route.params.table_id}`" color="secondary" class="mr-2 mb-1" @click="deleteItems"> + <v-btn v-if="token" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/query/create?tid=${$route.params.table_id}`" color="secondary" class=" mb-1" @click="deleteItems"> <v-icon left>mdi-wrench</v-icon> Create Subset </v-btn> - <v-btn v-if="token" class="mb-1" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/import`"> + <v-btn v-if="is_owner" class="ml-2 mb-1" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/import`"> <v-icon left>mdi-cloud-upload</v-icon> Import csv </v-btn> </v-toolbar-title> @@ -75,6 +75,7 @@ import EditTuple from '@/components/dialogs/EditTuple' import TimeTravel from '@/components/dialogs/TimeTravel' import { formatTimestampUTCLabel, formatDateUTC, formatTimestamp } from '@/utils' +import { decodeJwt } from 'jose' export default { components: { @@ -323,18 +324,11 @@ export default { } this.loadingData = false }, - async loadUser () { + loadUser () { if (!this.token) { return } - try { - const res = await this.$axios.put('/api/auth', {}, this.config) - this.user = res.data - console.debug('user', this.user) - } catch (err) { - this.$toast.error('Failed to get user details') - console.error('Failed to get user details', err) - } + this.user.username = decodeJwt(this.token).sub } } } diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue index 825b310f6617afa371033a5dcae4273e74c1abac..a737f70ca9a1fe5fdd481f11684f4d12f750358d 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue @@ -6,7 +6,7 @@ <span>Create Table</span> </v-toolbar-title> </v-toolbar> - <v-stepper v-model="step" vertical flat> + <v-stepper v-model="step" vertical flat tile> <v-stepper-step :complete="step > 1" step="1"> Table Information </v-stepper-step> @@ -54,6 +54,7 @@ <TableSchema :back="true" :columns="tableCreate.columns" @close="schemaClose" /> </v-stepper-content> </v-stepper> + <v-breadcrumbs :items="items" class="pa-0 mt-2" /> </div> </template> @@ -78,7 +79,16 @@ export default { name: null, description: null, columns: [] - } + }, + items: [ + { text: 'Databases', to: '/container', activeClass: '' }, + { + text: `${this.$route.params.database_id}`, + to: `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/info`, + activeClass: '' + }, + { text: 'Tables', to: `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table`, activeClass: '' } + ] } }, computed: { diff --git a/fda-ui/pages/user/index.vue b/fda-ui/pages/user/index.vue index 58b23989c909094c39ec1dfd08bf052d65d75878..f79028d264e974ac4be17075ac61720ebc27826f 100644 --- a/fda-ui/pages/user/index.vue +++ b/fda-ui/pages/user/index.vue @@ -11,7 +11,7 @@ </span> </v-toolbar-title> </v-toolbar> - <v-card flat> + <v-card flat tile> <v-card-title>Verify E-Mail-Address</v-card-title> <v-card-text> <v-form v-model="valid1" @submit.prevent="submit"> diff --git a/fda-ui/yarn.lock b/fda-ui/yarn.lock index 90cc331a76646df6d2c81d6864d34678292cfde2..099a9f4e7c5ed614221f75ff2449ca0051c6a206 100644 --- a/fda-ui/yarn.lock +++ b/fda-ui/yarn.lock @@ -7285,6 +7285,11 @@ jiti@^1.9.2: resolved "https://registry.npmjs.org/jiti/-/jiti-1.12.9.tgz" integrity sha512-TdcJywkQtcwLxogc4rSMAi479G2eDPzfW0fLySks7TPhgZZ4s/tM6stnzayIh3gS/db3zExWJyUx4cNWrwAmoQ== +jose@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.9.1.tgz#c1eef9f20f479d8aa55cdfd4bbc3d76322dd1b48" + integrity sha512-ETgCfJ2yxJavpJdVMgznN8ot3MJyZUiLyY2xiZ2sSNL/uEZh1EH74cmNYWhZgMSBwSdSz03ja5cMZU/9BSlqXg== + jpeg-js@^0.4.2: version "0.4.3" resolved "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz"