diff --git a/src/database/bank_database.Dockerfile b/src/database/bank_database.Dockerfile index d987054..9b6e3a8 100644 --- a/src/database/bank_database.Dockerfile +++ b/src/database/bank_database.Dockerfile @@ -6,7 +6,7 @@ EXPOSE 8080 ENV CONNECTOR_NAME=bank -RUN ./gradlew build +RUN gradle build CMD ["java", "-jar", "build/libs/filestorage-database.jar"] diff --git a/src/database/company_database.Dockerfile b/src/database/company_database.Dockerfile index 29845fa..50180fe 100644 --- a/src/database/company_database.Dockerfile +++ b/src/database/company_database.Dockerfile @@ -6,7 +6,7 @@ EXPOSE 8080 ENV CONNECTOR_NAME=company -RUN ./gradlew build +RUN gradle build CMD ["java", "-jar", "build/libs/filestorage-database.jar"] diff --git a/src/database/src/main/java/de/uni1/amos/filestorage/controller/ContractAgreementController.java b/src/database/src/main/java/de/uni1/amos/filestorage/controller/ContractAgreementController.java new file mode 100644 index 0000000..4df3689 --- /dev/null +++ b/src/database/src/main/java/de/uni1/amos/filestorage/controller/ContractAgreementController.java @@ -0,0 +1,34 @@ +package de.uni1.amos.filestorage.controller; + +import de.uni1.amos.filestorage.entity.ContractAgreement; +import de.uni1.amos.filestorage.service.ContractAgreementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/contracts") +public class ContractAgreementController { + + @Autowired + private ContractAgreementService contractAgreementService; + + @PostMapping("/store") + public ResponseEntity uploadFile(@RequestBody ContractAgreement contract) throws IOException { + contractAgreementService.saveContract(contract); + return new ResponseEntity<>("Contract saved!", HttpStatus.OK); + } + + @GetMapping("/get/{id}") + public ResponseEntity getContract(@PathVariable String id) { + ContractAgreement contract = contractAgreementService.getContractAgreement(id); + if (contract == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(contract, HttpStatus.OK); + } + +} diff --git a/src/database/src/main/java/de/uni1/amos/filestorage/entity/ContractAgreement.java b/src/database/src/main/java/de/uni1/amos/filestorage/entity/ContractAgreement.java new file mode 100644 index 0000000..eb84cda --- /dev/null +++ b/src/database/src/main/java/de/uni1/amos/filestorage/entity/ContractAgreement.java @@ -0,0 +1,81 @@ +package de.uni1.amos.filestorage.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class ContractAgreement { + + @Id + private String id; + + private String fileName; + + private String fileSize; + + private String title; + + private String date; + + private String author; + + private String contenttype; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public void setFileSize(String fileSize) { + this.fileSize = fileSize; + } + + public String getFileSize() { + return fileSize; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setDate(String date) { + this.date = date; + } + + public String getDate() { + return date; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthor() { + return author; + } + + public void setContenttype(String contenttype) { + this.contenttype = contenttype; + } + + public String getContenttype() { + return contenttype; + } + +} diff --git a/src/database/src/main/java/de/uni1/amos/filestorage/entity/FileEntity.java b/src/database/src/main/java/de/uni1/amos/filestorage/entity/FileEntity.java index 7b2baae..a145b9b 100644 --- a/src/database/src/main/java/de/uni1/amos/filestorage/entity/FileEntity.java +++ b/src/database/src/main/java/de/uni1/amos/filestorage/entity/FileEntity.java @@ -1,8 +1,6 @@ package de.uni1.amos.filestorage.entity; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Lob; diff --git a/src/database/src/main/java/de/uni1/amos/filestorage/repository/ContractAgreementRepository.java b/src/database/src/main/java/de/uni1/amos/filestorage/repository/ContractAgreementRepository.java new file mode 100644 index 0000000..02ba369 --- /dev/null +++ b/src/database/src/main/java/de/uni1/amos/filestorage/repository/ContractAgreementRepository.java @@ -0,0 +1,7 @@ +package de.uni1.amos.filestorage.repository; + +import de.uni1.amos.filestorage.entity.ContractAgreement; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContractAgreementRepository extends JpaRepository { +} diff --git a/src/database/src/main/java/de/uni1/amos/filestorage/service/ContractAgreementService.java b/src/database/src/main/java/de/uni1/amos/filestorage/service/ContractAgreementService.java new file mode 100644 index 0000000..613ff99 --- /dev/null +++ b/src/database/src/main/java/de/uni1/amos/filestorage/service/ContractAgreementService.java @@ -0,0 +1,24 @@ +package de.uni1.amos.filestorage.service; + +import de.uni1.amos.filestorage.repository.ContractAgreementRepository; +import de.uni1.amos.filestorage.entity.ContractAgreement; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public class ContractAgreementService { + + @Autowired + private ContractAgreementRepository contractRepository; + + public ContractAgreement saveContract(ContractAgreement contract) throws IOException { + return contractRepository.save(contract); + } + + public ContractAgreement getContractAgreement(String id) { + return contractRepository.findById(id).orElse(null); + } + +} diff --git a/src/database/taxadvisor_database.Dockerfile b/src/database/taxadvisor_database.Dockerfile index 671086e..7244b49 100644 --- a/src/database/taxadvisor_database.Dockerfile +++ b/src/database/taxadvisor_database.Dockerfile @@ -6,7 +6,7 @@ EXPOSE 8080 ENV CONNECTOR_NAME=taxadvisor -RUN ./gradlew build +RUN gradle build CMD ["java", "-jar", "build/libs/filestorage-database.jar"] diff --git a/src/edc-connector/bank_connector.Dockerfile b/src/edc-connector/bank_connector.Dockerfile index 25d9e7e..55f4ced 100644 --- a/src/edc-connector/bank_connector.Dockerfile +++ b/src/edc-connector/bank_connector.Dockerfile @@ -8,7 +8,9 @@ EXPOSE 19193 EXPOSE 19194 EXPOSE 19291 -RUN ./gradlew connector:build +RUN gradle connector:build -CMD ["java", "-Dedc.keystore=resources/certs/cert.pfx", "-Dedc.keystore.password=123456", "-Dedc.vault=resources/configuration/bank-vault.properties", "-Dedc.fs.config=resources/configuration/bank-configuration.properties", "-jar", "connector/build/libs/connector.jar"] +ENV EDC_FS_CONFIG=resources/configuration/bank-configuration.properties + +CMD ["java", "-jar", "connector/build/libs/connector.jar"] diff --git a/src/edc-connector/company_connector.Dockerfile b/src/edc-connector/company_connector.Dockerfile index 48c3fed..59d3c7c 100644 --- a/src/edc-connector/company_connector.Dockerfile +++ b/src/edc-connector/company_connector.Dockerfile @@ -8,7 +8,9 @@ EXPOSE 19193 EXPOSE 19194 EXPOSE 19291 -RUN ./gradlew connector:build +RUN gradle connector:build -CMD ["java", "-Dedc.keystore=resources/certs/cert.pfx", "-Dedc.keystore.password=123456", "-Dedc.vault=resources/configuration/company-vault.properties", "-Dedc.fs.config=resources/configuration/company-configuration.properties", "-jar", "connector/build/libs/connector.jar"] +ENV EDC_FS_CONFIG=resources/configuration/company-configuration.properties + +CMD ["java", "-jar", "connector/build/libs/connector.jar"] diff --git a/src/edc-connector/connector/build.gradle.kts b/src/edc-connector/connector/build.gradle.kts index e7b8ca8..a20a9f1 100644 --- a/src/edc-connector/connector/build.gradle.kts +++ b/src/edc-connector/connector/build.gradle.kts @@ -19,6 +19,7 @@ plugins { } dependencies { + implementation(libs.edc.control.api.configuration) implementation(libs.edc.control.plane.api.client) implementation(libs.edc.control.plane.api) implementation(libs.edc.control.plane.core) diff --git a/src/edc-connector/connector/src/main/java/org/eclipse/edc/extension/runtime/SeedVaultExtension.java b/src/edc-connector/connector/src/main/java/org/eclipse/edc/extension/runtime/SeedVaultExtension.java new file mode 100644 index 0000000..2e3c5f5 --- /dev/null +++ b/src/edc-connector/connector/src/main/java/org/eclipse/edc/extension/runtime/SeedVaultExtension.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.extension.runtime; + +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +public class SeedVaultExtension implements ServiceExtension { + + @Inject + private Vault vault; + + private static final String PUBLIC_KEY = """ + -----BEGIN CERTIFICATE----- + MIIDazCCAlOgAwIBAgIUZ3/sZXYzW4PjmOXKrZn6WBmUJ+4wDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAyMjMxNTA2MDNaFw0zMjAy + MjExNTA2MDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB + AQUAA4IBDwAwggEKAoIBAQDBl6XaJnXTL+6DWip3aBhU+MzmY4d1V9hbTm1tiZ3g + E0VbUrvGO3LoYaxpPv6zFmsg3uJv6JxVAde7EddidN0ITHB9cQNdAfdUJ5njmsGS + PbdQuOQTHw0aG7/QvTI/nsvfEE6e0lbV/0e7DHacZT/+OztBH1RwkG2ymM94Hf8H + I6x7q6yfRTAZOqeOMrPCYTcluAgE9NskoPvjX5qASakBtXISKIsOU84N0/2HDN3W + EGMXvoHUQu6vrij6BwiwxKaw1AKwWENKoga775bPXN3M+JTSaIKE7dZbKzvx0Zi0 + h5X+bxc3BJi3Z/CsUBCzE+Y0SFetOiYmyl/2YmnneYoVAgMBAAGjUzBRMB0GA1Ud + DgQWBBTvK1wVERwjni4B2vdH7KtEJeVWFzAfBgNVHSMEGDAWgBTvK1wVERwjni4B + 2vdH7KtEJeVWFzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBn + QHiPA7OBYukHd9gS7c0HXE+fsWcS3GZeLqcHfQQnV3pte1vTmu9//IVW71wNCJ1/ + rySRyODPQoPehxEcyHwupNZSzXK//nPlTdSgjMfFxscvt1YndyQLQYCfyOJMixAe + Aqrb14GTFHUUrdor0PyElhkULjkOXUrSIsdBrfWrwLTkelE8NK3tb5ZG8KPzD9Jy + +NwEPPr9d+iHkUkM7EFWw/cl56wka9ryBb97RI7DqbO6/j6OXHMk4GByxKv7DSIR + IvF9/Dw20qytajtaHV0pluFcOBuFc0NfiDvCaQlbTsfjzbc6UmZWbOi9YOJl3VQ/ + g3h+15GuzbsSzOCOEYOT + -----END CERTIFICATE----- + """; + + private static final String PRIVATE_KEY = """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBl6XaJnXTL+6D + Wip3aBhU+MzmY4d1V9hbTm1tiZ3gE0VbUrvGO3LoYaxpPv6zFmsg3uJv6JxVAde7 + EddidN0ITHB9cQNdAfdUJ5njmsGSPbdQuOQTHw0aG7/QvTI/nsvfEE6e0lbV/0e7 + DHacZT/+OztBH1RwkG2ymM94Hf8HI6x7q6yfRTAZOqeOMrPCYTcluAgE9NskoPvj + X5qASakBtXISKIsOU84N0/2HDN3WEGMXvoHUQu6vrij6BwiwxKaw1AKwWENKoga7 + 75bPXN3M+JTSaIKE7dZbKzvx0Zi0h5X+bxc3BJi3Z/CsUBCzE+Y0SFetOiYmyl/2 + YmnneYoVAgMBAAECggEBAJHXiN6bctAyn+DcoHlsNkhtVw+Jk5bXIutGXjHTJtiU + K//siAGC78IZMyXmi0KndPVCdBwShROVW8xWWIiXuZxy2Zvm872xqX4Ah3JsN7/Q + NrXdVBUDo38zwIGkxqIfIz9crZ4An+J/eq5zaTfRHzCLtswMqjRS2hFeBY5cKrBY + 4bkSDGTP/c5cP7xS/UwaiTR2Ptd41f4zTyd4l5rl30TYHpazQNlbdxcOV4jh2Rnp + E0+cFEvEfeagVq7RmfBScKG5pk4qcRG0q2QHMyK5y00hdYvhdRjSgN7xIDkeO5B8 + s8/tSLU78nCl2gA9IKxTXYLitpISwZ81Q04mEAKRRtECgYEA+6lKnhn//aXerkLo + ZOLOjWQZhh005jHdNxX7DZqLpTrrfxc8v15KWUkAK1H0QHqYvfPrbbsBV1MY1xXt + sKmkeu/k8fJQzCIvFN4K2J5W5kMfq9PSw5d3XPeDaQuXUVaxBVp0gzPEPHmkKRbA + AkUqY0oJwA9gMKf8dK+flmLZfbsCgYEAxO4Roj2G46/Oox1GEZGxdLpiMpr9rEdR + JlSZ9kMGfddNLV7sFp6yPXDcyc/AOqeNj7tw1MyoT3Ar454+V0q83EZzCXvs4U6f + jUrfFcoVWIwf9AV/J4KWzMIzfqPIeNwqymZKd6BrZgcXXvAEPWt27mwO4a1GhC4G + oZv0t3lAsm8CgYAQ8C0IhSF4tgBN5Ez19VoHpDQflbmowLRt77nNCZjajyOokyzQ + iI0ig0pSoBp7eITtTAyNfyew8/PZDi3IVTKv35OeQTv08VwP4H4EZGve5aetDf3C + kmBDTpl2qYQOwnH5tUPgTMypcVp+NXzI6lTXB/WuCprjy3qvc96e5ZpT3wKBgQC8 + Xny/k9rTL/eYTwgXBiWYYjBL97VudUlKQOKEjNhIxwkrvQBXIrWbz7lh0Tcu49al + BcaHxru4QLO6pkM7fGHq0fh3ufJ8EZjMrjF1xjdk26Q05o0aXe+hLKHVIRVBhlfo + ArB4fRo+HcpdJXjox0KcDQCvHe+1v9DYBTWvymv4QQKBgBy3YH7hKz35DcXvA2r4 + Kis9a4ycuZqTXockO4rkcIwC6CJp9JbHDIRzig8HYOaRqmZ4a+coqLmddXr2uOF1 + 7+iAxxG1KzdT6uFNd+e/j2cdUjnqcSmz49PRtdDswgyYhoDT+W4yVGNQ4VuKg6a3 + Z3pC+KTdoHSKeA2FyAGnSUpD + -----END PRIVATE KEY----- + """; + + @Override + public void initialize(ServiceExtensionContext context) { + vault.storeSecret("public-key", PUBLIC_KEY); + vault.storeSecret("private-key", PRIVATE_KEY); + } +} \ No newline at end of file diff --git a/src/edc-connector/connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/src/edc-connector/connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index 5134cab..b3c9124 100644 --- a/src/edc-connector/connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/src/edc-connector/connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -1,3 +1,4 @@ org.eclipse.edc.extension.status.StatusEndpointExtension org.eclipse.edc.extension.bootstrap.BootstrapLoaderExtension -org.eclipse.edc.extension.policy.PolicyFunctionsExtension \ No newline at end of file +org.eclipse.edc.extension.policy.PolicyFunctionsExtension +org.eclipse.edc.extension.runtime.SeedVaultExtension \ No newline at end of file diff --git a/src/edc-connector/gradle/libs.versions.toml b/src/edc-connector/gradle/libs.versions.toml index d949ff4..c183ad4 100644 --- a/src/edc-connector/gradle/libs.versions.toml +++ b/src/edc-connector/gradle/libs.versions.toml @@ -4,7 +4,7 @@ format.version = "1.1" [versions] assertj = "3.25.3" awaitility = "4.2.1" -edc = "0.7.0" +edc = "0.7.1" jakarta-json = "2.0.1" junit-pioneer = "2.2.0" jupiter = "5.10.2" @@ -26,10 +26,11 @@ edc-configuration-filesystem = { module = "org.eclipse.edc:configuration-filesys edc-connector-core = { module = "org.eclipse.edc:connector-core", version.ref = "edc" } edc-control-plane-api-client = { module = "org.eclipse.edc:control-plane-api-client", version.ref = "edc" } edc-control-plane-api = { module = "org.eclipse.edc:control-plane-api", version.ref = "edc" } +edc-control-api-configuration = { module = "org.eclipse.edc:control-api-configuration", version.ref = "edc" } edc-control-plane-core = { module = "org.eclipse.edc:control-plane-core", version.ref = "edc" } edc-control-plane-spi = { module = "org.eclipse.edc:control-plane-spi", version.ref = "edc" } edc-data-plane-control-api = { module = "org.eclipse.edc:data-plane-control-api", version.ref = "edc" } -edc-data-plane-public-api = { module = "org.eclipse.edc:data-plane-public-api", version.ref = "edc" } +edc-data-plane-public-api = { module = "org.eclipse.edc:data-plane-public-api-v2", version.ref = "edc" } edc-data-plane-aws-s3 = { module = "org.eclipse.edc:data-plane-aws-s3", version.ref = "edc" } edc-data-plane-azure-storage = { module = "org.eclipse.edc:data-plane-azure-storage", version.ref = "edc" } edc-data-plane-client = { module = "org.eclipse.edc:data-plane-client", version.ref = "edc" } diff --git a/src/edc-connector/resources/configuration/bank-configuration.properties b/src/edc-connector/resources/configuration/bank-configuration.properties index ebd0a7f..90a6f8d 100644 --- a/src/edc-connector/resources/configuration/bank-configuration.properties +++ b/src/edc-connector/resources/configuration/bank-configuration.properties @@ -1,20 +1,25 @@ -edc.api.auth.key=bank-pass edc.connector.name=bank edc.participant.id=bank -edc.dsp.callback.address=http://localhost:19194/protocol + +edc.api.auth.key=bank-pass + +edc.dsp.callback.address=http://bank:19194/protocol +edc.dataplane.api.public.baseurl=http://bank:19291/public +edc.dataplane.token.validation.endpoint=http://bank:19192/control/token + +edc.keystore=resources/certs/cert.pfx +edc.keystore.password=123456 + +edc.transfer.proxy.token.signer.privatekey.alias=private-key +edc.transfer.proxy.token.verifier.publickey.alias=public-key + web.http.port=19191 web.http.path=/connector-api +web.http.control.port=19192 +web.http.control.path=/control web.http.management.port=19193 web.http.management.path=/management web.http.protocol.port=19194 web.http.protocol.path=/protocol -edc.receiver.http.endpoint=http://localhost:4000/receiver/urn:connector:provider/callback -edc.public.key.alias=public-key -edc.transfer.dataplane.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.verifier.publickey.alias=public-key web.http.public.port=19291 -web.http.public.path=/public -web.http.control.port=19192 -web.http.control.path=/control -edc.dataplane.token.validation.endpoint=http://localhost:19192/control/token +web.http.public.path=/public \ No newline at end of file diff --git a/src/edc-connector/resources/configuration/bank-vault.properties b/src/edc-connector/resources/configuration/bank-vault.properties deleted file mode 100644 index 6ebdebd..0000000 --- a/src/edc-connector/resources/configuration/bank-vault.properties +++ /dev/null @@ -1 +0,0 @@ -public-key=-----BEGIN CERTIFICATE-----\r\nMIIDazCCAlOgAwIBAgIUZ3/sZXYzW4PjmOXKrZn6WBmUJ+4wDQYJKoZIhvcNAQEL\r\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\r\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAyMjMxNTA2MDNaFw0zMjAy\r\nMjExNTA2MDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\r\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\r\nAQUAA4IBDwAwggEKAoIBAQDBl6XaJnXTL+6DWip3aBhU+MzmY4d1V9hbTm1tiZ3g\r\nE0VbUrvGO3LoYaxpPv6zFmsg3uJv6JxVAde7EddidN0ITHB9cQNdAfdUJ5njmsGS\r\nPbdQuOQTHw0aG7/QvTI/nsvfEE6e0lbV/0e7DHacZT/+OztBH1RwkG2ymM94Hf8H\r\nI6x7q6yfRTAZOqeOMrPCYTcluAgE9NskoPvjX5qASakBtXISKIsOU84N0/2HDN3W\r\nEGMXvoHUQu6vrij6BwiwxKaw1AKwWENKoga775bPXN3M+JTSaIKE7dZbKzvx0Zi0\r\nh5X+bxc3BJi3Z/CsUBCzE+Y0SFetOiYmyl/2YmnneYoVAgMBAAGjUzBRMB0GA1Ud\r\nDgQWBBTvK1wVERwjni4B2vdH7KtEJeVWFzAfBgNVHSMEGDAWgBTvK1wVERwjni4B\r\n2vdH7KtEJeVWFzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBn\r\nQHiPA7OBYukHd9gS7c0HXE+fsWcS3GZeLqcHfQQnV3pte1vTmu9//IVW71wNCJ1/\r\nrySRyODPQoPehxEcyHwupNZSzXK//nPlTdSgjMfFxscvt1YndyQLQYCfyOJMixAe\r\nAqrb14GTFHUUrdor0PyElhkULjkOXUrSIsdBrfWrwLTkelE8NK3tb5ZG8KPzD9Jy\r\n+NwEPPr9d+iHkUkM7EFWw/cl56wka9ryBb97RI7DqbO6/j6OXHMk4GByxKv7DSIR\r\nIvF9/Dw20qytajtaHV0pluFcOBuFc0NfiDvCaQlbTsfjzbc6UmZWbOi9YOJl3VQ/\r\ng3h+15GuzbsSzOCOEYOT\r\n-----END CERTIFICATE----- diff --git a/src/edc-connector/resources/configuration/company-configuration.properties b/src/edc-connector/resources/configuration/company-configuration.properties index 8ed83d4..4eb1d41 100644 --- a/src/edc-connector/resources/configuration/company-configuration.properties +++ b/src/edc-connector/resources/configuration/company-configuration.properties @@ -1,20 +1,25 @@ -edc.api.auth.key=company-pass edc.connector.name=company edc.participant.id=company -edc.dsp.callback.address=http://localhost:19194/protocol + +edc.api.auth.key=company-pass + +edc.dsp.callback.address=http://company:19194/protocol +edc.dataplane.api.public.baseurl=http://company:19291/public +edc.dataplane.token.validation.endpoint=http://company:19192/control/token + +edc.keystore=resources/certs/cert.pfx +edc.keystore.password=123456 + +edc.transfer.proxy.token.signer.privatekey.alias=private-key +edc.transfer.proxy.token.verifier.publickey.alias=public-key + web.http.port=19191 web.http.path=/connector-api +web.http.control.port=19192 +web.http.control.path=/control web.http.management.port=19193 web.http.management.path=/management web.http.protocol.port=19194 web.http.protocol.path=/protocol -edc.receiver.http.endpoint=http://localhost:4000/receiver/urn:connector:provider/callback -edc.public.key.alias=public-key -edc.transfer.dataplane.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.verifier.publickey.alias=public-key web.http.public.port=19291 -web.http.public.path=/public -web.http.control.port=19192 -web.http.control.path=/control -edc.dataplane.token.validation.endpoint=http://localhost:19192/control/token +web.http.public.path=/public \ No newline at end of file diff --git a/src/edc-connector/resources/configuration/company-vault.properties b/src/edc-connector/resources/configuration/company-vault.properties deleted file mode 100644 index 6ebdebd..0000000 --- a/src/edc-connector/resources/configuration/company-vault.properties +++ /dev/null @@ -1 +0,0 @@ -public-key=-----BEGIN CERTIFICATE-----\r\nMIIDazCCAlOgAwIBAgIUZ3/sZXYzW4PjmOXKrZn6WBmUJ+4wDQYJKoZIhvcNAQEL\r\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\r\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAyMjMxNTA2MDNaFw0zMjAy\r\nMjExNTA2MDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\r\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\r\nAQUAA4IBDwAwggEKAoIBAQDBl6XaJnXTL+6DWip3aBhU+MzmY4d1V9hbTm1tiZ3g\r\nE0VbUrvGO3LoYaxpPv6zFmsg3uJv6JxVAde7EddidN0ITHB9cQNdAfdUJ5njmsGS\r\nPbdQuOQTHw0aG7/QvTI/nsvfEE6e0lbV/0e7DHacZT/+OztBH1RwkG2ymM94Hf8H\r\nI6x7q6yfRTAZOqeOMrPCYTcluAgE9NskoPvjX5qASakBtXISKIsOU84N0/2HDN3W\r\nEGMXvoHUQu6vrij6BwiwxKaw1AKwWENKoga775bPXN3M+JTSaIKE7dZbKzvx0Zi0\r\nh5X+bxc3BJi3Z/CsUBCzE+Y0SFetOiYmyl/2YmnneYoVAgMBAAGjUzBRMB0GA1Ud\r\nDgQWBBTvK1wVERwjni4B2vdH7KtEJeVWFzAfBgNVHSMEGDAWgBTvK1wVERwjni4B\r\n2vdH7KtEJeVWFzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBn\r\nQHiPA7OBYukHd9gS7c0HXE+fsWcS3GZeLqcHfQQnV3pte1vTmu9//IVW71wNCJ1/\r\nrySRyODPQoPehxEcyHwupNZSzXK//nPlTdSgjMfFxscvt1YndyQLQYCfyOJMixAe\r\nAqrb14GTFHUUrdor0PyElhkULjkOXUrSIsdBrfWrwLTkelE8NK3tb5ZG8KPzD9Jy\r\n+NwEPPr9d+iHkUkM7EFWw/cl56wka9ryBb97RI7DqbO6/j6OXHMk4GByxKv7DSIR\r\nIvF9/Dw20qytajtaHV0pluFcOBuFc0NfiDvCaQlbTsfjzbc6UmZWbOi9YOJl3VQ/\r\ng3h+15GuzbsSzOCOEYOT\r\n-----END CERTIFICATE----- diff --git a/src/edc-connector/resources/configuration/tax_advisor-configuration-old.properties b/src/edc-connector/resources/configuration/tax_advisor-configuration-old.properties index acf0cbf..551b34a 100644 --- a/src/edc-connector/resources/configuration/tax_advisor-configuration-old.properties +++ b/src/edc-connector/resources/configuration/tax_advisor-configuration-old.properties @@ -1,5 +1,6 @@ +edc.api.auth.key=taxadvisor-pass edc.participant.id=tax_advisor -edc.dsp.callback.address=http://localhost:29194/protocol +edc.dsp.callback.address=http://taxadvisor:29194/protocol web.http.port=29191 web.http.path=/api web.http.management.port=29193 diff --git a/src/edc-connector/resources/configuration/tax_advisor-configuration.properties b/src/edc-connector/resources/configuration/tax_advisor-configuration.properties index 2e94da6..1a69f07 100644 --- a/src/edc-connector/resources/configuration/tax_advisor-configuration.properties +++ b/src/edc-connector/resources/configuration/tax_advisor-configuration.properties @@ -1,20 +1,25 @@ +edc.connector.name=taxadvisor +edc.participant.id=taxadvisor + edc.api.auth.key=taxadvisor-pass -edc.connector.name=tax_advisor -edc.participant.id=tax_advisor -edc.dsp.callback.address=http://localhost:19194/protocol + +edc.dsp.callback.address=http://taxadvisor:19194/protocol +edc.dataplane.api.public.baseurl=http://taxadvisor:19291/public +edc.dataplane.token.validation.endpoint=http://taxadvisor:19192/control/token + +edc.keystore=resources/certs/cert.pfx +edc.keystore.password=123456 + +edc.transfer.proxy.token.signer.privatekey.alias=private-key +edc.transfer.proxy.token.verifier.publickey.alias=public-key + web.http.port=19191 web.http.path=/connector-api +web.http.control.port=19192 +web.http.control.path=/control web.http.management.port=19193 web.http.management.path=/management web.http.protocol.port=19194 web.http.protocol.path=/protocol -edc.receiver.http.endpoint=http://localhost:4000/receiver/urn:connector:provider/callback -edc.public.key.alias=public-key -edc.transfer.dataplane.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.signer.privatekey.alias=1 -edc.transfer.proxy.token.verifier.publickey.alias=public-key web.http.public.port=19291 -web.http.public.path=/public -web.http.control.port=19192 -web.http.control.path=/control -edc.dataplane.token.validation.endpoint=http://localhost:19192/control/token +web.http.public.path=/public \ No newline at end of file diff --git a/src/edc-connector/resources/configuration/tax_advisor-vault.properties b/src/edc-connector/resources/configuration/tax_advisor-vault.properties deleted file mode 100644 index 6ebdebd..0000000 --- a/src/edc-connector/resources/configuration/tax_advisor-vault.properties +++ /dev/null @@ -1 +0,0 @@ -public-key=-----BEGIN CERTIFICATE-----\r\nMIIDazCCAlOgAwIBAgIUZ3/sZXYzW4PjmOXKrZn6WBmUJ+4wDQYJKoZIhvcNAQEL\r\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\r\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAyMjMxNTA2MDNaFw0zMjAy\r\nMjExNTA2MDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\r\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\r\nAQUAA4IBDwAwggEKAoIBAQDBl6XaJnXTL+6DWip3aBhU+MzmY4d1V9hbTm1tiZ3g\r\nE0VbUrvGO3LoYaxpPv6zFmsg3uJv6JxVAde7EddidN0ITHB9cQNdAfdUJ5njmsGS\r\nPbdQuOQTHw0aG7/QvTI/nsvfEE6e0lbV/0e7DHacZT/+OztBH1RwkG2ymM94Hf8H\r\nI6x7q6yfRTAZOqeOMrPCYTcluAgE9NskoPvjX5qASakBtXISKIsOU84N0/2HDN3W\r\nEGMXvoHUQu6vrij6BwiwxKaw1AKwWENKoga775bPXN3M+JTSaIKE7dZbKzvx0Zi0\r\nh5X+bxc3BJi3Z/CsUBCzE+Y0SFetOiYmyl/2YmnneYoVAgMBAAGjUzBRMB0GA1Ud\r\nDgQWBBTvK1wVERwjni4B2vdH7KtEJeVWFzAfBgNVHSMEGDAWgBTvK1wVERwjni4B\r\n2vdH7KtEJeVWFzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBn\r\nQHiPA7OBYukHd9gS7c0HXE+fsWcS3GZeLqcHfQQnV3pte1vTmu9//IVW71wNCJ1/\r\nrySRyODPQoPehxEcyHwupNZSzXK//nPlTdSgjMfFxscvt1YndyQLQYCfyOJMixAe\r\nAqrb14GTFHUUrdor0PyElhkULjkOXUrSIsdBrfWrwLTkelE8NK3tb5ZG8KPzD9Jy\r\n+NwEPPr9d+iHkUkM7EFWw/cl56wka9ryBb97RI7DqbO6/j6OXHMk4GByxKv7DSIR\r\nIvF9/Dw20qytajtaHV0pluFcOBuFc0NfiDvCaQlbTsfjzbc6UmZWbOi9YOJl3VQ/\r\ng3h+15GuzbsSzOCOEYOT\r\n-----END CERTIFICATE----- diff --git a/src/edc-connector/resources/create-contract-definition.json b/src/edc-connector/resources/create-contract-definition.json index 05c9629..7564a9d 100644 --- a/src/edc-connector/resources/create-contract-definition.json +++ b/src/edc-connector/resources/create-contract-definition.json @@ -3,8 +3,8 @@ "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, "@id": "contract01", - "accessPolicyId": "aPolicy", - "contractPolicyId": "aPolicy", + "accessPolicyId": "aPolic", + "contractPolicyId": "aPolic", "assetsSelector": [ { "@type": "CriterionDto", diff --git a/src/edc-connector/resources/create-policy.json b/src/edc-connector/resources/create-policy.json index 8ae6c67..97ba85f 100644 --- a/src/edc-connector/resources/create-policy.json +++ b/src/edc-connector/resources/create-policy.json @@ -7,7 +7,7 @@ "name": "Policy A", "description": "This policy has no restrictions" }, - "@id": "aPolicy", + "@id": "aPolic", "policy": { "@context": "http://www.w3.org/ns/odrl.jsonld", "@type": "Set", diff --git a/src/edc-connector/resources/fetch-catalog.json b/src/edc-connector/resources/fetch-catalog.json index 370b4f2..8d78596 100644 --- a/src/edc-connector/resources/fetch-catalog.json +++ b/src/edc-connector/resources/fetch-catalog.json @@ -2,6 +2,6 @@ "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, - "counterPartyAddress": "http://localhost:19194/protocol", + "counterPartyAddress": "http://company:19194/protocol", "protocol": "dataspace-protocol-http" } diff --git a/src/edc-connector/resources/negotiate-contract.json b/src/edc-connector/resources/negotiate-contract.json index 1657c2e..9d612d9 100644 --- a/src/edc-connector/resources/negotiate-contract.json +++ b/src/edc-connector/resources/negotiate-contract.json @@ -3,13 +3,13 @@ "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, "@type": "ContractRequest", - "counterPartyAddress": "http://localhost:19194/protocol", + "counterPartyAddress": "http://company:19194/protocol", "protocol": "dataspace-protocol-http", "policy": { "@context": "http://www.w3.org/ns/odrl.jsonld", - "@id": "MDAxMA==:MDAxMA==:OWFkNDRjNDQtOGY5Yi00MGY4LWIwOTUtNmI0OGFmOGI0NGFl", + "@id": "Y29udHJhY3QwMQ==:YXNzZXQwMQ==:NzliOWE2N2YtM2IxMC00NzY3LWI4MjEtMWM4OGQ2ZGFlOWFl", "@type": "Offer", "assigner": "company", - "target": "0009" + "target": "asset01" } } \ No newline at end of file diff --git a/src/edc-connector/resources/start-transfer.json b/src/edc-connector/resources/start-transfer.json index 2029004..abc5c78 100644 --- a/src/edc-connector/resources/start-transfer.json +++ b/src/edc-connector/resources/start-transfer.json @@ -5,9 +5,12 @@ "@type": "TransferRequestDto", "connectorId": "company", "counterPartyAddress": "http://localhost:19194/protocol", - "contractId": "ef9d5aa1-44d8-4b27-8bb4-bb225c73788a", - "assetId": "0010", + "contractId": "0ef3fd8a-1e3f-4fcd-8a49-5dbab51f27d7", + "assetId": "1G4jOi3K2W", "protocol": "dataspace-protocol-http", + "transferType": "HttpProxy-PULL" +} + /* "dataDestination": { "type": "HttpData", "properties": { @@ -15,4 +18,6 @@ "method": "POST" } } -} + */ + + diff --git a/src/edc-connector/settings.gradle.kts b/src/edc-connector/settings.gradle.kts index 02ceeef..7995034 100644 --- a/src/edc-connector/settings.gradle.kts +++ b/src/edc-connector/settings.gradle.kts @@ -12,7 +12,7 @@ * */ -rootProject.name = "samples" +rootProject.name = "international-dataspace-station" pluginManagement { repositories { @@ -29,14 +29,3 @@ dependencyResolutionManagement { } include(":connector") - - -//policy -include(":policy:policy-01-policy-enforcement:policy-enforcement-company") -include(":policy:policy-01-policy-enforcement:policy-enforcement-tax_provider") -include(":policy:policy-01-policy-enforcement:policy-enforcement-bank") -include(":policy:policy-01-policy-enforcement:policy-functions") - -include(":util:http-request-logger") - -include(":system-tests") diff --git a/src/edc-connector/tax_advisor_connector.Dockerfile b/src/edc-connector/tax_advisor_connector.Dockerfile index e559682..8065f7e 100644 --- a/src/edc-connector/tax_advisor_connector.Dockerfile +++ b/src/edc-connector/tax_advisor_connector.Dockerfile @@ -8,7 +8,9 @@ EXPOSE 19193 EXPOSE 19194 EXPOSE 19291 -RUN ./gradlew connector:build +RUN gradle connector:build -CMD ["java", "-Dedc.keystore=resources/certs/cert.pfx", "-Dedc.keystore.password=123456", "-Dedc.vault=resources/configuration/tax_advisor-vault.properties", "-Dedc.fs.config=resources/configuration/tax_advisor-configuration.properties", "-jar", "connector/build/libs/connector.jar"] +ENV EDC_FS_CONFIG=resources/configuration/tax_advisor-configuration.properties + +CMD ["java", "-jar", "connector/build/libs/connector.jar"] diff --git a/src/edc-connector/test_transfer/notes.txt b/src/edc-connector/test_transfer/notes.txt deleted file mode 100644 index aed22d3..0000000 --- a/src/edc-connector/test_transfer/notes.txt +++ /dev/null @@ -1,2 +0,0 @@ -run "python3 -m http.server 8000" -- on "http://localhost:8000/test.txt" you should see the text file. \ No newline at end of file diff --git a/src/edc-connector/test_transfer/simple.server.py b/src/edc-connector/test_transfer/simple.server.py deleted file mode 100644 index 1b34653..0000000 --- a/src/edc-connector/test_transfer/simple.server.py +++ /dev/null @@ -1,29 +0,0 @@ -from flask import Flask, request, jsonify -import os - -app = Flask(__name__) - -@app.route('/receiver/urn:connector:provider/callback', methods=['POST']) -def receive_file(): - if not request.data: - return "No data received", 400 - - # Define the absolute path to the Desktop directory - desktop_path = os.path.expanduser("~/Desktop") - file_path = os.path.join(desktop_path, "final_file.txt") - - # Ensure the Desktop directory exists - if not os.path.exists(desktop_path): - return "Desktop directory does not exist", 500 - - # Save the received data to the final_file.txt on the Desktop - try: - with open(file_path, "wb") as file: - file.write(request.data) - except Exception as e: - return f"Error saving file: {str(e)}", 500 - - return jsonify({"message": "File received"}), 200 - -if __name__ == '__main__': - app.run(port=4000) diff --git a/src/edc-connector/test_transfer/test.txt b/src/edc-connector/test_transfer/test.txt deleted file mode 100644 index 1145ce1..0000000 --- a/src/edc-connector/test_transfer/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test. \ No newline at end of file diff --git a/src/edc-connector/util/README.md b/src/edc-connector/util/README.md deleted file mode 100644 index 65105c0..0000000 --- a/src/edc-connector/util/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Util - -Here there are modules that are used by different samples diff --git a/src/edc-connector/util/http-request-logger/Dockerfile b/src/edc-connector/util/http-request-logger/Dockerfile deleted file mode 100644 index 1891917..0000000 --- a/src/edc-connector/util/http-request-logger/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM gradle:jdk17 AS build - -WORKDIR /home/gradle/project -COPY --chown=gradle:gradle . /home/gradle/project -RUN gradle build - -FROM openjdk:17-slim - -WORKDIR /app -COPY --from=build /home/gradle/project/build/libs/http-request-logger.jar /app/http-request-logger.jar - -CMD ["java","-jar","/app/http-request-logger.jar"] \ No newline at end of file diff --git a/src/edc-connector/util/http-request-logger/build.gradle.kts b/src/edc-connector/util/http-request-logger/build.gradle.kts deleted file mode 100644 index aa623cc..0000000 --- a/src/edc-connector/util/http-request-logger/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -plugins { - id("java") -} - -tasks.withType { - from(sourceSets["main"].output) - manifest { - attributes["Main-Class"] = "org.eclipse.edc.samples.util.HttpRequestLoggerServer" - } - archiveFileName.set("http-request-logger.jar") -} diff --git a/src/edc-connector/util/http-request-logger/src/main/java/org/eclipse/edc/samples/util/HttpRequestLoggerServer.java b/src/edc-connector/util/http-request-logger/src/main/java/org/eclipse/edc/samples/util/HttpRequestLoggerServer.java deleted file mode 100644 index 1f93f37..0000000 --- a/src/edc-connector/util/http-request-logger/src/main/java/org/eclipse/edc/samples/util/HttpRequestLoggerServer.java +++ /dev/null @@ -1,45 +0,0 @@ -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Optional; - -public class HttpRequestLoggerServer { - - static final String HTTP_PORT = "HTTP_SERVER_PORT"; - - public static void main(String[] args) { - int port = Integer.parseInt(Optional.ofNullable(System.getenv(HTTP_PORT)).orElse("4000")); - try { - var server = HttpServer.create(new InetSocketAddress(port), 0); - server.createContext("/", new ReceiverHandler()); - server.setExecutor(null); - server.start(); - System.out.println("HTTP request server logger started at " + port); - } catch (IOException e) { - throw new RuntimeException("Unable to start server at port " + port, e); - } - } - - private static class ReceiverHandler implements HttpHandler { - - @Override - public void handle(HttpExchange exchange) throws IOException { - Headers responseHeaders = exchange.getResponseHeaders(); - responseHeaders.add("Access-Control-Allow-Origin", "*"); - responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - responseHeaders.add("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); - - System.out.println("Incoming request"); - System.out.println("Method: " + exchange.getRequestMethod()); - System.out.println("Path: " + exchange.getRequestURI()); - System.out.println("Body:"); - System.out.println(new String(exchange.getRequestBody().readAllBytes())); - System.out.println("============="); - exchange.sendResponseHeaders(200, -1); - } - } -} diff --git a/src/frontend/.dockerignore b/src/frontend/.dockerignore index e69de29..1380c2e 100644 --- a/src/frontend/.dockerignore +++ b/src/frontend/.dockerignore @@ -0,0 +1,2 @@ +node_modules +.next \ No newline at end of file diff --git a/src/frontend/actions/api.ts b/src/frontend/actions/api.ts index 0a2eedd..f10b189 100644 --- a/src/frontend/actions/api.ts +++ b/src/frontend/actions/api.ts @@ -1,4 +1,4 @@ -import { FileInfo, Asset, CatalogItem, Policy } from "@/data/interface/file"; +import { FileInfo, Asset, CatalogItem, Policy, EnrichedContractAgreement } from "@/data/interface/file"; export async function uploadFile(form: FormData) { try { @@ -194,13 +194,11 @@ export async function createAsset(file: FileInfo): Promise { size: file.size }) }); - if (!response.ok) { throw new Error(`API call failed with status ${response.status}`); } const data = await response.json(); - if (data.error) { throw new Error(data.error); } @@ -253,7 +251,137 @@ export async function createContractDefinition(contractId: string, policyId: str assetId: assetId }) }); + if (!response.ok) { + throw new Error(`API call failed with status ${response.status}`); + } + + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + + return true; + } catch (err) { + throw new Error("Error creating contract definition: ", err); + } +} +export async function getContractAgreementId(negotiationId: string): Promise { + try { + const agreementResponse = await fetch(`/api/getContractAgreementId?negotiationId=${negotiationId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + if (!agreementResponse.ok) { + throw new Error(`API call failed with status ${agreementResponse.status}`); + } + + const data = await agreementResponse.json(); + + if (data.error) { + throw new Error(data.error); + } + + return data['agreementId' as any]; + } catch (err) { + console.error('Error fetching assets: ', err); + throw new Error('Failed to fetch assets'); + } +} + + export async function startTransfer(contractId: string, assetId: string, counterPartyName: string): Promise<{ url: string, authorization: string }> { + try { + const response = await fetch('/api/startTransfer', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + contractId: contractId, + counterPartyName: counterPartyName, + assetId: assetId + }) + }); + if (!response.ok) { + throw new Error(`API call failed with status ${response.status}`); + } + + const data = await response.json(); + console.log("Transfer: ", data); + if (data.error) { + throw new Error(data.error); + } + return { url: data.url, authorization: data.authorization }; + } catch (err) { + throw new Error("Error starting transfer: ", err); + } + } + +export async function negotiateContract(item: CatalogItem) { + try { + const negotiateResponse = await fetch('/api/negotiateContract', { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + contractOfferId: item.contractIds[0], + assetId: item.id, + counterPartyName: item.author + }) + }); + + if (!negotiateResponse.ok) { + throw new Error(`Failed to negotiate contract: ${negotiateResponse.statusText}`); + } + const negotiationResult = await negotiateResponse.json(); + const negotiationId = negotiationResult['@id']; + return negotiationId; + + } catch (err) { + console.error("Error negotiating contract", err); + throw new Error("Failed to negotiate contract: ", err); + } +} + +interface ContractAgreement { + providerId: string; + [key: string]: any; +} + +export async function getNegotiatedContracts(counterpartyname: string) { + try { + const response = await fetch("/api/getNegotiatedContracts", { + method: "GET" + }); + if (!response.ok) { + throw new Error(`API call failed with status ${response.status}`); + } + + const data: ContractAgreement[] = await response.json(); + + return data.filter(item => item.providerId === counterpartyname); + + } catch (err) { + console.error('Error getting contract agreement info: ', err); + throw new Error('Failed to get contract agreement info'); + } +} + +export async function getContractAgreementInfo(agreementId: string) { + try { + const response = await fetch("/api/getContractAgreementInfo", { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + agreementId: agreementId + }) + }); if (!response.ok) { throw new Error(`API call failed with status ${response.status}`); } @@ -264,9 +392,58 @@ export async function createContractDefinition(contractId: string, policyId: str throw new Error(data.error); } + console.log("") + + return data; + } catch (err) { + console.error('Error getting contract agreement info: ', err); + throw new Error('Failed to get contract agreement info'); + } +} + +export async function uploadContractAgreementInfo(item: CatalogItem, agreementId: string) { + try { + const response = await fetch("/api/uploadContractAgreementInfo", { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + agreementId: agreementId, + fileName: item.name, + fileSize: item.size, + title: item.title, + date: item.date, + author: item.author, + contenttype: item.contenttype + }) + }); + if (!response.ok) { + throw new Error(`API call failed with status ${response.status}`); + } + return true; } catch (err) { - throw new Error("Error creating contract definition: ", err); + console.error('Error uploading contract agreement info: ', err); + throw new Error('Failed to upload contract agreement info'); + } +} + +export async function getEnrichedContractAgreements(counterpartyname: string): Promise { + try { + const negotiatedContracts = await getNegotiatedContracts(counterpartyname); + + const enrichedContractAgreements = []; + for (const contract of negotiatedContracts) { + const agreementId = contract["@id"]; + const contractAgreementInfo = await getContractAgreementInfo(agreementId); + contractAgreementInfo["assetId"] = contract["assetId"]; + enrichedContractAgreements.push(contractAgreementInfo); + } + + return enrichedContractAgreements; + } catch (err) { + throw new Error("Failed to enrich contract agreement"); } } @@ -308,7 +485,7 @@ export async function deleteContractDefinition(contractId: string) { export async function deleteFile(fileId: string) { try { - const response = await fetch('/api/deleteContractDefinition?fileId=' + fileId, { + const response = await fetch('/api/deleteFile?fileId=' + fileId, { method: 'GET' }); diff --git a/src/frontend/app/api/check_db_status/route.ts b/src/frontend/app/api/check_db_status/route.ts index 9cc11fa..e5180ca 100644 --- a/src/frontend/app/api/check_db_status/route.ts +++ b/src/frontend/app/api/check_db_status/route.ts @@ -13,14 +13,11 @@ function getDataBaseStatusUrl(connectorName: string | null) { async function checkDatabaseStatus(connectorName: string | null): Promise { const url = getDataBaseStatusUrl(connectorName); try { - console.log("Trying to fetch connector status from URL " + url); var result = await fetch(url, {cache: "no-store"}); var data = await result.json(); - console.log("Got a new result."); - console.log(data); return data.response === "Running!"; } catch (err) { - console.log(err); + console.error(err); return false; } } diff --git a/src/frontend/app/api/check_status/route.ts b/src/frontend/app/api/check_status/route.ts index 330a123..0c90ff6 100644 --- a/src/frontend/app/api/check_status/route.ts +++ b/src/frontend/app/api/check_status/route.ts @@ -16,11 +16,9 @@ async function checkConnectorStatus(connectorName: string | null): Promise setTimeout(resolve, WAIT_INTERVAL)); + negotiationStatus = await getContractNegotiationStatus(negotiationId); + } + const agreementId = negotiationStatus.contractAgreementId; + if (!agreementId) { + return NextResponse.json({ error: "Agreement ID not found" }, { status: 404 }); + } + return NextResponse.json({ agreementId }); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +}); \ No newline at end of file diff --git a/src/frontend/app/api/getContractAgreementInfo/route.ts b/src/frontend/app/api/getContractAgreementInfo/route.ts new file mode 100644 index 0000000..9625ce4 --- /dev/null +++ b/src/frontend/app/api/getContractAgreementInfo/route.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getContractAgreementInfo, uploadContractAgreementInfo } from '../database-functions'; +import { auth } from "@/auth" + +export const POST = auth(async function POST(req) { + try { + if (!req.auth) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } + const body = await req.json(); + const { agreementId } = body; + const contractAgreementInfo = await getContractAgreementInfo(agreementId); + return NextResponse.json(contractAgreementInfo, {status: 200}); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +}) diff --git a/src/frontend/app/api/getNegotiatedContracts/route.ts b/src/frontend/app/api/getNegotiatedContracts/route.ts new file mode 100644 index 0000000..0f28354 --- /dev/null +++ b/src/frontend/app/api/getNegotiatedContracts/route.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from 'next/server'; +import {getNegotiatedContracts} from '../connector-functions'; +import { auth } from "@/auth" + +export const GET = auth(async function GET(req) { + try { + if (!req.auth) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } + const data = await getNegotiatedContracts(); + + return NextResponse.json(data); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +}) diff --git a/src/frontend/app/api/negotiateContract/route.ts b/src/frontend/app/api/negotiateContract/route.ts index 4ccd1d9..f2b2d90 100644 --- a/src/frontend/app/api/negotiateContract/route.ts +++ b/src/frontend/app/api/negotiateContract/route.ts @@ -1,4 +1,4 @@ -import { NextRequest, NextResponse } from 'next/server'; +import { NextResponse } from 'next/server'; import { negotiateContract } from '../connector-functions'; import { auth } from "@/auth" diff --git a/src/frontend/app/api/startTransfer/route.ts b/src/frontend/app/api/startTransfer/route.ts index 19d9a81..542b2ff 100644 --- a/src/frontend/app/api/startTransfer/route.ts +++ b/src/frontend/app/api/startTransfer/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { startTransfer } from '../connector-functions'; +import { startTransfer, checkTransferStatus, getEndpointDataReference } from '../connector-functions'; import { auth } from "@/auth" export const POST = auth(async function POST(req) { @@ -9,9 +9,25 @@ export const POST = auth(async function POST(req) { } const body = await req.json(); const { contractId, assetId, counterPartyName } = body; - const result = await startTransfer(contractId, assetId, counterPartyName); - return NextResponse.json(result); + const transferProcess = await startTransfer(contractId, assetId, counterPartyName); + + // Check status until it reaches STARTED + let status; + do { + await new Promise(resolve => setTimeout(resolve, 5000)); // wait for 5 seconds + status = await checkTransferStatus(transferProcess["@id"]); + } while (status.state !== "STARTED"); + + // Fetch the endpoint data reference + const edr = await getEndpointDataReference(transferProcess["@id"]); + console.log("edr: ", edr); + + //const url = edr.endpoint.replace(counterPartyName, 'localhost'); + + //console.log(url); + return NextResponse.json({url: edr.endpoint, authorization: edr.authorization }); + } catch (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } -}) +}) \ No newline at end of file diff --git a/src/frontend/app/api/uploadContractAgreementInfo/route.ts b/src/frontend/app/api/uploadContractAgreementInfo/route.ts new file mode 100644 index 0000000..a91898c --- /dev/null +++ b/src/frontend/app/api/uploadContractAgreementInfo/route.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { uploadContractAgreementInfo } from '../database-functions'; +import { auth } from "@/auth" + +export const POST = auth(async function POST(req) { + try { + if (!req.auth) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } + const body = await req.json(); + const { agreementId, fileName, fileSize, title, date, author, contenttype } = body; + await uploadContractAgreementInfo(agreementId, fileName, fileSize, title, date, author, contenttype); + return NextResponse.json({status: 200}); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +}) diff --git a/src/frontend/app/dashboard/change_status.tsx b/src/frontend/app/dashboard/change_status.tsx index ed4b758..25669ee 100644 --- a/src/frontend/app/dashboard/change_status.tsx +++ b/src/frontend/app/dashboard/change_status.tsx @@ -16,7 +16,7 @@ export default function ChangeStatusButton({ connectorName, connectorStatus, cal const iconMapping: { [key: string]: React.ElementType } = { PauseCircleIcon: PauseCircleIcon, PlayCircleIcon: PlayCircleIcon - }; + }; const changeStatus = async () => { if (!connectorName) return; diff --git a/src/frontend/app/dashboard/download/page.tsx b/src/frontend/app/dashboard/download/page.tsx index 03ab01a..cfa5679 100644 --- a/src/frontend/app/dashboard/download/page.tsx +++ b/src/frontend/app/dashboard/download/page.tsx @@ -1,9 +1,11 @@ 'use client'; import React, { useState, useEffect } from 'react'; import { ArrowPathIcon } from '@heroicons/react/24/outline'; -import { fetchCatalogItems } from '@/actions/api'; +import { fetchCatalogItems, getContractAgreementId, getEnrichedContractAgreements, negotiateContract, startTransfer, uploadContractAgreementInfo} from '@/actions/api'; import participants from '@/data/participants.json'; -import { CatalogItem } from "@/data/interface/file"; +import { CatalogItem, EnrichedContractAgreement } from "@/data/interface/file"; +import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'; +import 'react-tabs/style/react-tabs.css'; import { toast } from 'react-toastify'; const DownloadPage: React.FC = () => { @@ -11,25 +13,29 @@ const DownloadPage: React.FC = () => { const [catalogItems, setCatalogItems] = useState([]); const [errorMessage, setErrorMessage] = useState(''); const [loadingItems, setLoadingItems] = useState(false); + const [errorMessageNegotiated, setErrorMessageNegotiated] = useState(""); + const [negotiatingId, setNegotiatingId] = useState(""); + const [negotiatedContracts, setNegotiatedContracts] = useState([]); + const [activeTabIndex, setActiveTabIndex] = useState(0); + const [downloadingFiles, setDownloadingFiles] = useState([]); useEffect(() => { - setErrorMessage(""); setCatalogItems([]); + setNegotiatedContracts([]); if (connector) { fetchItems(); - } else { - setCatalogItems([]); } }, [connector]); const fetchItems = async () => { + setErrorMessage(""); + setErrorMessageNegotiated(""); try { setLoadingItems(true); setErrorMessage(""); toast.dismiss(); const fetchedCatalog = await fetchCatalogItems(connector); - // Filter assets by connector if needed setCatalogItems(fetchedCatalog); } catch (error) { console.error('Error fetching assets:', error); @@ -37,16 +43,58 @@ const DownloadPage: React.FC = () => { toast.error("There was an error fetching the assets of " + participants.find(p => p.id === connector)?.displayName) } finally { setLoadingItems(false); + } + try { + const negotiatedContracts = await getEnrichedContractAgreements(connector); + setNegotiatedContracts(negotiatedContracts); + } catch (error) { + console.error("Error fetching negotiated contracts"); + setErrorMessageNegotiated("Error fetching contract agreements."); } }; - const handleDownload = (url: string) => { - const link = document.createElement('a'); - link.href = url; - link.download = url.split('/').pop() || 'download'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + const handleNegotiateClick = async (item: CatalogItem) => { + setNegotiatingId(item.id); + try { + const negotiationId = await negotiateContract(item); + // Get agreement ID + const agreementId = await getContractAgreementId(negotiationId); + + await uploadContractAgreementInfo(item, agreementId); + + fetchItems(); + + } catch (err) { + setErrorMessage(err.message); + console.error('Error negotiating contract', err); + } finally { + setNegotiatingId(""); + } + + }; + + const handleDownload = async (agreementId: string, assetId: string, counterPartyname: string, fileName: string) => { + try { + setDownloadingFiles(prevFiles => [...prevFiles, agreementId]); + const {url, authorization} = await startTransfer(agreementId, assetId, counterPartyname); + const downloadUrl = `/api/downloadTransferredFile?url=${(url)}&authorization=${authorization}&filename=${fileName}`; + + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = url.split('/').pop() || 'download'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading file:', error); + setErrorMessage('Error downloading file.'); + } finally { + setDownloadingFiles(prevFiles => prevFiles.filter(file => file !== agreementId)); + } + }; + + const handleTabSelect = (index: number) => { + setActiveTabIndex(index); // Update active tab index }; return ( @@ -71,50 +119,102 @@ const DownloadPage: React.FC = () => { {connector && ( <> -

Files for {participants.find(p => p.id === connector)?.displayName}

-
- - - - {['Name', 'Size', 'Title', 'Date', 'Author', 'Content Type', 'Actions'].map((label) => ( - - ))} - - - - {catalogItems.map((item) => ( - - - - - - - - - - ))} - -
- {label} -
{item.name}{item.size}{item.title}{item.date}{item.author}{item.contenttype} - -
-
+ + + + + Catalog Offers + + + Negotiated Contracts + + + +
+ + + + {['Name', 'Size', 'Title', 'Date', 'Author', 'Content Type', 'Actions'].map((label) => ( + + ))} + + + + {catalogItems.map((item) => ( + + + + + + + + + + ))} + +
+ {label} +
{item.title}{item.name}{item.size}{item.date}{item.author}{item.contenttype} + +
+
+ {errorMessage &&

{errorMessage}

} +
+ + +
+ + + + {['Name', 'Size', 'Title', 'Date', 'Author', 'Content Type', 'Actions'].map((label) => ( + + ))} + + + + {negotiatedContracts.map((item) => ( + + + + + + + + + + ))} + +
+ {label} +
{item.title}{item.fileName}{item.fileSize}{item.date}{item.author}{item.contenttype} + +
+
+ {errorMessageNegotiated &&

{errorMessageNegotiated}

} +
+
)} - {errorMessage &&

{errorMessage}

} + ); }; -export default DownloadPage; +export default DownloadPage; \ No newline at end of file diff --git a/src/frontend/app/dashboard/download/styles/react-tabs.css b/src/frontend/app/dashboard/download/styles/react-tabs.css new file mode 100644 index 0000000..e69de29 diff --git a/src/frontend/app/dashboard/upload/page.tsx b/src/frontend/app/dashboard/upload/page.tsx index 0388d33..d59f7aa 100644 --- a/src/frontend/app/dashboard/upload/page.tsx +++ b/src/frontend/app/dashboard/upload/page.tsx @@ -221,9 +221,9 @@ const UploadPage: React.FC = () => { const policy = getPolicyFromContract("contract-" + file.id); return ( - {file.name} + {file.title} + {file.name} {file.size} - {file.title} {file.date} {file.author} {file.contenttype} diff --git a/src/frontend/app/layout.tsx b/src/frontend/app/layout.tsx index ccff4d2..8893cfa 100644 --- a/src/frontend/app/layout.tsx +++ b/src/frontend/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import Head from "next/head"; const inter = Inter({ subsets: ["latin"] }); @@ -18,6 +19,9 @@ export default function RootLayout({ }>) { return ( + + + {children} { diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index ea58f95..2863a9a 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@heroicons/react": "^2.1.3", "axios": "^1.7.2", + "classnames": "^2.5.1", "clsx": "^2.1.1", "express": "^4.19.2", "js-cookie": "^3.0.5", @@ -19,6 +20,7 @@ "react": "^18", "react-dom": "^18", "react-loader-spinner": "^6.1.6", + "react-tabs": "^6.0.2", "react-toastify": "^10.0.5", "socket.io": "^4.7.5", "util-deprecate": "^1.0.2" @@ -1283,6 +1285,11 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1643,9 +1650,10 @@ } }, "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -1656,7 +1664,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -4342,7 +4350,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -4457,8 +4464,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-loader-spinner": { "version": "6.1.6", @@ -4483,6 +4489,18 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-tabs": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", + "integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==", + "dependencies": { + "clsx": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/react-toastify": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", @@ -4920,12 +4938,13 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", "dependencies": { "debug": "~4.3.4", - "ws": "~8.11.0" + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -5771,15 +5790,16 @@ "dev": true }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/src/frontend/package.json b/src/frontend/package.json index a0b1320..df2f805 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "@heroicons/react": "^2.1.3", "axios": "^1.7.2", + "classnames": "^2.5.1", "clsx": "^2.1.1", "express": "^4.19.2", "js-cookie": "^3.0.5", @@ -22,7 +23,8 @@ "react-loader-spinner": "^6.1.6", "react-toastify": "^10.0.5", "socket.io": "^4.7.5", - "util-deprecate": "^1.0.2" + "util-deprecate": "^1.0.2", + "react-tabs": "^6.0.2" }, "devDependencies": { "@types/node": "^20", diff --git a/src/frontend/public/favicon.ico b/src/frontend/public/favicon.ico new file mode 100644 index 0000000..113aa0c Binary files /dev/null and b/src/frontend/public/favicon.ico differ