diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..5b9b4d666e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +backend/ @airyhq/codeowners-backend +docs/ @airyhq/codeowners-backend @airyhq/codeowners-frontend @airyhq/codeowners-infrastructure +frontend/ @airyhq/codeowners-frontend +infrastructure/ @airyhq/codeowners-infrastructure +lib/go/ @airyhq/codeowners-infrastructure +lib/java/ @airyhq/codeowners-backend +lib/typescript/ @airyhq/codeowners-frontend +scripts/ @airyhq/codeowners-infrastructure +*.bzl @lucapette @chrismatix +BUILD @lucapette @chrismatix diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index 8cf6585443..8e93154460 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -1,4 +1,4 @@ feature: ['feature/*', 'feat/*'] fix: ['bug/*', 'fix/*', 'hotfix/*'] -chore: chore/* +chore: ['chore/*', 'dependabot/*'] docs: ['docs/*', 'doc/*'] diff --git a/.prettierrc.json b/.prettierrc.json index fe40eab2e3..4e5a49f6ce 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -11,6 +11,14 @@ "options": { "parser": "typescript" } + }, + { + "files": "*.md", + "options": { + "singleQuote": false, + "quoteProps": "preserve", + "trailingComma": "none" + } } ] } diff --git a/BUILD b/BUILD index fe6e2882a8..5089d92cee 100644 --- a/BUILD +++ b/BUILD @@ -1,11 +1,17 @@ load("@rules_java//java:defs.bzl", "java_library", "java_plugin") load("@io_bazel_rules_docker//container:container.bzl", "container_image") +load("@com_github_atlassian_bazel_tools//multirun:def.bzl", "multirun") +load("@bazel_gazelle//:def.bzl", "gazelle") package(default_visibility = ["//visibility:public"]) -alias( +multirun( name = "fix", - actual = "//tools/code-format:fix", + commands = [ + "@com_github_airyhq_bazel_tools//code-format:fix_prettier", + "@com_github_airyhq_bazel_tools//code-format:fix_buildifier", + ], + visibility = ["//visibility:public"], ) container_image( @@ -158,6 +164,9 @@ exports_files( ".prettierrc.json", "yarn.lock", "tsconfig.json", - "checkstyle.xml", ], ) + +# gazelle:build_file_name BUILD +# gazelle:prefix +gazelle(name = "gazelle") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41b759aab5..32985e4a2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,9 @@ # Contributing -We love every form of contribution. By participating to this project, you +We love every form of contribution. By participating in this project, you agree to abide to the `Airy` [code of conduct](/code_of_conduct.md). -Please read to our [contribuing guide](/docs/docs/contributing.md) to +Please read our [contributing guide](/docs/docs/guides/contributing.md) to learn how to develop with the `Airy Core Platform` and what conventions we follow. diff --git a/README.md b/README.md index b003224f79..8b13d10a73 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ $ AIRY_VERSION=beta ./scripts/bootstrap.sh The bootstrap installation requires [Vagrant](https://www.vagrantup.com/downloads) and [VirtualBox](https://www.virtualbox.org/wiki/Downloads). If they are not -found, the script will attempt to install them for you. Check out our [user -guide](/docs/docs/user-guide.md) for detailed information. +found, the script will attempt to install them for you. Check out our [test +deployment guide](/docs/docs/guides/airy-core-in-test-env.md) for detailed information. ## Components @@ -97,7 +97,7 @@ read more about it [here](/docs/docs/guidelines/design-principles.md) We welcome (and love) every form of contribution! Good entry points to the project are: -- Our [contributing guidelines](/docs/docs/guidelines/contributing.md) +- Our [contributing guide](/docs/docs/guides/contributing.md) - Issues with the tag [gardening](https://github.com/airyhq/airy/issues?q=is%3Aissue+is%3Aopen+label%3Agardening) - Issues with the tag [good first diff --git a/VERSION b/VERSION index 0ea3a944b3..0d91a54c7d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +0.3.0 diff --git a/WORKSPACE b/WORKSPACE index 375b8230e8..063b1ef4f1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -6,43 +6,31 @@ workspace( load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") -http_archive( - name = "bazel_skylib", - sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c", - urls = [ - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", - ], +# Airy Bazel tools + +git_repository( + name = "com_github_airyhq_bazel_tools", + commit = "fd79bd3344b9c95a09eaa94597a49069f943e089", + remote = "https://github.com/airyhq/bazel-tools.git", + shallow_since = "1607079534 +0100", ) -load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") -bazel_skylib_workspace() +airy_bazel_tools_dependencies() ### Java tooling -RULES_JVM_EXTERNAL_TAG = "3.2" - -RULES_JVM_EXTERNAL_SHA = "82262ff4223c5fda6fb7ff8bd63db8131b51b413d26eb49e3131037e79e324af" - -http_archive( - name = "rules_jvm_external", - sha256 = RULES_JVM_EXTERNAL_SHA, - strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, - url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, -) - load("@rules_jvm_external//:defs.bzl", "maven_install") maven_install( - artifacts = [ + artifacts = airy_jvm_deps + [ "com.fasterxml.jackson.core:jackson-annotations:2.10.0", "com.fasterxml.jackson.core:jackson-core:2.10.0", "com.fasterxml.jackson.core:jackson-databind:2.10.0", "com.fasterxml.jackson.module:jackson-module-afterburner:2.10.0", "com.google.auth:google-auth-library-oauth2-http:0.20.0", "com.jayway.jsonpath:json-path:2.4.0", - "com.puppycrawl.tools:checkstyle:8.37", "com.twilio.sdk:twilio:7.51.0", "cz.habarta.typescript-generator:typescript-generator-core:2.26.723", "io.confluent:kafka-avro-serializer:5.5.1", @@ -60,8 +48,8 @@ maven_install( "org.apache.logging.log4j:log4j-core:2.12.1", "org.apache.logging.log4j:log4j-slf4j-impl:2.12.1", "org.slf4j:slf4j-api:1.7.29", - "org.apache.avro:avro-tools:1.9.1", - "org.apache.avro:avro:1.9.1", + "org.apache.avro:avro-tools:1.10.0", + "org.apache.avro:avro:1.10.0", "org.apache.curator:curator-test:4.2.0", "org.apache.kafka:connect-api:2.5.1", "org.apache.kafka:connect-transforms:2.5.1", @@ -69,6 +57,9 @@ maven_install( "org.apache.kafka:kafka-clients:jar:test:2.5.1", "org.apache.kafka:kafka-streams:2.5.1", "org.apache.kafka:kafka_2.12:2.5.1", + "org.apache.lucene:lucene-queryparser:8.7.0", + "org.apache.lucene:lucene-analyzers-common:8.7.0", + "org.apache.lucene:lucene-core:8.7.0", "org.bouncycastle:bcpkix-jdk15on:1.63", "org.flywaydb:flyway-core:5.2.4", "org.hamcrest:hamcrest-library:2.1", @@ -124,15 +115,13 @@ load("@maven//:defs.bzl", "pinned_maven_install") pinned_maven_install() ### Golang tooling - # This needs to come before any rules_docker usage as it brings its own version of Gazelle - http_archive( - name = "io_bazel_rules_go", - sha256 = "a8d6b1b354d371a646d2f7927319974e0f9e52f73a2452d2b3877118169eb6bb", + name = "bazel_gazelle", + sha256 = "b85f48fa105c4403326e9525ad2b2cc437babaa6e15a3fc0b1dbab0ab064bc7c", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.23.3/rules_go-v0.23.3.tar.gz", - "https://github.com/bazelbuild/rules_go/releases/download/v0.23.3/rules_go-v0.23.3.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.2/bazel-gazelle-v0.22.2.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.2/bazel-gazelle-v0.22.2.tar.gz", ], ) @@ -142,6 +131,10 @@ go_rules_dependencies() go_register_toolchains() +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") + +gazelle_dependencies() + git_repository( name = "com_google_protobuf", commit = "09745575a923640154bcf307fba8aedff47f240a", @@ -207,12 +200,6 @@ _go_image_repos() ### Frontend build tooling -http_archive( - name = "build_bazel_rules_nodejs", - sha256 = "f9e7b9f42ae202cc2d2ce6d698ccb49a9f7f7ea572a78fd451696d03ef2ee116", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.6.0/rules_nodejs-1.6.0.tar.gz"], -) - load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install") node_repositories() @@ -233,12 +220,6 @@ ts_setup_workspace() ### Bazel tooling -http_archive( - name = "com_github_bazelbuild_buildtools", - strip_prefix = "buildtools-master", - url = "https://github.com/bazelbuild/buildtools/archive/master.zip", -) - git_repository( name = "com_github_atlassian_bazel_tools", commit = "dc5e715035b6b17f24f1d40a7eac08f8f2ac8a11", diff --git a/backend/BUILD b/backend/BUILD index 7c1ed6be6c..a55d6f2287 100644 --- a/backend/BUILD +++ b/backend/BUILD @@ -9,8 +9,8 @@ java_library( "//:lombok", "//:spring", "//:springboot", - "//backend/lib/log", - "//backend/lib/spring/core:spring-core", + "//lib/java/log", + "//lib/java/spring/core:spring-core", ], ) @@ -19,7 +19,7 @@ java_library( exports = [ "//:junit", "//:springboot_test", - "//backend/lib/test", + "//lib/java/test", ], ) @@ -27,7 +27,7 @@ java_library( name = "channel", exports = [ "//backend/avro/communication:channel", - "//backend/lib/kafka/schema:application-communication-channels", + "//lib/java/kafka/schema:application-communication-channels", ], ) @@ -35,7 +35,7 @@ java_library( name = "message", exports = [ "//backend/avro/communication:message", - "//backend/lib/kafka/schema:application-communication-messages", + "//lib/java/kafka/schema:application-communication-messages", ], ) @@ -43,7 +43,7 @@ java_library( name = "metadata", exports = [ "//backend/avro/communication:metadata-action", - "//backend/lib/kafka/schema:application-communication-metadata", + "//lib/java/kafka/schema:application-communication-metadata", ], ) @@ -51,7 +51,7 @@ java_library( name = "read-receipt", exports = [ "//backend/avro/communication:read-receipt", - "//backend/lib/kafka/schema:application-communication-read-receipts", + "//lib/java/kafka/schema:application-communication-read-receipts", ], ) @@ -59,7 +59,7 @@ java_library( name = "tag", exports = [ "//backend/avro/communication:tag", - "//backend/lib/kafka/schema:application-communication-tags", + "//lib/java/kafka/schema:application-communication-tags", ], ) @@ -67,6 +67,6 @@ java_library( name = "webhook", exports = [ "//backend/avro/communication:webhook", - "//backend/lib/kafka/schema:application-communication-webhooks", + "//lib/java/kafka/schema:application-communication-webhooks", ], ) diff --git a/backend/api/admin/BUILD b/backend/api/admin/BUILD index 7a8a461d4a..0ab199c815 100644 --- a/backend/api/admin/BUILD +++ b/backend/api/admin/BUILD @@ -8,12 +8,12 @@ app_deps = [ "//backend:channel", "//backend:tag", "//backend:webhook", - "//backend/lib/payload", - "//backend/lib/uuid", - "//backend/lib/spring/auth:spring-auth", - "//backend/lib/spring/web:spring-web", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/payload", + "//lib/java/uuid", + "//lib/java/spring/auth:spring-auth", + "//lib/java/spring/web:spring-web", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -31,8 +31,8 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", - "//backend/lib/spring/test:spring-test", + "//lib/java/kafka/test:kafka-test", + "//lib/java/spring/test:spring-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/sources/twilio/TwilioWhatsappSource.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/sources/twilio/TwilioWhatsappSource.java index 34c1a836cf..d84b5bd087 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/sources/twilio/TwilioWhatsappSource.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/sources/twilio/TwilioWhatsappSource.java @@ -1,7 +1,6 @@ package co.airy.core.api.admin.sources.twilio; import co.airy.core.api.admin.Source; -import co.airy.core.api.admin.SourceApiException; import co.airy.core.api.admin.dto.ChannelMetadata; import org.springframework.stereotype.Service; diff --git a/backend/api/admin/src/main/resources/application.properties b/backend/api/admin/src/main/resources/application.properties index 2e8895f318..67b1c4388f 100644 --- a/backend/api/admin/src/main/resources/application.properties +++ b/backend/api/admin/src/main/resources/application.properties @@ -3,6 +3,6 @@ kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.cleanup=${KAFKA_CLEANUP:false} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS:30000} -facebook.app-id=${FB_APP_ID} -facebook.app-secret=${FB_APP_SECRET} -auth.jwt-secret=${JWT_SECRET} \ No newline at end of file +facebook.app-id=${FACEBOOK_APP_ID} +facebook.app-secret=${FACEBOOK_APP_SECRET} +auth.jwt-secret=${JWT_SECRET} diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java index 68a9381d7f..10dae14e36 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java @@ -28,7 +28,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) -@TestPropertySource(value = "classpath:test.properties") +@TestPropertySource(value = "classpath:test.properties", properties = { + "ALLOWED_ORIGINS=origin1,origin2", +}) @AutoConfigureMockMvc @ExtendWith(SpringExtension.class) public class WebhooksControllerTest { diff --git a/backend/api/auth/BUILD b/backend/api/auth/BUILD index 6eee0e6075..1e3f46c895 100644 --- a/backend/api/auth/BUILD +++ b/backend/api/auth/BUILD @@ -6,10 +6,10 @@ app_deps = [ "//backend:base_app", "//:springboot_actuator", "//:jdbi", - "//backend/lib/payload", - "//backend/lib/spring/auth:spring-auth", - "//backend/lib/spring/web:spring-web", - "//backend/lib/pagination", + "//lib/java/payload", + "//lib/java/spring/auth:spring-auth", + "//lib/java/spring/web:spring-web", + "//lib/java/pagination", "@maven//:org_bouncycastle_bcpkix_jdk15on", "@maven//:org_flywaydb_flyway_core", "@maven//:org_postgresql_postgresql", @@ -35,7 +35,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/spring/test:spring-test", + "//lib/java/spring/test:spring-test", "@maven//:io_zonky_test_embedded_database_spring_test", ] + app_deps, ) diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java index d2da9826a8..6fd2560836 100644 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java @@ -15,10 +15,10 @@ import co.airy.core.api.auth.dto.User; import co.airy.core.api.auth.services.Mail; import co.airy.core.api.auth.services.Password; +import co.airy.spring.jwt.Jwt; import co.airy.payload.response.EmptyResponsePayload; import co.airy.payload.response.RequestErrorResponsePayload; import co.airy.spring.auth.IgnoreAuthPattern; -import co.airy.spring.auth.Jwt; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; diff --git a/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java b/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java index 5718c3d678..92aecf3bf7 100644 --- a/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java +++ b/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java @@ -5,7 +5,7 @@ import co.airy.core.api.auth.dao.UserDAO; import co.airy.core.api.auth.dto.User; import co.airy.core.api.auth.services.Mail; -import co.airy.spring.auth.Jwt; +import co.airy.spring.jwt.Jwt; import co.airy.spring.core.AirySpringBootApplication; import co.airy.spring.test.WebTestHelper; import com.fasterxml.jackson.databind.JsonNode; diff --git a/backend/api/communication/BUILD b/backend/api/communication/BUILD index 8990f60cef..d16eec3328 100644 --- a/backend/api/communication/BUILD +++ b/backend/api/communication/BUILD @@ -11,14 +11,17 @@ app_deps = [ "//backend:channel", "//backend:metadata", "//backend:read-receipt", - "//backend/lib/mapping", - "//backend/lib/payload", - "//backend/lib/pagination", - "//backend/lib/spring/auth:spring-auth", - "//backend/lib/spring/web:spring-web", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/mapping", + "//lib/java/payload", + "//lib/java/pagination", + "//lib/java/spring/auth:spring-auth", + "//lib/java/spring/web:spring-web", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", "@maven//:org_springframework_security_spring_security_core", + "@maven//:org_apache_lucene_lucene_queryparser", + "@maven//:org_apache_lucene_lucene_analyzers_common", + "@maven//:org_apache_lucene_lucene_core", ] springboot( @@ -43,8 +46,8 @@ java_library( ":app", ":test-util", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", - "//backend/lib/spring/test:spring-test", + "//lib/java/kafka/test:kafka-test", + "//lib/java/spring/test:spring-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java index 3689f60dc4..f72eb53dcc 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java @@ -5,18 +5,24 @@ import co.airy.avro.communication.MetadataKeys; import co.airy.avro.communication.ReadReceipt; import co.airy.core.api.communication.dto.Conversation; -import co.airy.core.api.communication.filter.Filter; +import co.airy.core.api.communication.dto.ConversationIndex; +import co.airy.core.api.communication.dto.LuceneQueryResult; +import co.airy.core.api.communication.lucene.ReadOnlyLuceneStore; import co.airy.core.api.communication.payload.ConversationByIdRequestPayload; import co.airy.core.api.communication.payload.ConversationListRequestPayload; import co.airy.core.api.communication.payload.ConversationListResponsePayload; import co.airy.core.api.communication.payload.ConversationResponsePayload; import co.airy.core.api.communication.payload.ConversationTagRequestPayload; -import co.airy.core.api.communication.payload.QueryFilterPayload; +import co.airy.core.api.communication.payload.ResponseMetadata; import co.airy.pagination.Page; import co.airy.pagination.Paginator; import co.airy.payload.response.RequestErrorResponsePayload; import org.apache.kafka.streams.state.KeyValueIterator; import org.apache.kafka.streams.state.ReadOnlyKeyValueStore; +import org.apache.lucene.analysis.core.WhitespaceAnalyzer; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.search.Query; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -34,32 +40,71 @@ @RestController public class ConversationsController { private final Stores stores; - private final List> conversationFilters; private final Mapper mapper; - ConversationsController(Stores stores, List> conversationFilters, Mapper mapper) { + ConversationsController(Stores stores, Mapper mapper) { this.stores = stores; - this.conversationFilters = conversationFilters; this.mapper = mapper; } @PostMapping("/conversations.list") - ResponseEntity conversationList(@RequestBody @Valid ConversationListRequestPayload requestPayload) { - List conversations = fetchAllConversations(); + ResponseEntity conversationList(@RequestBody @Valid ConversationListRequestPayload requestPayload) throws Exception { + final String queryFilter = requestPayload.getFilters(); + if (queryFilter == null) { + return listConversations(requestPayload); + } - conversations.sort(comparing(conversation -> ((Conversation) conversation).getLastMessage().getSentAt()).reversed()); + return queryConversations(requestPayload); + } - final QueryFilterPayload filterPayload = requestPayload.getFilter(); + private ResponseEntity queryConversations(ConversationListRequestPayload requestPayload) throws Exception { + final ReadOnlyLuceneStore conversationLuceneStore = stores.getConversationLuceneStore(); + final ReadOnlyKeyValueStore conversationsStore = stores.getConversationsStore(); - final int totalSize = conversations.size(); + final QueryParser simpleQueryParser = new QueryParser("id", new WhitespaceAnalyzer()); - if (filterPayload != null) { - conversations = conversations.stream() - .filter(conversation -> conversationFilters.stream().allMatch(filter -> filter.filter(conversation, filterPayload))) - .collect(toList()); + final Query query; + try { + query = simpleQueryParser.parse(requestPayload.getFilters()); + } catch (ParseException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new RequestErrorResponsePayload("Failed to parse Lucene query: " + e.getMessage())); } - final int filteredTotal = conversations.size(); + final LuceneQueryResult queryResult = conversationLuceneStore.query(query); + + final List conversationIndices = queryResult.getConversations(); + + final Paginator paginator = new Paginator<>(conversationIndices, ConversationIndex::getId) + .from(requestPayload.getCursor()).perPage(requestPayload.getPageSize()); + + final Page page = paginator.page(); + + final List response = paginator.page().getData() + .stream() + .map((conversationIndex -> conversationsStore.get(conversationIndex.getId()))) + .map(mapper::fromConversation) + .collect(toList()); + + int totalSize = queryResult.getTotal(); + + return ResponseEntity.ok( + ConversationListResponsePayload.builder() + .data(response) + .responseMetadata( + ResponseMetadata.builder() + .filteredTotal(conversationIndices.size()) + .nextCursor(page.getNextCursor()) + .previousCursor(page.getPreviousCursor()) + .total(totalSize) + .build() + ).build()); + } + + private ResponseEntity listConversations(ConversationListRequestPayload requestPayload) { + final List conversations = fetchAllConversations(); + int totalSize = conversations.size(); + conversations.sort(comparing(conversation -> ((Conversation) conversation).getLastMessage().getSentAt()).reversed()); final Paginator paginator = new Paginator<>(conversations, Conversation::getId) .from(requestPayload.getCursor()).perPage(requestPayload.getPageSize()); @@ -75,8 +120,8 @@ ResponseEntity conversationList(@RequestBody @V ConversationListResponsePayload.builder() .data(response) .responseMetadata( - ConversationListResponsePayload.ResponseMetadata.builder() - .filteredTotal(filteredTotal) + ResponseMetadata.builder() + .filteredTotal(conversations.size()) .nextCursor(page.getNextCursor()) .previousCursor(page.getPreviousCursor()) .total(totalSize) @@ -84,7 +129,6 @@ ResponseEntity conversationList(@RequestBody @V ).build()); } - @PostMapping("/conversations.info") ResponseEntity conversationInfo(@RequestBody @Valid ConversationByIdRequestPayload requestPayload) { final ReadOnlyKeyValueStore store = stores.getConversationsStore(); @@ -112,9 +156,7 @@ private List fetchAllConversations() { @PostMapping("/conversations.read") ResponseEntity conversationMarkRead(@RequestBody @Valid ConversationByIdRequestPayload requestPayload) { final ReadOnlyKeyValueStore store = stores.getConversationsStore(); - final String conversationId = requestPayload.getConversationId().toString(); - final Conversation conversation = store.get(conversationId); if (conversation == null) { diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/Mapper.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/Mapper.java index 166f935360..d2508360f9 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/Mapper.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/Mapper.java @@ -4,6 +4,7 @@ import co.airy.avro.communication.MetadataKeys; import co.airy.avro.communication.MetadataMapper; import co.airy.core.api.communication.dto.Conversation; +import co.airy.core.api.communication.dto.DisplayName; import co.airy.core.api.communication.payload.ContactResponsePayload; import co.airy.core.api.communication.payload.ConversationResponsePayload; import co.airy.core.api.communication.payload.MessageResponsePayload; @@ -13,8 +14,8 @@ import java.util.Map; +import static co.airy.avro.communication.MetadataKeys.PUBLIC; import static co.airy.avro.communication.MetadataMapper.filterPrefix; -import static co.airy.core.api.communication.payload.MessageResponsePayload.getAlignment; import static co.airy.payload.format.DateFormat.isoFromMillis; @Component @@ -37,20 +38,27 @@ public ConversationResponsePayload fromConversation(Conversation conversation) { .unreadMessageCount(conversation.getUnreadCount()) .tags(MetadataMapper.getTags(metadata)) .createdAt(isoFromMillis(conversation.getCreatedAt())) - .contact(ContactResponsePayload.builder() - .avatarUrl(metadata.get(MetadataKeys.Source.Contact.AVATAR_URL)) - .firstName(metadata.get(MetadataKeys.Source.Contact.FIRST_NAME)) - .lastName(metadata.get(MetadataKeys.Source.Contact.LAST_NAME)) - .info(filterPrefix(metadata, "user.contact-info")) - .build()) + .contact(getContact(conversation)) .lastMessage(fromMessage(conversation.getLastMessage())) .build(); } + private ContactResponsePayload getContact(Conversation conversation) { + final Map metadata = conversation.getMetadata(); + final DisplayName displayName = conversation.getDisplayNameOrDefault(); + + return ContactResponsePayload.builder() + .avatarUrl(metadata.get(MetadataKeys.Source.Contact.AVATAR_URL)) + .firstName(displayName.getFirstName()) + .lastName(displayName.getLastName()) + .info(filterPrefix(metadata, PUBLIC)) + .build(); + } + public MessageResponsePayload fromMessage(Message message) { return MessageResponsePayload.builder() .content(contentMapper.renderWithDefaultAndLog(message)) - .alignment(getAlignment(message.getSenderType())) + .senderType(message.getSenderType().toString().toLowerCase()) .deliveryState(message.getDeliveryState().toString().toLowerCase()) .id(message.getId()) .sentAt(isoFromMillis(message.getSentAt())) diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/MetadataController.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/MetadataController.java new file mode 100644 index 0000000000..de36a1d794 --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/MetadataController.java @@ -0,0 +1,61 @@ +package co.airy.core.api.communication; + +import co.airy.avro.communication.MetadataAction; +import co.airy.avro.communication.MetadataActionType; +import co.airy.core.api.communication.payload.RemoveMetadataRequestPayload; +import co.airy.core.api.communication.payload.SetMetadataRequestPayload; +import co.airy.payload.response.EmptyResponsePayload; +import co.airy.payload.response.RequestErrorResponsePayload; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.time.Instant; + +import static co.airy.avro.communication.MetadataKeys.PUBLIC; + +@RestController +public class MetadataController { + private final Stores stores; + + public MetadataController(Stores stores) { + this.stores = stores; + } + + @PostMapping("/metadata.set") + ResponseEntity setMetadata(@RequestBody @Valid SetMetadataRequestPayload setMetadataRequestPayload) { + final MetadataAction metadataAction = MetadataAction.newBuilder() + .setActionType(MetadataActionType.SET) + .setTimestamp(Instant.now().toEpochMilli()) + .setConversationId(setMetadataRequestPayload.getConversationId()) + .setValue(setMetadataRequestPayload.getValue()) + .setKey(PUBLIC + "." + setMetadataRequestPayload.getKey()) + .build(); + try { + stores.storeMetadata(metadataAction); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestErrorResponsePayload(e.getMessage())); + } + return ResponseEntity.ok(new EmptyResponsePayload()); + } + + @PostMapping("/metadata.remove") + ResponseEntity removeMetadata(@RequestBody @Valid RemoveMetadataRequestPayload removeMetadataRequestPayload) { + final MetadataAction metadataAction = MetadataAction.newBuilder() + .setActionType(MetadataActionType.REMOVE) + .setTimestamp(Instant.now().toEpochMilli()) + .setConversationId(removeMetadataRequestPayload.getConversationId()) + .setKey(PUBLIC + "." + removeMetadataRequestPayload.getKey()) + .setValue("") + .build(); + try { + stores.storeMetadata(metadataAction); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestErrorResponsePayload(e.getMessage())); + } + return ResponseEntity.ok(new EmptyResponsePayload()); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java index fedc50de9e..e2c7357142 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java @@ -10,6 +10,10 @@ import co.airy.core.api.communication.dto.CountAction; import co.airy.core.api.communication.dto.MessagesTreeSet; import co.airy.core.api.communication.dto.UnreadCountState; +import co.airy.core.api.communication.lucene.IndexingProcessor; +import co.airy.core.api.communication.lucene.LuceneDiskStore; +import co.airy.core.api.communication.lucene.LuceneProvider; +import co.airy.core.api.communication.lucene.ReadOnlyLuceneStore; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; @@ -50,23 +54,30 @@ public class Stores implements HealthIndicator, ApplicationListener producer; private final WebSocketController webSocketController; + private final LuceneProvider luceneProvider; private final String messagesStore = "messages-store"; private final String conversationsStore = "conversations-store"; + private final String conversationsLuceneStore = "conversations-lucene-store"; private final String applicationCommunicationMetadata = new ApplicationCommunicationMetadata().name(); private final String applicationCommunicationReadReceipts = new ApplicationCommunicationReadReceipts().name(); Stores(KafkaStreamsWrapper streams, KafkaProducer producer, - WebSocketController webSocketController) { + WebSocketController webSocketController, + LuceneProvider luceneProvider + ) { this.streams = streams; this.producer = producer; this.webSocketController = webSocketController; + this.luceneProvider = luceneProvider; } private void startStream() { final StreamsBuilder builder = new StreamsBuilder(); + builder.addStateStore(new LuceneDiskStore.Builder(conversationsLuceneStore, luceneProvider)); + final KStream messageStream = builder.stream(new ApplicationCommunicationMessages().name()) .selectKey((messageId, message) -> message.getConversationId()) .peek((conversationId, message) -> webSocketController.onNewMessage(message)); @@ -158,7 +169,9 @@ private void startStream() { conversation.setUnreadCount(unreadCountState.getUnreadCount()); } return conversation; - }, Materialized.as(conversationsStore)); + }, Materialized.as(conversationsStore)) + .toStream() + .process(IndexingProcessor.getSupplier(conversationsLuceneStore), conversationsLuceneStore); streams.start(builder.build(), appId); } @@ -171,6 +184,10 @@ public ReadOnlyKeyValueStore getMessagesStore() { return streams.acquireLocalStore(messagesStore); } + public ReadOnlyLuceneStore getConversationLuceneStore() { + return luceneProvider; + } + public void storeReadReceipt(ReadReceipt readReceipt) throws ExecutionException, InterruptedException { producer.send(new ProducerRecord<>(applicationCommunicationReadReceipts, readReceipt.getConversationId(), readReceipt)).get(); } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java index a25695056a..200d03abbb 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java @@ -1,7 +1,7 @@ package co.airy.core.api.communication; +import co.airy.spring.jwt.Jwt; import co.airy.log.AiryLoggerFactory; -import co.airy.spring.auth.Jwt; import org.slf4j.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Conversation.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Conversation.java index 14d37e9b35..b93330bdf4 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Conversation.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Conversation.java @@ -3,6 +3,8 @@ import co.airy.avro.communication.Channel; import co.airy.avro.communication.Message; import co.airy.avro.communication.MetadataKeys; +import co.airy.core.api.communication.payload.ContactResponsePayload; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,13 +14,16 @@ import java.util.HashMap; import java.util.Map; +import static co.airy.avro.communication.MetadataKeys.PUBLIC; +import static co.airy.avro.communication.MetadataMapper.filterPrefix; +import static org.springframework.util.StringUtils.capitalize; + @Data @Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor public class Conversation implements Serializable { private Long createdAt; - private String channelId; private Message lastMessage; private String sourceConversationId; private Channel channel; @@ -28,21 +33,37 @@ public class Conversation implements Serializable { @Builder.Default private Map metadata = new HashMap<>(); - public String getDisplayName() { - final String firstName = this.metadata.get(MetadataKeys.Source.Contact.FIRST_NAME); - final String lastName = this.metadata.get(MetadataKeys.Source.Contact.LAST_NAME); + @JsonIgnore + public DisplayName getDisplayNameOrDefault() { + String firstName = metadata.get(MetadataKeys.Source.Contact.FIRST_NAME); + String lastName = metadata.get(MetadataKeys.Source.Contact.LAST_NAME); + // Default to a display name that looks like: "Facebook 4ecb3" if (firstName == null && lastName == null) { - return null; + firstName = prettifySource(channel.getSource()); + lastName = getId().substring(31); // UUIDs have a fixed length of 36 } - return String.format("%s %s", firstName, lastName).trim(); + return new DisplayName(firstName, lastName); + } + + /** + * - Remove the source provider (see docs/docs/glossary.md#source-provider) + * - Capitalize first letter + * E.g. twilio.sms -> Sms, facebook -> Facebook + */ + private String prettifySource(String source) { + final String[] splits = source.split("\\."); + source = splits[splits.length - 1]; + return capitalize(source); } + @JsonIgnore public String getId() { return this.lastMessage.getConversationId(); } + @JsonIgnore public String getChannelId() { return this.lastMessage.getChannelId(); } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java new file mode 100644 index 0000000000..41a547336e --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java @@ -0,0 +1,36 @@ +package co.airy.core.api.communication.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ConversationIndex implements Serializable { + private String id; + private String displayName; + private String channelId; + private Long createdAt; + private Integer unreadCount; + + @Builder.Default + private Map metadata = new HashMap<>(); + + public static ConversationIndex fromConversation(Conversation conversation) { + return ConversationIndex.builder() + .id(conversation.getId()) + .channelId(conversation.getChannelId()) + .displayName(conversation.getDisplayNameOrDefault().toString()) + .metadata(new HashMap<>(conversation.getMetadata())) + .createdAt(conversation.getCreatedAt()) + .unreadCount(conversation.getUnreadCount()) + .build(); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/DisplayName.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/DisplayName.java new file mode 100644 index 0000000000..f44ba9280f --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/DisplayName.java @@ -0,0 +1,19 @@ +package co.airy.core.api.communication.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.Objects; + +@Data +@Builder +@AllArgsConstructor +public class DisplayName { + private String firstName; + private String lastName; + + public String toString() { + return String.format("%s %s", Objects.toString(firstName, ""), Objects.toString(lastName, "")).trim(); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java new file mode 100644 index 0000000000..8ad5b06d27 --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java @@ -0,0 +1,13 @@ +package co.airy.core.api.communication.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class LuceneQueryResult { + private List conversations; + private int total; +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ChannelIdsFilter.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ChannelIdsFilter.java deleted file mode 100644 index fb236a9557..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ChannelIdsFilter.java +++ /dev/null @@ -1,21 +0,0 @@ -package co.airy.core.api.communication.filter; - -import co.airy.core.api.communication.dto.Conversation; -import co.airy.core.api.communication.payload.QueryFilterPayload; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class ChannelIdsFilter implements Filter { - @Override - public boolean filter(Conversation conversation, QueryFilterPayload filterPayload) { - final List channelIds = filterPayload.getChannelIds(); - - if (channelIds == null) { - return true; - } - - return channelIds.contains(conversation.getChannelId()); - } -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ConversationIdsFilter.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ConversationIdsFilter.java deleted file mode 100644 index d8526c9466..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/ConversationIdsFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package co.airy.core.api.communication.filter; - -import co.airy.core.api.communication.dto.Conversation; -import co.airy.core.api.communication.payload.QueryFilterPayload; -import org.springframework.stereotype.Component; - -import java.util.UUID; - -import static java.util.stream.Collectors.toList; - -@Component -public class ConversationIdsFilter implements Filter { - @Override - public boolean filter(Conversation conversation, QueryFilterPayload filterPayload) { - if (filterPayload.getConversationIds() == null) { - return true; - } - - return filterPayload.getConversationIds().stream().map(UUID::toString).collect(toList()) - .contains(conversation.getId()); - } -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/DisplayNameFilter.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/DisplayNameFilter.java deleted file mode 100644 index 0e83681438..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/DisplayNameFilter.java +++ /dev/null @@ -1,26 +0,0 @@ -package co.airy.core.api.communication.filter; - -import co.airy.core.api.communication.dto.Conversation; -import co.airy.core.api.communication.payload.QueryFilterPayload; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class DisplayNameFilter implements Filter { - @Override - public boolean filter(Conversation conversation, QueryFilterPayload filterPayload) { - final List displayNamesFilter = filterPayload.getDisplayNames(); - - if(displayNamesFilter == null) { - return true; - } else if (conversation.getDisplayName() == null) { - return false; - } - - - return displayNamesFilter - .stream() - .anyMatch(displayName -> conversation.getDisplayName().toLowerCase().trim().contains(displayName.toLowerCase().trim())); - } -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/Filter.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/Filter.java deleted file mode 100644 index 806fa93610..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/filter/Filter.java +++ /dev/null @@ -1,9 +0,0 @@ -package co.airy.core.api.communication.filter; - -import co.airy.core.api.communication.payload.QueryFilterPayload; - -public interface Filter { - - boolean filter(T objectToFilter, QueryFilterPayload filterPayload); - -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java new file mode 100644 index 0000000000..3f8fe25f1f --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java @@ -0,0 +1,63 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.ConversationIndex; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.IntPoint; +import org.apache.lucene.document.LongPoint; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.IndexableField; + +import java.io.IOException; +import java.util.Map; + +import static java.util.stream.Collectors.toMap; + +public class DocumentMapper { + final ObjectMapper objectMapper = new ObjectMapper(); + + public Document fromConversationIndex(ConversationIndex conversation) { + final Document document = new Document(); + document.add(new StringField("id", conversation.getId(), Field.Store.YES)); + document.add(new StringField("channel_id", conversation.getChannelId(), Field.Store.YES)); + + if (conversation.getDisplayName() != null) { + document.add(new TextField("display_name", conversation.getDisplayName(), Field.Store.YES)); + } + + document.add(new LongPoint("createdAt", conversation.getCreatedAt())); + document.add(new StoredField("createdAt", conversation.getCreatedAt())); + document.add(new IntPoint("unreadCount", conversation.getUnreadCount())); + document.add(new StoredField("unreadCount", conversation.getUnreadCount())); + + for (Map.Entry entry : conversation.getMetadata().entrySet()) { + document.add(new TextField("metadata." + entry.getKey(), entry.getValue(), Field.Store.YES)); + } + + return document; + } + + public ConversationIndex fromDocument(Document document) { + + final Long createdAt = document.getField("createdAt").numericValue().longValue(); + final Integer unreadCount = document.getField("unreadCount").numericValue().intValue(); + + final Map metadata = document.getFields().stream() + .filter((field) -> field.name().startsWith("metadata")) + .collect(toMap( + (field) -> field.name().replace("metadata.", ""), + IndexableField::stringValue + )); + + return ConversationIndex.builder() + .id(document.get("id")) + .unreadCount(unreadCount) + .createdAt(createdAt) + .metadata(metadata) + .displayName(document.get("displayName")) + .build(); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java new file mode 100644 index 0000000000..fdab18889b --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java @@ -0,0 +1,48 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.Conversation; +import co.airy.core.api.communication.dto.ConversationIndex; +import org.apache.kafka.streams.processor.Processor; +import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.ProcessorSupplier; + +import java.io.IOException; + +public class IndexingProcessor implements Processor { + + private ProcessorContext context; + private LuceneStore store; + private final String storeName; + + public IndexingProcessor(String storeName) { + this.storeName = storeName; + } + + @Override + public void init(ProcessorContext context) { + this.context = context; + this.store = (LuceneStore) context.getStateStore(this.storeName); + } + + @Override + public void process(String key, Conversation value) { + try { + if (value == null) { + store.delete(key); + } else { + store.put(ConversationIndex.fromConversation(value)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + context.forward(key, value); + } + + @Override + public void close() { + } + + public static ProcessorSupplier getSupplier(String luceneStoreName) { + return () -> new IndexingProcessor(luceneStoreName); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java new file mode 100644 index 0000000000..bf37839df3 --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java @@ -0,0 +1,168 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.ConversationIndex; +import co.airy.core.api.communication.dto.LuceneQueryResult; +import co.airy.kafka.core.serdes.KafkaHybridSerde; +import co.airy.kafka.core.serializer.KafkaJacksonSerializer; +import co.airy.log.AiryLoggerFactory; +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.common.utils.Bytes; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.processor.AbstractNotifyingBatchingRestoreCallback; +import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.StateStore; +import org.apache.kafka.streams.processor.internals.ProcessorStateManager; +import org.apache.kafka.streams.state.StateSerdes; +import org.apache.kafka.streams.state.StoreBuilder; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.search.Query; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; + +public class LuceneDiskStore implements StateStore, LuceneStore { + private static final Logger log = AiryLoggerFactory.getLogger(LuceneDiskStore.class); + + final String name; + final LuceneProvider lucene; + ProcessorContext initialProcessorContext; + StoreChangeLogger changeLogger; + + public LuceneDiskStore(String name, LuceneProvider provider) { + this.name = name; + this.lucene = provider; + } + + private IndexWriter getWriter() { + return lucene.getWriter(); + } + + @Override + public String name() { + return name; + } + + @Override + public void init(ProcessorContext context, StateStore stateStore) { + final String topic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), name()); + changeLogger = new StoreChangeLogger<>( + name(), + context, + new StateSerdes<>(topic, Serdes.String(), new KafkaHybridSerde())); + + initialProcessorContext = context; + + context.register(stateStore, (key, value) -> { + // Restoration callback + try { + final ConversationIndex conversation = (ConversationIndex) context.valueSerde().deserializer().deserialize(topic, value); + lucene.put(conversation); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + void log(final String key, + final Serializable value) { + changeLogger.logChange(key, value); + } + + @Override + public void flush() { + try { + getWriter().flush(); + } catch (IOException e) { + log.error("Failed to flush Lucene store", e); + } + } + + @Override + public void close() { + try { + getWriter().close(); + } catch (IOException e) { + log.error("Failed to close Lucene store", e); + } + } + + @Override + public boolean persistent() { + return true; + } + + @Override + public boolean isOpen() { + return getWriter().isOpen(); + } + + @Override + public void put(ConversationIndex conversationIndex) throws IOException { + lucene.put(conversationIndex); + log(conversationIndex.getId(), conversationIndex); + } + + @Override + public void delete(String id) throws IOException { + lucene.delete(id); + log(id, null); + } + + @Override + public LuceneQueryResult query(Query query) { + return lucene.query(query); + } + + public static class Builder implements StoreBuilder { + final String name; + final LuceneProvider provider; + + public Builder(String name, LuceneProvider provider) { + this.name = name; + this.provider = provider; + } + + @Override + public StoreBuilder withCachingEnabled() { + return this; + } + + @Override + public StoreBuilder withCachingDisabled() { + return this; + } + + @Override + public StoreBuilder withLoggingEnabled(Map config) { + return this; + } + + @Override + public StoreBuilder withLoggingDisabled() { + return this; + } + + @Override + public LuceneDiskStore build() { + return new LuceneDiskStore(name, provider); + } + + @Override + public Map logConfig() { + return Map.of(); + } + + @Override + public boolean loggingEnabled() { + return true; + } + + @Override + public String name() { + return name; + } + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java new file mode 100644 index 0000000000..3d5e0588ed --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java @@ -0,0 +1,91 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.ConversationIndex; +import co.airy.core.api.communication.dto.LuceneQueryResult; +import co.airy.log.AiryLoggerFactory; +import org.apache.kafka.streams.KeyValue; +import org.apache.lucene.analysis.core.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.FSDirectory; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@Component +public class LuceneProvider implements LuceneStore { + private static final Logger log = AiryLoggerFactory.getLogger(LuceneDiskStore.class); + + final IndexWriter writer; + final DocumentMapper documentMapper; + DirectoryReader reader; + + public LuceneProvider() throws IOException { + boolean testMode = System.getenv("TEST_TARGET") != null; + FSDirectory dir = FSDirectory.open(Paths.get(testMode ? System.getenv("TEST_TMPDIR") : "/tmp/lucene")); + IndexWriterConfig config = new IndexWriterConfig(new WhitespaceAnalyzer()); + writer = new IndexWriter(dir, config); + reader = DirectoryReader.open(writer, true, true); + documentMapper = new DocumentMapper(); + } + + @Override + public void put(ConversationIndex conversation) throws IOException { + final Document document = this.documentMapper.fromConversationIndex(conversation); + writer.updateDocument(new Term("id", conversation.getId()), document); + } + + @Override + public void delete(String id) throws IOException { + writer.deleteDocuments(new Term("id", id)); + } + + @Override + public LuceneQueryResult query(Query query) { + try { + refreshReader(); + final IndexSearcher indexSearcher = new IndexSearcher(reader); + final TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE); + + List conversations = new ArrayList<>(topDocs.scoreDocs.length); + for (ScoreDoc scoreDoc : topDocs.scoreDocs) { + final Document doc = indexSearcher.doc(scoreDoc.doc); + conversations.add(documentMapper.fromDocument(doc)); + } + + return LuceneQueryResult.builder() + .conversations(conversations) + .total(reader.maxDoc()).build(); + } catch (Exception e) { + log.error("Failed to query Lucene store with query {}", query, e); + return LuceneQueryResult.builder().conversations(List.of()) + .total(reader.maxDoc()).build(); + } + } + + public IndexWriter getWriter() { + return this.writer; + } + + private void refreshReader() throws IOException { + final DirectoryReader newReader = DirectoryReader.openIfChanged(reader, writer); + if (newReader != null && newReader != reader) { + reader.close(); + reader = newReader; + } + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java new file mode 100644 index 0000000000..93438eddef --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java @@ -0,0 +1,10 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.ConversationIndex; + +import java.io.IOException; + +public interface LuceneStore extends ReadOnlyLuceneStore { + void put(ConversationIndex conversation) throws IOException; + void delete(String id) throws IOException; +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java new file mode 100644 index 0000000000..45a1c3c8fc --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java @@ -0,0 +1,8 @@ +package co.airy.core.api.communication.lucene; + +import co.airy.core.api.communication.dto.LuceneQueryResult; +import org.apache.lucene.search.Query; + +public interface ReadOnlyLuceneStore { + LuceneQueryResult query(Query query); +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java new file mode 100644 index 0000000000..309ed86d15 --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package co.airy.core.api.communication.lucene; + +import org.apache.kafka.common.serialization.Serializer; +import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.internals.ProcessorStateManager; +import org.apache.kafka.streams.processor.internals.RecordCollector; +import org.apache.kafka.streams.state.StateSerdes; + +/** + * This file is copied and licensed as-is from the Kafka Streams source + * since the underlying class is not public. + * + * see https://github.com/apache/kafka/blob/33ba2820f4fbb459172022a83d761a7c674a8fdd/streams/src/main/java/org/apache/kafka/streams/state/internals/StoreChangeLogger.java#L33 + */ +class StoreChangeLogger { + + private final String topic; + private final int partition; + private final ProcessorContext context; + private final RecordCollector collector; + private final Serializer keySerializer; + private final Serializer valueSerializer; + + StoreChangeLogger(final String storeName, + final ProcessorContext context, + final StateSerdes serialization) { + this(storeName, context, context.taskId().partition, serialization); + } + + private StoreChangeLogger(final String storeName, + final ProcessorContext context, + final int partition, + final StateSerdes serialization) { + topic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName); + this.context = context; + this.partition = partition; + this.collector = ((RecordCollector.Supplier) context).recordCollector(); + keySerializer = serialization.keySerializer(); + valueSerializer = serialization.valueSerializer(); + } + + void logChange(final K key, + final V value) { + logChange(key, value, context.timestamp()); + } + + void logChange(final K key, + final V value, + final long timestamp) { + // Sending null headers to changelog topics (KIP-244) + collector.send(topic, key, value, null, partition, timestamp, keySerializer, valueSerializer); + } +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java index a3945ca91e..1173530c46 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java @@ -8,7 +8,7 @@ @AllArgsConstructor @NoArgsConstructor public class ConversationListRequestPayload { - private QueryFilterPayload filter; + private String filters; private String cursor; private int pageSize = 10; } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java index 2ff25664c2..8a7bf6cf5d 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java @@ -14,15 +14,4 @@ public class ConversationListResponsePayload { private List data; private ResponseMetadata responseMetadata; - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class ResponseMetadata { - private String previousCursor; - private String nextCursor; - private long filteredTotal; - private long total; //total conversation count - } } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageResponsePayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageResponsePayload.java index 83743ac315..00e34b7ca9 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageResponsePayload.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageResponsePayload.java @@ -14,16 +14,7 @@ public class MessageResponsePayload { private String id; private Content content; - private String alignment; + private String senderType; private String sentAt; private String deliveryState; - - public static String getAlignment(SenderType senderType) { - switch (senderType) { - case APP_USER: - case SOURCE_USER: return "LEFT"; - case SOURCE_CONTACT: return "RIGHT"; - default: throw new RuntimeException("Unknown sender type " + senderType); - } - } } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/QueryFilterPayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java similarity index 52% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/QueryFilterPayload.java rename to backend/api/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java index a4cf29d33d..a6b3928a83 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/QueryFilterPayload.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java @@ -5,16 +5,15 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; -import java.util.UUID; +import javax.validation.constraints.NotNull; -@Data +@Builder @NoArgsConstructor @AllArgsConstructor -@Builder -public class QueryFilterPayload { - private List conversationIds; - private List channelIds; - private List displayNames; +@Data +public class RemoveMetadataRequestPayload { + @NotNull + private String conversationId; + @NotNull + private String key; } - diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ResponseMetadata.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ResponseMetadata.java new file mode 100644 index 0000000000..7850c674fe --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ResponseMetadata.java @@ -0,0 +1,17 @@ +package co.airy.core.api.communication.payload; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ResponseMetadata { + private String previousCursor; + private String nextCursor; + private long filteredTotal; + private long total; //total conversation count +} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java new file mode 100644 index 0000000000..009258f42d --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java @@ -0,0 +1,21 @@ +package co.airy.core.api.communication.payload; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class SetMetadataRequestPayload { + @NotNull + private String conversationId; + @NotNull + private String key; + @NotNull + private String value; +} diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java index 34b2b1e192..7176a7c6df 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java @@ -13,6 +13,8 @@ import co.airy.payload.format.DateFormat; import co.airy.spring.core.AirySpringBootApplication; import co.airy.spring.test.WebTestHelper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -134,22 +136,34 @@ void canFetchAllConversations() throws Exception { @Test void canFilterByConversationId() throws Exception { - String payload = "{\"filter\": {\"conversation_ids\": [\"" + conversationIdToFind + "\"]}}"; + final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - checkOneConversationExists(payload); + final ObjectNode request = jsonNodeFactory.objectNode(); + request.put("filters", "id:\"" + conversationIdToFind.replace("-", "\\-") + "\""); + + checkConversationsFound(request.toString(), 1); } @Test void canFilterByDisplayName() throws Exception { - String payload = "{\"filter\": {\"display_names\": [\"" + firstNameToFind + "\"]}}"; + String payload = "{\"filters\": \"display_name:" + firstNameToFind + "\"}"; + checkConversationsFound(payload, 1); + } + + @Test + void canFilterByCombinedQueries() throws Exception { + final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + + final ObjectNode request = jsonNodeFactory.objectNode(); + request.put("filters", "display_name:" + firstNameToFind + + " OR id:\"" + conversationIdToFind.replace("-", "\\-") + "\""); - checkOneConversationExists(payload); + checkConversationsFound(request.toString(), 2); } @Test void canFilterForUnknownNames() throws Exception { - String payload = "{\"filter\": {\"display_names\": [\"Ada\"]}}"; - + String payload = "{\"filters\": \"display_name:Ada\"}"; checkNoConversationReturned(payload); } @@ -161,11 +175,12 @@ private void checkNoConversationReturned(String payload) throws Exception { "Expected no conversations returned"); } - private void checkOneConversationExists(String payload) throws InterruptedException { + private void checkConversationsFound(String payload, int count) throws InterruptedException { retryOnException( () -> webTestHelper.post("/conversations.list", payload, userId) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))) + .andExpect(jsonPath("$.data", hasSize(count))) + .andExpect(jsonPath("response_metadata.filtered_total", is(count))) .andExpect(jsonPath("response_metadata.total", is(conversations.size()))), "Expected one conversation returned"); } diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java new file mode 100644 index 0000000000..cb5304afad --- /dev/null +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java @@ -0,0 +1,133 @@ +package co.airy.core.api.communication; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; +import co.airy.core.api.communication.util.TestConversation; +import co.airy.kafka.schema.application.ApplicationCommunicationChannels; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; +import co.airy.kafka.schema.application.ApplicationCommunicationReadReceipts; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.spring.core.AirySpringBootApplication; +import co.airy.spring.test.WebTestHelper; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.UUID; + +import static co.airy.test.Timing.retryOnException; +import static org.hamcrest.core.Is.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +public class MetadataControllerTest { + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + + private static KafkaTestHelper kafkaTestHelper; + + @Autowired + private WebTestHelper webTestHelper; + + private static final ApplicationCommunicationMessages applicationCommunicationMessages = new ApplicationCommunicationMessages(); + private static final ApplicationCommunicationChannels applicationCommunicationChannels = new ApplicationCommunicationChannels(); + private static final ApplicationCommunicationMetadata applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); + private static final ApplicationCommunicationReadReceipts applicationCommunicationReadReceipts = new ApplicationCommunicationReadReceipts(); + + @BeforeAll + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, + applicationCommunicationMessages, + applicationCommunicationChannels, + applicationCommunicationMetadata, + applicationCommunicationReadReceipts); + + kafkaTestHelper.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + @BeforeEach + void beforeEach() throws Exception { + webTestHelper.waitUntilHealthy(); + } + @Test + void canSetMetadata() throws Exception { + final Channel channel = Channel.newBuilder() + .setConnectionState(ChannelConnectionState.CONNECTED) + .setId("channel-id") + .setName("channel-name") + .setSource("facebook") + .setSourceChannelId("ps-id") + .build(); + final String conversationId = UUID.randomUUID().toString(); + + kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channel.getId(), channel)); + kafkaTestHelper.produceRecords(TestConversation.generateRecords(conversationId, channel, 1)); + + retryOnException( + () -> webTestHelper.post("/metadata.set", + "{\"conversation_id\":\"" + conversationId + "\", \"key\": \"awesome.key\", \"value\": \"awesome-value\"}", + "user-id") + .andExpect(status().isOk()), + "Error setting metadata" + ); + } + + @Test + void canRemoveMetadata() throws Exception { + final Channel channel = Channel.newBuilder() + .setConnectionState(ChannelConnectionState.CONNECTED) + .setId("channel-id") + .setName("channel-name") + .setSource("facebook") + .setSourceChannelId("ps-id") + .build(); + final String conversationId = UUID.randomUUID().toString(); + + kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channel.getId(), channel)); + kafkaTestHelper.produceRecords(TestConversation.generateRecords(conversationId, channel, 1)); + + retryOnException( + () -> webTestHelper.post("/metadata.set", + "{\"conversation_id\":\"" + conversationId + "\", \"key\": \"awesome.key\", \"value\": \"awesome-value\"}", + "user-id") + .andExpect(status().isOk()), + "Error setting metadata" + ); + + retryOnException( + () -> webTestHelper.post("/metadata.remove", + "{\"conversation_id\":\"" + conversationId + "\", \"key\": \"awesome.key\"}", + "user-id") + .andExpect(status().isOk()), + "Error removing metadata" + ); + + retryOnException( + () -> webTestHelper.post("/metadata.remove", + "{\"conversation_id\":\"" + conversationId + "\", \"key\": \"non-existing\"}", + "user-id") + .andExpect(status().isOk()), + "Error removing metadata" + ); + } +} diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java index defb79a734..45842d0971 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java @@ -5,6 +5,7 @@ import co.airy.core.api.communication.payload.MessageUpsertPayload; import co.airy.core.api.communication.payload.UnreadCountPayload; import co.airy.core.api.communication.util.TestConversation; +import co.airy.spring.jwt.Jwt; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; @@ -12,7 +13,6 @@ import co.airy.kafka.test.KafkaTestHelper; import co.airy.kafka.test.junit.SharedKafkaTestResource; import co.airy.payload.response.ChannelPayload; -import co.airy.spring.auth.Jwt; import co.airy.spring.core.AirySpringBootApplication; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; @@ -20,7 +20,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/backend/avro/communication/metadata-action/src/main/java/co/airy/avro/communication/MetadataKeys.java b/backend/avro/communication/metadata-action/src/main/java/co/airy/avro/communication/MetadataKeys.java index ae0f3a3d5d..7bab6258da 100644 --- a/backend/avro/communication/metadata-action/src/main/java/co/airy/avro/communication/MetadataKeys.java +++ b/backend/avro/communication/metadata-action/src/main/java/co/airy/avro/communication/MetadataKeys.java @@ -4,12 +4,8 @@ * JSON dot notation keys for pre-defined metadata */ public class MetadataKeys { - - public static String SOURCE = "source"; - + public static String PUBLIC = "public"; public static class Source { - public static String CONTACT = "source.contact"; - public static class Contact { public static final String FIRST_NAME = "source.contact.first_name"; public static final String LAST_NAME = "source.contact.last_name"; diff --git a/backend/sources/chat-plugin/BUILD b/backend/sources/chat-plugin/BUILD index d6f863f133..1558a08e68 100644 --- a/backend/sources/chat-plugin/BUILD +++ b/backend/sources/chat-plugin/BUILD @@ -10,12 +10,12 @@ app_deps = [ "//:springboot_security", "//backend:channel", "//backend:message", - "//backend/lib/uuid", - "//backend/lib/payload", - "//backend/lib/mapping", - "//backend/lib/spring/web:spring-web", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/uuid", + "//lib/java/payload", + "//lib/java/mapping", + "//lib/java/spring/web:spring-web", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", "@maven//:javax_xml_bind_jaxb_api", ] @@ -34,7 +34,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java index 6f0ff0b46c..096c4f35ac 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java @@ -7,7 +7,6 @@ import co.airy.core.chat_plugin.config.Jwt; import co.airy.core.chat_plugin.payload.AuthenticationRequestPayload; import co.airy.core.chat_plugin.payload.AuthenticationResponsePayload; -import co.airy.core.chat_plugin.payload.MessageResponsePayload; import co.airy.core.chat_plugin.payload.SendMessageRequestPayload; import co.airy.payload.response.RequestErrorResponsePayload; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Mapper.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Mapper.java index e6e7fed099..637e7c80fc 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Mapper.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Mapper.java @@ -19,19 +19,10 @@ public class Mapper { public MessageResponsePayload fromMessage(Message message) { return MessageResponsePayload.builder() .content(contentMapper.renderWithDefaultAndLog(message)) - .alignment(getAlignment(message.getSenderType())) + .senderType(message.getSenderType().toString().toLowerCase()) .state(message.getDeliveryState().toString().toLowerCase()) .id(message.getId()) .sentAt(isoFromMillis(message.getSentAt())) .build(); } - - public static String getAlignment(SenderType senderType) { - switch (senderType) { - case APP_USER: - case SOURCE_USER: return "LEFT"; - case SOURCE_CONTACT: return "RIGHT"; - default: throw new RuntimeException("Unknown sender type " + senderType); - } - } } diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java index f1b72104ad..ec1679c39f 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -41,10 +42,11 @@ protected void configure(final HttpSecurity http) throws Exception { @Bean - CorsConfigurationSource corsConfigurationSource() { + CorsConfigurationSource corsConfigurationSource(final Environment environment) { + final String allowed = environment.getProperty("ALLOWED_ORIGINS", ""); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOrigin("*"); // TODO should come from env + config.addAllowedOrigin(allowed); config.addAllowedHeader("*"); config.setAllowedMethods(List.of("GET", "POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java index 01d4b372c9..6be8584d82 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java @@ -9,7 +9,6 @@ import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.stomp.StompCommand; @@ -23,9 +22,6 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; -import java.util.List; -import java.util.Map; - @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageResponsePayload.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageResponsePayload.java index 2823d9177d..a7287e8f72 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageResponsePayload.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageResponsePayload.java @@ -16,7 +16,7 @@ public class MessageResponsePayload implements Serializable { private String id; private Content content; private String state; - private String alignment; + private String senderType; private String sentAt; private String deliveryState; } diff --git a/backend/sources/facebook/events-router/BUILD b/backend/sources/facebook/events-router/BUILD index 666224466c..ad407f81f8 100644 --- a/backend/sources/facebook/events-router/BUILD +++ b/backend/sources/facebook/events-router/BUILD @@ -6,12 +6,12 @@ app_deps = [ "//backend:base_app", "//backend:channel", "//backend:message", - "//backend/lib/uuid", - "//backend/lib/payload", - "//backend/lib/log", - "//backend/lib/kafka/schema:source-facebook-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/uuid", + "//lib/java/payload", + "//lib/java/log", + "//lib/java/kafka/schema:source-facebook-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -29,7 +29,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/facebook/events-router/src/main/resources/application.properties b/backend/sources/facebook/events-router/src/main/resources/application.properties index 284b625615..ceafe31aa6 100644 --- a/backend/sources/facebook/events-router/src/main/resources/application.properties +++ b/backend/sources/facebook/events-router/src/main/resources/application.properties @@ -2,5 +2,4 @@ kafka.brokers=${KAFKA_BROKERS} kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.cleanup=${KAFKA_CLEANUP:false} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS:30000} - -facebook.app-id=${FB_APP_ID} +facebook.app-id=${FACEBOOK_APP_ID} diff --git a/backend/sources/facebook/sender/BUILD b/backend/sources/facebook/sender/BUILD index 6d5a9ac0ad..08ddd19d77 100644 --- a/backend/sources/facebook/sender/BUILD +++ b/backend/sources/facebook/sender/BUILD @@ -6,9 +6,9 @@ app_deps = [ "//backend:base_app", "//backend:channel", "//backend:message", - "//backend/lib/log", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/log", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -26,7 +26,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/facebook/sender/src/main/java/co/airy/core/sources/facebook/Sender.java b/backend/sources/facebook/sender/src/main/java/co/airy/core/sources/facebook/Sender.java index 36b62cde7b..9f39d30cc8 100644 --- a/backend/sources/facebook/sender/src/main/java/co/airy/core/sources/facebook/Sender.java +++ b/backend/sources/facebook/sender/src/main/java/co/airy/core/sources/facebook/Sender.java @@ -23,8 +23,6 @@ import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; -import java.time.Instant; - import static co.airy.avro.communication.MessageRepository.updateDeliveryState; @Component diff --git a/backend/sources/facebook/webhook/BUILD b/backend/sources/facebook/webhook/BUILD index d1717e287b..9b87d117f8 100644 --- a/backend/sources/facebook/webhook/BUILD +++ b/backend/sources/facebook/webhook/BUILD @@ -5,9 +5,9 @@ load("//tools/build:container_push.bzl", "container_push") app_deps = [ "//backend:base_app", "//:springboot_actuator", - "//backend/lib/kafka/schema:source-facebook-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/healthcheck", + "//lib/java/kafka/schema:source-facebook-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/healthcheck", ] springboot( @@ -25,7 +25,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/google/events-router/BUILD b/backend/sources/google/events-router/BUILD index fca7eb2bd8..04522d102e 100644 --- a/backend/sources/google/events-router/BUILD +++ b/backend/sources/google/events-router/BUILD @@ -6,11 +6,11 @@ app_deps = [ "//backend:base_app", "//backend:channel", "//backend:message", - "//backend/lib/uuid", - "//backend/lib/payload", - "//backend/lib/kafka/schema:source-google-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/uuid", + "//lib/java/payload", + "//lib/java/kafka/schema:source-google-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -28,7 +28,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java b/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java index dfd776526c..bf502e93a8 100644 --- a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java +++ b/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java @@ -1,10 +1,6 @@ package co.airy.core.sources.google; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java b/backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java index 8d6c3457fc..daecb03cc6 100644 --- a/backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java +++ b/backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java @@ -21,7 +21,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; diff --git a/backend/sources/google/sender/BUILD b/backend/sources/google/sender/BUILD index 2952a3b792..e81073a849 100644 --- a/backend/sources/google/sender/BUILD +++ b/backend/sources/google/sender/BUILD @@ -5,8 +5,8 @@ load("//tools/build:container_push.bzl", "container_push") app_deps = [ "//backend:base_app", "//backend:message", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", "@maven//:com_google_auth_google_auth_library_oauth2_http", ] @@ -25,7 +25,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/Sender.java b/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/Sender.java index ad356e04be..bdf2ef4082 100644 --- a/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/Sender.java +++ b/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/Sender.java @@ -20,8 +20,6 @@ import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; -import java.time.Instant; - import static co.airy.avro.communication.MessageRepository.updateDeliveryState; @Component diff --git a/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/services/Api.java b/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/services/Api.java index bdfe6bee13..7a2365397a 100644 --- a/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/services/Api.java +++ b/backend/sources/google/sender/src/main/java/co/airy/core/sources/google/services/Api.java @@ -3,11 +3,9 @@ import co.airy.core.sources.google.ApiException; import co.airy.core.sources.google.model.GoogleServiceAccount; import co.airy.core.sources.google.model.SendMessagePayload; -import co.airy.log.AiryLoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; -import org.slf4j.Logger; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.ApplicationListener; diff --git a/backend/sources/google/webhook/BUILD b/backend/sources/google/webhook/BUILD index bfcdc40054..ac88c3e14d 100644 --- a/backend/sources/google/webhook/BUILD +++ b/backend/sources/google/webhook/BUILD @@ -5,10 +5,10 @@ load("//tools/build:container_push.bzl", "container_push") app_deps = [ "//backend:base_app", "//:springboot_actuator", - "//backend/lib/payload", - "//backend/lib/kafka/schema:source-google-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/healthcheck", + "//lib/java/payload", + "//lib/java/kafka/schema:source-google-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/healthcheck", ] springboot( @@ -25,7 +25,7 @@ springboot( resources = glob(["src/test/resources/**/*"]), deps = [ ":app", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/twilio/events-router/BUILD b/backend/sources/twilio/events-router/BUILD index 481929e089..e3c6f1c1f7 100644 --- a/backend/sources/twilio/events-router/BUILD +++ b/backend/sources/twilio/events-router/BUILD @@ -6,12 +6,12 @@ app_deps = [ "//backend:base_app", "//backend:channel", "//backend:message", - "//backend/lib/uuid", - "//backend/lib/payload", - "//backend/lib/log", - "//backend/lib/kafka/schema:source-twilio-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/uuid", + "//lib/java/payload", + "//lib/java/log", + "//lib/java/kafka/schema:source-twilio-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -29,7 +29,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java b/backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java index 59392f9540..7bf7db3dc9 100644 --- a/backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java +++ b/backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java @@ -10,7 +10,6 @@ import co.airy.kafka.test.junit.SharedKafkaTestResource; import co.airy.spring.core.AirySpringBootApplication; import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; diff --git a/backend/sources/twilio/sender/BUILD b/backend/sources/twilio/sender/BUILD index 4987f8dd26..d9d1255d28 100644 --- a/backend/sources/twilio/sender/BUILD +++ b/backend/sources/twilio/sender/BUILD @@ -6,11 +6,11 @@ app_deps = [ "//backend:base_app", "//backend:channel", "//backend:message", - "//backend/lib/log", - "//backend/lib/mapping", + "//lib/java/log", + "//lib/java/mapping", "@maven//:com_twilio_sdk_twilio", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", ] springboot( @@ -28,7 +28,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/twilio/webhook/BUILD b/backend/sources/twilio/webhook/BUILD index a4428c7c1e..4ea4d399c2 100644 --- a/backend/sources/twilio/webhook/BUILD +++ b/backend/sources/twilio/webhook/BUILD @@ -6,9 +6,9 @@ app_deps = [ "//backend:base_app", "//:springboot_actuator", "@maven//:com_twilio_sdk_twilio", - "//backend/lib/kafka/schema:source-twilio-events", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/healthcheck", + "//lib/java/kafka/schema:source-twilio-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/healthcheck", ] springboot( @@ -27,7 +27,7 @@ springboot( ":app", "//backend:base_test", "@maven//:javax_xml_bind_jaxb_api", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/sources/twilio/webhook/src/main/java/co/airy/core/sources/twilio/TwilioWebhook.java b/backend/sources/twilio/webhook/src/main/java/co/airy/core/sources/twilio/TwilioWebhook.java index c3ca3d14e4..7d2f03bd2e 100644 --- a/backend/sources/twilio/webhook/src/main/java/co/airy/core/sources/twilio/TwilioWebhook.java +++ b/backend/sources/twilio/webhook/src/main/java/co/airy/core/sources/twilio/TwilioWebhook.java @@ -12,8 +12,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationListener; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; diff --git a/backend/webhook/consumer/pkg/scheduler/BUILD b/backend/webhook/consumer/pkg/scheduler/BUILD deleted file mode 100644 index 2ef17ec37c..0000000000 --- a/backend/webhook/consumer/pkg/scheduler/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "scheduler", - srcs = ["scheduler.go"], - importmap = "backend/webhook/consumer/pkg/scheduler", - importpath = "redis-worker/pkg/scheduler", - visibility = ["//visibility:public"], - deps = [ - "//backend/webhook/consumer/pkg/scheduler/consumer", - "//backend/webhook/consumer/pkg/scheduler/queue", - ], -) - -go_library( - name = "go_default_library", - srcs = ["scheduler.go"], - importpath = "scheduler/pkg/scheduler", - visibility = ["//visibility:public"], - deps = [ - "//backend/webhook/consumer/pkg/scheduler/consumer", - "//backend/webhook/consumer/pkg/scheduler/queue", - ], -) diff --git a/backend/webhook/publisher/BUILD b/backend/webhook/publisher/BUILD index 430244fb92..bfcd630b4f 100644 --- a/backend/webhook/publisher/BUILD +++ b/backend/webhook/publisher/BUILD @@ -6,11 +6,11 @@ app_deps = [ "//backend:base_app", "//backend:message", "//backend:webhook", - "//backend/lib/uuid", - "//backend/lib/payload", - "//backend/lib/mapping", - "//backend/lib/spring/kafka/core:spring-kafka-core", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", + "//lib/java/uuid", + "//lib/java/payload", + "//lib/java/mapping", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", "@maven//:io_lettuce_lettuce_core", "@maven//:org_springframework_data_spring_data_redis", ] @@ -30,7 +30,7 @@ springboot( deps = [ ":app", "//backend:base_test", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/kafka/test:kafka-test", ] + app_deps, ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java index 066a27039b..3519f57731 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java @@ -4,6 +4,8 @@ import co.airy.core.webhook.publisher.model.Postback; import co.airy.core.webhook.publisher.model.WebhookBody; import co.airy.mapping.ContentMapper; +import co.airy.mapping.model.Content; +import co.airy.mapping.model.Text; import org.springframework.stereotype.Component; import java.util.Map; @@ -19,16 +21,23 @@ public class Mapper { } public WebhookBody fromMessage(Message message) throws Exception { - contentMapper.render(message); + final Content content = contentMapper.renderWithDefaultAndLog(message); + + if (!(content instanceof Text)) { + throw new NotATextMessage(); + } return WebhookBody.builder() .conversationId(message.getConversationId()) .id(message.getId()) - .text(message.getContent()) + .text(((Text) content).getText()) .source(message.getSource()) .postback(buildPostback(message)) .sentAt(isoFromMillis(message.getSentAt())) - .sender(new WebhookBody.Sender(message.getSenderId())) + .sender(WebhookBody.Sender.builder() + .id(message.getSenderId()) + .type(message.getSenderType().toString().toLowerCase()) + .build()) .build(); } diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java new file mode 100644 index 0000000000..0cdddea4ec --- /dev/null +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java @@ -0,0 +1,4 @@ +package co.airy.core.webhook.publisher; + +public class NotATextMessage extends Exception { +} diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java index 57f155d964..60dcf0172d 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java @@ -46,7 +46,7 @@ private void startStream() { builder.stream(new ApplicationCommunicationMessages().name()) .filter(((messageId, message) -> DeliveryState.DELIVERED.equals(message.getDeliveryState()) && message.getUpdatedAt() == null)) - .peek((messageId, message) -> { + .foreach((messageId, message) -> { try { final ReadOnlyKeyValueStore webhookStore = streams.acquireLocalStore(webhooksStore); final Webhook webhook = webhookStore.get(allWebhooksKey); @@ -59,6 +59,7 @@ private void startStream() { .body(mapper.fromMessage(message)) .build()); } + } catch (NotATextMessage expected) { } catch (Exception e) { log.error("failed to publish webhook", e); } diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java index ee9152a846..85e123d4e7 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java @@ -21,8 +21,9 @@ public class WebhookBody implements Serializable { private Postback postback; @Data - @AllArgsConstructor + @Builder public static class Sender { - String id; + private String id; + private String type; } } diff --git a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java index df1d890c10..5d649eacc2 100644 --- a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java +++ b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java @@ -28,7 +28,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.sql.Time; import java.time.Instant; import java.util.ArrayList; import java.util.List; diff --git a/backend/webhook/consumer/BUILD b/backend/webhook/redis-worker/BUILD similarity index 63% rename from backend/webhook/consumer/BUILD rename to backend/webhook/redis-worker/BUILD index af8e9e09f1..20bea2b6d3 100644 --- a/backend/webhook/consumer/BUILD +++ b/backend/webhook/redis-worker/BUILD @@ -1,29 +1,29 @@ -# gazelle:prefix scheduler +# gazelle:prefix redis-worker load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@io_bazel_rules_docker//go:image.bzl", "go_image") load("//tools/build:container_push.bzl", "container_push") -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "scheduler", - visibility = ["//visibility:private"], - deps = ["//backend/webhook/consumer/pkg/scheduler"], -) - go_binary( - name = "scheduler_binary", - embed = [":go_default_library"], + name = "redis-worker_binary", + embed = [":redis-worker_lib"], visibility = ["//visibility:public"], ) go_image( name = "image", - embed = [":go_default_library"], + embed = [":redis-worker_lib"], ) container_push( registry = "ghcr.io/airyhq/webhook", repository = "consumer", ) + +go_library( + name = "redis-worker_lib", + srcs = ["main.go"], + importpath = "redis-worker", + visibility = ["//visibility:private"], + deps = ["//backend/webhook/redis-worker/pkg/scheduler"], +) diff --git a/backend/webhook/consumer/go.mod b/backend/webhook/redis-worker/go.mod similarity index 100% rename from backend/webhook/consumer/go.mod rename to backend/webhook/redis-worker/go.mod diff --git a/backend/webhook/redis-worker/go.sum b/backend/webhook/redis-worker/go.sum new file mode 100644 index 0000000000..fcea59a2bb --- /dev/null +++ b/backend/webhook/redis-worker/go.sum @@ -0,0 +1,67 @@ +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.13.3/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.2.2 h1:A1tQgdeVF23Ojc1TIRpVuVfOadUdIM0vFVURigoPEMM= +github.com/go-redis/redis/v8 v8.2.2/go.mod h1:ysgGY09J/QeDYbu3HikWEIPCwaeOkuNoTgKayTEaEOw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +go.opentelemetry.io/otel v0.11.0 h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E= +go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/webhook/consumer/main.go b/backend/webhook/redis-worker/main.go similarity index 100% rename from backend/webhook/consumer/main.go rename to backend/webhook/redis-worker/main.go diff --git a/backend/webhook/redis-worker/pkg/scheduler/BUILD b/backend/webhook/redis-worker/pkg/scheduler/BUILD new file mode 100644 index 0000000000..5970fb3651 --- /dev/null +++ b/backend/webhook/redis-worker/pkg/scheduler/BUILD @@ -0,0 +1,22 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "scheduler", + srcs = ["scheduler.go"], + importpath = "redis-worker/pkg/scheduler", + visibility = ["//visibility:public"], + deps = [ + "//backend/webhook/redis-worker/pkg/scheduler/consumer", + "//backend/webhook/redis-worker/pkg/scheduler/queue", + ], +) + +go_test( + name = "scheduler_test", + srcs = ["scheduler_test.go"], + embed = [":scheduler"], + deps = [ + "//backend/webhook/redis-worker/pkg/scheduler/queue", + "@com_github_alicebob_miniredis_v2//:miniredis", + ], +) diff --git a/backend/webhook/consumer/pkg/scheduler/consumer/BUILD b/backend/webhook/redis-worker/pkg/scheduler/consumer/BUILD similarity index 57% rename from backend/webhook/consumer/pkg/scheduler/consumer/BUILD rename to backend/webhook/redis-worker/pkg/scheduler/consumer/BUILD index fa533701f4..8537a5fe11 100644 --- a/backend/webhook/consumer/pkg/scheduler/consumer/BUILD +++ b/backend/webhook/redis-worker/pkg/scheduler/consumer/BUILD @@ -3,11 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "consumer", srcs = ["consumer.go"], - importmap = "backend/webhook/consumer/pkg/consumer", importpath = "redis-worker/pkg/scheduler/consumer", visibility = ["//visibility:public"], deps = [ - "//backend/webhook/consumer/pkg/scheduler/queue", - "@com_github_go_redis_redis_v8//:go_default_library", + "//backend/webhook/redis-worker/pkg/scheduler/queue", + "@com_github_go_redis_redis_v8//:redis", ], ) diff --git a/backend/webhook/consumer/pkg/scheduler/consumer/consumer.go b/backend/webhook/redis-worker/pkg/scheduler/consumer/consumer.go similarity index 96% rename from backend/webhook/consumer/pkg/scheduler/consumer/consumer.go rename to backend/webhook/redis-worker/pkg/scheduler/consumer/consumer.go index b0db3b42c2..767643464b 100644 --- a/backend/webhook/consumer/pkg/scheduler/consumer/consumer.go +++ b/backend/webhook/redis-worker/pkg/scheduler/consumer/consumer.go @@ -27,9 +27,9 @@ type Task struct { } type AiryMessage struct { - Endpoint string - Headers map[string]string - Body map[string]interface{} + Endpoint string + Headers map[string]string + Body map[string]interface{} } func StartConsumer(rdb *redis.Client, queue string) Task { diff --git a/backend/webhook/consumer/pkg/scheduler/queue/BUILD b/backend/webhook/redis-worker/pkg/scheduler/queue/BUILD similarity index 75% rename from backend/webhook/consumer/pkg/scheduler/queue/BUILD rename to backend/webhook/redis-worker/pkg/scheduler/queue/BUILD index e2540b6332..7423da6174 100644 --- a/backend/webhook/consumer/pkg/scheduler/queue/BUILD +++ b/backend/webhook/redis-worker/pkg/scheduler/queue/BUILD @@ -5,5 +5,5 @@ go_library( srcs = ["queue.go"], importpath = "redis-worker/pkg/scheduler/queue", visibility = ["//visibility:public"], - deps = ["@com_github_go_redis_redis_v8//:go_default_library"], + deps = ["@com_github_go_redis_redis_v8//:redis"], ) diff --git a/backend/webhook/consumer/pkg/scheduler/queue/queue.go b/backend/webhook/redis-worker/pkg/scheduler/queue/queue.go similarity index 100% rename from backend/webhook/consumer/pkg/scheduler/queue/queue.go rename to backend/webhook/redis-worker/pkg/scheduler/queue/queue.go diff --git a/backend/webhook/consumer/pkg/scheduler/scheduler.go b/backend/webhook/redis-worker/pkg/scheduler/scheduler.go similarity index 100% rename from backend/webhook/consumer/pkg/scheduler/scheduler.go rename to backend/webhook/redis-worker/pkg/scheduler/scheduler.go diff --git a/backend/webhook/consumer/pkg/scheduler/scheduler_test.go b/backend/webhook/redis-worker/pkg/scheduler/scheduler_test.go similarity index 100% rename from backend/webhook/consumer/pkg/scheduler/scheduler_test.go rename to backend/webhook/redis-worker/pkg/scheduler/scheduler_test.go diff --git a/backend/webhook/redis-worker/redis-worker b/backend/webhook/redis-worker/redis-worker new file mode 100755 index 0000000000..e3ce65a2df Binary files /dev/null and b/backend/webhook/redis-worker/redis-worker differ diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index b620f0ac4c..0000000000 --- a/checkstyle.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 2c3dc22104..26e434eb6e 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -1,3 +1,14 @@ -load("//tools/code-format:prettier.bzl", "check_pkg") +load("@com_github_airyhq_bazel_tools//code-format:prettier.bzl", "prettier") -check_pkg() +prettier( + srcs = glob([ + "**/*.md", + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + "**/*.scss", + "**/*.css", + ]), + config = "//:.prettierrc.json", +) diff --git a/docs/README.md b/docs/README.md index 111c5bed35..eef11491b2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,2 +1 @@ # docs.airy.co source code - diff --git a/docs/docs/api/http.md b/docs/docs/api/http.md index 154dca5bec..c66f39d541 100644 --- a/docs/docs/api/http.md +++ b/docs/docs/api/http.md @@ -1,36 +1,35 @@ --- -title: API +title: HTTP sidebar_label: HTTP --- -This documents aims to offer an high-level overview of the different parts that -compose our API. +This documents offers a high-level overview of the different parts that +compose the Airy API. ## Introduction -Our HTTP endpoints adhere to the following conventions: +The HTTP endpoints adhere to the following conventions: - Endpoints only accept `POST` JSON requests. -- Except for the `/users.login` and `/users.signup` endpoints, communication - always requires a valid [JWT token](#authorization). -- We use dots for name-spacing URLS (eg `/things.add`). +- Communication always requires a valid [JWT token](#authorization), except for + `/users.login` and `/users.signup` endpoints. +- We use dots for namespacing URLs (eg `/things.add`). ## Authentication -In order to communicate with our API endpoints, you need a valid -[JWT](https://jwt.io/) token. To get a valid token you need to use the login endpoint -[login](#login). - -The login endpoints returns a short-lived JWT token you can use for API requests +In order to communicate with the API endpoints, you need a valid +[JWT](https://jwt.io/) token. Get a valid token by sending a request to the +login endpoint [login](#login). It returns short-lived JWT token you can use for +API requests. ### Login -As the purpose of this endpoint is to obtain valid JWT tokens, this endpoint +As the purpose of this endpoint is to obtain valid JWT tokens, it does not require a valid token to be present in the headers. `POST /users.login` -**Sample Request** +**Sample request** ```json5 { @@ -39,7 +38,7 @@ does not require a valid token to be present in the headers. } ``` -**Sample Response** +**Sample response** ```json { @@ -52,8 +51,8 @@ does not require a valid token to be present in the headers. ## Endpoints -The way we group endpoints reflects the high level entities of the [Airy Core Data -Model](glossary.md). +The grouping of endpoints reflects the high-level entities of the [Airy Core +Data Model](glossary.md). ### Users @@ -64,7 +63,7 @@ information. `POST /users.signup` -**Sample Request** +**Sample request** ```json { @@ -75,9 +74,9 @@ information. } ``` -The password _MUST_ be at least 6 (six) characters long +The password **must** be at least 6 characters long. -**Sample Response** +**Sample response** ```json { @@ -90,30 +89,6 @@ The password _MUST_ be at least 6 (six) characters long This endpoint returns the same response as the login. -#### Reset password - -`POST /users.password-reset` - -This endpoint sets a new password given a valid reset token. Used or expired -tokens produce errors. - -**Sample Request** - -```json5 -{ - token: "a-valid-reset-token", - new_password: "i-hope-i-will-remember-this-one" -} -``` - -**Sample Response** - -```json5 -{} -``` - -The new password _MUST_ be at least 6 (six) characters long - ### Conversations Please refer to our [conversation](glossary.md#conversation) definition @@ -125,21 +100,35 @@ for more information. This is a [paginated](#pagination) endpoint. -**Sample Request** +**Filtering** -```json +This endpoint allows you to query conversations using the human readable [Lucene +Query Syntax](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html). You +can query on all fields defined in [this +class](https://github.com/airyhq/airy/blob/main/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java). + +**Sample request** + +Find all users with the last name "Lovelace": + +```json5 +{ + "filters": "display_name:*Lovelace", // optional + "cursor": "next-page-uuid", + "page_size": 2 +} +``` + +**Sample request** + +```json5 { - "filter": { - "conversation_ids": ["uuid"], - "channel_ids": ["channel-42"], - "display_names": ["Grace Hopper"] - }, "cursor": "next-page-uuid", "page_size": 2 } ``` -**Sample Response** +**Sample response** ```json5 { @@ -157,11 +146,9 @@ This is a [paginated](#pagination) endpoint. "avatar_url": "https://assets.airy.co/AirySupportIcon.jpg", "first_name": "Airy Support", "last_name": null, - "id": "36d07b7b-e242-4612-a82c-76832cfd1026", + "id": "36d07b7b-e242-4612-a82c-76832cfd1026" }, - "tags": [ - "f339c325-8614-43cb-a70a-e83d81bf56fc" - ], + "tags": ["f339c325-8614-43cb-a70a-e83d81bf56fc"], "last_message": { id: "{UUID}", content: { @@ -172,12 +159,12 @@ This is a [paginated](#pagination) endpoint. // typed source message model state: "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + sender_type: "{string/enum}", + // See glossary + sent_at: "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients }, - "unread_message_count": 1, + "unread_message_count": 1 } ], "response_metadata": { @@ -194,7 +181,7 @@ This is a [paginated](#pagination) endpoint. `POST /conversations.info` -**Sample Request** +**Sample request** ```json { @@ -202,7 +189,7 @@ This is a [paginated](#pagination) endpoint. } ``` -**Sample Response** +**Sample response** ```json5 { @@ -219,24 +206,22 @@ This is a [paginated](#pagination) endpoint. // optional "last_name": null, // optional - "id": "36d07b7b-e242-4612-a82c-76832cfd1026", + "id": "36d07b7b-e242-4612-a82c-76832cfd1026" }, - "tags": [ - "f339c325-8614-43cb-a70a-e83d81bf56fc" - ], + "tags": ["f339c325-8614-43cb-a70a-e83d81bf56fc"], "last_message": { - id: "{UUID}", - content: { - text: "{String}", - type: "text" + "id": "{UUID}", + "content": { + "text": "{String}", + "type": "text" // Determines the schema of the content }, // typed source message model - delivery_state: "{String}", + "delivery_state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients }, "unread_message_count": 1 @@ -247,9 +232,9 @@ This is a [paginated](#pagination) endpoint. `POST /conversations.read` -Resets the unread count of a conversation and returns `202 (Accepted)`. +Resets the unread count of a conversation and returns status code `202 (Accepted)`. -**Sample Request** +**Sample request** ```json { @@ -257,7 +242,7 @@ Resets the unread count of a conversation and returns `202 (Accepted)`. } ``` -**Sample Response** +**Sample response** ```json5 {} @@ -265,20 +250,20 @@ Resets the unread count of a conversation and returns `202 (Accepted)`. #### Tag a conversation -Tags an existing conversation with an existing tag. Returns 200 if successful. +Tags an existing conversation with an existing tag. Returns status code `200` if successful. `POST /conversations.tag` -**Sample Request** +**Sample request** ```json5 { "conversation_id": "CONVERSATION_ID", - "tag_id": "TAG_ID", + "tag_id": "TAG_ID" } ``` -**Sample Response** +**Sample response** ```json5 {} @@ -286,19 +271,18 @@ Tags an existing conversation with an existing tag. Returns 200 if successful. #### Untag a conversation - `POST /conversations.untag` -**Sample Request** +**Sample request** ```json5 { "conversation_id": "CONVERSATION_ID", - "tag_id": "TAG_ID", + "tag_id": "TAG_ID" } ``` -**Sample Response** +**Sample response** ```json5 {} @@ -313,36 +297,36 @@ information. `POST /messages.list` -This is a [paginated](#pagination) endpoint and messages are sorted from oldest to latest. +This is a [paginated](#pagination) endpoint. Messages are sorted from oldest to latest. -**Sample Request** +**Sample request** ```json5 { "conversation_id": "4242-4242-4242-424242", "cursor": "next-page-uuid", // optional - "page_size": 2 // optional + "page_size": 2 // optional } ``` -**Sample Response** +**Sample response** ```json5 { "data": [ { - id: "{UUID}", - content: { - text: "{String}", - type: "text" + "id": "{UUID}", + "content": { + "text": "{String}", + "type": "text" // Determines the schema of the content }, // typed source message model - state: "{String}", + "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } ], @@ -361,68 +345,67 @@ This is a [paginated](#pagination) endpoint and messages are sorted from oldest Sends a message to a conversation and returns a payload. -**Sample Request** +**Sample request** ```json5 { - conversation_id: "a688d36c-a85e-44af-bc02-4248c2c97622", - message: { - text: "{String}" + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "text": "{String}" } } ``` -**Sample Response** +**Sample response** ```json5 { - id: "{UUID}", - content: { - text: "{String}", - type: "text" + "id": "{UUID}", + "content": { + "text": "{String}", + "type": "text" // Determines the schema of the content }, // typed source message model - state: "{String}", + "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } ``` ### Channels -#### Connecting Channels +#### Connecting channels `POST /channels.connect` -A synchronous endpoint that makes a request to the source on behalf of the user +A synchronous endpoint that makes a request to the source to connect the channel. -This action is idempotent, so if the channel is already connected the status -will be `202`. +This action is idempotent, so if the channel is already connected, the request returns status code `202`. -Connecting a channel is source specific by nature, refer to the relevant documentation for the correct payload: +Connecting a channel is source-specific by nature, refer to the relevant documentation for the correct payload: -- [Facebook](../sources/facebook.md#connecting-a-channel) -- [Google](../sources/google.md#connecting-a-channel) -- [SMS - Twilio](../sources/sms-twilio.md#connecting-a-channel) -- [Whatsapp - Twilio](../sources/whatsapp-twilio.md#connecting-a-channel) +- [Facebook](/sources/facebook.md#connecting-a-channel) +- [Google](/sources/google.md#connecting-a-channel) +- [SMS - Twilio](/sources/sms-twilio.md#connecting-a-channel) +- [WhatsApp - Twilio](/sources/whatsapp-twilio.md#connecting-a-channel) #### Disconnecting Channels `POST /channels.disconnect` -A synchronous endpoint that makes a request to the source on behalf of the user +A synchronous endpoint that makes a request to the source to disconnect the channel. It marks the channel as disconnected and deletes the auth token. -This action is idempotent, so if the channel is disconnected the status will be `202`. -If the channel is unknown, the response status will be `400`. +This action is idempotent, so if the channel is disconnected, the request returns status code `202`. +If the channel is unknown, the request returns status code `400`. -**Sample Request** +**Sample request** ```json5 { @@ -430,14 +413,14 @@ If the channel is unknown, the response status will be `400`. } ``` -#### Explore Channels +#### Explore channels `POST /channels.explore` -A synchronous endpoint that makes a request to the source on behalf of the user -to list all the channels that are available. Some of those channels may already +A synchronous endpoint that makes a request to the source +to list all the available channels. Some of those channels may already be connected, which is accounted for in the boolean field `connected`. Due to -the nature of the request, response time may vary. +the nature of the request, the response time may vary. @@ -445,7 +428,7 @@ The request requires an authentication `token`, which has a different meaning fo - `facebook` The user access token -**Sample Request** +**Sample request** ```json5 { @@ -454,62 +437,61 @@ The request requires an authentication `token`, which has a different meaning fo } ``` -**Sample Response** +**Sample response** ```json5 { - "data": [ - { - "name": "my page 1", - "source": "facebook", - "source_channel_id": "fb-page-id-1", - "connected": false, - "image_url": "http://example.org/avatar.jpeg" // optional - }, - { - "name": "my page 2", - "source": "facebook", - "source_channel_id": "fb-page-id-2", - "connected": true - } - ] + "data": [ + { + "name": "my page 1", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "connected": false, + "image_url": "http://example.org/avatar.jpeg" // optional + }, + { + "name": "my page 2", + "source": "facebook", + "source_channel_id": "fb-page-id-2", + "connected": true + } + ] } ``` -#### List Channels +#### List channels `POST /channels.list` -**Sample Response** +**Sample response** ```json5 { - "data": [ - { - "id": "channel-uuid-1", - "name": "my page 1", - "source": "facebook", - "source_channel_id": "fb-page-id-1", - "image_url": "http://example.org/avatar.jpeg" // optional - }, - { - "id": "channel-uuid-2", - "name": "my page 2", - "source": "facebook", - "source_channel_id": "fb-page-id-2" - } - ] + "data": [ + { + "id": "channel-uuid-1", + "name": "my page 1", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "image_url": "http://example.org/avatar.jpeg" // optional + }, + { + "id": "channel-uuid-2", + "name": "my page 2", + "source": "facebook", + "source_channel_id": "fb-page-id-2" + } + ] } ``` - ### Tags #### Creating a tag `POST /tags.create` -Example body: +**Sample request** ```json5 { @@ -518,14 +500,13 @@ Example body: } ``` +If the tag is successfully created, the request returns status code `201` (created) with the tag ID in the response body. -If the tag is successfully created the endpoint will return `201` (created) with the tag id in the response body. - -Example response: +**Sample response** ```json5 { - id: "TAG-UUID" + "id": "TAG-UUID" } ``` @@ -533,6 +514,8 @@ Example response: `POST /tags.update` +**Sample request** + ```json { "id": "TAG-ID", @@ -541,19 +524,20 @@ Example response: } ``` -If action is successful, returns HTTP status `200`. +If action is successful, the request returns status code `200`. -Example response: +**Sample response** ```json5 {} ``` - #### Deleting a tag `POST /tags.delete` +**Sample request** + ```json { "id": "ID-OF-THE-TAG" @@ -562,7 +546,7 @@ Example response: If action is successful, returns HTTP status `200`. -Example response: +**Sample response** ```json5 {} @@ -572,26 +556,25 @@ Example response: `POST /tags.list` -Example response: +**Sample response** ```json5 { - tags: [ + "tags": [ { - id: "TAG-ID", - name: "name of the tag", - color: "RED" + "id": "TAG-ID", + "name": "name of the tag", + "color": "RED" } ] } - ``` ## Pagination -By default, paginated endpoints return at max 20 elements of the first page. +By default, paginated endpoints return a maximum of 20 elements on the first page. -The size of the returned page can be controller via the `page_size` field of the +The size of the returned page can be controlled by the `page_size` field of the body. You can move back and forth between pages using the `cursor` field of the body. @@ -626,12 +609,12 @@ The response comes in two parts: - `previous_cursor` - The id of first elements in the previous page of data. Empty if the returned + The ID of first elements in the previous page of data. Empty if the returned page is the first one. - `next_cursor` - The id of first elements in the next page of data. Empty if the returned + The ID of first elements in the next page of data. Empty if the returned page is the last one. - `filtered_total` @@ -643,3 +626,35 @@ The response comes in two parts: - `total` The total number of elements across all pages. + +### Metadata + +Refer to our [metadata](glossary.md#metadata) definition for more +information. + +### Setting metadata + +`POST /metadata.set` + +```json +{ + "conversation_id": "conversation-id", + "key": "ad.id", + "value": "Grace" +} +``` + +The endpoint returns status code `200` if the operation was successful, and `400` if not. + +### Removing metadata + +`POST /metadata.remove` + +```json +{ + "conversation_id": "conversation-id", + "key": "ad.id" +} +``` + +This endpoint returns status code `200` if the operation was successful, and `500` if not. diff --git a/docs/docs/api/webhook.md b/docs/docs/api/webhook.md index 0c67741cf0..c986850f32 100644 --- a/docs/docs/api/webhook.md +++ b/docs/docs/api/webhook.md @@ -7,12 +7,12 @@ The webhook integration enables you to programmatically participate in conversations by sending messages or reacting to them. Here's a common integration pattern: -- Call the [subscribe](#subscribing) endpoint +- Call the [subscribe](#subscribing) endpoint - Consume on your URL of choice [events](#event-payload) - React to those events by calling the [send message](api/http.md#send-a-message) endpoint -You must de-duplicate messages on arrival as the webhook *does not* guarantee +You must de-duplicate messages on arrival as the webhook _does not_ guarantee events uniqueness. ## Subscribing @@ -21,7 +21,7 @@ events uniqueness. Subscribes the webhook for the first time or update its parameters. -**Sample Request** +**Sample request** ```json5 { @@ -32,11 +32,10 @@ Subscribes the webhook for the first time or update its parameters. } ``` -**Sample Response** +**Sample response** ```json5 { - "url": "https://my-url-to-be-hit", "headers": { "X-Custom-Header": "custom-code-for-header" @@ -50,11 +49,10 @@ Subscribes the webhook for the first time or update its parameters. `POST /webhooks.unsubscribe` -**Sample Response** +**Sample response** ```json5 { - "url": "https://my-url-to-be-hit", "headers": { "X-Custom-Header": "custom-code-for-header" @@ -68,7 +66,7 @@ Subscribes the webhook for the first time or update its parameters. `POST /webhooks.info` -**Sample Response** +**Sample response** ```json5 { @@ -82,19 +80,23 @@ Subscribes the webhook for the first time or update its parameters. ## Event Payload -After [subscribing](#subscribing-to-a-webhook) to an Airy webhook, you will start receiving events on your -URL of choice. The event will *always* be a POST request with the following -structure: +After [subscribing](#subscribing-to-a-webhook) to an Airy webhook, you will +start receiving events on your URL of choice. The event will _always_ be a POST +request with the following structure: ```json5 { "conversation_id": "4242424242", "id": "7560bf66-d9c4-48f8-b7f1-27ab6c40a40a", "sender": { - "id": "adac9220-fe7b-40a8-98e5-2fcfaf4a53b5" + "id": "adac9220-fe7b-40a8-98e5-2fcfaf4a53b5", + "type": "source_contact" }, "source": "FACEBOOK", "sent_at": "2020-07-20T14:18:08.584Z", "text": "Message to be sent" } ``` + +For possible values of `sender.type` see the [Message model +documentation](glossary.md#fields) diff --git a/docs/docs/api/websocket.md b/docs/docs/api/websocket.md index 34b7c52d95..1c16f48a73 100644 --- a/docs/docs/api/websocket.md +++ b/docs/docs/api/websocket.md @@ -1,6 +1,6 @@ --- -title: WebSocket -sidebar_label: Websocket +title: WebSocket +sidebar_label: WebSocket --- ## Introduction @@ -22,25 +22,25 @@ deliver JSON encoded payloads. Incoming payloads notify connected clients that a message was created or updated. -**Sample Payload** +**Sample payload** ```json5 { - conversation_id: "{UUID}", - channelId: "{UUID}", - message: { - id: "{UUID}", - content: { - text: "{String}", - type: "text" + "conversation_id": "{UUID}", + "channel_id": "{UUID}", + "message": { + "id": "{UUID}", + "content": { + "text": "{String}", + "type": "text" // Determines the schema of the content }, // typed source message model - state: "{String}", + "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } } @@ -55,7 +55,7 @@ specific conversation at the time of delivery. Clients should keep track of the latest time the unread count for a specific conversation was updated and update the value only for a more recent count. -**Sample Payload** +**Sample payload** ```json5 { @@ -74,19 +74,19 @@ the value only for a more recent count. Incoming payloads notify connected clients whenever a channel was connected or updated. -**Sample Payload** +**Sample payload** ```json5 { - "id": "{UUID}", - "name": "my page 1", - "source": "facebook", - "source_channel_id": "fb-page-id-1", - "image_url": "http://example.org/avatar.jpeg" // optional + "id": "{UUID}", + "name": "my page 1", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "image_url": "http://example.org/avatar.jpeg" // optional } ``` ------- +--- ### Channel disconnected @@ -94,13 +94,14 @@ Incoming payloads notify connected clients whenever a channel was disconnected. `/queue/channel/disconnected` -**Sample Payload** +**Sample payload** ```json5 { - "id": "{UUID}", - "name": "my page 1", - "source": "facebook", - "source_channel_id": "fb-page-id-1", - "image_url": "http://example.org/avatar.jpeg" // optional + "id": "{UUID}", + "name": "my page 1", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "image_url": "http://example.org/avatar.jpeg" // optional } +``` diff --git a/docs/docs/glossary.md b/docs/docs/glossary.md index 4343f2b390..f40643dba0 100644 --- a/docs/docs/glossary.md +++ b/docs/docs/glossary.md @@ -8,34 +8,20 @@ This document aims to provide an high-level overview of the Airy Core Platform technical vocabulary. It provides definition of the most important terms used both in the code and in the rest of the documentation. -**Please note this document is constantly being worked on.** - -## Introduction - -Our Avro schemas provide a machine readable up-to-date version of our backend -data model. If you are looking for details like null constraints and such, the -Avro schemas folder is the right place. Furthermore, it is worth underlining -that the Avro data model and glossary do not correspond exactly. The former is -the exact machine representation of the data we store and the latter is a -conceptual artifact we created to discuss and solve problems of a typical -messaging system. +Our [Avro schemas](https://github.com/airyhq/airy/tree/main/backend/avro) +provide a machine readable up-to-date version of our backend data model. If you +are looking for details like null constraints and such, the Avro schemas folder +is the right place. Furthermore, it is worth underlining that the Avro data +model and glossary do not correspond exactly. The former is the exact machine +representation of the data we store and the latter is a conceptual artifact we +created to discuss and solve problems. The Airy Core Platform allows its [users](#user) to process messaging data from -a variety of [sources](#source), which are integrated via [source providers](#source-provider). -Users connect to sources via [channels](#channel). Once the channel is connected, -the Airy Core Platform ingests source data and transforms them into [conversations](#conversation), -[contacts](#contact), and [messages](#message). - -## Source - -A source represents a system that generates messaging data that a user wants to -process with the Airy Core Platform. - -## Source provider - -Source providers are API platforms that allow the Airy Core Platform to connect to -one or more of their sources typically via a webhook. E.g. Twilio is a source provider -for the Twilio SMS and Whatsapp sources. +a variety of [sources](#source), which are integrated via [source +providers](#provider). Users] connect sources via [channels](#channel). +Once the channel is connected, the Airy Core Platform ingests source data and +transforms them into [conversations](#conversation), [contacts](#contact), and +[messages](#message). ## Channel @@ -45,7 +31,7 @@ Platform. ## Contact A contact represents the [source](#source) participant. A -[conversation](#conversation) exists *only* if it has *at least one* message +[conversation](#conversation) exists _only_ if it has _at least one_ message from a contact. ## Conversation @@ -60,30 +46,28 @@ A message wraps the data that is being transferred from and to the dependent and it can be plain text, rich media like videos or sound, images, or templates. -### Fields - - `id` uuid Unique message id for deduplication. - `headers` string map - Optional headers: + Optional headers: - - `postback.payload` string postback payloads used for source automations - - `postback.referral` string facebook specific referral identifier + - `postback.payload` string postback payloads used for source automations + - `postback.referral` string facebook specific referral identifier - `senderType` string What type of actor inserted the message. One of: - - `SOURCE_CONTACT` sent to the source by a contact - - `SOURCE_USER` sent to the source by the user but not via app - - `APP_USER` sent to source via app + - `source_contact` sent to the source by a contact + - `source_user` sent to the source by the user but not via app + - `app_user` sent to source via app - `senderId` string -Identifies the participant that sent the message. Interpretation is based on the value of `senderType` like so: +Identifies the participant that sent the message. Interpretation is based on the value of `senderType`: | senderType | senderId | | -------------- | --------------------------------------------------- | @@ -91,12 +75,11 @@ Identifies the participant that sent the message. Interpretation is based on the | SOURCE_USER | source dependent (e.g. Facebook third party app id) | | APP_USER | app channel id | - - `conversationId` uuid - `channelId` uuid -- `content` string Immutable string version of the ingested content. APIs dynamically parse and map it to a schema using the mapping library. +- `content` string Immutable string version of the ingested content. APIs dynamically parse and map it to a schema using the mapping library. - `offset` long sequence number of message within a conversation @@ -104,24 +87,45 @@ Identifies the participant that sent the message. Interpretation is based on the - `deliveryState` string - One of: + One of: - - `PENDING` message to be sent out - - `DELIVERED` message has been sent to source - - `FAILED` message sending has terminally failed + - `pending` message to be sent out + - `delivered` message has been sent to source + - `failed` message sending has terminally failed - `sentAt` timestamp - `updatedAt` timestamp null for messages that are inserted first time - -#### Headers +### Headers Header data contains information that is important for downstream processing. It also includes the message preview and tags that are useful for certain apps like automations. +## Metadata + +Metadata is data attached to a conversation consisting of a set of Key/Value +pairs. A key can use the dot notation to represent namespaces. + +e.g. + +| Key | Value | +| -------------------------- | ------- | +| "sender.id" | "123A" | +| "sender.contact.first_name | "Grace" | + +## Source + +A source represents a system that generates messaging data that a user wants to +process with the Airy Core Platform. + +### Provider + +Source providers are API platforms that allow the Airy Core Platform to connect to +one or more of their sources typically via a webhook. E.g. Twilio is a source provider +for the Twilio SMS and Whatsapp sources. -# User +## User A user represents one authorized agent in the Airy Core Platform. diff --git a/docs/docs/guides/airy-core-and-rasa.md b/docs/docs/guides/airy-core-and-rasa.md new file mode 100644 index 0000000000..2bb8d82fce --- /dev/null +++ b/docs/docs/guides/airy-core-and-rasa.md @@ -0,0 +1,101 @@ +--- +title: How to connect the Airy Core Platform and Rasa +sidebar_label: Connecting Rasa +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +:::tip What you will learn + +- How to forward Airy Core Platform messages to Rasa +- How to configure Rasa to receive and reply to messages using Airy + +::: + +> Rasa is an open source machine learning framework for automated text and voice-based conversations. +> Understand messages, hold conversations, and connect to messaging channels and APIs. +> +> \- From the [Rasa documentation](https://rasa.com/docs/rasa/) + +Out of the box Rasa offers you a standard set of messaging channels to connect +to. However, you can only connect to one Facebook page for instance at a time. +This is perfectly fine for simple use cases, but as your platform grows and you +want to scale your bot interactions across many channels you will need a +dedicated solution for storing and routing messages. + +This is where the Airy Core Platform can provide great scale benefits: You can +connect a wide array of messaging channels and service them in a single inbox. +For Rasa, you can think of it as a forward messaging router that will persist +your data and make it available for export to anywhere within your organization. + +This guide covers how to configure your Rasa installation so that it can use the +Airy Core Platform to send and receive messages. + +:::note Prerequisites + +- A running Airy Core Platform installation [Get Started](index.md#bootstrapping-the-airy-core-platform) +- A local Rasa setup: For convenience, we recommend [the Docker setup](https://rasa.com/docs/rasa/docker/building-in-docker/) or [a demo repository](https://github.com/airyhq/rasa-demo) we created for this guide + +::: + +## Configuring Airy + +The Airy Core Platform can forward messages from your sources (Messenger, +WhatsApp etc.) to downstream messaging frameworks like Rasa, which can in turn +reply using the Airy Core Platform API. + +To do this we follow the [webhook documentation](api/webhook.md) to forward in- +and outbound messages to Rasa. When doing so set the `url` parameter so that it +points to your Rasa installation. If it is running locally you can obtain a +public url by using [ngrok](https://ngrok.com/). Run: + +```shell script +ngrok http 5005 +``` + +to get an ngrok URL that points to your local Rasa installation at +`localhost:5005`. With the public ngrok URL your webhook subscription payload +should look like so: + +```json +{ + "url": "https://e3effaceb9c5.ngrok.io/webhooks/airy/webhook" +} +``` + +Once you have done this, the Airy Core Platform will start sending messages to +the url you specified. + +successful webhook connection log + +## Configuring Rasa + +We will be implementing a [custom +connector](https://rasa.com/docs/rasa/connectors/custom-connectors/) as we want +to make use of the Airy Core Platform API. + +The first step is to create a new directory `channels/` in our Rasa project and +copy this [connector +file](https://github.com/airyhq/rasa-demo/blob/master/channels/airy.py) into it. +The connector requires the following configuration values: + +- `auth_token` the Airy Core Platform JWT token you used + to connect the webhook. +- `api_host` The url where you host your Airy Core Platform API (`http://api.airy` for a local installation). + +Add them to your existing Rasa `credentials.yml`: + +```yaml +channels.airy.AiryInput: + api_host: "your JWT authentication token" + auth_token: "http://api.airy" +``` + +Now you should have a working integration 🎉. + +To test the connection write a message to one of your channels: The Airy Core +Platform will forward it to your Rasa installation, which will respond using the +Airy Core Platform API. This is what you will see in the logs of the demo +repository: + +send message successful connection log diff --git a/docs/docs/guides/airy-core-in-production.md b/docs/docs/guides/airy-core-in-production.md index 16f6e5d94c..9cf50e38fb 100644 --- a/docs/docs/guides/airy-core-in-production.md +++ b/docs/docs/guides/airy-core-in-production.md @@ -3,97 +3,155 @@ title: Running the Airy Core Platform in production sidebar_label: Production --- -This document provides our recommendations on how to run the Airy Core Platform in production environments. If you are not familiar with the architecture of the system, we suggest you read the [Architecture](architecture.md) document before proceeding. - +This document provides our recommendations on how to run the Airy Core Platform +in production environments. If you are not familiar with the architecture of the +system, we suggest you read the [Architecture](/overview/architecture.md) document before +proceeding. ## Requirements -The `Airy apps` are the services which comprise the `Airy Core Platform`. They run as docker containers and require access to several other services to be in place before they can be started: -- `Kafka cluster`: Kafka, Zookeeper and the Confluent Schema registry. These three services comprise the Kafka store. They are the default storage system of the Airy Core Platform. Kafka requires Zookeeper to work. The Confluent Schema registry facilitates Avro typed data pipelines. All our Kafka based applications require the registry to work. +The `Airy apps` are the services which comprise the `Airy Core Platform`. They +run as docker containers and require access to several other services to be in +place before they can be started: + +- `Kafka cluster`: Kafka, Zookeeper and the Confluent Schema registry. These + three services comprise the Kafka store. They are the default storage system + of the Airy Core Platform. Kafka requires Zookeeper to work. The Confluent + Schema registry facilitates Avro typed data pipelines. All our Kafka based + applications require the registry to work. - `PostgreSQL`: Where we store authentication data. - `Redis`: The queuing system used by our webhook system relies on Redis. ### Kafka cluster -The Kafka store requires multiple Kafka brokers, Zookeepers, and Confluent Schema registry servers. We recommend using at least five Kafka brokers and set the replication factor of _all_ topics to `3`. So that even if two brokers would become unavailable at the same time, the system would still function. +The Kafka store requires multiple Kafka brokers, Zookeepers, and Confluent +Schema registry servers. We recommend using at least five Kafka brokers and set +the replication factor of _all_ topics to `3`. So that even if two brokers +would become unavailable at the same time, the system would still function. We also recommend running at least three Zookeepers and two instances of the Confluent Schema registry. -If you decide to run the Kafka cluster on Kubernetes, we recommend running Kafka and Zookeeper as StatefulSet workloads, for persistent storage. The Confluent Schema registry can be run as a Deployment, as it is stateless. +If you decide to run the Kafka cluster on Kubernetes, we recommend running Kafka +and Zookeeper as StatefulSet workloads, for persistent storage. The Confluent +Schema registry can be run as a Deployment, as it is stateless. Apart from running your own Kafka cluster, a managed version can also be used. The Kafka cluster is usually started in the following order: -- Start the `ZooKeeper` nodes. The ZooKeeper nodes need to have the ports `2888` and `3888` open between them and the port `2181` open to the Kafka servers. Note down the hostnames of the ZooKeeper servers because they are required for the configuration of the Kafka brokers. -- Start and connect the `Kafka` nodes. The Kafka nodes need the configuration how to connect to the zookeeper nodes, through the configuration variable `zookeeper.connect`, specified in the `/etc/kafka/server.properties` file. -- Once Kafka and ZooKeeper are up and running, the confluent Schema registry can be started. It requires Kafka brokers hostnames, usually through the parameter `kafkastore.bootstrap.server` of the configuration file `/etc/schema-registry/schema-registry.properties`. +- Start the `ZooKeeper` nodes. The ZooKeeper nodes need to have the ports `2888` + and `3888` open between them and the port `2181` open to the Kafka servers. + Note down the hostnames of the ZooKeeper servers because they are required for + the configuration of the Kafka brokers. +- Start and connect the `Kafka` nodes. The Kafka nodes need the configuration + how to connect to the zookeeper nodes, through the configuration variable + `zookeeper.connect`, specified in the `/etc/kafka/server.properties` file. +- Once Kafka and ZooKeeper are up and running, the confluent Schema registry can + be started. It requires Kafka brokers hostnames, usually through the + parameter `kafkastore.bootstrap.server` of the configuration file + `/etc/schema-registry/schema-registry.properties`. The location of the configuration files can vary depending on the particular installation. -Once the Kafka cluster is up and running, the required topics must be created. You can find them in the Bash script `infrastructure/scripts/provision/create-topics.sh`. The script requires the following environment variables to run: +Once the Kafka cluster is up and running, the required topics must be created. +You can find them in the Bash script +`infrastructure/scripts/provision/create-topics.sh`. The script requires the +following environment variables to run: + - `ZOOKEEPER` (default: airy-cp-zookeeper:2181) - `PARTITIONS` (default: 10) - `REPLICAS` (default: 1) -We do not recommend running Kafka on docker for production environments. However, we provide a way to deploy the whole Kafka cluster on top of Kubernetes with Helm as we use this approach for test installations. +We do not recommend running Kafka on docker for production environments. +However, we provide a way to deploy the whole Kafka cluster on top of Kubernetes +with Helm as we use this approach for test installations. To deploy Kafka on Kubernetes with Helm, you can run: + ```sh helm install airy infrastructure/helm-chart/charts/kafka/ ``` -By default, the `Confluent Schema registry` deployment is created with `replicas=0`. We want to ensure ZooKeeper and Kafka are running properly before we start the service so we can avoid errors. +By default, the `Confluent Schema registry` deployment is created with +`replicas=0`. We want to ensure ZooKeeper and Kafka are running properly before +we start the service so we can avoid errors. -To customize the deployment, you can edit the configuration files in `infrastructure/helm-chart/charts/kafka/charts`. +To customize the deployment, you can edit the configuration files in +`infrastructure/helm-chart/charts/kafka/charts`. ### PostgreSQL -A cluster of PostgreSQL servers can be used or a managed one such as AWS RDS. -If deployed on Kubernetes, consider running the app on a StatefulSet workload, for persistent storage. +A cluster of PostgreSQL servers can be used or a managed one such as AWS RDS. If +deployed on Kubernetes, consider running the app on a StatefulSet workload, for +persistent storage. + +After the server has been provisioned, a database must be created, along with +username and password which have full privileges on the database. Note down +these parameters as they are required to start the `Airy apps`. -After the server has been provisioned, a database must be created, along with username and password which have full privileges on the database. Note down these parameters as they are required to start the `Airy apps`. +We provide a Helm chart to deploy a PostgreSQL server Kubernetes. Set the +`pgPassword` variable in `infrastructure/helm-chart/charts/postgres/values.yaml` +file and run: -We provide a Helm chart to deploy a PostgreSQL server Kubernetes. Set the `pgPassword` variable in `infrastructure/helm-chart/charts/postgres/values.yaml` file and run: ```sh helm install postgres infrastructure/helm-chart/charts/postgres/ ``` ### Redis -A cluster of redis servers can be used or a managed one such as AWS ElastiCache for Redis. -If deployed on Kubernetes, consider running the app on a StatefulSet workload, for persistent storage. +A cluster of redis servers can be used or a managed one such as AWS ElastiCache +for Redis. If deployed on Kubernetes, consider running the app on a StatefulSet +workload, for persistent storage. We provided a Helm chart for a Redis cluster as well. You can run: + ```sh helm install redis infrastructure/helm-chart/charts/redis/ ``` ### Kubernetes -The `Airy apps` run on top of Kubernetes which is our preferred runtime. We recommend Kubernetes for more robust and reliable deployment in production environments. However, the `Airy apps` do not depend on Kubernetes in any way. +The `Airy apps` run on top of Kubernetes which is our preferred runtime. We +recommend Kubernetes for more robust and reliable deployment in production +environments. However, the `Airy apps` do not depend on Kubernetes in any way. + +If you prefer to deploy the `Airy apps` on a Docker runtime, you can refer to +our the Kubernetes manifests, where all the Docker images and configuration +parameters are specified. In order to generate the Kubernetes manifests, run: -If you prefer to deploy the `Airy apps` on a Docker runtime, you can refer to our the Kubernetes manifests, where all the Docker images and configuration parameters are specified. In order to generate the Kubernetes manifests, run: ```sh helm template ./infrastructure/helm-chart ``` ## Running the Airy apps -So far the Airy Core Platform has been tested on K3s, Minikube and AWS EKS. The following configuration and deployment instructions are applicable to any Kubernetes implementation as they depend on widely supported Kubernetes features. In order to proceed with deploying the apps, we assume that you have a running Kubernetes cluster, properly configured KUBECONF file and properly set context. +So far the Airy Core Platform has been tested on K3s, Minikube and AWS EKS. The +following configuration and deployment instructions are applicable to any +Kubernetes implementation as they depend on widely supported Kubernetes +features. In order to proceed with deploying the apps, we assume that you have a +running Kubernetes cluster, properly configured KUBECONF file and properly set +context. ### Configuration -After the [required services](#requirements) are deployed, you're ready to start the `Airy apps` inside a Kubernetes cluster. Connecting the `Airy apps` to the Kafka cluster, PostgreSQL and Redis can be done by creating a configuration file, prior to deploying the apps. Make sure that the `Airy apps` also have network connectivity to the required services. +After the [required services](#requirements) are deployed, you're ready to start +the `Airy apps` inside a Kubernetes cluster. Connecting the `Airy apps` to the +Kafka cluster, PostgreSQL and Redis can be done by creating a configuration +file, prior to deploying the apps. Make sure that the `Airy apps` also have +network connectivity to the required services. -The file `infrastructure/airy.conf.all` contains an example of all possible configuration parameters. This file should be copied to `airy.conf` and edited according to your environment: +The file `infrastructure/airy.conf.all` contains an example of all possible +configuration parameters. This file should be copied to `airy.conf` and edited +according to your environment: ```sh cd infrastructure cp airy.conf.all airy.conf ``` -Edit the file to configure connections to the base services. Make sure that the following sections are configured correctly, so that the `Airy apps` to start properly: +Edit the file to configure connections to the base services. Make sure that the +following sections are configured correctly, so that the `Airy apps` to start +properly: ``` apps: @@ -107,7 +165,8 @@ apps: ### Deployment -We provided a Helm chart to deploy the `Airy apps`. Before you can run helm, you must configure the system via the `airy.conf` file, then you can proceed: +We provided a Helm chart to deploy the `Airy apps`. Before you can run helm, you +must configure the system via the `airy.conf` file, then you can proceed: ```sh cp airy.conf ./helm-chart/charts/apps/values.yaml @@ -115,6 +174,7 @@ helm install airy-apps ./helm-chart/charts/apps/ --timeout 1000s ``` By default, the `Airy apps` deployments start with `replicas=0` so to scale them up, run: + ```sh kubectl scale deployment -l type=api --replicas=1 kubectl scale deployment -l type=frontend --replicas=1 @@ -127,20 +187,24 @@ kubectl scale deployment -l type=sources-twilio --replicas=1 At this point you should have a running `Airy Core Platform` in your environment 🎉. -To deploy with a different `image tag` (for example `beta` from the `develop` branch), you can run: +To deploy with a different `image tag` (for example `beta` from the `develop` +branch), you can run: + ```sh export AIRY_VERSION=beta helm install airy-apps ./helm-chart/charts/apps/ --set global.appImageTag=${AIRY_VERSION} --timeout 1000s ``` -If afterwards you need to modify or add other config parameters in the `airy.conf` file, after editing the file run: +If afterwards you need to modify or add other config parameters in the +`airy.conf` file, after editing the file run: ```sh cp airy.conf ./helm-chart/charts/apps/values.yaml helm upgrade airy-apps ./helm-chart/charts/apps/ --timeout 1000s ``` -If you deploy the Airy Core Platform with a specific version tag, you must export the `AIRY_VERSION` variable before running `helm upgrade`: +If you deploy the Airy Core Platform with a specific version tag, you must +export the `AIRY_VERSION` variable before running `helm upgrade`: ```sh cp airy.conf ./helm-chart/charts/apps/values.yaml @@ -150,9 +214,12 @@ helm upgrade airy-apps ./helm-chart/charts/apps/ --set global.appImageTag=${AIRY ## Network -### Sources +### Connect Sources -The helm chart creates separate NodePort service for every source with the naming convention `sources-SOURCE_NAME-webhook`. These services must be exposed publicly on the Internet, so that the sources can send messages and events to the webhook services, inside the Kubernetes cluster. +The helm chart creates separate NodePort service for every source with the +naming convention `sources-SOURCE_NAME-webhook`. These services must be exposed +publicly on the Internet, so that the sources can send messages and events to +the webhook services, inside the Kubernetes cluster. To get the ports on which the webhook services are running, you can run: @@ -160,19 +227,30 @@ To get the ports on which the webhook services are running, you can run: kubectl get service -l airy=sources.webhook --output jsonpath={.items[*].spec.ports[*].nodePort} ``` -As these ports are randomly assigned by Kubernetes, they need to be exposed publicly through a `Load balancer`. If you are running in a cloud environment, you can convert the services from type `NodePort` to `Loadbalancer`, for which cloud providers usually create a dedicated Load balancer. +As these ports are randomly assigned by Kubernetes, they need to be exposed +publicly through a `Load balancer`. If you are running in a cloud environment, +you can convert the services from type `NodePort` to `Loadbalancer`, for which +cloud providers usually create a dedicated Load balancer. -To see the hostname for the loadbalancer, you can run +To see the hostname for the load balancer, you can run ```sh kubectl get service -l airy=sources.webhook ``` -The public endpoint will be in the `EXTERNAL-IP` column. +The public endpoint will be in the `EXTERNAL-IP` column. ### Services -Inside the Kubernetes cluster, the helm chart installs the API services (with prefix `api-`) and the frontend services (with prefix `frontend-`). To access these services outside the Kubernetes cluster they need to be exposed with Ingress resources. You can choose an [Kubernetes ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) in accordance to your needs or preferences. If you are using the [Traefik](https://traefik.io/) ingress controller, you can edit the `infrastructure/network/ingress.yaml` file to modify the `host` records and directly apply it to your Kubernetes cluster. +Inside the Kubernetes cluster, the helm chart installs the API services (with +prefix `api-`) and the frontend services (with prefix `frontend-`). To access +these services outside the Kubernetes cluster they need to be exposed with +Ingress resources. You can choose an [Kubernetes ingress +controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) +in accordance to your needs or preferences. If you are using the +[Traefik](https://traefik.io/) ingress controller, you can edit the +`infrastructure/network/ingress.yaml` file to modify the `host` records and +directly apply it to your Kubernetes cluster. ```sh kubectl apply -f infrastructure/network/ingress.yaml @@ -184,9 +262,13 @@ You must set different `host` attributes for the following: - Demo (defaults to `demo.airy`) - Chat plugin (defaults to `chatplugin.airy`) -If you are not using Traefik, you can use the `infrastructure/network/ingress.yaml` file as a guide to create your own Kubernetes manifest for your preferred ingress controller. +If you are not using Traefik, you can use the +`infrastructure/network/ingress.yaml` file as a guide to create your own +Kubernetes manifest for your preferred ingress controller. -If your Kubernetes cluster is not directly reachable on the Internet, you will need a public LoadBalancer or a `reverse proxy` to tunnel the traffic to the ports exposed by the ingress controller (usually ports `80` and `443`). +If your Kubernetes cluster is not directly reachable on the Internet, you will +need a public LoadBalancer or a `reverse proxy` to tunnel the traffic to the +ports exposed by the ingress controller (usually ports `80` and `443`). ## AWS Cloud Services diff --git a/docs/docs/guides/airy-core-in-test-env.md b/docs/docs/guides/airy-core-in-test-env.md index 9162e0be53..6d9f2b1ebb 100644 --- a/docs/docs/guides/airy-core-in-test-env.md +++ b/docs/docs/guides/airy-core-in-test-env.md @@ -3,22 +3,25 @@ title: Running the Airy Core Platform in a test environment sidebar_label: Test --- -The goal of this document is to provide an overview of how to run the Airy Core Platform on your local machine. +The goal of this document is to provide an overview of how to run the Airy Core +Platform on your local machine. -To facilitate bootstrapping the Airy Core Platform on a single machine, we included a [Vagrant](https://www.vagrantup.com) configuration, inside the `infrastructure` directory. +To facilitate bootstrapping the Airy Core Platform on a single machine, we +included a [Vagrant](https://www.vagrantup.com) configuration, inside the +`infrastructure` directory. -The Vagrant box is based on Alpine Linux and contains a pre-configured Kubernetes cluster [K3OS](https://k3os.io/) to deploy and run the Airy Core Platform. -components. +The Vagrant box is based on Alpine Linux and contains a pre-configured +Kubernetes cluster [K3OS](https://k3os.io/) to deploy and run the Airy Core +Platform. components. ## Getting started - To bootstrap a test installation, refer to the [bootstrapping](/index.md#bootstrapping-the-airy-core-platform) document. +## Manage your Vagrant box -## Debug your installation - -You can ssh inside the Airy Core Platform box for testing and debugging purposes with `vagrant ssh` or run commands directly with `vagrant ssh -c COMMAND` +You can ssh inside the Airy Core Platform box for testing and debugging purposes +with `vagrant ssh` or run commands directly with `vagrant ssh -c COMMAND` ### Status @@ -37,6 +40,7 @@ vagrant ssh -c /vagrant/scripts/status.sh ``` The status command will print the following information: + ```sh "Your public url for the Facebook Webhook is:" ${FACEBOOK_WEBHOOK_PUBLIC_URL}/facebook @@ -51,10 +55,9 @@ ${TWILIO_WEBHOOK_PUBLIC_URL}/twilio "http://api.airy/" "Example:" -"curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' +"curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' ``` - ### Inspect Kubernetes ```sh @@ -87,7 +90,9 @@ vagrant up ## Access the API -The API services are available under the domain `http://api.airy` from your local machine. You can see an example request to the API by running the `status` command. +The API services are available under the domain `http://api.airy` from your +local machine. You can see an example request to the API by running the +`status` command. ## Access the frontend UI @@ -95,14 +100,23 @@ The frontend UI for the demo app can be accessed through http://demo.airy. The frontend UI for the Airy chat plugin can be accessed through http://chatplugin.airy/example.html. - ## Public webhooks -The public webhook URLs are generated during the bootstrap process and are displayed after the process finishes. Find your current webhook URLs and your API local address by running the `status` command. +The public webhook URLs are generated during the bootstrap process and are +displayed after the process finishes. Find your current webhook URLs and your +API local address by running the `status` command. -In order to integrate with the webhook of most sources on your local machine, weincluded a [Ngrok](https://ngrok.com/) client as a sidecar container in each `sources-SOURCE_NAME-webhook` pods. Ngrok is an open source reverse proxy which creates a secure tunnel from a public endpoint to a local service. The Ngrok client connects to a Ngrok server which has public access to the internet and then provides a reversed proxy connectivity back to the webhook services, running inside the Kubernetes cluster. +In order to integrate with the webhook of most sources on your local machine, +we included a [ngrok](https://ngrok.com/) client as a sidecar container in each +`sources-SOURCE_NAME-webhook` pods. ngrok is an open source reverse proxy which +creates a secure tunnel from a public endpoint to a local service. The ngrok +client connects to a ngrok server which has public access to the internet and +then provides a reversed proxy connectivity back to the webhook services, +running inside the Kubernetes cluster. -By default, the Ngrok client is configured to use the Ngrok server created by Airy and running on https://tunnel.airy.co. This configuration is specified in the `ngrok-client-config` ConfigMap. +By default, the ngrok client is configured to use the ngrok server created by +Airy and running on https://tunnel.airy.co. This configuration is specified in +the `ngrok-client-config` ConfigMap. ``` apiVersion: v1 @@ -116,9 +130,17 @@ data: trust_host_root_certs: true ``` -If you prefer to use your own Ngrok implementation or point the Ngrok client to connect to the service provided by the Ngrok company at `https://ngrok.io`, change the setting for `server_addr` in the ConfigMap or in this helm chart document `infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml`. +If you prefer to use your own ngrok implementation or point the ngrok client to +connect to the service provided by the ngrok company at `https://ngrok.io`, +change the setting for `server_addr` in the ConfigMap or in this helm chart +document +`infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml`. -The bootstrap process creates a random URL which is then provisioned inside the Helm chart. To configure these URLs, you can specify them in the `infrastructure/helm-chart/charts/apps/charts/airy-co)fig/values.yaml` document. Alternatively you can edit the `airy.conf` file by setting the following parameter (see `airy.conf.all` for more examples): +The bootstrap process creates a random URL which is then provisioned inside the +Helm chart. To configure these URLs, you can specify them in the +`infrastructure/helm-chart/charts/apps/charts/airy-co)fig/values.yaml` document. +Alternatively you can edit the `airy.conf` file by setting the following +parameter (see `airy.conf.all` for more examples): ``` sources: @@ -138,7 +160,10 @@ helm upgrade airy ~/airy-core/helm-chart/charts/apps/ --timeout 1000s ## Connect sources -Integrating sources into the `Airy Core Platform` often requires specific configuration settings, refer to the source specific docs for details. You must provide the settings in `infrastructure/airy.conf` configuration file. An example of the configuration can be found in `airy.conf.all`. +Integrating sources into the `Airy Core Platform` often requires specific +configuration settings, refer to the source specific docs for details. You must +provide the settings in `infrastructure/airy.conf` configuration file. An +example of the configuration can be found in `airy.conf.all`. After setting the configuration run: @@ -158,7 +183,9 @@ vagrant destroy ## Known Issues -If you have just installed Virtualbox and see this error during the bootstrap you should [give Virtualbox permissions](https://www.howtogeek.com/658047/how-to-fix-virtualboxs-%E2%80%9Ckernel-driver-not-installed-rc-1908-error/). +If you have just installed Virtualbox and see this error during the bootstrap +you should [give Virtualbox +permissions](https://www.howtogeek.com/658047/how-to-fix-virtualboxs-%E2%80%9Ckernel-driver-not-installed-rc-1908-error/). ``` There was an error while executing `VBoxManage`, a CLI used by Vagrant diff --git a/docs/docs/guidelines/contributing.md b/docs/docs/guides/contributing.md similarity index 96% rename from docs/docs/guidelines/contributing.md rename to docs/docs/guides/contributing.md index 29d0839c50..e817bdfd75 100644 --- a/docs/docs/guidelines/contributing.md +++ b/docs/docs/guides/contributing.md @@ -5,7 +5,7 @@ sidebar_label: Contributing We ❤️ every form of contribution. The following document aims to provide enough context to work with our codebase and to open pull requests that follow our -convention. If this document does not provide enough help, open a [new +conventions. If this document does not provide enough help, open a [new issue](https://github.com/airyhq/airy/issues/new) and we'll gladly help you get started. @@ -59,7 +59,7 @@ Java and prettier are ran as test targets for each package, so you can run: ```shell script bazel test //my/package:checkstyle -bazel test //my/package:prettier +bazel test //my/package:prettier ``` To execute the buildifier linter run: @@ -76,7 +76,6 @@ bazel run //:fix to try fixing issues automatically (not supported for checkstyle). - ### Managing dependencies If you add, remove, or change a dependency from the maven_install, you must @@ -135,7 +134,7 @@ Branches must abide to the following format: - `doc` or `docs` for documentation changes - `chore` for maintenance tasks on the repo -The `description field` must use kebab case. +The `description field` must use kebab case. Given these conventions here are a few examples: diff --git a/docs/docs/index.md b/docs/docs/index.md index f7452dd545..15c9896439 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -4,9 +4,11 @@ title: Home slug: / --- +import useBaseUrl from '@docusaurus/useBaseUrl'; + The Airy Core Platform is a fully-featured, production ready messaging platform -that allows its user to process messaging data from a variety of sources (like -facebook messenger or google business messages). The core platform contains the +that allows you to process messaging data from a variety of sources (like +Facebook messenger or Google business messages). The core platform contains the following components: - An ingestion platform that heavily relies on [Apache @@ -15,19 +17,19 @@ following components: independent contacts, conversations, and messages (see our [glossary](glossary.md) for formal definitions). -- An [HTTP api](api/http.md) to manage the data sets the platform +- An [HTTP api](api/http.md) that allows to manage the data sets the platform handles. -- A [webhook](api/webhook) integration server that allows its users to programmatically - participate in conversations by sending messages (the webhook integration - exposes events users can "listen" to and react programmatically.) +- A [webhook](api/webhook) integration server that allows to programmatically + participate in conversations by sending messages. The webhook integration + exposes events users can "listen" to and react programmatically. -- A [WebSocket](api/websocket) server that allows clients to receive near real-time updates about - data flowing through the system. +- A [WebSocket](api/websocket) server that allows to receive near real-time updates about + the data flowing through the system. ## Bootstrapping the Airy Core Platform -You can run the Airy Core Platform locally by running the following commands: +Run the Airy Core Platform locally by entering the following commands: ```bash git clone -b main https://github.com/airyhq/airy @@ -37,14 +39,17 @@ cd airy The bootstrap installation requires [Vagrant](https://www.vagrantup.com/downloads) and -[VirtualBox](https://www.virtualbox.org/wiki/Downloads). If they are not -found, the script will attempt to install them for you. +[VirtualBox](https://www.virtualbox.org/wiki/Downloads). If they are not found, +the script will attempt to install them for you. -If Vagrant or VirtualBox cannot be installed with the `bootstrap.sh` script, you will need to install them manually. +If Vagrant or VirtualBox cannot be installed with the `bootstrap.sh` script, you +need to install them manually. -The script will also ask for your administrative credentials as we are using the -[Vagrant Host Manager Plugin](https://github.com/devopsgroup-io/vagrant-hostmanager) to add entries to your hosts file. -You can skip this step and add the following lines to your hosts file yourself. +The script will also ask for your administrative credentials as we are using the +[Vagrant Host Manager +Plugin](https://github.com/devopsgroup-io/vagrant-hostmanager) to add entries to +your hosts file. You can skip this step and add the following lines to your +hosts file yourself. ``` 192.168.50.5 demo.airy @@ -56,9 +61,11 @@ Check out our [guide for running in test environment](guides/airy-core-in-test-e ## Connecting a chat plugin source -The chat plugin source is well suited for a first integration because it does not require any configuration. +The chat plugin source is well suited for a first integration because it does +not require any configuration. -Once you [signed up](api/http#signup), you must [log in](api/http#login) so you can obtain a valid JWT token for the up-coming API calls: +Once you [signed up](api/http#signup), you must [log in](api/http#login) so you +can obtain a valid JWT token for the upcoming API calls: ```bash token=$(echo $(curl -H 'Content-Type: application/json' -d \ @@ -74,31 +81,38 @@ curl -H "Content-Type: application/json" -H "Authorization: $token" -d \ }" api.airy/channels.connect ``` -![channels_connect](media/channels_connect.gif) -The id from the response is the `channel_id`, note it down as it's required in the next steps. +channels_connect + +The ID from the response is the `channel_id`. It is required for +the next steps, so note it down. ## Sending messages with the chat plugin -Pass the `channel_id` as query parameter when opening the demo page in your browser. This will authenticate the chat plugin and enable you to send messages immediately: +Pass the `channel_id` as a query parameter when opening the demo page in your +browser. This authenticates the chat plugin and enables you to send messages +immediately: ``` http://chatplugin.airy/example.html?channel_id= ``` -You can now type a message in the text box and send it 🎉 +You can now type a message in the text box and send it 🎉 -![chatplugin](media/chatplugin.gif) +chatplugin working -To see how messages are flowing through the system, you can now [list conversations](api/http.md#list-conversations) for the channel you just created which should return the message you just sent. +To see how messages are flowing through the system, [list +conversations](api/http.md#list-conversations) for the channel you have just created. +it should return the message you have just sent. -![conversations.list](media/conversation.list.png) +conversations.list ```bash curl -H "Content-Type: application/json" -H "Authorization: $token" -d "{}" \ api.airy/conversations.list | jq . ``` -You can also consume the messages directly from the Kafka `application.communication.messages` topic: +You can also consume the messages directly from the Kafka +`application.communication.messages` topic: ```bash cd infrastructure && vagrant ssh @@ -109,4 +123,4 @@ kafka-console-consumer \ --from-beginning ``` -![kafka_topic](media/kafka.gif) \ No newline at end of file +Kafka Topic diff --git a/docs/docs/media/conversation.list.png b/docs/docs/media/conversation.list.png deleted file mode 100644 index 63f532fb96..0000000000 Binary files a/docs/docs/media/conversation.list.png and /dev/null differ diff --git a/docs/docs/guides/architecture.md b/docs/docs/overview/architecture.md similarity index 59% rename from docs/docs/guides/architecture.md rename to docs/docs/overview/architecture.md index 34701318de..f20d8f352a 100644 --- a/docs/docs/guides/architecture.md +++ b/docs/docs/overview/architecture.md @@ -1,22 +1,25 @@ --- -title: Architecture of the Airy Core Plaform +title: The Airy Core Plaform architecture sidebar_label: Architecture --- ## Overview -The Airy Core Platform is a messaging platform comprised of a backend and frontend system. +The Airy Core Platform is a messaging platform that contains a backend and frontend system. -The `backend` system is a streaming platform and its role is to: +The `backend` system is a streaming platform. Its role is to: -- Ingest conversational events from different sources (mostly via webhook integrations), process them and store them in an Apache Kafka cluster -- Once processed, the events are available and accessible through an [API](api/http.md) -- Expose conversational events via a [webhook](api/webhook.md) integration -- Manage authentication and authorization features +- Ingest conversational events from different sources (mostly via webhook + integrations), process them, and store them in an Apache Kafka cluster. +- Make the processed events available and accessible through the Airy [API](api/http.md). +- Expose conversational events via a [webhook](api/webhook.md) integration. +- Manage authentication and authorization features. -The `frontend` system is comprised of a demo application and the JavaScript integration of the [Chat Plugin](sources/chat-plugin.md). +The `frontend` system contains a demo application and the JavaScript integration +of the [Chat Plugin](sources/chat-plugin.md). -Having that in mind, these are the docker containers or the `Airy apps` which run as part of the Airy Core Platform: +Having that in mind, these are the docker containers – or the `Airy apps` – +which run as part of the Airy Core Platform: ## Sources @@ -25,14 +28,17 @@ Having that in mind, these are the docker containers or the `Airy apps` which ru - sources-`SOURCE_NAME`-sender - Send events (mostly messages) to a `SOURCE_NAME` source ## API + - api-admin - Backend services for administration of messaging sources and destinations - api-auth - Backend services for authentication and authorization - api-communication - Backend services which expose conversations and messages ## Webhook + - webhook-publisher - Process conversational data and write in Redis the events to be exposed to external parties. - webhook-consumer - Read from Redis and send events to external webhooks ## Frontend + - frontend-demo - Web application for viewing messages - frontend-chat-plugin - Web chat plugin diff --git a/docs/docs/guidelines/design-principles.md b/docs/docs/overview/design-principles.md similarity index 100% rename from docs/docs/guidelines/design-principles.md rename to docs/docs/overview/design-principles.md diff --git a/docs/docs/guidelines/kafka.md b/docs/docs/overview/kafka.md similarity index 100% rename from docs/docs/guidelines/kafka.md rename to docs/docs/overview/kafka.md diff --git a/docs/docs/guidelines/release-process.md b/docs/docs/overview/release-process.md similarity index 58% rename from docs/docs/guidelines/release-process.md rename to docs/docs/overview/release-process.md index 208291ce5f..6f70200d67 100644 --- a/docs/docs/guidelines/release-process.md +++ b/docs/docs/overview/release-process.md @@ -13,13 +13,29 @@ Here's an outline of the process: - We branch from `develop` unless it's a hot-fix (we'd use `main` in that case) - Once release days comes, we execute the following steps: - We create an issue "Release x.y.z" - - We create a release branch `release/x.y.z` from `develop` + - We create a release branch `release/x.y.z` from the latest `develop` and push it: + - `git checkout develop` + - `git pull origin develop` + - `git checkout -b release/x.y.z` + - `git push origin release/x.y.z` - We test our release (`AIRY_VERSION=release ./scripts/bootstrap.sh`) and any - additional hot-fix is committed directly to the release branch. + additional hot-fix is committed directly to the release branch - Once we're satisfied with the release, we update the `VERSION` file with the current release number. The commit message must be `Fixes #issue-number` - where `issue-number` is the number of the current release issue. - - We merge the release branch into `main`, tag `main` with `x.y.z`and push to `main` - - We merge the release branch back into `develop` + where `issue-number` is the number of the current release issue + - We merge the release branch into `main`, tag `main` with `x.y.z`and push to `main`: + - `git checkout main` + - `git pull origin main` + - `git merge --no-ff release/x.y.z` + - `git tag x.y.z` + - `git push origin main` + - `git push origin x.y.z` + - We merge the release branch back into `develop`: + - `git checkout develop` + - `git merge --no-ff release/x.y.z` + - `git push origin develop` - We archive cards in the done column of the [work in progress](https://github.com/airyhq/airy/projects/1) board - - We announce the release! \ No newline at end of file + - We rename the current draft release to `x.y.z` and publish it + - We announce the release! + +You can check out existing releases on [GitHub](https://github.com/airyhq/airy/releases). diff --git a/docs/docs/sources/chat-plugin.md b/docs/docs/sources/chat-plugin.md index 286782ddb2..848b7062fc 100644 --- a/docs/docs/sources/chat-plugin.md +++ b/docs/docs/sources/chat-plugin.md @@ -7,11 +7,15 @@ The Airy Core chat plugin is a fully-featured [source](/glossary.md#source) that enables conversations with anonymous website visitors through a web chat plugin. -This document covers how to connect a chat plugin as as -[source](/glossary.md#source), how to install the chat plugin web widget, and the -HTTP and WebSocket APIs that power it. +:::tip What you will learn -## Connecting a channel +- How to connect a chat plugin +- How to install the chat plugin web widget +- How to use the HTTP and WebSocket APIs that power the chat plugin + +::: + +## Connect a channel Connects a chat plugin source to the Airy Core Platform. @@ -19,7 +23,7 @@ Connects a chat plugin source to the Airy Core Platform. POST /channels.connect ``` -- `source` *must* be `chat_plugin` +- `source` _must_ be `chat_plugin` - `source_channel_id` is a unique identifier of your choice ```json5 @@ -33,10 +37,10 @@ POST /channels.connect ```json5 { - "id": "channel-uuid-1", - "name": "Chat plugin", - "source": "chat_plugin", - "source_channel_id": "awesome-website-42" + "id": "channel-uuid-1", + "name": "Chat plugin", + "source": "chat_plugin", + "source_channel_id": "awesome-website-42" } ``` @@ -46,16 +50,17 @@ To install the chat plugin UI on your website add the following script tag to the `` section: ```html - ``` @@ -65,7 +70,9 @@ of your chat plugin server. When using the local vagrant environment `SCRIPT_HOST` must be set to `chatplugin.airy`. :::note + `chatplugin.airy` is not publicly accessible. The setup will only work for local web pages. + ::: To test the setup, replace the `CHANNEL_ID` in the URL @@ -102,6 +109,9 @@ WebSocket connection handshake. #### Send message +You must set the `token` obtained on the [authorization endpoint](#authenticating-web-users) as an `Authorization` +header. + `POST /chatplugin.send` **Sample Request** @@ -126,9 +136,9 @@ WebSocket connection handshake. }, state: "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", + sender_type: "{string/enum}", + // See glossary + sent_at: "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } ``` @@ -150,15 +160,15 @@ The WebSocket connection endpoint is at `/ws.chatplugin`. ```json5 { message: { - id: "{UUID}", - content: "{String}", - // source content string - state: "{String}", - // delivery state of message, one of PENDING, FAILED, DELIVERED - alignment: "{string/enum}", - // LEFT, RIGHT, CENTER - horizontal placement of message - sent_at: "{string}", - //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients + id: "{UUID}", + content: "{String}", + // source content string + state: "{String}", + // delivery state of message, one of PENDING, FAILED, DELIVERED + sender_type: "{string/enum}", + // See glossary + sent_at: "{string}" + //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } } ``` diff --git a/docs/docs/sources/facebook.md b/docs/docs/sources/facebook.md index 3177e4387d..94eb09b704 100644 --- a/docs/docs/sources/facebook.md +++ b/docs/docs/sources/facebook.md @@ -3,22 +3,90 @@ title: Facebook sidebar_label: Facebook --- -The Facebook source provides a channel of communication between a Facebook page and your running instance of the Airy Core Platform. +import useBaseUrl from '@docusaurus/useBaseUrl'; + +This document provides a step by step guide to integrate Facebook with your Airy +Core Platform instance. + +:::tip What you will learn + +- The required steps to configure the Facebook source +- How to connect a Facebook page to the Airy Core Platform + +::: ## Configuration -For Facebook to start sending events to your local instance, it must first -verify your instance with a challenge. To verify your Facebook webhook +The Facebook source requires the following configuration: + +- An app id and an app secret so that the platform can send messages back via + your Facebook application +- A webhook integration so that the platform can ingest messages from your + Facebook pages +- A page token for each facebook page you intend to integrate + +Let's proceed step by step. + +### Find the app id and secret + +To connect a page, you must have an approved Facebook app. If you don't have +one, you must to create it before proceeding. Once you are done with the +configuration, you should see something like this: + +Facebook apps page + +Note down the `App ID` of your Facebook application and then head to the basic +settings page. Here you will find your `App Secret`: + +Facebook apps page + +Now you can use the app id and the app secret for the following environment variables: + +- `FACEBOOK_APP_ID` +- `FACEBOOK_APP_SECRET` + +:::note + +Refer to the [test](/guides/airy-core-in-test-env#connect-sources) guide or the [production](/guides/airy-core-in-production#connect-sources) one to set these variables in your Airy Core Platform instance. + +::: + +### Configure the webook integration + +For Facebook to start sending events to your running instance, it must first +verify your integration with a challenge. To verify your Facebook webhook integration, you must set the environment variable `FACEBOOK_WEBHOOK_SECRET` to -the same value you supply in the webhook configuration on the Facebook -integration. +a value of your choice. + +:::note + +Refer to the [test](/guides/airy-core-in-test-env#connect-sources) guide or the [production](/guides/airy-core-in-production#connect-sources) one to set these variables in your Airy Core Platform instance. + +::: + +Then you are ready to configure the webhook integration. Head to the dashboard +of your Facebook application, find the "Webhooks" link on the left menu and then +click on "Edit subscription". You will see something like this: -You must also configure the webhook URL to `https:///facebook`. +Facebook edit subscription Once the verification process has been completed, Facebook will immediately start sending events to your Airy Core Platform instance. -## Connecting a channel +### Obtain a page token + +The next step is to obtain a page token, so the Airy Core Platform can send messages +on behalf of your page. The fastest way to get one is to use the graph explorer that +Facebook provides [Graph +Explorer](https://developers.facebook.com/tools/explorer/). + +On the `User or Page` option, select `Get Page Token` and click on `Generate Access Token`: + +Facebook token page + +You're now ready to connect a Facebook page to the Airy Core Platform 🎉. + +## Connect a channel Connects a Facebook page to the Airy Core Platform. @@ -26,7 +94,7 @@ Connects a Facebook page to the Airy Core Platform. POST /channels.connect ``` -- `source` *must* be `facebook` +- `source` _must_ be `facebook` - `source_channel_id` is the Facebook page id - `token` is the page Access Token @@ -44,10 +112,10 @@ POST /channels.connect ```json5 { - "id": "channel-uuid-1", - "name": "My custom name for this page", - "image_url": "https://example.org/custom-image.jpg", // optional - "source": "facebook", - "source_channel_id": "fb-page-id-1" + "id": "channel-uuid-1", + "name": "My custom name for this page", + "image_url": "https://example.org/custom-image.jpg", // optional + "source": "facebook", + "source_channel_id": "fb-page-id-1" } -``` \ No newline at end of file +``` diff --git a/docs/docs/sources/google.md b/docs/docs/sources/google.md index 342200a7c0..41beb246db 100644 --- a/docs/docs/sources/google.md +++ b/docs/docs/sources/google.md @@ -6,9 +6,16 @@ sidebar_label: Google The Google source provides a channel of communication between your Google Business Location and your running instance of the Airy Core Platform. +:::tip What you will learn + +- The required steps to configure the Google source +- How to connect a Google Business Location to the Airy core Platform + +::: + ## Configuration -First step is to copy the Google Service Account file provided by Google to +The first step is to copy the Google Service Account file provided by Google to `infrastructure/airy.conf` as a one line string ``` @@ -22,7 +29,7 @@ against your partner key. You must also set the environment variable Once the verification process has been completed, Google will immediately start sending events to your Airy Core Platform instance. -## Connecting a channel +## Connect a channel Connects a Google Business Account to the Airy Core Platform. @@ -30,8 +37,8 @@ Connects a Google Business Account to the Airy Core Platform. POST /channels.connect ``` -- `source` *must* be `google` -- `source_channel_id` The id of your Google Business Message [agent](https://developers.google.com/business-communications/business-messages/reference/business-communications/rest/v1/brands.agents#Agent). +- `source` _must_ be `google` +- `source_channel_id` The id of your Google Business Message [agent](https://developers.google.com/business-communications/business-messages/reference/business-communications/rest/v1/brands.agents#Agent). - `token` leave empty. To allow authentication you must provide a [Google service account key file](https://developers.google.com/business-communications/business-messages/guides/quickstarts/prerequisite-setup) in your runtime configuration. ```json5 @@ -47,10 +54,10 @@ POST /channels.connect ```json5 { - "id": "channel-uuid-1", - "name": "My custom name for this location", - "image_url": "https://example.com/custom-image.jpg", - "source": "google", - "source_channel_id": "gbm-id" + "id": "channel-uuid-1", + "name": "My custom name for this location", + "image_url": "https://example.com/custom-image.jpg", + "source": "google", + "source_channel_id": "gbm-id" } ``` diff --git a/docs/docs/sources/sms-twilio.md b/docs/docs/sources/sms-twilio.md index a11e078d05..47856546b7 100644 --- a/docs/docs/sources/sms-twilio.md +++ b/docs/docs/sources/sms-twilio.md @@ -6,15 +6,19 @@ sidebar_label: SMS - Twilio The Twilio sms source provides a channel for sending and receiving SMS using the [Twilio API](https://www.twilio.com/). +:::note + This document assumes that you have a Twilio account. +::: + ## Configuration - + import TwilioSource from './twilio-source.mdx' -## Connecting a channel +## Connect a channel After you created a Twilio phone number you must [point its webhook integration](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-java#configure-your-webhook-url) @@ -26,9 +30,9 @@ Next call the Platform API: POST /channels.connect ``` -- `source` *must* be `twilio.sms` -- `source_channel_id` The phone number as listed in your [Twilio - dashboard](https://www.twilio.com/console/phone-numbers/). It must *not* contain +- `source` _must_ be `twilio.sms` +- `source_channel_id` The phone number as listed in your [Twilio + dashboard](https://www.twilio.com/console/phone-numbers/). It must _not_ contain spaces and must include the country code. **Sample Request** @@ -46,10 +50,10 @@ POST /channels.connect ```json5 { - "id": "channel-uuid-1", - "name": "SMS for receipts", - "image_url": "https://example.com/custom-image.jpg", - "source": "twilio.sms", - "source_channel_id": "+491234567" + "id": "channel-uuid-1", + "name": "SMS for receipts", + "image_url": "https://example.com/custom-image.jpg", + "source": "twilio.sms", + "source_channel_id": "+491234567" } ``` diff --git a/docs/docs/sources/twilio-source.mdx b/docs/docs/sources/twilio-source.mdx index f8ebfd6ec2..d90905bffe 100644 --- a/docs/docs/sources/twilio-source.mdx +++ b/docs/docs/sources/twilio-source.mdx @@ -1,4 +1,6 @@ -You must create a [Twilio auth token](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them) and add it to `infrastructure/airy.conf` together with your account sid: +You must create a [Twilio auth +token](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them) +and add it to `infrastructure/airy.conf` together with your account sid: ``` TWILIO_AUTH_TOKEN= diff --git a/docs/docs/sources/whatsapp-twilio.md b/docs/docs/sources/whatsapp-twilio.md index cc2655daf2..2d63e66085 100644 --- a/docs/docs/sources/whatsapp-twilio.md +++ b/docs/docs/sources/whatsapp-twilio.md @@ -3,22 +3,26 @@ title: Whatsapp via Twilio sidebar_label: Whatsapp - Twilio --- -The Twilio Whatsapp source provides a channel for sending and receiving Whatsapp +The Twilio WhatsApp source provides a channel for sending and receiving WhatsApp messages using the [Twilio API](https://www.twilio.com/). +:::note + This document assumes that you have a Twilio account connected to -[Whatsapp](https://www.twilio.com/whatsapp). +[WhatsApp](https://www.twilio.com/whatsapp). + +::: ## Configuration - + import TwilioSource from './twilio-source.mdx' -## Connecting a channel +## Connect a channel After you created a Twilio phone number, you must [point its -webhook integratuion](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-java#configure-your-webhook-url) +webhook integration](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-java#configure-your-webhook-url) to your Airy Core Platform running instance. Next call the Airy Core Platform API for connecting channels: @@ -27,11 +31,11 @@ Next call the Airy Core Platform API for connecting channels: POST /channels.connect ``` -- `source` *must* be `twilio.whatsapp` -- `source_channel_id` The phone number as listed in your [Twilio - dashboard](https://www.twilio.com/console/phone-numbers/). - It must *not* have spaces, must include the country - code, and be prefixed by `whatsapp:` +- `source` _must_ be `twilio.whatsapp` +- `source_channel_id` The phone number as listed in your [Twilio + dashboard](https://www.twilio.com/console/phone-numbers/). + It must _not_ have spaces, must include the country + code, and be prefixed by `whatsapp:` **Sample Request** @@ -39,7 +43,7 @@ POST /channels.connect { "source": "twilio.whatsapp", "source_channel_id": "whatsapp:+491234567", - "name": "Whatsapp Marketing", + "name": "WhatsApp Marketing", "image_url": "https://example.com/custom-image.jpg" // optional } ``` @@ -48,10 +52,10 @@ POST /channels.connect ```json5 { - "id": "channel-uuid-1", - "name": "Whatsapp Marketing", - "image_url": "https://example.com/custom-image.jpg", - "source": "twilio.whatsapp", - "source_channel_id": "whatsapp:+491234567" + "id": "channel-uuid-1", + "name": "WhatsApp Marketing", + "image_url": "https://example.com/custom-image.jpg", + "source": "twilio.whatsapp", + "source_channel_id": "whatsapp:+491234567" } ``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index cb0c83aec6..7eb2bec065 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -12,6 +12,7 @@ module.exports = { apiKey: '768788b65303eb29ca1f195847ed1e78', indexName: 'airy', }, + hideableSidebar: true, prism: { theme: require('prism-react-renderer/themes/github'), darkTheme: require('./src/plugins/prism_themes/monokai'), @@ -29,6 +30,7 @@ module.exports = { copyright: `Copyright © ${new Date().getFullYear()} Airy, Inc.`, }, }, + plugins: ['plugin-image-zoom'], scripts: ['https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js'], presets: [ [ diff --git a/docs/package.json b/docs/package.json index c01a4f38d1..733a2dc0a4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,6 +15,7 @@ "@docusaurus/preset-classic": "2.0.0-alpha.66", "@mdx-js/react": "^1.5.8", "clsx": "^1.1.1", + "plugin-image-zoom": "ataft/plugin-image-zoom", "react": "^16.8.4", "react-dom": "^16.8.4" }, diff --git a/docs/sidebars.js b/docs/sidebars.js index 2b30308de3..4bdeca8203 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -2,7 +2,7 @@ module.exports = { docs: [ 'index', { - API: ['api/http', 'api/websocket', 'api/webhook'], + Overview: ['overview/architecture', 'overview/design-principles', 'overview/release-process', 'overview/kafka'], }, { Sources: [ @@ -13,23 +13,18 @@ module.exports = { 'sources/whatsapp-twilio', ], }, - { - Guidelines: [ - 'guidelines/contributing', - 'guidelines/design-principles', - 'guidelines/release-process', - 'guidelines/kafka', - ], - }, { Guides: [ - 'guides/architecture', + 'guides/contributing', { Deployment: ['guides/airy-core-in-test-env', 'guides/airy-core-in-production'], }, + 'guides/airy-core-and-rasa', ], }, - + { + 'API Reference': ['api/http', 'api/websocket', 'api/webhook'], + }, 'glossary', ], }; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index abba00ae94..8d51acc649 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -15,13 +15,13 @@ --ifm-color-danger-lighter: #ff96b0; --ifm-color-danger-lightest: #ffcbd8; - --ifm-color-success: #a9dc76; - --ifm-color-success-dark: #98d55b; - --ifm-color-success-darker: #90d24e; - --ifm-color-success-darkest: #76bc30; - --ifm-color-success-light: #bae391; - --ifm-color-success-lighter: #c2e69e; - --ifm-color-success-lightest: #dcf1c7; + --ifm-color-success: #00a400; + --ifm-color-success-dark: #009400; + --ifm-color-success-darker: #008b00; + --ifm-color-success-darkest: #007300; + --ifm-color-success-light: #00b400; + --ifm-color-success-lighter: #00bd00; + --ifm-color-success-lightest: #00d500; --ifm-color-info: #78dce8; --ifm-color-info-dark: #5ad4e3; @@ -51,6 +51,10 @@ --ifm-code-font-size: 95%; } +.alert--success h5 { + color: white; +} + .footer.footer--dark { --ifm-footer-background-color: #272822; } diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico index a788eb756e..6c9f5a0553 100644 Binary files a/docs/static/img/favicon.ico and b/docs/static/img/favicon.ico differ diff --git a/docs/static/img/guides/airy-core-and-rasa/success.jpg b/docs/static/img/guides/airy-core-and-rasa/success.jpg new file mode 100644 index 0000000000..af9b384081 Binary files /dev/null and b/docs/static/img/guides/airy-core-and-rasa/success.jpg differ diff --git a/docs/static/img/guides/airy-core-and-rasa/webhook_success.jpg b/docs/static/img/guides/airy-core-and-rasa/webhook_success.jpg new file mode 100644 index 0000000000..73c199a50f Binary files /dev/null and b/docs/static/img/guides/airy-core-and-rasa/webhook_success.jpg differ diff --git a/docs/docs/media/channels_connect.gif b/docs/static/img/home/channels_connect.gif similarity index 100% rename from docs/docs/media/channels_connect.gif rename to docs/static/img/home/channels_connect.gif diff --git a/docs/docs/media/chatplugin.gif b/docs/static/img/home/chatplugin.gif similarity index 100% rename from docs/docs/media/chatplugin.gif rename to docs/static/img/home/chatplugin.gif diff --git a/docs/static/img/home/conversation.list.jpg b/docs/static/img/home/conversation.list.jpg new file mode 100644 index 0000000000..b3f2b08778 Binary files /dev/null and b/docs/static/img/home/conversation.list.jpg differ diff --git a/docs/docs/media/kafka.gif b/docs/static/img/home/kafka.gif similarity index 100% rename from docs/docs/media/kafka.gif rename to docs/static/img/home/kafka.gif diff --git a/docs/static/img/sources/facebook/apps.jpg b/docs/static/img/sources/facebook/apps.jpg new file mode 100644 index 0000000000..ba7457d40e Binary files /dev/null and b/docs/static/img/sources/facebook/apps.jpg differ diff --git a/docs/static/img/sources/facebook/secret.png b/docs/static/img/sources/facebook/secret.png new file mode 100644 index 0000000000..3523278573 Binary files /dev/null and b/docs/static/img/sources/facebook/secret.png differ diff --git a/docs/static/img/sources/facebook/token.jpg b/docs/static/img/sources/facebook/token.jpg new file mode 100644 index 0000000000..ee549167b9 Binary files /dev/null and b/docs/static/img/sources/facebook/token.jpg differ diff --git a/docs/static/img/sources/facebook/webhook.png b/docs/static/img/sources/facebook/webhook.png new file mode 100644 index 0000000000..b11ae780c5 Binary files /dev/null and b/docs/static/img/sources/facebook/webhook.png differ diff --git a/docs/yarn.lock b/docs/yarn.lock index e3a10a548a..bfcbe6b0e1 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -5316,9 +5316,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== inline-style-parser@0.1.1: version "0.1.1" @@ -6305,6 +6305,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +medium-zoom@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/medium-zoom/-/medium-zoom-1.0.6.tgz#9247f21ca9313d8bbe9420aca153a410df08d027" + integrity sha512-UdiUWfvz9fZMg1pzf4dcuqA0W079o0mpqbTnOz5ip4VGYX96QjmbM+OgOU/0uOzAytxC0Ny4z+VcYQnhdifimg== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -7254,6 +7259,12 @@ pkg-up@3.1.0, pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +plugin-image-zoom@ataft/plugin-image-zoom: + version "0.0.0" + resolved "https://codeload.github.com/ataft/plugin-image-zoom/tar.gz/513e840ac62cfe95a696b1886dfd03dd6338b58d" + dependencies: + medium-zoom "^1.0.4" + pnp-webpack-plugin@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" diff --git a/frontend/README.md b/frontend/README.md index 44a0e0d641..40bc51d5d8 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -22,9 +22,9 @@ Here is a quick introduction to the frontend projects: - `demo` - This project is a minimum UI implementation of the provided [Airy Core Platform API](https://docs.airy.co/api/http). - Unlike `showcase` it does not use the `npm` version of the `components` library, but instead uses the - local repository version. + This project is a minimum UI implementation of the provided [Airy Core Platform API](https://docs.airy.co/api/http). + Unlike `showcase` it does not use the `npm` version of the `components` library, but instead uses the + local repository version. - `chat_plugin` diff --git a/frontend/chat-plugin/BUILD b/frontend/chat-plugin/BUILD index a19bf4f4f0..d71937cd45 100644 --- a/frontend/chat-plugin/BUILD +++ b/frontend/chat-plugin/BUILD @@ -1,12 +1,12 @@ load("@npm_bazel_typescript//:index.bzl", "ts_config") -load("//tools/build/web:typescript.bzl", "ts_library") -load("//tools/build/web:webapp.bzl", "webapp") -load("//tools/build/web:web_library.bzl", "web_library") +load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_library") +load("@com_github_airyhq_bazel_tools//web:web_app.bzl", "web_app") +load("@com_github_airyhq_bazel_tools//web:web_library.bzl", "web_library") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("//tools/build:container_push.bzl", "container_push") load("@io_bazel_rules_docker//container:container.bzl", "container_image") -webapp( +web_app( name = "bundle", app_lib = ":app", entry = "frontend/chat-plugin/src/iframe.js", @@ -28,6 +28,7 @@ ts_library( name = "app", tsconfig = ":widget_tsconfig", deps = [ + "//lib/typescript/types", "@npm//@stomp/stompjs", "@npm//@types/node", "@npm//linkifyjs", diff --git a/frontend/chat-plugin/README.md b/frontend/chat-plugin/README.md index b252acc9d3..f4b1ddb745 100644 --- a/frontend/chat-plugin/README.md +++ b/frontend/chat-plugin/README.md @@ -4,9 +4,8 @@ This app demos a minimal frontend that allows contacts to communicate with the [ ## Develop -Run with [Bazelisk](https://github.com/bazelbuild/bazelisk) +Run with [Bazelisk](https://github.com/bazelbuild/bazelisk) ```bash ibazel run //frontend/chat-plugin:bundle_server -``` - +``` diff --git a/frontend/chat-plugin/src/airyRenderProps/AiryMessage/index.tsx b/frontend/chat-plugin/src/airyRenderProps/AiryMessage/index.tsx index 97743db64e..0707ff0c7f 100644 --- a/frontend/chat-plugin/src/airyRenderProps/AiryMessage/index.tsx +++ b/frontend/chat-plugin/src/airyRenderProps/AiryMessage/index.tsx @@ -5,7 +5,7 @@ import style from './index.module.scss'; type Props = { message: { - alignment: string; + sender_type: string; content: { text: string; }; @@ -13,7 +13,7 @@ type Props = { }; const AiryMessage = ({message}: Props) => { - const isInbound = message.alignment === 'LEFT'; + const isInbound = message.sender_type === 'source_contact'; const messageDisplay = linkifyString(message.content.text, { className: `${isInbound ? style.messageLinkRight : style.messageLinkLeft}`, }); diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index d38cc2413b..b3bdb3593b 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -19,7 +19,7 @@ import AiryBubble from '../../airyRenderProps/AiryBubble'; let ws: Websocket; const welcomeMessage = { - alignment: 'RIGHT', + sender_type: 'app_user', id: '19527d24-9b47-4e18-9f79-fd1998b95059', sent_at: 'undefined', content: { diff --git a/frontend/demo/BUILD b/frontend/demo/BUILD index 724afde541..b452f4c152 100644 --- a/frontend/demo/BUILD +++ b/frontend/demo/BUILD @@ -1,5 +1,5 @@ -load("//tools/build/web:typescript.bzl", "ts_library") -load("//tools/build/web:webapp.bzl", "webapp") +load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_library") +load("@com_github_airyhq_bazel_tools//web:web_app.bzl", "web_app") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("//tools/build:container_push.bzl", "container_push") load("@io_bazel_rules_docker//container:container.bzl", "container_image") @@ -7,21 +7,26 @@ load("@io_bazel_rules_docker//container:container.bzl", "container_image") ts_library( name = "app", deps = [ + "//lib/typescript/types", "@npm//@airyhq/components", + "@npm//@types/facebook-js-sdk", "@npm//@types/node", "@npm//@types/prop-types", "@npm//@types/react", "@npm//@types/react-redux", "@npm//emoji-mart", + "@npm//lodash-es", "@npm//react", + "@npm//react-facebook-login", "@npm//react-router-dom", "@npm//redux", "@npm//redux-starter-kit", + "@npm//reselect", "@npm//typesafe-actions", ], ) -webapp( +web_app( name = "bundle", app_lib = ":app", entry = "frontend/demo/src/index.js", diff --git a/frontend/demo/src/App.tsx b/frontend/demo/src/App.tsx index 6ecb553f25..19f6d9cbbf 100644 --- a/frontend/demo/src/App.tsx +++ b/frontend/demo/src/App.tsx @@ -9,7 +9,11 @@ import NotFound from './pages/NotFound'; import Sidebar from './components/Sidebar'; import {StateModel} from './reducers'; -import {LOGIN_ROUTE, ROOT_ROUTE} from './routes/routes'; + +import {CHANNELS_ROUTE, LOGIN_ROUTE, ROOT_ROUTE, TAGS_ROUTE} from './routes/routes'; + +import {Tags} from './pages/Tags'; +import Channels from './pages/Channels'; import styles from './App.module.scss'; @@ -55,7 +59,9 @@ class App extends Component & RouteComponentPro {this.isAuthSuccess ? : } + + diff --git a/frontend/demo/src/actions/channel/index.ts b/frontend/demo/src/actions/channel/index.ts new file mode 100644 index 0000000000..a7018bb5c3 --- /dev/null +++ b/frontend/demo/src/actions/channel/index.ts @@ -0,0 +1,82 @@ +import {createAction} from 'typesafe-actions'; +import _, {Dispatch} from 'redux'; + +import {doFetchFromBackend} from '../../api/airyConfig'; + +import { + Channel, + ChannelApiPayload, + ChannelsPayload, + channelsMapper, + channelMapper, + connectChannelApiMapper, + disconnectChannelApiMapper, + ConnectChannelRequestPayload, + ExploreChannelRequestPayload, + DisconnectChannelRequestPayload, +} from '../../model/Channel'; + +const SET_CURRENT_CHANNELS = '@@channel/SET_CHANNELS'; +const ADD_CHANNELS = '@@channel/ADD_CHANNELS'; + +export const setCurrentChannelsAction = createAction(SET_CURRENT_CHANNELS, resolve => (channels: Channel[]) => + resolve(channels) +); + +export const addChannelsAction = createAction(ADD_CHANNELS, resolve => (channels: Channel[]) => resolve(channels)); + +export function getChannels() { + return async (dispatch: Dispatch) => { + return doFetchFromBackend('channels.list') + .then((response: ChannelsPayload) => { + const channels = channelsMapper(response); + dispatch(setCurrentChannelsAction(channels)); + return Promise.resolve(channels); + }) + .catch((error: Error) => { + return Promise.reject(error); + }); + }; +} + +export function exploreChannels(requestPayload: ExploreChannelRequestPayload) { + return async (dispatch: Dispatch) => { + return doFetchFromBackend('channels.explore', requestPayload) + .then((response: ChannelsPayload) => { + const channels = channelsMapper(response, requestPayload.source); + dispatch(addChannelsAction(channels)); + return Promise.resolve(channels); + }) + .catch((error: Error) => { + return Promise.reject(error); + }); + }; +} + +export function connectChannel(requestPayload: ConnectChannelRequestPayload) { + return async (dispatch: Dispatch) => { + return doFetchFromBackend('channels.connect', connectChannelApiMapper(requestPayload)) + .then((response: ChannelApiPayload) => { + const channel = channelMapper(response); + dispatch(addChannelsAction([channel])); + return Promise.resolve(channel); + }) + .catch((error: Error) => { + return Promise.reject(error); + }); + }; +} + +export function disconnectChannel(requestPayload: DisconnectChannelRequestPayload) { + return async (dispatch: Dispatch) => { + return doFetchFromBackend('channels.disconnect', disconnectChannelApiMapper(requestPayload)) + .then((response: ChannelsPayload) => { + const channels = channelsMapper(response); + dispatch(setCurrentChannelsAction(channels)); + return Promise.resolve(channels); + }) + .catch((error: Error) => { + return Promise.reject(error); + }); + }; +} diff --git a/frontend/demo/src/actions/settings/index.tsx b/frontend/demo/src/actions/settings/index.tsx new file mode 100644 index 0000000000..28a569fb54 --- /dev/null +++ b/frontend/demo/src/actions/settings/index.tsx @@ -0,0 +1,18 @@ +import _, {Dispatch} from 'redux'; + +import {fakeData} from '../../pages/Tags/FAKESETTINGS'; + +export const ADD_SETTINGS_TO_STORE = 'ADD_SETTINGS_TO_STORE'; + +export function fetchSettings() { + return { + type: ADD_SETTINGS_TO_STORE, + colors: fakeData(), + }; +} + +export function fakeSettingsAPICall() { + return function(dispatch: Dispatch) { + dispatch(fetchSettings()); + }; +} diff --git a/frontend/demo/src/actions/tags/index.tsx b/frontend/demo/src/actions/tags/index.tsx new file mode 100644 index 0000000000..663c62111b --- /dev/null +++ b/frontend/demo/src/actions/tags/index.tsx @@ -0,0 +1,78 @@ +import _, {Dispatch} from 'redux'; +import {createAction} from 'typesafe-actions'; + +import {doFetchFromBackend} from '../../api/airyConfig'; +import {Tag, TagPayload, CreateTagRequestPayload, GetTagsResponse, tagsMapper} from '../../model/Tag'; + +const UPSERT_TAG = 'UPSERT_TAG'; +const DELETE_TAG = 'DELETE_TAG'; +const EDIT_TAG = 'EDIT_TAG'; +const ERROR_TAG = 'ERROR_TAG'; +const ADD_TAGS_TO_STORE = 'ADD_TAGS_TO_STORE'; +const SET_TAG_FILTER = 'SET_TAG_FILTER'; + +export const fetchTagAction = createAction(ADD_TAGS_TO_STORE, resolve => (tags: Tag[]) => resolve(tags)); +export const addTagAction = createAction(UPSERT_TAG, resolve => (tag: Tag) => resolve(tag)); +export const editTagAction = createAction(EDIT_TAG, resolve => (tag: Tag) => resolve(tag)); +export const deleteTagAction = createAction(DELETE_TAG, resolve => (id: string) => resolve(id)); +export const filterTagAction = createAction(SET_TAG_FILTER, resolve => (filter: string) => resolve(filter)); +export const errorTagAction = createAction(ERROR_TAG, resolve => (status: string) => resolve(status)); + +export function getTags(query: string = '') { + return function(dispatch: Dispatch) { + return doFetchFromBackend('tags.list').then((response: GetTagsResponse) => { + dispatch(fetchTagAction(tagsMapper(response.data))); + }); + }; +} + +export function createTag(requestPayload: CreateTagRequestPayload) { + return async (dispatch: Dispatch) => { + return doFetchFromBackend('tags.create', requestPayload) + .then((response: TagPayload) => { + const tag: Tag = { + id: response.id, + name: requestPayload.name, + color: requestPayload.color, + }; + dispatch(addTagAction(tag)); + return Promise.resolve(true); + }) + .catch((error: string) => { + dispatch(errorTagAction(error)); + return Promise.resolve(false); + }); + }; +} + +export function updateTag(tag: Tag) { + return function(dispatch: Dispatch) { + doFetchFromBackend('tags.update', { + id: tag.id, + name: tag.name, + color: tag.color, + }).then((responseTag: Tag) => dispatch(editTagAction(tag))); + }; +} + +export function deleteTag(id: string) { + return function(dispatch: Dispatch) { + doFetchFromBackend('tags.delete', { + id, + }).then(() => { + dispatch(deleteTagAction(id)); + }); + }; +} + +export function filterTags(filter: string) { + return function(dispatch: Dispatch) { + dispatch(filterTagAction(filter)); + }; +} + +export function errorTag(status: string, data?: string) { + return function(dispatch: Dispatch) { + dispatch(errorTagAction(status)); + }; +} diff --git a/frontend/demo/src/actions/user/index.tsx b/frontend/demo/src/actions/user/index.ts similarity index 87% rename from frontend/demo/src/actions/user/index.tsx rename to frontend/demo/src/actions/user/index.ts index 5c949de69e..64639649b2 100644 --- a/frontend/demo/src/actions/user/index.tsx +++ b/frontend/demo/src/actions/user/index.ts @@ -30,12 +30,13 @@ export function loginViaEmail(requestPayload: LoginViaEmailRequestPayload) { return async (dispatch: Dispatch) => { return doFetchFromBackend('users.login', requestPayload) .then((response: UserPayload) => { - dispatch(setCurrentUserAction(userMapper(response))); - return true; + const user = userMapper(response); + dispatch(setCurrentUserAction(user)); + return Promise.resolve(user); }) .catch((error: Error) => { dispatch(userAuthErrorAction(error)); - return false; + return Promise.reject(error); }); }; } diff --git a/frontend/demo/src/api/airyConfig.ts b/frontend/demo/src/api/airyConfig.ts index 64c2e76cef..d3b76d00c6 100644 --- a/frontend/demo/src/api/airyConfig.ts +++ b/frontend/demo/src/api/airyConfig.ts @@ -1,15 +1,16 @@ import {getAuthToken} from './webStore'; export class AiryConfig { - static API_URL = 'airy.api'; + static API_URL = 'http://api.airy'; static NODE_ENV = process.env.NODE_ENV; + static FACEBOOK_APP_ID = 'CHANGE_ME'; } const headers = { Accept: 'application/json', }; -export const doFetchFromBackend = async (url: string, body?: Object, retryCount: number = 0): Promise => { +export const doFetchFromBackend = async (url: string, body?: Object): Promise => { const token = getAuthToken(); if (token) { headers['Authorization'] = token; @@ -22,17 +23,13 @@ export const doFetchFromBackend = async (url: string, body?: Object, retryCount: headers['Content-Type'] = 'application/json'; } - try { - const response: Response = await fetch(`${AiryConfig.API_URL}/${url}`, { - method: 'POST', - headers: headers, - body: body as BodyInit, - }); + const response: Response = await fetch(`${AiryConfig.API_URL}/${url}`, { + method: 'POST', + headers: headers, + body: body as BodyInit, + }); - return parseBody(response); - } catch (error) { - return error; - } + return parseBody(response); }; async function parseBody(response: Response): Promise { diff --git a/frontend/demo/src/assets/images/empty-state/tags-empty-state.svg b/frontend/demo/src/assets/images/empty-state/tags-empty-state.svg new file mode 100644 index 0000000000..942ad82455 --- /dev/null +++ b/frontend/demo/src/assets/images/empty-state/tags-empty-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/demo/src/assets/images/icons/close.svg b/frontend/demo/src/assets/images/icons/close.svg new file mode 100644 index 0000000000..309d38d8cf --- /dev/null +++ b/frontend/demo/src/assets/images/icons/close.svg @@ -0,0 +1,9 @@ + + + + close + Created with Sketch. + + + + diff --git a/frontend/demo/src/assets/images/icons/edit.svg b/frontend/demo/src/assets/images/icons/edit.svg new file mode 100644 index 0000000000..f02b83a7e4 --- /dev/null +++ b/frontend/demo/src/assets/images/icons/edit.svg @@ -0,0 +1,15 @@ + + + + icon/interfaces/trash copy + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/frontend/demo/src/assets/images/icons/plus.svg b/frontend/demo/src/assets/images/icons/plus.svg new file mode 100644 index 0000000000..c75201d7eb --- /dev/null +++ b/frontend/demo/src/assets/images/icons/plus.svg @@ -0,0 +1,15 @@ + + + + icon/interfaces/plus + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/frontend/demo/src/assets/images/icons/price-tag.svg b/frontend/demo/src/assets/images/icons/price-tag.svg new file mode 100644 index 0000000000..076e03d80d --- /dev/null +++ b/frontend/demo/src/assets/images/icons/price-tag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/demo/src/assets/images/icons/trash.svg b/frontend/demo/src/assets/images/icons/trash.svg new file mode 100644 index 0000000000..9f6f11a965 --- /dev/null +++ b/frontend/demo/src/assets/images/icons/trash.svg @@ -0,0 +1,11 @@ + + + + trash + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/frontend/demo/src/assets/scss/README.md b/frontend/demo/src/assets/scss/README.md index c4dc97433c..b0d70088d6 100644 --- a/frontend/demo/src/assets/scss/README.md +++ b/frontend/demo/src/assets/scss/README.md @@ -1,15 +1,15 @@ Here is the list of global css definitions like colors and other things that might be useful while developing in this app. -If you need to modify third party dependencies, put them into the +If you need to modify third party dependencies, put them into the `third-party` folder so that we have them isolated in one place. ### Colors -These are the colors we use in the app. Simply import them with +These are the colors we use in the app. Simply import them with ```css -@import 'assets/scss/colors.scss'; +@import "assets/scss/colors.scss"; ``` After that you have the following variables defined: @@ -114,22 +114,22 @@ These are our font sizes: ```js
    -
  • font-s
  • -
  • font-base
  • -
  • font-m
  • -
  • font-l
  • -
  • font-xl
  • -
  • font-xxl
  • +
  • font-s
  • +
  • font-base
  • +
  • font-m
  • +
  • font-l
  • +
  • font-xl
  • +
  • font-xxl
``` -You can either use them as mixins via +You can either use them as mixins via ```scss .element { @include font-s; } -``` +``` or directly as class names. diff --git a/frontend/demo/src/components/ColorSelector.module.scss b/frontend/demo/src/components/ColorSelector.module.scss new file mode 100644 index 0000000000..924fca9628 --- /dev/null +++ b/frontend/demo/src/components/ColorSelector.module.scss @@ -0,0 +1,166 @@ +@import '../assets/scss/colors.scss'; + +.colorSelector { + position: relative; + overflow: hidden; + div { + display: inline-block; + width: 24px; + animation-timing-function: cubic-bezier(0, 1, 1, 0); + margin: 0 16px 0 0; + } + + .colorPicker { + display: none; + + + label { + margin-bottom: 0; + span { + display: inline-block; + width: 24px; + height: 24px; + vertical-align: middle; + cursor: pointer; + border-radius: 50%; + background-repeat: no-repeat; + background-position: center; + text-align: center; + line-height: 44px; + img { + opacity: 0; + transition: all 0.3s ease; + } + } + } + + &.colorRed + label span { + background-color: var(--color-tag-red); + } + + &.colorBlue + label span { + background-color: var(--color-airy-blue); + } + + &.colorGreen + label span { + background-color: var(--color-tag-green); + } + + &.colorPurple + label span { + background-color: var(--color-tag-purple); + } + + &:checked + label span { + background-position: center; + background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/242518/check-icn.svg'); + background-size: 51%; + } + } + &.done { + div { + left: 6px !important; + } + div:nth-child(2) { + animation: ellipse5 0.6s; + } + + div:nth-child(3) { + animation: ellipse6 0.6s; + } + + div:nth-child(4) { + animation: ellipse7 0.6s; + } + } + div { + animation-direction: normal; + } + div:nth-child(1) { + left: 6px; + animation: ellipse1 0.6s; + } + + div:nth-child(2) { + left: 42px; + animation: ellipse2 0.6s; + } + + div:nth-child(3) { + left: 68px; + animation: ellipse2 0.6s; + } + + div:nth-child(4) { + left: 94px; + animation: ellipse4 0.6s; + } + &:after { + content: ''; + display: table; + clear: both; + } +} + +/* Tag color selector animations */ + +@keyframes ellipse1 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(0, 0); + } +} + +@keyframes ellipse2 { + 0% { + transform: translate(-42px, 0); + } + 100% { + transform: translate(0, 0); + } +} + +@keyframes ellipse3 { + 0% { + transform: translate(-68px, 0); + } + 100% { + transform: translate(0, 0); + } +} + +@keyframes ellipse4 { + 0% { + transform: translate(-94px, 0); + } + 100% { + transform: translate(0px, 0); + } +} + +@keyframes ellipse5 { + 0% { + transform: translate(42px, 0); + } + 100% { + transform: translate(0px, 0); + } +} + +@keyframes ellipse6 { + 0% { + transform: translate(68px, 0); + } + 100% { + transform: translate(0, 0); + } +} + +@keyframes ellipse7 { + 0% { + transform: translate(94px, 0); + } + 100% { + transform: translate(0, 0); + } +} diff --git a/frontend/demo/src/components/ColorSelector.tsx b/frontend/demo/src/components/ColorSelector.tsx new file mode 100644 index 0000000000..47e78b8b95 --- /dev/null +++ b/frontend/demo/src/components/ColorSelector.tsx @@ -0,0 +1,92 @@ +import React, {useCallback, useState} from 'react'; +import {connect} from 'react-redux'; +import {RootState} from '../reducers'; +import {TagSettings} from '../model/Tag'; + +import styles from './ColorSelector.module.scss'; + +type ColorSelectorProps = { + handleUpdate: (event: React.ChangeEvent) => void; + color: string; + editing?: boolean; + id?: string; +}; + +type ColorSelectorState = { + tagSettings: TagSettings; +}; + +const ColorSelector = ({handleUpdate, color, editing, id, tagSettings}: ColorSelectorProps & ColorSelectorState) => { + const getColorValue = useCallback((color: string) => (tagSettings && tagSettings.colors[color].default) || '1578D4', [ + tagSettings, + ]); + + return ( +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ); +}; + +const mapStateToProps = (state: RootState) => { + return { + tagSettings: state.data.settings, + }; +}; + +export default connect(mapStateToProps)(ColorSelector); diff --git a/frontend/demo/src/components/Dialog.module.scss b/frontend/demo/src/components/Dialog.module.scss new file mode 100644 index 0000000000..3a8cbddac8 --- /dev/null +++ b/frontend/demo/src/components/Dialog.module.scss @@ -0,0 +1,22 @@ +.dialog { + position: absolute; + background-color: white; + border: 1px solid var(--color-light-gray); + border-radius: 4px; + z-index: 4; +} + +.nonOverlayDialog { + @extend .dialog; + position: relative; + width: 233px; +} + +.clickCover { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2; +} diff --git a/frontend/demo/src/components/Dialog.tsx b/frontend/demo/src/components/Dialog.tsx new file mode 100644 index 0000000000..01c1792f95 --- /dev/null +++ b/frontend/demo/src/components/Dialog.tsx @@ -0,0 +1,45 @@ +import React, {Fragment, useEffect, useCallback} from 'react'; + +import styles from './Dialog.module.scss'; + +type DialogProps = { + children: React.ReactNode; + /** Additional style paramaters, for example top/bottom/left/right for positioning of the dialog */ + style?: React.CSSProperties; + /** Additional style for the full background, ideal to dim the rest of the page while the dialog is shown */ + coverStyle?: React.CSSProperties; + /** Should the dialog be an overlay or a normal div? defaults to true, + * meaning: overlay. */ + overlay?: boolean; + close: () => void; +}; + +const Dialog = ({children, close, style, coverStyle, overlay}: DialogProps) => { + const keyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + close(); + } + }, + [close] + ); + + useEffect(() => { + document.addEventListener('keydown', keyDown); + + return () => { + document.removeEventListener('keydown', keyDown); + }; + }, [keyDown]); + + return ( + +
+
+ {children} +
+ + ); +}; + +export default Dialog; diff --git a/frontend/demo/src/components/SearchField.tsx b/frontend/demo/src/components/SearchField.tsx new file mode 100644 index 0000000000..a3e65de008 --- /dev/null +++ b/frontend/demo/src/components/SearchField.tsx @@ -0,0 +1,46 @@ +import React, {createRef, useCallback} from 'react'; + +import closeIcon from '../assets/images/icons/close.svg'; +import searchIcon from '../assets/images/icons/search.svg'; +import styles from './style.module.scss'; + +type SearchFieldProps = { + id?: string; + placeholder?: string; + value: string; + setValue: (value: string) => void; + resetClicked?: () => void; + autoFocus?: boolean; +}; + +export const SearchField = ({id, placeholder, value, setValue, resetClicked, autoFocus}: SearchFieldProps) => { + const inputRef = createRef(); + const resetButton = useCallback(() => { + setValue(''); + if (resetClicked) { + resetClicked(); + } + }, [value, setValue]); + + return ( +
+
+ +
+ setValue(event.target.value)} + type="search" + autoFocus={autoFocus} + /> + {value !== '' && ( + + )} +
+ ); +}; diff --git a/frontend/demo/src/components/Sidebar/index.tsx b/frontend/demo/src/components/Sidebar/index.tsx index 491f05121a..d4ef67d170 100644 --- a/frontend/demo/src/components/Sidebar/index.tsx +++ b/frontend/demo/src/components/Sidebar/index.tsx @@ -2,8 +2,9 @@ import React from 'react'; import {withRouter, Link, matchPath, RouteProps} from 'react-router-dom'; import {ReactComponent as PlugIcon} from '../../assets/images/icons/git-merge.svg'; +import {ReactComponent as TagIcon} from '../../assets/images/icons/price-tag.svg'; -import {CHANNELS_ROUTE} from '../../routes/routes'; +import {CHANNELS_ROUTE, TAGS_ROUTE} from '../../routes/routes'; import styles from './index.module.scss'; @@ -21,6 +22,12 @@ const Sidebar = (props: RouteProps) => { Channels
+
+ + + Tags + +
); diff --git a/frontend/demo/src/components/style.module.scss b/frontend/demo/src/components/style.module.scss new file mode 100644 index 0000000000..b50e4b2915 --- /dev/null +++ b/frontend/demo/src/components/style.module.scss @@ -0,0 +1,65 @@ +@import '../assets/scss/fonts.scss'; + +.component { + display: flex; + flex-direction: row; + border: 1px solid var(--color-light-gray); + border-radius: 8px; + padding: 4px 8px; + + input { + @include font-base; + border: none; + width: 100%; + border-radius: 0; + -webkit-appearance: none; + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button { + -webkit-appearance: none; + } + + &:focus { + border: none; + outline: none; + } + } + + &:focus-within { + border-color: var(--color-airy-blue); + } +} + +.searchIcon { + svg { + width: 22px; + height: 22px; + padding-top: 2px; + margin-right: 4px; + position: relative; + top: 2px; + + path { + fill: var(--color-text-gray); + } + } +} + +.resetButton { + background-color: rgba(0, 0, 0, 0); + border: none; + cursor: pointer; + padding: 0px 4px; +} + +.closeIcon { + svg { + margin: 0; + width: 10px; + height: 10px; + + path { + fill: var(--color-text-gray); + } + } +} diff --git a/frontend/demo/src/model/Channel.ts b/frontend/demo/src/model/Channel.ts new file mode 100644 index 0000000000..47b8db58aa --- /dev/null +++ b/frontend/demo/src/model/Channel.ts @@ -0,0 +1,87 @@ +export interface Channel { + name: string; + source: string; + sourceChannelId: string; + connected: boolean; + imageUrl?: string; +} + +export interface ChannelApiPayload { + id: string; + name: string; + image_url: string; + source: string; + source_channel_id: string; +} + +export interface ExploreChannelRequestPayload { + source: string; + token: string; +} + +export interface ChannelsPayload { + data: Channel[]; +} + +export interface ConnectChannelRequestPayload { + source: string; + sourceChannelId: string; + token: string; + name?: string; + imageUrl?: string; +} + +export interface ConnectChannelRequestApiPayload { + source: string; + source_channel_id: string; + token: string; + name?: string; + image_url?: string; +} + +export interface DisconnectChannelRequestPayload { + channelId: string; +} + +export interface DisconnectChannelRequestApiPayload { + channel_id: string; +} + +export const channelsMapper = (payload: ChannelsPayload, source?: string): Channel[] => { + return payload.data.map( + (entry: Channel): Channel => { + return { + source, + ...entry, + }; + } + ); +}; + +export const channelMapper = (payload: ChannelApiPayload): Channel => { + return { + name: payload.name, + source: payload.source, + sourceChannelId: payload.source_channel_id, + imageUrl: payload.image_url, + connected: true, + }; +}; + +export const connectChannelApiMapper = (payload: ConnectChannelRequestPayload): ConnectChannelRequestApiPayload => { + return { + source: payload.source, + source_channel_id: payload.sourceChannelId, + token: payload.token, + name: payload.name, + image_url: payload.imageUrl, + }; +}; + +export const disconnectChannelApiMapper = ( + payload: DisconnectChannelRequestPayload +): DisconnectChannelRequestApiPayload => { + return { + channel_id: payload.channelId, + }; +}; diff --git a/frontend/demo/src/model/Tag.ts b/frontend/demo/src/model/Tag.ts new file mode 100644 index 0000000000..bea9ec5dbb --- /dev/null +++ b/frontend/demo/src/model/Tag.ts @@ -0,0 +1,76 @@ +export interface Tag { + id: string; + name: string; + color: string; +} + +export interface TagPayload { + id: string; +} + +export interface CreateTagRequestPayload { + name: string; + color: string; +} + +export interface GetTagsResponse { + data: Tag[]; +} + +export interface ColorSettings { + default: string; + background: string; + font: string; + position: number; + border: string; +} + +export interface TagSettings { + colors: ColorSettings[]; + enabled: boolean; + channels: Tag[]; +} + +export interface ErrorTag { + status: string; + data?: string; +} + +export interface ModalType { + modal: { + type: string; + tagId: string; + tagName: string; + delete: string; + error: string; + }; +} + +export const tagsMapper = (serverTags: Tag[]): Tag[] => { + const tags: Tag[] = []; + const _ = serverTags.map((tag: Tag) => { + tag.color = colorMapper(tag.color); + tags.push(tag); + }); + return tags; +}; + +export const colorMapper = (color: string): string => { + switch (color) { + case 'BLUE': + color = 'tag-blue'; + break; + case 'RED': + color = 'tag-red'; + break; + case 'GREEN': + color = 'tag-green'; + break; + case 'PURPLE': + color = 'tag-purple'; + break; + default: + color = 'tag-blue'; + } + return color; +}; diff --git a/frontend/demo/src/pages/Channels/index.module.scss b/frontend/demo/src/pages/Channels/index.module.scss new file mode 100644 index 0000000000..fa4b1f1653 --- /dev/null +++ b/frontend/demo/src/pages/Channels/index.module.scss @@ -0,0 +1,75 @@ +@import '../../assets/scss/fonts'; +@import '../../assets/scss/colors'; + +.headline { + display: flex; + justify-content: space-between; + margin-bottom: 64px; +} + +.headlineText { + @include font-xl; + font-weight: bold; +} + +.channelsWrapper { + width: 100%; + background: white; + padding: 32px; + margin: 88px 2.5em 5em 7.5em; + border-radius: 10px; + box-sizing: border-box; + overflow: visible; + min-height: calc(100vh - 170px); +} + +.connectButton { + @include font-m; + font-weight: 700; + line-height: 16px; + font-size: 20px; + height: 40px; + background-color: var(--color-airy-blue); + color: white; + border-radius: 4px; + text-align: center; + border: none; + cursor: pointer; + padding: 8px 16px; + margin: 0 0; + &:hover { + background-color: var(--color-airy-blue-hover); + } + + &:active { + background: var(--color-airy-blue-pressed); + } + + &:disabled { + cursor: not-allowed; + color: var(--color-text-gray) !important; + background-color: var(--color-light-gray) !important; + border: none; + } +} + +.channelList { + list-style: none; +} + +.channelListEntry { + display: flex; + border-bottom: 1px solid var(--color-light-gray); + align-items: center; + padding: 8px 0; +} + +.channelName { + padding: 0 16px; +} + +.channelAction { + flex-grow: 1; + justify-content: right; + display: flex; +} diff --git a/frontend/demo/src/pages/Channels/index.tsx b/frontend/demo/src/pages/Channels/index.tsx new file mode 100644 index 0000000000..26bc2d3aba --- /dev/null +++ b/frontend/demo/src/pages/Channels/index.tsx @@ -0,0 +1,117 @@ +/* global FB */ +import React, {useCallback, useEffect, useState} from 'react'; +import {connect, ConnectedProps} from 'react-redux'; +import {RouteComponentProps} from 'react-router-dom'; +import FacebookLogin from 'react-facebook-login'; +import {Button} from '@airyhq/components'; + +import {AiryConfig} from '../../api/airyConfig'; +import {Channel} from '../../model/Channel'; +import {getChannels, exploreChannels, connectChannel, disconnectChannel} from '../../actions/channel'; +import {StateModel} from '../../reducers/index'; + +import styles from './index.module.scss'; + +const mapDispatchToProps = { + getChannels, + exploreChannels, + connectChannel, + disconnectChannel, +}; + +const mapStateToProps = (state: StateModel) => { + return { + channels: state.data.channels, + }; +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type ChannelsConnectProps = {} & ConnectedProps & RouteComponentProps; + +const Channels = (props: ChannelsConnectProps) => { + const [facebookToken, setFacebookToken] = useState(''); + useEffect(() => { + props.getChannels(); + }, []); + + const connect = (token: string) => { + props.exploreChannels({ + source: 'facebook', + token, + }); + }; + + const fetchPages = () => { + FB.getLoginStatus(loginResponse => { + if (loginResponse.status === 'connected') { + setFacebookToken(loginResponse.authResponse.accessToken); + connect(loginResponse.authResponse.accessToken); + } else { + FB.login(loginResponse => { + setFacebookToken(loginResponse.authResponse.accessToken); + connect(loginResponse.authResponse.accessToken); + }); + } + }); + }; + + const connectClicked = useCallback( + (channel: Channel) => { + props.connectChannel({ + source: channel.source, + sourceChannelId: channel.sourceChannelId, + token: facebookToken, + }); + }, + [facebookToken] + ); + + const disconnectClicked = (channel: Channel) => { + props.disconnectChannel({channelId: channel.sourceChannelId}); + }; + + return ( +
+
+

Channels

+ ( + + )} + /> +
+
    + {props.channels.map((channel: Channel) => ( +
  • + +
    {channel.name}
    +
    + {channel.connected ? ( + + ) : ( + + )} +
    +
  • + ))} +
+
+ ); +}; + +export default connector(Channels); diff --git a/frontend/demo/src/pages/Login/index.tsx b/frontend/demo/src/pages/Login/index.tsx index 7175bed560..34c7642be0 100644 --- a/frontend/demo/src/pages/Login/index.tsx +++ b/frontend/demo/src/pages/Login/index.tsx @@ -28,8 +28,11 @@ const Login = (props: LoginConnectProps) => { email: formData.get('email') as string, password: formData.get('password') as string, }) - .then((success: boolean) => { - return success ? props.history.push('/') : setCredentialError(true); + .then(() => { + props.history.push('/'); + }) + .catch(() => { + setCredentialError(true); }); }; @@ -92,4 +95,4 @@ const Login = (props: LoginConnectProps) => { ); }; -export default connect(null, mapDispatchToProps)(Login); +export default connector(Login); diff --git a/frontend/demo/src/pages/Tags/EmptyStateTags.tsx b/frontend/demo/src/pages/Tags/EmptyStateTags.tsx new file mode 100644 index 0000000000..277b315aa0 --- /dev/null +++ b/frontend/demo/src/pages/Tags/EmptyStateTags.tsx @@ -0,0 +1,34 @@ +import React, {useState} from 'react'; + +import styles from './index.module.scss'; +import {Button} from '@airyhq/components'; +import emptyImage from '../../assets/images/empty-state/tags-empty-state.svg'; +import SimpleTagForm from './SimpleTagForm'; + +const EmptyStateTags = () => { + const [show, setShow] = useState(false); + + return ( + <> + {!show && ( +
+
+

You don't have tags yet.

+

+ Tags provide a useful way to group related conversations together and to quickly filter and search them. +

+ + +
+
+ )} + {show && ( +
+ setShow(false)} /> +
+ )} + + ); +}; + +export default EmptyStateTags; diff --git a/frontend/demo/src/pages/Tags/FAKESETTINGS.ts b/frontend/demo/src/pages/Tags/FAKESETTINGS.ts new file mode 100644 index 0000000000..05a31a2619 --- /dev/null +++ b/frontend/demo/src/pages/Tags/FAKESETTINGS.ts @@ -0,0 +1,11 @@ +export const fakeData = () => { + return { + colors: { + 'tag-green': {default: '0E764F', background: 'F5FFFB', font: '0E764F', position: 3, border: '0E764F'}, + 'tag-blue': {default: '1578D4', background: 'F1FAFF', font: '1578D4', position: 1, border: '1578D4'}, + 'tag-red': {default: 'E0243A', background: 'FFF7F9', font: 'E0243A', position: 2, border: 'E0243A'}, + 'tag-purple': {default: '730A80', background: 'FEF7FF', font: '730A80', position: 4, border: '730A80'}, + enabled: true, + }, + }; +}; diff --git a/frontend/demo/src/pages/Tags/SimpleTagForm.module.scss b/frontend/demo/src/pages/Tags/SimpleTagForm.module.scss new file mode 100644 index 0000000000..9c7fdda551 --- /dev/null +++ b/frontend/demo/src/pages/Tags/SimpleTagForm.module.scss @@ -0,0 +1,44 @@ +@import '../../assets/scss/fonts.scss'; +@import '../../assets/scss/colors.scss'; + +.tagCreate { + padding: 1em; + max-width: 700px; + @media screen and (max-width: 850px) { + max-width: 500px; + } + + .headline { + font-weight: 900; + color: var(--color-text-gray); + } + + .errorMessage { + color: var(--color-red-alert); + width: 100%; + margin: 0; + padding: 0; + } + + .buttonRow { + margin-top: 16px; + } + + .searchTags { + padding-left: 8px; + } + + .description { + color: var(--color-text-gray); + @include font-base; + width: 100%; + margin: 0; + padding: 0; + } + + .tagName { + display: inline-block; + padding: 3px 8px 5px; + margin: 8px 0; + } +} diff --git a/frontend/demo/src/pages/Tags/SimpleTagForm.tsx b/frontend/demo/src/pages/Tags/SimpleTagForm.tsx new file mode 100644 index 0000000000..8696d1ad97 --- /dev/null +++ b/frontend/demo/src/pages/Tags/SimpleTagForm.tsx @@ -0,0 +1,113 @@ +import React, {useState, Fragment} from 'react'; +import {connect} from 'react-redux'; + +import {createTag, getTags, errorTag, filterTags} from '../../actions/tags'; +import {filteredTags} from '../../selectors/tags'; + +import {Button, Input} from '@airyhq/components'; +import Dialog from '../../components/Dialog'; +import ColorSelector from '../../components/ColorSelector'; + +import Tag from '../../pages/Tags/Tag'; +import {Tag as TagModel, CreateTagRequestPayload, ErrorTag} from '../../model/Tag'; + +import styles from './SimpleTagForm.module.scss'; +import {RootState} from '../../reducers'; + +type SimpleTagFormProps = { + errorMessage: string; + createTag: (CreateTagRequestPayload) => Promise; + errorTag: (ErrorTag) => void; + onClose: () => void; + tags: TagModel[]; +}; + +const SimpleTagForm = ({errorMessage, createTag, errorTag, onClose, tags}: SimpleTagFormProps) => { + const [name, setName] = useState(''); + const [color, setColor] = useState('tag-blue'); + const [showError, setShowError] = useState(true); + const handleCreate = () => { + if (name.trim().length) { + createTag({name: name.trim(), color}).then((success: boolean) => { + if (success) { + errorTag({status: ''}); + onClose(); + } else { + setShowError(true); + } + }); + } else { + errorTag({status: 'empty', data: ''}); + } + }; + + const keyPressed = (e: React.KeyboardEvent) => { + const code = e.keyCode || e.which; + if (code === 13) { + handleCreate(); + } else if (code === 27) { + onClose(); + } + }; + + return ( + +
+

Add a tag

+ ) => { + setName(e.target.value); + setShowError(false); + }} + onKeyDown={keyPressed} + height={32} + value={name} + name="tag_name" + placeholder="Please enter a tag name" + autoComplete="off" + autoFocus={true} + fontClass="font-m" + maxLength={50} + /> +

{(!name.length || showError) && errorMessage}

+ {name && ( +
+ +
+ )} + +

Pick a color

+ ) => setColor(e.target.value)} + color={color} + editing={true} + /> +
+ +
+
+
+
+ ); +}; + +const mapStateToProps = (state: RootState) => { + return { + tags: filteredTags(state), + errorMessage: state.data.tags.error, + }; +}; + +const mapDispatchToProps = { + createTag, + errorTag, + getTags, + filterTags, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export default connector(SimpleTagForm); diff --git a/frontend/demo/src/pages/Tags/TableRow.module.scss b/frontend/demo/src/pages/Tags/TableRow.module.scss new file mode 100644 index 0000000000..5460b9cd4b --- /dev/null +++ b/frontend/demo/src/pages/Tags/TableRow.module.scss @@ -0,0 +1,77 @@ +@import '../../assets/scss/colors.scss'; + +.tableRow { + cursor: pointer; + &:hover { + background-color: var(--color-background-blue); + } + border-bottom: 1px solid var(--color-background-gray); +} + +.tableCell { + padding: 17px 0; + padding-right: 4px; +} + +.tagColor { + display: inline-block; + width: 24px; + height: 24px; + border-radius: 50%; + line-height: 44px; + margin-top: 6px; +} + +.editInput { + border: 1px solid #cad5db; + background-color: #f7f7f7; + border-radius: 4px; + padding: 6px; + font-size: 16px; + &::placeholder { + color: var(--color-text-gray); + } + + &:focus { + outline: none; + background-color: var(--color-background-blue); + border: 1px solid var(--color-airy-blue); + } +} + +.cancelButton { + margin-top: 4px; +} + +.actions { + width: 100%; + display: flex; + justify-content: flex-end; + padding-right: 8px; + align-items: center; + + > div, + > button { + margin-left: 8px; + } +} + +.actionSVG { + width: 16px; + height: 16px; + path { + fill: var(--color-dark-elements-gray); + } + &:hover { + path { + fill: var(--color-airy-blue); + } + } +} + +.actionButton { + cursor: pointer; + border: none; + background: none; + padding: 0; +} diff --git a/frontend/demo/src/pages/Tags/TableRow.tsx b/frontend/demo/src/pages/Tags/TableRow.tsx new file mode 100644 index 0000000000..60bc253533 --- /dev/null +++ b/frontend/demo/src/pages/Tags/TableRow.tsx @@ -0,0 +1,157 @@ +import React, {useState, useCallback} from 'react'; +import _, {connect, ConnectedProps} from 'react-redux'; + +import styles from './TableRow.module.scss'; +import {updateTag} from '../../actions/tags'; +import {Button, LinkButton} from '@airyhq/components'; +import {ReactComponent as EditIcon} from '../../assets/images/icons/edit.svg'; +import {ReactComponent as TrashIcon} from '../../assets/images/icons/trash.svg'; +import ColorSelector from '../../components/ColorSelector'; +import Tag from './Tag'; +import {Tag as TagModel, TagSettings} from '../../model/Tag'; +import {RootState} from '../../reducers'; + +type TableRowProps = { + tag: TagModel; + tagSettings: TagSettings; + showModal(label: string, id: string, name: string): void; +} & ConnectedProps; + +const TableRowComponent = (props: TableRowProps) => { + const {tag, updateTag, tagSettings, showModal} = props; + + const [tagState, setTagState] = useState({ + edit: false, + id: '', + name: '', + color: '', + }); + + const handleUpdate = useCallback( + (e: React.ChangeEvent) => { + e.persist(); + if (e.target.name === 'tag_name') { + setTagState({...tagState, name: e.target && e.target.value}); + } else { + setTagState({...tagState, color: e.target && e.target.value}); + } + }, + [tagState, setTagState] + ); + + const handleTagUpdate = useCallback(() => { + const currentTag = { + id: tag.id, + name: tagState.name, + color: tagState.color, + }; + updateTag(currentTag); + setTagState({ + ...tagState, + edit: false, + }); + }, [tag, tagState, updateTag, setTagState]); + + const cancelTagUpdate = useCallback(() => { + setTagState({ + ...tagState, + edit: false, + }); + }, [setTagState]); + + const onTagKeyPressed = useCallback( + (e: React.KeyboardEvent) => { + const code = e.keyCode || e.which; + if (code === 13 && tagState.name.length) { + handleTagUpdate(); + } else if (code === 27) { + cancelTagUpdate(); + } + }, + [tagState, handleTagUpdate, cancelTagUpdate] + ); + + const deleteClicked = useCallback( + (event: React.BaseSyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + showModal('confirmDelete', tag.id, tag.name); + }, + [showModal, tag] + ); + + const getColorValue = useCallback((color: string) => (tagSettings && tagSettings.colors[color].default) || '1578D4', [ + tagSettings, + ]); + + const isEditing = tagState.edit && tagState.id === tag.id; + + if (isEditing) { + return ( + + + + + + + + + +
+ +
+ Cancel +
+
+ + + ); + } + + return ( + setTagState({...tag, edit: true})}> + + + + + + + + +
+ + +
+ + + ); +}; + +const mapStateToProps = (state: RootState) => { + return { + tagSettings: state.data.settings, + }; +}; + +const mapDispatchToProps = { + updateTag, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export const TableRow = connector(TableRowComponent); diff --git a/frontend/demo/src/pages/Tags/Tag.module.scss b/frontend/demo/src/pages/Tags/Tag.module.scss new file mode 100644 index 0000000000..b18c1dcd24 --- /dev/null +++ b/frontend/demo/src/pages/Tags/Tag.module.scss @@ -0,0 +1,74 @@ +.tag { + display: inline-block; + padding: 4px; + max-width: 100%; +} + +.tagInner { + display: inline-flex; + justify-content: space-between; + align-items: center; + padding: 0px 8px; + background: var(--color-airy-blue); + color: #fff; + border-radius: 16px; + line-height: 24px; + max-width: 100%; + white-space: nowrap; + + &:hover { + word-break: break-all; + white-space: normal; + line-height: 16px; + padding: 4px 8px; + } + + &.clickable { + cursor: pointer; + } +} + +.removeTag { + cursor: pointer; + margin-bottom: auto; +} + +.closeButton { + display: inline-block; + padding-left: 4px; + + svg { + path { + fill: #fff; + } + } +} + +.tagName { + min-width: 0; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; +} + +.tagNameExpanded { + @extend .tagName; + white-space: unset; + overflow-wrap: break-word; + margin-right: 4px; +} + +.#{tag-} { + &green { + background-color: var(--color-tag-green); + } + &blue { + background-color: var(--color-airy-blue); + } + &red { + background-color: var(--color-tag-red); + } + &purple { + background-color: var(--color-tag-purple); + } +} diff --git a/frontend/demo/src/pages/Tags/Tag.tsx b/frontend/demo/src/pages/Tags/Tag.tsx new file mode 100644 index 0000000000..8673fce04e --- /dev/null +++ b/frontend/demo/src/pages/Tags/Tag.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import {Tag as TagModel} from '../../model/Tag'; + +import close from '../../assets/images/icons/close.svg'; +import styles from './Tag.module.scss'; +import {RootState} from '../../reducers'; +import {TagSettings} from '../../model/Tag'; + +type TagProps = { + tag: TagModel; + expanded?: boolean; + onClick?: () => void; + removeTagFromContact?: () => void; + variant?: 'default' | 'light'; + type?: string; +}; + +type tagState = { + tagSettings: TagSettings; +}; + +export const Tag = ({tag, expanded, variant, onClick, removeTagFromContact, tagSettings}: TagProps & tagState) => { + const tagColor = (tagSettings && tagSettings.colors[tag.color]) || { + background: 'F1FAFF', + border: '1578D4', + default: '1578D4', + font: '1578D4', + }; + + const tagStyle = () => { + if (variant === 'light') { + return { + backgroundColor: `#${tagColor.background}`, + color: `#${tagColor.font}`, + border: `1px solid #${tagColor.border}`, + }; + } + + return {backgroundColor: `#${tagColor.default}`}; + }; + + return ( +
+
+ {tag.name} + {removeTagFromContact && ( + + + + )} +
+
+ ); +}; + +const mapStateToProps = (state: RootState) => { + return { + tagSettings: state.data.settings, + }; +}; + +export default connect(mapStateToProps)(Tag); diff --git a/frontend/demo/src/pages/Tags/index.module.scss b/frontend/demo/src/pages/Tags/index.module.scss new file mode 100644 index 0000000000..50591516b5 --- /dev/null +++ b/frontend/demo/src/pages/Tags/index.module.scss @@ -0,0 +1,187 @@ +@import '../../assets/scss/fonts.scss'; +@import '../../assets/scss/colors.scss'; + +.tagsWrapper { + .cardRaised { + width: 100%; + background: #fff; + } + + display: block; + padding-left: 96px; + padding-top: 88px; + width: 100%; + overflow-y: auto; +} + +.organizationSectionHeadline { + @include font-l; + font-weight: bold; + margin-bottom: 48px; +} + +.organizationContainer { + position: relative; + height: auto; + transition: height 0.3s; +} + +.organizationContainerSettingsItem { + display: flex; + flex-direction: column; + margin-bottom: 20px; + &:last-child { + margin-bottom: 0; + } +} + +.cardRaised { + padding: 1.5em; + border-radius: 8px 0px 8px 0px; + width: 94%; + color: var(--color-text-contrast); + h2 { + @include font-m; + } + &.accountStatus { + padding-bottom: 5px; + & > div { + padding-bottom: 5px; + } + a { + color: var(--color-airy-blue); + } + } + &.airySupport { + padding-bottom: 0.5em; + a { + color: #212529; + @include font-base; + } + .faqLink { + padding-top: 12px; + margin-top: 10px; + border-top: 1px solid var(--color-airy-blue); + } + } +} + +.tagsHeader { + display: flex; + align-items: flex-start; + flex-wrap: nowrap; + padding-bottom: 10px; +} + +.searchContainer { + display: inline-block; + flex-grow: 1; + margin-bottom: 27px; +} + +.searchIcon { + position: absolute; + top: 9px; + left: 9px; +} + +.addButton { + display: flex; + flex-direction: row; + margin-left: 8px; + margin-top: 6px; + padding-top: 4px; + padding-bottom: 6px; + position: relative; + color: var(--color-airy-blue); + cursor: pointer; + background: white; + border: none; + &:hover { + color: var(--color-airy-blue-hover); + .plusButton { + svg { + path { + fill: var(--color-airy-blue-hover); + } + } + } + } + + .plusButton { + display: inherit; + margin-left: 4px; + svg { + width: 12px; + position: relative; + top: 1px; + } + } +} + +.confirmDelete { + @include font-base; + p { + margin-bottom: 8px; + max-width: 480px; + strong { + font-weight: 700; + word-wrap: break-word; + } + } +} + +.confirmDeleteActions { + text-align: center; + + button { + margin-right: 16px; + margin-top: 16px; + } +} + +.tagsTable { + width: 100%; +} + +.tagsTableHeader { + @include font-m; + font-weight: 700; + text-align: left; + padding-bottom: 41px; + padding-left: 2px; +} + +.emptyStateTitle { + position: relative; + left: 40%; + padding-top: 170px; + padding-bottom: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + + h1 { + color: var(--color-airy-blue); + @include font-m; + } + + p { + color: var(--color-text-gray); + @include font-base; + padding-top: 8px; + padding-bottom: 32px; + width: 25%; + } + + img { + margin-bottom: 40px; + } +} + +.noResultHeadline { + @include font-m; + font-weight: bold; + color: var(--color-text-contrast); + margin-bottom: 4px; +} diff --git a/frontend/demo/src/pages/Tags/index.tsx b/frontend/demo/src/pages/Tags/index.tsx new file mode 100644 index 0000000000..0d188fd072 --- /dev/null +++ b/frontend/demo/src/pages/Tags/index.tsx @@ -0,0 +1,225 @@ +import React, {Component} from 'react'; +import _, {connect, ConnectedProps} from 'react-redux'; + +import {SettingsModal, LinkButton, Button, SearchField, Input} from '@airyhq/components'; + +import plus from '../../assets/images/icons/plus.svg'; + +import {getTags, deleteTag, filterTags, errorTag} from '../../actions/tags'; +import {fakeSettingsAPICall} from '../../actions/settings'; +import {filteredTags} from '../../selectors/tags'; +import {Tag} from '../../model/Tag'; + +import styles from './index.module.scss'; +import {TableRow} from './TableRow'; +import SimpleTagForm from './SimpleTagForm'; +import EmptyStateTags from './EmptyStateTags'; +import {RootState} from '../../reducers'; +import {ModalType} from '../../model/Tag'; + +const initialState = { + modal: { + type: null, + tagId: null, + tagName: null, + delete: '', + error: '', + }, + tagQuery: '', + createDrawer: false, +}; + +class TagsComponent extends Component, typeof initialState> { + state = initialState; + + componentDidMount() { + this.props.getTags(); + this.props.fakeSettingsAPICall(); + this.props.filterTags(''); + } + + handleSearch = (value: string) => { + this.setState({ + tagQuery: value, + }); + this.props.filterTags(value); + }; + + handleDelete = (e: React.ChangeEvent) => { + e.persist(); + this.setState((state: ModalType) => { + return { + modal: { + ...state.modal, + delete: e.target && e.target.value, + }, + }; + }); + }; + + handleTagDrawer = () => { + this.setState({ + createDrawer: !this.state.createDrawer, + }); + this.props.errorTag(''); + }; + + keyPressed = (e: React.KeyboardEvent) => { + const code = e.keyCode || e.which; + if (code === 13) { + e.preventDefault(); + this.confirmDelete(); + } else if (code === 27) { + e.preventDefault(); + this.closeModal(); + } + }; + + showModal = (modalType: string, id: string, name: string) => { + this.setState({ + modal: { + type: modalType, + tagId: id, + tagName: name, + delete: '', + error: '', + }, + }); + }; + + closeModal = () => { + this.setState({ + modal: { + type: null, + tagId: null, + tagName: '', + delete: '', + error: '', + }, + }); + }; + + confirmDelete = () => { + if (this.state.modal.delete.toLowerCase() === 'delete') { + this.props.deleteTag(this.state.modal.tagId); + this.closeModal(); + } else { + this.setState((state: ModalType) => { + return { + modal: { + ...state.modal, + error: "Please type 'delete' in the input field before deleting", + }, + }; + }); + } + }; + + renderConfirmDelete = () => { + if (this.state.modal.type === 'confirmDelete') { + return ( + +
+

+ You're about to permanently delete "{this.state.modal.tagName}" from your organization's + tags. +

+

+ This action cannot be undone. Once you delete the tag, no one in your organization will + be able to use it. It will also removed from all corresponding contacts. +

+

+ Type DELETE to confirm: +

+ +

{this.state.modal.error}

+
+ Cancel + +
+
+
+ ); + } + }; + + renderTagList() { + const {tags} = this.props; + return ( +
+
+

Tags

+
+
+
+
+ +
+ +
+ {this.state.createDrawer && } + {tags.length > 0 ? ( + + + + + + + {tags && + tags.map((tag: Tag, idx: number) => { + return ; + })} + +
Tag nameColor +
+ ) : ( +
+

Result not found.

+

Try to search for a different term.

+
+ )} +
+ {this.renderConfirmDelete()} +
+ ); + } + + render() { + const {allTagsCount} = this.props; + return
{allTagsCount == 0 ? : this.renderTagList()}
; + } +} + +const mapStateToProps = (state: RootState) => ({ + tags: filteredTags(state), + allTagsCount: state.data.tags.all.length, + tagQuery: state.data.tags.query, + errorMessage: state.data.tags.error, + userData: state.data.user, +}); + +const mapDispatchToProps = { + getTags, + deleteTag, + errorTag, + filterTags, + fakeSettingsAPICall, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export const Tags = connector(TagsComponent); diff --git a/frontend/demo/src/reducers/data/channels/index.ts b/frontend/demo/src/reducers/data/channels/index.ts new file mode 100644 index 0000000000..ade05218ad --- /dev/null +++ b/frontend/demo/src/reducers/data/channels/index.ts @@ -0,0 +1,26 @@ +import {ActionType, getType} from 'typesafe-actions'; +import {Channel} from '../../../model/Channel'; +import * as actions from '../../../actions/channel'; +import {unionWith} from 'lodash-es'; + +type Action = ActionType; + +export const initialState = []; + +const mergeChannels = (channels: Channel[], newChannels: Channel[]) => + unionWith(newChannels, channels, (channelA: Channel, channelB: Channel) => { + return channelA.sourceChannelId === channelB.sourceChannelId; + }); + +const channelsReducer: any = (state = initialState, action: Action): Channel[] | {} => { + switch (action.type) { + case getType(actions.setCurrentChannelsAction): + return action.payload; + case getType(actions.addChannelsAction): + return mergeChannels(state, action.payload); + default: + return state; + } +}; + +export default channelsReducer; diff --git a/frontend/demo/src/reducers/data/index.ts b/frontend/demo/src/reducers/data/index.ts index 55b3dbeca0..b42bad56f1 100644 --- a/frontend/demo/src/reducers/data/index.ts +++ b/frontend/demo/src/reducers/data/index.ts @@ -1,14 +1,26 @@ import _, {combineReducers, Reducer} from 'redux-starter-kit'; import {User} from '../../model/User'; +import {Tags} from './tags'; +import {Settings} from './settings'; +import {Channel} from '../../model/Channel'; import user from './user'; +import tags from './tags'; +import settings from './settings'; +import channels from './channels'; export type DataState = { user: User; + tags: Tags; + settings: Settings; + channels: Channel[]; }; const reducers: Reducer = combineReducers({ user, + tags, + settings, + channels, }); export default reducers; diff --git a/frontend/demo/src/reducers/data/settings/index.ts b/frontend/demo/src/reducers/data/settings/index.ts new file mode 100644 index 0000000000..ceb6d1dc26 --- /dev/null +++ b/frontend/demo/src/reducers/data/settings/index.ts @@ -0,0 +1,29 @@ +import {ActionType} from 'typesafe-actions'; +import * as actions from '../../../actions/settings'; +import {DataState} from '../../data'; + +type Action = ActionType; + +export type SettingsState = { + data: DataState; +}; + +export type Settings = { + colors: {}; +}; + +const defaultState = { + colors: [], +}; + +export default function tagsReducer(state = defaultState, action: Action): Settings { + switch (action.type) { + case actions.ADD_SETTINGS_TO_STORE: + return { + ...state, + colors: action.colors.colors, + }; + default: + return state; + } +} diff --git a/frontend/demo/src/reducers/data/tags/index.ts b/frontend/demo/src/reducers/data/tags/index.ts new file mode 100644 index 0000000000..da2aa61de7 --- /dev/null +++ b/frontend/demo/src/reducers/data/tags/index.ts @@ -0,0 +1,84 @@ +import {ActionType, getType} from 'typesafe-actions'; +import * as actions from '../../../actions/tags'; +import {Tag} from '../../../model/Tag'; +import {DataState} from '../../data'; + +type Action = ActionType; + +export type TagState = { + data: DataState; +}; + +export type Tags = { + all: Tag[]; + query: string; + error: string; +}; + +const defaultState = { + all: [], + query: '', + error: '', +}; + +const errorMessage = (status: number | string) => { + switch (status) { + case 'empty': + return 'Please enter a name for the tag'; + case 400: + case 422: + return 'A tag with this name already exists, please choose a different name'; + case 406: + return 'Please try again later'; + default: + return ''; + } +}; + +export default function tagsReducer(state = defaultState, action: Action): any { + switch (action.type) { + case getType(actions.fetchTagAction): + return { + ...state, + all: action.payload, + }; + case getType(actions.deleteTagAction): + return { + ...state, + all: state.all.filter((tag: Tag) => tag.id !== action.payload), + }; + case getType(actions.addTagAction): + let updatedTag = false; + const mappedTags = state.all.map((tag: Tag) => { + if (tag.id === action.payload.id) { + updatedTag = true; + return { + ...tag, + ...action.payload, + }; + } + return tag; + }); + return { + ...state, + all: updatedTag ? mappedTags : state.all.concat([action.payload]), + }; + case getType(actions.editTagAction): + return { + ...state, + all: state.all.map((tag: Tag) => (tag.id === action.payload.id ? action.payload : tag)), + }; + case getType(actions.errorTagAction): + return { + ...state, + error: errorMessage(action.payload), + }; + case getType(actions.filterTagAction): + return { + ...state, + query: action.payload, + }; + default: + return state; + } +} diff --git a/frontend/demo/src/reducers/index.ts b/frontend/demo/src/reducers/index.ts index de0df62237..393e5fa276 100644 --- a/frontend/demo/src/reducers/index.ts +++ b/frontend/demo/src/reducers/index.ts @@ -15,6 +15,8 @@ const applicationReducer = combineReducers({ data, }); +export type RootState = ReturnType; + const rootReducer: (state: any, action: any) => CombinedState = (state, action) => { if (action.type === getType(authActions.logoutUserAction)) { clearUserData(); diff --git a/frontend/demo/src/routes/routes.ts b/frontend/demo/src/routes/routes.ts index 5c5a4e24d0..261238730c 100644 --- a/frontend/demo/src/routes/routes.ts +++ b/frontend/demo/src/routes/routes.ts @@ -1,5 +1,6 @@ export const ROOT_ROUTE = '/'; export const LOGIN_ROUTE = '/login'; export const CHANNELS_ROUTE = '/channels'; +export const TAGS_ROUTE = '/tags'; export const ROUTES_WITHOUT_NAVBAR = [LOGIN_ROUTE]; diff --git a/frontend/demo/src/selectors/tags.ts b/frontend/demo/src/selectors/tags.ts new file mode 100644 index 0000000000..a1f8046480 --- /dev/null +++ b/frontend/demo/src/selectors/tags.ts @@ -0,0 +1,21 @@ +import _redux from 'redux'; +import _, {createSelector} from 'reselect'; +import {Tag} from '../model/Tag'; +import {RootState} from '../reducers'; + +const tags = (state: RootState) => state.data.tags.all; +const queries = (state: RootState) => state.data.tags.query; +const filter = (tags: Tag[], filter: string) => { + if (filter === '') { + return tags; + } + return ( + filter && + filter.length && + tags.filter((tag: Tag) => { + return tag.name.toLowerCase().includes(filter.toLowerCase()); + }) + ); +}; + +export const filteredTags = createSelector(tags, queries, filter); diff --git a/go_repositories.bzl b/go_repositories.bzl index 11f222c443..86bc1ef979 100644 --- a/go_repositories.bzl +++ b/go_repositories.bzl @@ -13,6 +13,13 @@ def go_repositories(): sum = "h1:kohgdtN58KW/r9ZDVmMJE3MrfbumwsDQStd0LPAGmmw=", version = "v2.13.3", ) + + go_repository( + name = "com_github_armon_consul_api", + importpath = "github.com/armon/consul-api", + sum = "h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=", + version = "v0.0.0-20180202201655-eb2c6b5be1b6", + ) go_repository( name = "com_github_cespare_xxhash_v2", importpath = "github.com/cespare/xxhash/v2", @@ -37,11 +44,30 @@ def go_repositories(): sum = "h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=", version = "v0.0.0-20180213035817-a1ea475d72b1", ) + + go_repository( + name = "com_github_coreos_etcd", + importpath = "github.com/coreos/etcd", + sum = "h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=", + version = "v3.3.10+incompatible", + ) + go_repository( + name = "com_github_coreos_go_etcd", + importpath = "github.com/coreos/go-etcd", + sum = "h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=", + version = "v2.0.0+incompatible", + ) + go_repository( + name = "com_github_coreos_go_semver", + importpath = "github.com/coreos/go-semver", + sum = "h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=", + version = "v0.2.0", + ) go_repository( name = "com_github_davecgh_go_spew", importpath = "github.com/davecgh/go-spew", - sum = "h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=", - version = "v1.1.0", + sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=", + version = "v1.1.1", ) go_repository( name = "com_github_dgryski_go_rendezvous", @@ -52,8 +78,8 @@ def go_repositories(): go_repository( name = "com_github_fsnotify_fsnotify", importpath = "github.com/fsnotify/fsnotify", - sum = "h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=", - version = "v1.4.9", + sum = "h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=", + version = "v1.4.7", ) go_repository( name = "com_github_go_redis_redis_v8", @@ -73,12 +99,50 @@ def go_repositories(): sum = "h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=", version = "v0.5.1", ) + + go_repository( + name = "com_github_hashicorp_hcl", + importpath = "github.com/hashicorp/hcl", + sum = "h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=", + version = "v1.0.0", + ) go_repository( name = "com_github_hpcloud_tail", importpath = "github.com/hpcloud/tail", sum = "h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=", version = "v1.0.0", ) + go_repository( + name = "com_github_kr_pretty", + importpath = "github.com/kr/pretty", + sum = "h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=", + version = "v0.2.1", + ) + go_repository( + name = "com_github_kr_pty", + importpath = "github.com/kr/pty", + sum = "h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_kr_text", + importpath = "github.com/kr/text", + sum = "h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=", + version = "v0.1.0", + ) + + go_repository( + name = "com_github_magiconair_properties", + importpath = "github.com/magiconair/properties", + sum = "h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=", + version = "v1.8.0", + ) + go_repository( + name = "com_github_mitchellh_mapstructure", + importpath = "github.com/mitchellh/mapstructure", + sum = "h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=", + version = "v1.1.2", + ) go_repository( name = "com_github_nxadm_tail", importpath = "github.com/nxadm/tail", @@ -97,12 +161,56 @@ def go_repositories(): sum = "h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=", version = "v1.10.2", ) + + go_repository( + name = "com_github_pelletier_go_toml", + importpath = "github.com/pelletier/go-toml", + sum = "h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=", + version = "v1.2.0", + ) go_repository( name = "com_github_pmezard_go_difflib", importpath = "github.com/pmezard/go-difflib", sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=", version = "v1.0.0", ) + + go_repository( + name = "com_github_spf13_afero", + importpath = "github.com/spf13/afero", + sum = "h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=", + version = "v1.1.2", + ) + go_repository( + name = "com_github_spf13_cast", + importpath = "github.com/spf13/cast", + sum = "h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=", + version = "v1.3.0", + ) + go_repository( + name = "com_github_spf13_cobra", + importpath = "github.com/spf13/cobra", + sum = "h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=", + version = "v0.0.3", + ) + go_repository( + name = "com_github_spf13_jwalterweatherman", + importpath = "github.com/spf13/jwalterweatherman", + sum = "h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_spf13_pflag", + importpath = "github.com/spf13/pflag", + sum = "h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=", + version = "v1.0.3", + ) + go_repository( + name = "com_github_spf13_viper", + importpath = "github.com/spf13/viper", + sum = "h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=", + version = "v1.3.1", + ) go_repository( name = "com_github_stretchr_objx", importpath = "github.com/stretchr/objx", @@ -115,6 +223,19 @@ def go_repositories(): sum = "h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=", version = "v1.6.1", ) + + go_repository( + name = "com_github_ugorji_go_codec", + importpath = "github.com/ugorji/go/codec", + sum = "h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=", + version = "v0.0.0-20181204163529-d75b2dcb6bc8", + ) + go_repository( + name = "com_github_xordataexchange_crypt", + importpath = "github.com/xordataexchange/crypt", + sum = "h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=", + version = "v0.0.3-0.20170626215501-b2862e3d0a77", + ) go_repository( name = "com_github_yuin_gopher_lua", importpath = "github.com/yuin/gopher-lua", @@ -142,8 +263,8 @@ def go_repositories(): go_repository( name = "in_gopkg_yaml_v2", importpath = "gopkg.in/yaml.v2", - sum = "h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=", - version = "v2.3.0", + sum = "h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=", + version = "v2.2.2", ) go_repository( name = "in_gopkg_yaml_v3", @@ -151,6 +272,13 @@ def go_repositories(): sum = "h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=", version = "v3.0.0-20200313102051-9f266ea9e77c", ) + go_repository( + name = "io_goji", + importpath = "goji.io", + sum = "h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c=", + version = "v2.0.2+incompatible", + ) + go_repository( name = "io_opentelemetry_go_otel", importpath = "go.opentelemetry.io/otel", @@ -167,8 +295,8 @@ def go_repositories(): go_repository( name = "org_golang_x_crypto", importpath = "golang.org/x/crypto", - sum = "h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=", - version = "v0.0.0-20190308221718-c2843e01d9a2", + sum = "h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=", + version = "v0.0.0-20181203042331-505ab145d0a9", ) go_repository( name = "org_golang_x_net", @@ -185,14 +313,14 @@ def go_repositories(): go_repository( name = "org_golang_x_sys", importpath = "golang.org/x/sys", - sum = "h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=", - version = "v0.0.0-20200519105757-fe76b779f299", + sum = "h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=", + version = "v0.0.0-20181205085412-a5c9d58dba9a", ) go_repository( name = "org_golang_x_text", importpath = "golang.org/x/text", - sum = "h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=", - version = "v0.3.2", + sum = "h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=", + version = "v0.3.0", ) go_repository( name = "org_golang_x_tools", diff --git a/infrastructure/README.md b/infrastructure/README.md index d6c64ac516..772cc2aa80 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -24,7 +24,7 @@ All of the necessary services exposed with [Traefik](https://traefik.io/) ingres Through that ingress controller, the internal services are exposed and can be accessed from outside of the Vagrant box. -Since k3os kubernetes clusters are usually not exposed to the public internet, +Since k3os kubernetes clusters are usually not exposed to the public internet, we included an ngrok client to facilitate the integration of sources (via webhooks). For the Airy Core Platform to be accessible from the outside (for @@ -37,6 +37,7 @@ the traffic to the local Facebook webhook pod. When starting, the Airy Core Platform prints the public URL for the Facebook webhook. You can also check it by running the `/vagrant/scripts/status.sh` script from inside the Airy Core Platform box or directly: + ```sh vagrant ssh -c /vagrant/scripts/status.sh ``` diff --git a/infrastructure/airy.conf.all b/infrastructure/airy.conf.all index a91c98635f..bddaf7efe1 100644 --- a/infrastructure/airy.conf.all +++ b/infrastructure/airy.conf.all @@ -41,3 +41,4 @@ apps: mailUsername: "changeme" mailPassword: "changeme" jwtSecret: "long-random-generated-jwt-secret" + allowedOrigins: "*" diff --git a/infrastructure/airy.conf.tpl b/infrastructure/airy.conf.tpl index 2845fc1fa3..88a6d9e169 100644 --- a/infrastructure/airy.conf.tpl +++ b/infrastructure/airy.conf.tpl @@ -20,3 +20,4 @@ apps: mailUrl: "changeme" mailUsername: "changeme" mailPassword: "changeme" + allowedOrigins: "*" diff --git a/infrastructure/cli/BUILD b/infrastructure/cli/BUILD new file mode 100644 index 0000000000..ee37f6ecec --- /dev/null +++ b/infrastructure/cli/BUILD @@ -0,0 +1,30 @@ +# gazelle:prefix cli +# gazelle:importmap_prefix infrastructure +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") + +go_library( + name = "cli_lib", + srcs = ["main.go"], + importmap = "infrastructure", + importpath = "cli", + visibility = ["//visibility:private"], + deps = ["//infrastructure/cli/cmd"], +) + +go_binary( + name = "airy", + out = "airy", + embed = [":cli_lib"], + visibility = ["//visibility:public"], +) + +go_test( + name = "cli_test", + srcs = ["main_test.go"], + data = [ + "airy", + "//infrastructure/cli/pkg/tests/golden:golden_files", + ], + embed = [":cli_lib"], + deps = ["//infrastructure/cli/pkg/tests"], +) diff --git a/infrastructure/cli/cmd/BUILD b/infrastructure/cli/cmd/BUILD new file mode 100644 index 0000000000..77f0424c35 --- /dev/null +++ b/infrastructure/cli/cmd/BUILD @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "cmd", + srcs = [ + "root.go", + "version.go", + ], + importmap = "infrastructure/cmd", + importpath = "cli/cmd", + visibility = ["//visibility:public"], + x_defs = { + "CLIVersion": "{STABLE_VERSION}", + "GitCommit": "{STABLE_GIT_COMMIT}", + }, + deps = [ + "//infrastructure/cli/cmd/auth", + "//infrastructure/cli/cmd/bootstrap", + "//infrastructure/cli/cmd/config", + "//infrastructure/cli/cmd/demo", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/infrastructure/cli/cmd/auth/BUILD b/infrastructure/cli/cmd/auth/BUILD new file mode 100644 index 0000000000..2eedf71c7a --- /dev/null +++ b/infrastructure/cli/cmd/auth/BUILD @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "auth", + srcs = ["auth.go"], + importmap = "infrastructure/cmd/auth", + importpath = "cli/cmd/auth", + visibility = ["//visibility:public"], + deps = [ + "//lib/go/apiclient", + "//lib/go/apiclient/payloads", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/infrastructure/cli/cmd/auth/auth.go b/infrastructure/cli/cmd/auth/auth.go new file mode 100644 index 0000000000..85c6dbeaf6 --- /dev/null +++ b/infrastructure/cli/cmd/auth/auth.go @@ -0,0 +1,49 @@ +package auth + +import ( + "fmt" + "log" + + "apiclient" + "apiclient/payloads" + + "github.com/spf13/cobra" +) + +// AuthCmd subcommand for Airy Core +var AuthCmd = &cobra.Command{ + Use: "auth", + TraverseChildren: true, + Short: "Create a default user and return a JWT token", + Long: ``, + Run: auth, +} + +func auth(cmd *cobra.Command, args []string) { + url, _ := cmd.Flags().GetString("url") + email, _ := cmd.Flags().GetString("email") + password, _ := cmd.Flags().GetString("password") + c := apiclient.NewClient() + c.BaseURL = url + + loginRequestPayload := payloads.LoginRequestPayload{Email: email, Password: password} + + res, err := c.Login(loginRequestPayload) + if err != nil { + signupRequestPayload := payloads.SignupRequestPayload{FirstName: "Firstname", LastName: "Lastname", Email: email, Password: password} + res, err := c.Signup(signupRequestPayload) + if err != nil { + log.Fatal(err) + } + fmt.Println(res.Token) + return + } + fmt.Println(res.Token) +} + +func init() { + var url, email, password string + AuthCmd.Flags().StringVarP(&url, "url", "u", "http://api.airy", "The url of the Airy API") + AuthCmd.Flags().StringVarP(&email, "email", "e", "grace@hopper.com", "Email to use for the authentication") + AuthCmd.Flags().StringVarP(&password, "password", "p", "the_answer_is_42", "Password to use for the authentication") +} diff --git a/infrastructure/cli/cmd/bootstrap/BUILD b/infrastructure/cli/cmd/bootstrap/BUILD new file mode 100644 index 0000000000..d016d3af30 --- /dev/null +++ b/infrastructure/cli/cmd/bootstrap/BUILD @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "bootstrap", + srcs = ["bootstrap.go"], + importmap = "infrastructure/cmd/bootstrap", + importpath = "cli/cmd/bootstrap", + visibility = ["//visibility:public"], + deps = [ + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/infrastructure/cli/cmd/bootstrap/bootstrap.go b/infrastructure/cli/cmd/bootstrap/bootstrap.go new file mode 100644 index 0000000000..66301513d6 --- /dev/null +++ b/infrastructure/cli/cmd/bootstrap/bootstrap.go @@ -0,0 +1,31 @@ +package bootstrap + +import ( + "log" + + "github.com/spf13/cobra" +) + +// ResponsePayload for receiving the request + +// BootstrapCmd subcommand for Airy Core +var BootstrapCmd = &cobra.Command{ + Use: "bootstrap", + TraverseChildren: true, + Short: "Bootstrap Airy Core Platform locally", + Long: `This will install the Airy Core Platform in the current directory unless you choose a different one. + It will also try to install Vagrant and VirtualBox.`, + Run: bootstrap, +} + +func bootstrap(cmd *cobra.Command, args []string) { + // Initialize the api request + + log.Println("BootstrapCmd called") + +} + +func init() { + var imageTag string + BootstrapCmd.Flags().StringVarP(&imageTag, "image-tag", "i", "", "The docker image tag that the Airy apps will use.") +} diff --git a/infrastructure/cli/cmd/config/BUILD b/infrastructure/cli/cmd/config/BUILD new file mode 100644 index 0000000000..102a335cff --- /dev/null +++ b/infrastructure/cli/cmd/config/BUILD @@ -0,0 +1,10 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = ["config.go"], + importmap = "infrastructure/cmd/config", + importpath = "cli/cmd/config", + visibility = ["//visibility:public"], + deps = ["@com_github_spf13_cobra//:cobra"], +) diff --git a/infrastructure/cli/cmd/config/config.go b/infrastructure/cli/cmd/config/config.go new file mode 100644 index 0000000000..68fa0c78f6 --- /dev/null +++ b/infrastructure/cli/cmd/config/config.go @@ -0,0 +1,28 @@ +package config + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// ResponsePayload for receiving the request + +// ConfigCmd subcommand for Airy Core +var ConfigCmd = &cobra.Command{ + Use: "config", + TraverseChildren: true, + Short: "Reloads configuration based on airy.conf", + Long: ``, + Run: config, +} + +func config(cmd *cobra.Command, args []string) { + // Initialize the api request + + fmt.Println("ConfigCmd called") + +} + +func init() { +} diff --git a/infrastructure/cli/cmd/demo/BUILD b/infrastructure/cli/cmd/demo/BUILD new file mode 100644 index 0000000000..7bf9222d39 --- /dev/null +++ b/infrastructure/cli/cmd/demo/BUILD @@ -0,0 +1,10 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "demo", + srcs = ["demo.go"], + importmap = "infrastructure/cmd/demo", + importpath = "cli/cmd/demo", + visibility = ["//visibility:public"], + deps = ["@com_github_spf13_cobra//:cobra"], +) diff --git a/infrastructure/cli/cmd/demo/demo.go b/infrastructure/cli/cmd/demo/demo.go new file mode 100644 index 0000000000..7bb8e0132c --- /dev/null +++ b/infrastructure/cli/cmd/demo/demo.go @@ -0,0 +1,47 @@ +package demo + +import ( + "fmt" + "log" + "os/exec" + "runtime" + + "github.com/spf13/cobra" +) + +// ResponsePayload for receiving the request + +// DemoCmd subcommand for Airy Core +var DemoCmd = &cobra.Command{ + Use: "demo", + TraverseChildren: true, + Short: "Opens the demo page in the browser", + Long: ``, + Run: demo, +} + +func demo(cmd *cobra.Command, args []string) { + // Initialize the api request + + url := "http://chatplugin.airy/example.html" + + var err error + + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform") + } + if err != nil { + log.Fatal(err) + } + +} + +func init() { +} diff --git a/infrastructure/cli/cmd/root.go b/infrastructure/cli/cmd/root.go new file mode 100644 index 0000000000..d7268ec9fe --- /dev/null +++ b/infrastructure/cli/cmd/root.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + "os" + + "cli/cmd/auth" + "cli/cmd/bootstrap" + "cli/cmd/config" + "cli/cmd/demo" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "airy", + Short: "Airy CLI", + Long: ``, + TraverseChildren: true, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } + +} + +func init() { + RootCmd.AddCommand(bootstrap.BootstrapCmd) + RootCmd.AddCommand(auth.AuthCmd) + RootCmd.AddCommand(config.ConfigCmd) + RootCmd.AddCommand(demo.DemoCmd) +} diff --git a/infrastructure/cli/cmd/version.go b/infrastructure/cli/cmd/version.go new file mode 100644 index 0000000000..5a5eeefb49 --- /dev/null +++ b/infrastructure/cli/cmd/version.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var CLIVersion string +var GitCommit string + +// StatusCmd cli kafka version +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Return current version", + Long: ``, + Run: version, +} + +func version(cmd *cobra.Command, args []string) { + fmt.Printf("Version: %s, GitCommit: %s", CLIVersion, GitCommit) +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/infrastructure/cli/go.mod b/infrastructure/cli/go.mod new file mode 100644 index 0000000000..74107669f0 --- /dev/null +++ b/infrastructure/cli/go.mod @@ -0,0 +1,13 @@ +module cli + +go 1.12 + +require ( + apiclient v0.0.0 + github.com/kr/pretty v0.2.1 + github.com/spf13/cobra v0.0.3 + github.com/spf13/viper v1.3.1 + goji.io v2.0.2+incompatible +) + +replace apiclient => ../../lib/go/apiclient diff --git a/infrastructure/cli/go.sum b/infrastructure/cli/go.sum new file mode 100644 index 0000000000..a53a9ff9a8 --- /dev/null +++ b/infrastructure/cli/go.sum @@ -0,0 +1,63 @@ +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.28.3 h1:FnkDp+fz4JHWUW3Ust2Wh89RpdGif077Wjis/sMrGKM= +github.com/aws/aws-sdk-go v1.28.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY= +github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= +goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/infrastructure/cli/main.go b/infrastructure/cli/main.go new file mode 100644 index 0000000000..abf40a3da2 --- /dev/null +++ b/infrastructure/cli/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "cli/cmd" +) + +func main() { + + cmd.Execute() +} diff --git a/infrastructure/cli/main_test.go b/infrastructure/cli/main_test.go new file mode 100644 index 0000000000..7a82256b7a --- /dev/null +++ b/infrastructure/cli/main_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" + + airytests "cli/pkg/tests" + "reflect" +) + +const binaryName = "./airy" + +func TestCli(t *testing.T) { + tests := []struct { + name string + args []string + golden string + wantErr bool + }{ + {"no args", []string{}, "cli.no-args.golden", false}, + {"auth", []string{"auth", "--url", "http://localhost:3001"}, "cli.auth.golden", false}, + {"auth", []string{"auth", "--url", "http://localhost:3001", "--email", "example@email.com"}, "cli.auth.golden", false}, + {"auth", []string{"auth", "--url", "http://localhost:3001", "--email", "example@email.com", "--password", "examplepassword"}, "cli.auth.golden", false}, + // {"bootstrap", []string{"bootstrap"}, "cli.bootstrap.golden", false}, + // {"config", []string{"config"}, "cli.config.no-args.golden", true}, + {"version", []string{"version"}, "cli.version.golden", false}, + } + + go func() { + airytests.MockServer() + }() + + for _, tt := range tests { + t.Run(tt.name, func(testing *testing.T) { + cmd := exec.Command(binaryName, tt.args...) + output, err := cmd.CombinedOutput() + + if (err != nil) != tt.wantErr { + t.Fatalf("Test expected to fail: %t. Did the test pass: %t. Error message: %v\n", tt.wantErr, err == nil, err) + } + fmt.Println(output) + + actual := string(output) + golden := airytests.NewGoldenFile(t, tt.golden) + expected := golden.Load() + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("diff: %v", airytests.Diff(actual, expected)) + } + + }) + + } +} diff --git a/infrastructure/cli/pkg/tests/BUILD b/infrastructure/cli/pkg/tests/BUILD new file mode 100644 index 0000000000..21f5fede37 --- /dev/null +++ b/infrastructure/cli/pkg/tests/BUILD @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +package(default_visibility = ["//visibility:public"]) + +go_library( + name = "tests", + srcs = [ + "golden.go", + "mockserver.go", + ], + importmap = "infrastructure/pkg/tests", + importpath = "cli/pkg/tests", + deps = [ + "@com_github_kr_pretty//:pretty", + "@io_goji//:goji.io", + "@io_goji//pat", + ], +) diff --git a/infrastructure/cli/pkg/tests/golden.go b/infrastructure/cli/pkg/tests/golden.go new file mode 100644 index 0000000000..537f5f1582 --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden.go @@ -0,0 +1,61 @@ +package tests + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/kr/pretty" +) + +// TestFile struct +type TestFile struct { + t *testing.T + name string + dir string +} + +// Diff function +func Diff(expected, actual interface{}) []string { + return pretty.Diff(expected, actual) +} + +// NewGoldenFile function +func NewGoldenFile(t *testing.T, name string) *TestFile { + return &TestFile{t: t, name: name, dir: "./pkg/tests/golden/"} +} + +func (tf *TestFile) path() string { + tf.t.Helper() + return filepath.Join(tf.dir, tf.name) +} + +func (tf *TestFile) write(content string) { + tf.t.Helper() + err := ioutil.WriteFile(tf.path(), []byte(content), 0644) + if err != nil { + tf.t.Fatalf("could not write %s: %v", tf.name, err) + } +} + +func (tf *TestFile) asFile() *os.File { + tf.t.Helper() + file, err := os.Open(tf.path()) + if err != nil { + tf.t.Fatalf("could not open %s: %v", tf.name, err) + } + return file +} + +// Load method +func (tf *TestFile) Load() string { + tf.t.Helper() + + content, err := ioutil.ReadFile(tf.path()) + if err != nil { + tf.t.Fatalf("could not read file %s: %v", tf.name, err) + } + + return string(content) +} diff --git a/infrastructure/cli/pkg/tests/golden/BUILD b/infrastructure/cli/pkg/tests/golden/BUILD new file mode 100644 index 0000000000..61fb98b9de --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden/BUILD @@ -0,0 +1,11 @@ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "golden_files", + srcs = [ + "api.signup.golden", + "cli.auth.golden", + "cli.no-args.golden", + "cli.version.golden", + ], +) diff --git a/infrastructure/cli/pkg/tests/golden/api.signup.golden b/infrastructure/cli/pkg/tests/golden/api.signup.golden new file mode 100644 index 0000000000..4470091587 --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden/api.signup.golden @@ -0,0 +1 @@ +{"first_name": "Grace","last_name": "Hopper","token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4","email": "grace@example.com"} \ No newline at end of file diff --git a/infrastructure/cli/pkg/tests/golden/cli.auth.golden b/infrastructure/cli/pkg/tests/golden/cli.auth.golden new file mode 100644 index 0000000000..7511a74d6d --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden/cli.auth.golden @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4 diff --git a/infrastructure/cli/pkg/tests/golden/cli.no-args.golden b/infrastructure/cli/pkg/tests/golden/cli.no-args.golden new file mode 100644 index 0000000000..52769b5c49 --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden/cli.no-args.golden @@ -0,0 +1,17 @@ +Airy CLI + +Usage: + airy [command] + +Available Commands: + auth Create a default user and return a JWT token + bootstrap Bootstrap Airy Core Platform locally + config Reloads configuration based on airy.conf + demo Opens the demo page in the browser + help Help about any command + version Return current version + +Flags: + -h, --help help for airy + +Use "airy [command] --help" for more information about a command. diff --git a/infrastructure/cli/pkg/tests/golden/cli.version.golden b/infrastructure/cli/pkg/tests/golden/cli.version.golden new file mode 100644 index 0000000000..1f0266aa66 --- /dev/null +++ b/infrastructure/cli/pkg/tests/golden/cli.version.golden @@ -0,0 +1 @@ +Version: {STABLE_VERSION}, GitCommit: {STABLE_GIT_COMMIT} \ No newline at end of file diff --git a/infrastructure/cli/pkg/tests/mockserver.go b/infrastructure/cli/pkg/tests/mockserver.go new file mode 100644 index 0000000000..1484765de8 --- /dev/null +++ b/infrastructure/cli/pkg/tests/mockserver.go @@ -0,0 +1,48 @@ +package tests + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "time" + + "goji.io" + "goji.io/pat" +) + +// MockServer starts the local server that returns the corresponding golden files for each endpoint +func MockServer() { + mux := goji.NewMux() + mux.HandleFunc(pat.Post("/users.signup"), mockUserSignupHandler) + mux.HandleFunc(pat.Post("/users.login"), mockUserLoginHandler) + + log.Println("starting mock server on port localhost:3001") + s := &http.Server{ + Addr: ":3001", + Handler: mux, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + s.ListenAndServe() +} + +func mockUserSignupHandler(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadFile("pkg/tests/golden/api.signup.golden") + if err != nil { + fmt.Fprint(w, err) + } + w.Write(data) +} + +func mockUserLoginHandler(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadFile("pkg/tests/golden/api.signup.golden") + if err != nil { + fmt.Fprint(w, err) + } + w.Write(data) +} + +func main() { + MockServer() +} diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml index 95c31db8d2..b61d1ade50 100644 --- a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml @@ -9,4 +9,5 @@ data: MAIL_USERNAME: {{ .Values.api.mailUsername }} MAIL_PASSWORD: {{ .Values.api.mailPassword }} JWT_SECRET: {{ randAlphaNum 128 | quote }} + ALLOWED_ORIGINS: {{ .Values.api.allowedOrigins | quote }} \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml index 929a60067a..ecf7917d89 100644 --- a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml @@ -4,16 +4,16 @@ kind: ConfigMap metadata: name: sources-config data: - FACEBOOK_APP_ID: {{ .Values.sources.facebook.appId }} - FACEBOOK_APP_SECRET: {{ .Values.sources.facebook.appSecret }} - FACEBOOK_WEBHOOK_SECRET: {{ .Values.sources.facebook.webhookSecret }} - FACEBOOK_WEBHOOK_PUBLIC_URL: https://fb-{{ $ingressID }}.tunnel.airy.co}} + FACEBOOK_APP_ID: {{ .Values.sources.facebook.appId | quote }} + FACEBOOK_APP_SECRET: {{ .Values.sources.facebook.appSecret | quote }} + FACEBOOK_WEBHOOK_SECRET: {{ .Values.sources.facebook.webhookSecret | quote }} + FACEBOOK_WEBHOOK_PUBLIC_URL: https://fb-{{ $ingressID }}.tunnel.airy.co GOOGLE_PARTNER_KEY: {{ .Values.sources.google.partnerKey }} GOOGLE_SA_FILE: {{ .Values.sources.google.saFile }} - GOOGLE_WEBHOOK_PUBLIC_URL: https://gl-{{ $ingressID }}.tunnel.airy.co}} + GOOGLE_WEBHOOK_PUBLIC_URL: https://gl-{{ $ingressID }}.tunnel.airy.co TWILIO_AUTH_TOKEN: {{ .Values.sources.twilio.authToken }} TWILIO_ACCOUNT_SID: {{ .Values.sources.twilio.accountSid }} - TWILIO_WEBHOOK_PUBLIC_URL: https://tw-{{ $ingressID }}.tunnel.airy.co}} + TWILIO_WEBHOOK_PUBLIC_URL: https://tw-{{ $ingressID }}.tunnel.airy.co --- apiVersion: v1 kind: ConfigMap diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/values.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/values.yaml index 6a2d1faaf7..dd8e925c39 100644 --- a/infrastructure/helm-chart/charts/apps/charts/airy-config/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/values.yaml @@ -28,3 +28,4 @@ api: mailUsername: "changeme" mailPassword: "changeme" jwtSecret: "" + allowedOrigins: "*" diff --git a/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml index e29d0302d1..016e6e3b91 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml @@ -53,6 +53,11 @@ spec: configMapKeyRef: name: api-config key: JWT_SECRET + - name: ALLOWED_ORIGINS + valueFrom: + configMapKeyRef: + name: api-config + key: ALLOWED_ORIGINS livenessProbe: httpGet: path: /actuator/health diff --git a/infrastructure/helm-chart/charts/apps/charts/api-auth/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api-auth/templates/deployment.yaml index e90db300e2..4608c0b412 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api-auth/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api-auth/templates/deployment.yaml @@ -76,6 +76,11 @@ spec: configMapKeyRef: name: api-config key: MAIL_FROM + - name: ALLOWED_ORIGINS + valueFrom: + configMapKeyRef: + name: api-config + key: ALLOWED_ORIGINS livenessProbe: httpGet: path: /actuator/health diff --git a/infrastructure/helm-chart/charts/apps/charts/api-communication/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api-communication/templates/deployment.yaml index 40d8ba47c4..9f72c80eca 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api-communication/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api-communication/templates/deployment.yaml @@ -43,6 +43,11 @@ spec: configMapKeyRef: name: api-config key: JWT_SECRET + - name: ALLOWED_ORIGINS + valueFrom: + configMapKeyRef: + name: api-config + key: ALLOWED_ORIGINS livenessProbe: httpGet: path: /actuator/health diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/deployment.yaml index c89289811d..c4179609d2 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/deployment.yaml @@ -58,10 +58,10 @@ spec: command: - /bin/bash - -c - - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="$WEBHOOK_SUBDOMAIN" 8080 + - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="${WEBHOOK:8:13}" 8080 image: ghcr.io/airyhq/infrastructure/ngrok-client:latest env: - - name: WEBHOOK_SUBDOMAIN + - name: WEBHOOK valueFrom: configMapKeyRef: name: sources-config diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/service.yaml index 38aec0ce03..75298f0cbf 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/service.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-webhook/templates/service.yaml @@ -3,8 +3,6 @@ kind: Service metadata: name: sources-facebook-webhook namespace: default - labels: - airy: sources.webhook spec: ports: - port: 80 @@ -12,4 +10,4 @@ spec: protocol: TCP type: NodePort selector: - app: sources-facebook-webhook \ No newline at end of file + app: sources-facebook-webhook diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/deployment.yaml index 0a93ab17cf..33a110bc8b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/deployment.yaml @@ -57,10 +57,10 @@ spec: command: - /bin/bash - -c - - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="$WEBHOOK_SUBDOMAIN" 8080 + - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="${WEBHOOK:8:13}" 8080 image: ghcr.io/airyhq/infrastructure/ngrok-client:latest env: - - name: WEBHOOK_SUBDOMAIN + - name: WEBHOOK valueFrom: configMapKeyRef: name: sources-config diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/service.yaml index 9986be5713..0c2582acbc 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/service.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-google-webhook/templates/service.yaml @@ -3,8 +3,6 @@ kind: Service metadata: name: sources-google-webhook namespace: default - labels: - airy: sources.webhook spec: ports: - port: 80 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/deployment.yaml index 40f96b2d08..c722c2318e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/deployment.yaml @@ -58,10 +58,10 @@ spec: command: - /bin/bash - -c - - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="$WEBHOOK_SUBDOMAIN" 8080 + - /usr/local/bin/ngrok -config=/etc/ngrok/config.yml -log=stdout -subdomain="${WEBHOOK:8:13}" 8080 image: ghcr.io/airyhq/infrastructure/ngrok-client:latest env: - - name: WEBHOOK_SUBDOMAIN + - name: WEBHOOK valueFrom: configMapKeyRef: name: sources-config diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/service.yaml index ef27aedf5a..1145d11e6d 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/service.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-webhook/templates/service.yaml @@ -3,8 +3,6 @@ kind: Service metadata: name: sources-twilio-webhook namespace: default - labels: - airy: sources.webhook spec: ports: - port: 80 diff --git a/infrastructure/scripts/conf.sh b/infrastructure/scripts/conf.sh index dc6fc9cb41..a1ba175833 100755 --- a/infrastructure/scripts/conf.sh +++ b/infrastructure/scripts/conf.sh @@ -3,8 +3,25 @@ set -eo pipefail IFS=$'\n\t' source /vagrant/scripts/lib/k8s.sh -APP_IMAGE_TAG=${AIRY_VERSION:-latest} +if [ -z ${AIRY_VERSION+x} ]; then + branch_name="$(git symbolic-ref HEAD 2>/dev/null)" || + branch_name="(unnamed branch)" # detached HEAD + + branch_name=${branch_name##refs/heads/} + case "$branch_name" in + develop ) + AIRY_VERSION=beta + ;; + release* ) + AIRY_VERSION=release + ;; + * ) + AIRY_VERSION=latest + ;; + esac +fi +kubectl delete pod startup-helper --force 2>/dev/null || true kubectl run startup-helper --image busybox --command -- /bin/sh -c "tail -f /dev/null" cd /vagrant/scripts @@ -12,7 +29,7 @@ if [ -f "/vagrant/airy.conf" ]; then cp /vagrant/airy.conf ~/airy-core/helm-chart/charts/apps/values.yaml fi -helm upgrade airy ~/airy-core/helm-chart/ --set global.appImageTag=${APP_IMAGE_TAG} --version 0.5.0 --timeout 1000s > /dev/null 2>&1 +helm upgrade airy ~/airy-core/helm-chart/ --set global.appImageTag=${AIRY_VERSION} --version 0.5.0 --timeout 1000s > /dev/null 2>&1 kubectl scale deployment airy-cp-schema-registry --replicas=1 diff --git a/infrastructure/scripts/lib/api.sh b/infrastructure/scripts/lib/api.sh new file mode 100755 index 0000000000..d056ab6b61 --- /dev/null +++ b/infrastructure/scripts/lib/api.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +content_type='Content-Type: application/json' + +function apiCall { + local endpoint=${1} + local request_payload=${2} + local expected_http_response_code=${3} + local token=${4:-no-auth} + local host=${5:-api} + local url="${host}.airy/${endpoint}" + + if [ "$token" = "no-auth" ]; then + response=$(curl -H ${content_type} -s -w "\n%{http_code}\n" ${url} -d ${request_payload} 2>&1) + else + response=$(curl -H ${content_type} -H "Authorization: $token" -s -w "\n%{http_code}\n" ${url} -d ${request_payload} 2>&1) + fi + response_payload=$(head -1 <<< "${response}") + response_http_code=$(tail -1 <<< "${response}") + + if [ "${response_http_code}" != "${expected_http_response_code}" ]; then + >&2 echo "${url} response code was ${response_http_code}. expected: ${expected_http_response_code}" + exit + fi + + echo ${response_payload} +} + +function extractFromPayload { + local payload=$(tail -1 <<< "${1}") + echo ${payload} | jq -r ".${2}" +} + +function generateChatPluginMessages { + local id=${1} + local file=${2} + + while read chatplugin_token + do + apiCall "chatplugin.send" "{\"message\": {\"text\": \"You deserve it ${id} !\"}}" 200 ${chatplugin_token} chatplugin + done < ${file} +} diff --git a/infrastructure/scripts/smoke-generator.sh b/infrastructure/scripts/smoke-generator.sh new file mode 100755 index 0000000000..90b1ddd554 --- /dev/null +++ b/infrastructure/scripts/smoke-generator.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +SCRIPT_PATH=$(dirname ${BASH_SOURCE[0]}) +source ${SCRIPT_PATH}/lib/api.sh + +channels=${CHANNELS:-10} +messages=${MESSAGES:-50} +delay=${DELAY:-0.05} + +login_response=$(apiCall "users.login" '{"email":"grace@example.com","password":"the_answer_is_42"}' 200) +token=$(echo $login_response | jq -r '.token') + +echo Using token: $token + +# Create channels +printf "" > /tmp/chatplugin_tokens +for i in `seq 1 1 ${channels}` +do + payload="{\"source\": \"chat_plugin\", \"source_channel_id\": \"my-channel-${i}\", \"token\": \"wat\", \"name\": \"chat plugin source\", \"image_url\": \"\"}" + channels_connect_response=$(apiCall "channels.connect" ${payload} 200 ${token}) + channel_id=$(echo $channels_connect_response | jq -r '.id') + chatplugin_authenticate_response=$(apiCall "chatplugin.authenticate" "{\"channel_id\": \"$channel_id\"}" 200 ${token} chatplugin) + chatplugin_token=$(extractFromPayload $chatplugin_authenticate_response "token") + + echo $chatplugin_token >> /tmp/chatplugin_tokens +done + +# Send messages to all channels +for i in `seq 1 1 ${messages}` +do + generateChatPluginMessages ${i} "/tmp/chatplugin_tokens" & + sleep ${delay} +done diff --git a/infrastructure/scripts/smoke-test.sh b/infrastructure/scripts/smoke-test.sh index ee15a7970c..3487f235d3 100755 --- a/infrastructure/scripts/smoke-test.sh +++ b/infrastructure/scripts/smoke-test.sh @@ -1,39 +1,8 @@ #!/bin/bash - set -euo pipefail IFS=$'\n\t' - -content_type='Content-Type: application/json' - - -function apiCall { - local endpoint=${1} - local request_payload=${2} - local expected_http_response_code=${3} - local token=${4:-no-auth} - local host=${5:-api} - local url="${host}.airy/${endpoint}" - - if [ "$token" = "no-auth" ]; then - response=$(curl -H ${content_type} -s -w "%{stderr}%{http_code}\n" ${url} -d ${request_payload} 2>&1) - else - response=$(curl -H ${content_type} -H "Authorization: $token" -s -w "%{stderr}%{http_code}\n" ${url} -d ${request_payload} 2>&1) - fi - response_http_code=$(head -1 <<< "${response}") - response_payload=$(tail -1 <<< "${response}") - - if [ "${response_http_code}" != "${expected_http_response_code}" ]; then - >&2 echo "${url} response code was ${response_http_code}. expected: ${expected_http_response_code}" - exit - fi - - echo ${response_payload} -} - -function extractFromPayload { - local payload=$(tail -1 <<< "${1}") - echo ${payload} | jq -r ".${2}" -} +SCRIPT_PATH=$(dirname ${BASH_SOURCE[0]}) +source ${SCRIPT_PATH}/lib/api.sh login_response=$(apiCall "users.login" '{"email":"grace@example.com","password":"the_answer_is_42"}' 200) diff --git a/infrastructure/scripts/status.sh b/infrastructure/scripts/status.sh index bac980d28b..8a48e8f626 100755 --- a/infrastructure/scripts/status.sh +++ b/infrastructure/scripts/status.sh @@ -4,6 +4,7 @@ IFS=$'\n\t' source /vagrant/scripts/lib/k8s.sh +kubectl delete pod startup-helper --force 2>/dev/null || true kubectl run startup-helper --image busybox --command -- /bin/sh -c "tail -f /dev/null" wait-for-ingress-service diff --git a/infrastructure/scripts/trigger/start.sh b/infrastructure/scripts/trigger/start.sh index 034b629be9..0eba276c6b 100755 --- a/infrastructure/scripts/trigger/start.sh +++ b/infrastructure/scripts/trigger/start.sh @@ -4,6 +4,7 @@ IFS=$'\n\t' source /vagrant/scripts/lib/k8s.sh +kubectl delete pod startup-helper --force 2>/dev/null || true kubectl run startup-helper --image busybox --command -- /bin/sh -c "tail -f /dev/null" wait-for-running-pod startup-helper diff --git a/infrastructure/tools/topics/BUILD b/infrastructure/tools/topics/BUILD index a12c57ed77..7d5e0c55f8 100644 --- a/infrastructure/tools/topics/BUILD +++ b/infrastructure/tools/topics/BUILD @@ -1,7 +1,7 @@ load("@rules_java//java:defs.bzl", "java_binary") app_deps = [ - "//backend/lib/kafka/schema:all-topics", + "//lib/java/kafka/schema:all-topics", ] java_binary( diff --git a/lib/go/apiclient/BUILD b/lib/go/apiclient/BUILD new file mode 100644 index 0000000000..786605d4df --- /dev/null +++ b/lib/go/apiclient/BUILD @@ -0,0 +1,15 @@ +# gazelle:prefix apiclient +# gazelle:importmap_prefix lib/go +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "apiclient", + srcs = [ + "apiclient.go", + "users.go", + ], + importmap = "lib/go", + importpath = "apiclient", + visibility = ["//visibility:public"], + deps = ["//lib/go/apiclient/payloads"], +) diff --git a/lib/go/apiclient/api-client-test/BUILD b/lib/go/apiclient/api-client-test/BUILD new file mode 100644 index 0000000000..8503cff8b3 --- /dev/null +++ b/lib/go/apiclient/api-client-test/BUILD @@ -0,0 +1,11 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") + +go_test( + name = "api-client-test_test", + srcs = ["users_test.go"], + deps = [ + "//lib/go/apiclient", + "//lib/go/apiclient/payloads", + "@com_github_stretchr_testify//assert", + ], +) diff --git a/lib/go/apiclient/api-client-test/users_test.go b/lib/go/apiclient/api-client-test/users_test.go new file mode 100644 index 0000000000..0dd8e8b295 --- /dev/null +++ b/lib/go/apiclient/api-client-test/users_test.go @@ -0,0 +1,50 @@ +package tests + +import ( + "apiclient" + "apiclient/payloads" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSignup(t *testing.T) { + c := apiclient.NewClient() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "{\"id\":\"a6c413a7-8d42-4c2b-8736-d033134eec59\",\"first_name\":\"Grace\",\"last_name\":\"Hopper\",\"token\":\"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA\"}") + })) + c.BaseURL = ts.URL + + signupRequestPayload := payloads.SignupRequestPayload{FirstName: "Grace", LastName: "Hopper", Password: "the_answer_is_42", Email: "grace@example.com"} + + res, err := c.Signup(signupRequestPayload) + + assert.Nil(t, err, "expecting nil error") + assert.NotNil(t, res, "expecting non-nil result") + + assert.NotEmpty(t, res.Token, "expecting non-empty token") + +} + +func TestLogin(t *testing.T) { + c := apiclient.NewClient() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "{\"id\":\"a6c413a7-8d42-4c2b-8736-d033134eec59\",\"first_name\":\"Grace\",\"last_name\":\"Hopper\",\"token\":\"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA\"}") + })) + c.BaseURL = ts.URL + + loginRequestPayload := payloads.LoginRequestPayload{Password: "the_answer_is_42", Email: "grace@example.com"} + + res, err := c.Login(loginRequestPayload) + + assert.Nil(t, err, "expecting nil error") + assert.NotNil(t, res, "expecting non-nil result") + + assert.NotEmpty(t, res.Token, "expecting non-empty token") + +} diff --git a/lib/go/apiclient/apiclient.go b/lib/go/apiclient/apiclient.go new file mode 100644 index 0000000000..8270630366 --- /dev/null +++ b/lib/go/apiclient/apiclient.go @@ -0,0 +1,65 @@ +package apiclient + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" +) + +const ( + BaseURL = "http://api.airy" +) + +type Client struct { + BaseURL string + HTTPClient *http.Client +} + +func NewClient() *Client { + return &Client{ + BaseURL: BaseURL, + HTTPClient: &http.Client{ + Timeout: time.Minute, + }, + } +} + +type errorResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (c *Client) sendRequest(requestDataJSON []byte, endpoint string, v interface{}) error { + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/%s", c.BaseURL, endpoint), bytes.NewBuffer(requestDataJSON)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/json; charset=utf-8") + + res, err := c.HTTPClient.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + + if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { + var errRes errorResponse + if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil { + return errors.New(errRes.Message) + } + + return fmt.Errorf("unknown error, status code: %d", res.StatusCode) + } + + if err = json.NewDecoder(res.Body).Decode(v); err != nil { + return err + } + + return nil +} diff --git a/lib/go/apiclient/go.mod b/lib/go/apiclient/go.mod new file mode 100644 index 0000000000..5ecbb88fd0 --- /dev/null +++ b/lib/go/apiclient/go.mod @@ -0,0 +1,8 @@ +module apiclient + +go 1.12 + +require ( + github.com/stretchr/testify v1.6.1 + goji.io v2.0.2+incompatible +) diff --git a/lib/go/apiclient/go.sum b/lib/go/apiclient/go.sum new file mode 100644 index 0000000000..5c8284cf73 --- /dev/null +++ b/lib/go/apiclient/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= +goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/go/apiclient/payloads/BUILD b/lib/go/apiclient/payloads/BUILD new file mode 100644 index 0000000000..a5de8df673 --- /dev/null +++ b/lib/go/apiclient/payloads/BUILD @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "payloads", + srcs = [ + "login_request.go", + "login_response.go", + "signup_request.go", + "signup_response.go", + ], + importmap = "lib/go/payloads", + importpath = "apiclient/payloads", + visibility = ["//visibility:public"], +) diff --git a/lib/go/apiclient/payloads/login_request.go b/lib/go/apiclient/payloads/login_request.go new file mode 100644 index 0000000000..f1c8f8d9f5 --- /dev/null +++ b/lib/go/apiclient/payloads/login_request.go @@ -0,0 +1,6 @@ +package payloads + +type LoginRequestPayload struct { + Email string `json:"email"` + Password string `json:"password"` +} diff --git a/lib/go/apiclient/payloads/login_response.go b/lib/go/apiclient/payloads/login_response.go new file mode 100644 index 0000000000..83cefe4077 --- /dev/null +++ b/lib/go/apiclient/payloads/login_response.go @@ -0,0 +1,8 @@ +package payloads + +type LoginResponsePayload struct { + ID string + FirstName string + LastName string + Token string +} diff --git a/lib/go/apiclient/payloads/signup_request.go b/lib/go/apiclient/payloads/signup_request.go new file mode 100644 index 0000000000..b87c93a8b7 --- /dev/null +++ b/lib/go/apiclient/payloads/signup_request.go @@ -0,0 +1,8 @@ +package payloads + +type SignupRequestPayload struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Password string `json:"password"` + Email string `json:"email"` +} diff --git a/lib/go/apiclient/payloads/signup_response.go b/lib/go/apiclient/payloads/signup_response.go new file mode 100644 index 0000000000..019f526018 --- /dev/null +++ b/lib/go/apiclient/payloads/signup_response.go @@ -0,0 +1,8 @@ +package payloads + +type SignupResponsePayload struct { + ID string `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Token string `json:"token"` +} diff --git a/lib/go/apiclient/users.go b/lib/go/apiclient/users.go new file mode 100644 index 0000000000..b4b290669a --- /dev/null +++ b/lib/go/apiclient/users.go @@ -0,0 +1,36 @@ +package apiclient + +import ( + "apiclient/payloads" + "encoding/json" +) + +func (c *Client) Signup(signupRequestPayload payloads.SignupRequestPayload) (*payloads.SignupResponsePayload, error) { + requestDataJSON, err := json.Marshal(signupRequestPayload) + if err != nil { + return nil, err + } + res := payloads.SignupResponsePayload{} + + if err := c.sendRequest(requestDataJSON, "users.signup", &res); err != nil { + return nil, err + } + + return &res, nil + +} + +func (c *Client) Login(loginRequestPayload payloads.LoginRequestPayload) (*payloads.LoginResponsePayload, error) { + requestDataJSON, err := json.Marshal(loginRequestPayload) + if err != nil { + return nil, err + } + res := payloads.LoginResponsePayload{} + + if err := c.sendRequest(requestDataJSON, "users.login", &res); err != nil { + return nil, err + } + + return &res, nil + +} diff --git a/backend/lib/kafka/core/BUILD b/lib/java/kafka/core/BUILD similarity index 94% rename from backend/lib/kafka/core/BUILD rename to lib/java/kafka/core/BUILD index 3a6c850212..f9d24270ad 100644 --- a/backend/lib/kafka/core/BUILD +++ b/lib/java/kafka/core/BUILD @@ -13,7 +13,7 @@ custom_java_library( deps = [ "//:jackson", "//:kafka_core", - "//backend/lib/log", + "//lib/java/log", "@maven//:com_fasterxml_jackson_module_jackson_module_afterburner", ], ) diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/KafkaConsumerWrapper.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/KafkaConsumerWrapper.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/KafkaConsumerWrapper.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/KafkaConsumerWrapper.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaHybridDeserializer.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaHybridDeserializer.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaHybridDeserializer.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaHybridDeserializer.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaJacksonDeserializer.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaJacksonDeserializer.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaJacksonDeserializer.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/deserializer/KafkaJacksonDeserializer.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/Configurations.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/Configurations.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/Configurations.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/Configurations.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/HybridObjectMapper.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/HybridObjectMapper.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/HybridObjectMapper.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/HybridObjectMapper.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/KafkaHybridSerde.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/KafkaHybridSerde.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serdes/KafkaHybridSerde.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serdes/KafkaHybridSerde.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/AvroGenericArraySerializer.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/AvroGenericArraySerializer.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/AvroGenericArraySerializer.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/AvroGenericArraySerializer.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaHybridSerializer.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaHybridSerializer.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaHybridSerializer.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaHybridSerializer.java diff --git a/backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaJacksonSerializer.java b/lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaJacksonSerializer.java similarity index 100% rename from backend/lib/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaJacksonSerializer.java rename to lib/java/kafka/core/src/main/java/co/airy/kafka/core/serializer/KafkaJacksonSerializer.java diff --git a/backend/lib/kafka/schema/BUILD b/lib/java/kafka/schema/BUILD similarity index 100% rename from backend/lib/kafka/schema/BUILD rename to lib/java/kafka/schema/BUILD diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/AbstractTopic.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/AbstractTopic.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/AbstractTopic.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/AbstractTopic.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/ApplicationCommunication.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/ApplicationCommunication.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/ApplicationCommunication.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/ApplicationCommunication.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/OpsApplication.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/OpsApplication.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/OpsApplication.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/OpsApplication.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceFacebook.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceFacebook.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceFacebook.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceFacebook.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceGoogle.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceGoogle.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceGoogle.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceGoogle.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceTwilio.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceTwilio.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/SourceTwilio.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/SourceTwilio.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/Topic.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/Topic.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/Topic.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/Topic.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationChannels.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationChannels.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationChannels.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationChannels.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMessages.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMessages.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMessages.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMessages.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMetadata.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMetadata.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMetadata.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationMetadata.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationReadReceipts.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationReadReceipts.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationReadReceipts.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationReadReceipts.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationTags.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationTags.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationTags.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationTags.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationWebhooks.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationWebhooks.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationWebhooks.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/application/ApplicationCommunicationWebhooks.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/ops/OpsApplicationHealth.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/ops/OpsApplicationHealth.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/ops/OpsApplicationHealth.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/ops/OpsApplicationHealth.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceFacebookEvents.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceFacebookEvents.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceFacebookEvents.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceFacebookEvents.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceGoogleEvents.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceGoogleEvents.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceGoogleEvents.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceGoogleEvents.java diff --git a/backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceTwilioEvents.java b/lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceTwilioEvents.java similarity index 100% rename from backend/lib/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceTwilioEvents.java rename to lib/java/kafka/schema/src/main/java/co/airy/kafka/schema/source/SourceTwilioEvents.java diff --git a/backend/lib/kafka/streams/BUILD b/lib/java/kafka/streams/BUILD similarity index 76% rename from backend/lib/kafka/streams/BUILD rename to lib/java/kafka/streams/BUILD index 0a43be5a7b..cdbe7cd945 100644 --- a/backend/lib/kafka/streams/BUILD +++ b/lib/java/kafka/streams/BUILD @@ -3,9 +3,9 @@ load("//tools/build:junit5.bzl", "junit5") lib_deps = [ "@maven//:org_apache_kafka_kafka_streams", - "//backend/lib/kafka/schema:kafka-schema", - "//backend/lib/kafka/core:kafka-core", - "//backend/lib/log", + "//lib/java/kafka/schema:kafka-schema", + "//lib/java/kafka/core:kafka-core", + "//lib/java/log", "//:lombok", "@maven//:org_rocksdb_rocksdbjni", ] @@ -29,8 +29,8 @@ custom_java_library( resources = glob(["src/test/resources/**/*"]), deps = [ ":kafka-streams", - "//backend/lib/spring/kafka/streams:spring-kafka-streams", - "//backend/lib/kafka/test:kafka-test", + "//lib/java/spring/kafka/streams:spring-kafka-streams", + "//lib/java/kafka/test:kafka-test", ] + lib_deps, ) for file in glob(["src/test/java/**/*IntegrationTest.java"]) diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/CustomRocksDbConfig.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/CustomRocksDbConfig.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/CustomRocksDbConfig.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/CustomRocksDbConfig.java diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/HostStoreInfo.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/HostStoreInfo.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/HostStoreInfo.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/HostStoreInfo.java diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/KafkaStreamsWrapper.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/KafkaStreamsWrapper.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/KafkaStreamsWrapper.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/KafkaStreamsWrapper.java diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/MetadataService.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/MetadataService.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/MetadataService.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/MetadataService.java diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotFoundException.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotFoundException.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotFoundException.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotFoundException.java diff --git a/backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotReadyException.java b/lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotReadyException.java similarity index 100% rename from backend/lib/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotReadyException.java rename to lib/java/kafka/streams/src/main/java/co/airy/kafka/streams/StoreNotReadyException.java diff --git a/backend/lib/kafka/test/BUILD b/lib/java/kafka/test/BUILD similarity index 81% rename from backend/lib/kafka/test/BUILD rename to lib/java/kafka/test/BUILD index fab79dd21b..69f1a716af 100644 --- a/backend/lib/kafka/test/BUILD +++ b/lib/java/kafka/test/BUILD @@ -8,9 +8,9 @@ custom_java_library( "//:jackson", "//:lombok", "//backend:base_test", - "//backend/lib/kafka/core:kafka-core", - "//backend/lib/kafka/schema:kafka-schema", - "//backend/lib/log", + "//lib/java/kafka/core:kafka-core", + "//lib/java/kafka/schema:kafka-schema", + "//lib/java/log", "@maven//:org_apache_curator_curator_test", "@maven//:org_apache_kafka_kafka_2_12", "@maven//:org_apache_kafka_kafka_clients_test", diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestCluster.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestCluster.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestCluster.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestCluster.java diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestHelper.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestHelper.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestHelper.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestHelper.java diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestServer.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestServer.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestServer.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/KafkaTestServer.java diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/SchemaRegistryTestServer.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/SchemaRegistryTestServer.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/SchemaRegistryTestServer.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/SchemaRegistryTestServer.java diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/ZookeeperTestServer.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/ZookeeperTestServer.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/ZookeeperTestServer.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/ZookeeperTestServer.java diff --git a/backend/lib/kafka/test/src/main/java/co/airy/kafka/test/junit/SharedKafkaTestResource.java b/lib/java/kafka/test/src/main/java/co/airy/kafka/test/junit/SharedKafkaTestResource.java similarity index 100% rename from backend/lib/kafka/test/src/main/java/co/airy/kafka/test/junit/SharedKafkaTestResource.java rename to lib/java/kafka/test/src/main/java/co/airy/kafka/test/junit/SharedKafkaTestResource.java diff --git a/backend/lib/log/BUILD b/lib/java/log/BUILD similarity index 100% rename from backend/lib/log/BUILD rename to lib/java/log/BUILD diff --git a/backend/lib/log/src/main/java/co/airy/log/AiryLoggerFactory.java b/lib/java/log/src/main/java/co/airy/log/AiryLoggerFactory.java similarity index 100% rename from backend/lib/log/src/main/java/co/airy/log/AiryLoggerFactory.java rename to lib/java/log/src/main/java/co/airy/log/AiryLoggerFactory.java diff --git a/backend/lib/mapping/BUILD b/lib/java/mapping/BUILD similarity index 92% rename from backend/lib/mapping/BUILD rename to lib/java/mapping/BUILD index 25d9c51e0e..5e67419ff6 100644 --- a/backend/lib/mapping/BUILD +++ b/lib/java/mapping/BUILD @@ -6,7 +6,7 @@ lib_deps = [ "//:lombok", "//:spring", "//:jackson", - "//backend/lib/log", + "//lib/java/log", "//backend/avro/communication:message", "@maven//:javax_validation_validation_api", ] @@ -35,7 +35,7 @@ java_binary( deps = lib_deps + [ ":mapping", "//backend:base_test", - "//backend/lib/spring/core:spring-core", + "//lib/java/spring/core:spring-core", ], ) for file in glob(["src/test/java/**/*Test.java"]) diff --git a/backend/lib/mapping/README.md b/lib/java/mapping/README.md similarity index 95% rename from backend/lib/mapping/README.md rename to lib/java/mapping/README.md index e31500d298..c30432db17 100644 --- a/backend/lib/mapping/README.md +++ b/lib/java/mapping/README.md @@ -24,5 +24,5 @@ the content model in `backend/lib/mapping/src/main/java/co/airy/mapping/model/`: ```shell script -bazel run //backend/lib/mapping:ts-generator +bazel run //lib/java/mapping:ts-generator ``` diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/ContentMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/ContentMapper.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/ContentMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/ContentMapper.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/OutboundMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/OutboundMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/SourceMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/SourceMapper.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/SourceMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/SourceMapper.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/model/Content.java b/lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/model/Content.java rename to lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/model/Text.java b/lib/java/mapping/src/main/java/co/airy/mapping/model/Text.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/model/Text.java rename to lib/java/mapping/src/main/java/co/airy/mapping/model/Text.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java similarity index 100% rename from backend/lib/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java diff --git a/backend/lib/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java similarity index 94% rename from backend/lib/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java rename to lib/java/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java index 1ab23c6fff..ffe4a92dc4 100644 --- a/backend/lib/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java +++ b/lib/java/mapping/src/main/java/co/airy/mapping/sources/twilio/TwilioMapper.java @@ -3,8 +3,6 @@ import co.airy.mapping.SourceMapper; import co.airy.mapping.model.Content; import co.airy.mapping.model.Text; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; import java.net.URLDecoder; diff --git a/backend/lib/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java similarity index 100% rename from backend/lib/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java rename to lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java diff --git a/backend/lib/mapping/src/test/java/co/airy/mapping/FacebookTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java similarity index 100% rename from backend/lib/mapping/src/test/java/co/airy/mapping/FacebookTest.java rename to lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java diff --git a/backend/lib/mapping/src/test/java/co/airy/mapping/GoogleTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java similarity index 100% rename from backend/lib/mapping/src/test/java/co/airy/mapping/GoogleTest.java rename to lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java diff --git a/backend/lib/mapping/src/test/java/co/airy/mapping/TwilioTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/TwilioTest.java similarity index 89% rename from backend/lib/mapping/src/test/java/co/airy/mapping/TwilioTest.java rename to lib/java/mapping/src/test/java/co/airy/mapping/TwilioTest.java index c8efba9a9e..51611d4885 100644 --- a/backend/lib/mapping/src/test/java/co/airy/mapping/TwilioTest.java +++ b/lib/java/mapping/src/test/java/co/airy/mapping/TwilioTest.java @@ -1,13 +1,11 @@ package co.airy.mapping; import co.airy.mapping.model.Text; -import co.airy.mapping.sources.google.GoogleMapper; import co.airy.mapping.sources.twilio.TwilioMapper; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertEquals; public class TwilioTest { private final TwilioMapper mapper = new TwilioMapper(); diff --git a/backend/lib/mapping/src/test/resources/facebook/text.json b/lib/java/mapping/src/test/resources/facebook/text.json similarity index 100% rename from backend/lib/mapping/src/test/resources/facebook/text.json rename to lib/java/mapping/src/test/resources/facebook/text.json diff --git a/backend/lib/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java b/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java similarity index 95% rename from backend/lib/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java rename to lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java index 02dd8c7e47..a56e9d5f0b 100644 --- a/backend/lib/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java +++ b/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java @@ -27,7 +27,7 @@ public static void main(String[] args) { parameters.debug = false; parameters.classNamePatterns = List.of("co.airy.mapping.model.**"); - final File output = new File(System.getenv().get("BUILD_WORKSPACE_DIRECTORY") + "/frontend/components/src/content.ts"); + final File output = new File(System.getenv().get("BUILD_WORKSPACE_DIRECTORY") + "/frontend/types/content.ts"); settings.validateFileName(output); generator.generateTypeScript(Input.from(parameters), Output.to(output)); diff --git a/backend/lib/pagination/BUILD b/lib/java/pagination/BUILD similarity index 100% rename from backend/lib/pagination/BUILD rename to lib/java/pagination/BUILD diff --git a/backend/lib/pagination/src/main/java/co/airy/pagination/Page.java b/lib/java/pagination/src/main/java/co/airy/pagination/Page.java similarity index 100% rename from backend/lib/pagination/src/main/java/co/airy/pagination/Page.java rename to lib/java/pagination/src/main/java/co/airy/pagination/Page.java diff --git a/backend/lib/pagination/src/main/java/co/airy/pagination/Paginator.java b/lib/java/pagination/src/main/java/co/airy/pagination/Paginator.java similarity index 100% rename from backend/lib/pagination/src/main/java/co/airy/pagination/Paginator.java rename to lib/java/pagination/src/main/java/co/airy/pagination/Paginator.java diff --git a/backend/lib/pagination/src/test/java/co/airy/pagination/PaginatorTest.java b/lib/java/pagination/src/test/java/co/airy/pagination/PaginatorTest.java similarity index 100% rename from backend/lib/pagination/src/test/java/co/airy/pagination/PaginatorTest.java rename to lib/java/pagination/src/test/java/co/airy/pagination/PaginatorTest.java diff --git a/backend/lib/payload/BUILD b/lib/java/payload/BUILD similarity index 100% rename from backend/lib/payload/BUILD rename to lib/java/payload/BUILD diff --git a/backend/lib/payload/src/main/java/co/airy/payload/format/DateFormat.java b/lib/java/payload/src/main/java/co/airy/payload/format/DateFormat.java similarity index 100% rename from backend/lib/payload/src/main/java/co/airy/payload/format/DateFormat.java rename to lib/java/payload/src/main/java/co/airy/payload/format/DateFormat.java diff --git a/backend/lib/payload/src/main/java/co/airy/payload/response/ChannelPayload.java b/lib/java/payload/src/main/java/co/airy/payload/response/ChannelPayload.java similarity index 100% rename from backend/lib/payload/src/main/java/co/airy/payload/response/ChannelPayload.java rename to lib/java/payload/src/main/java/co/airy/payload/response/ChannelPayload.java diff --git a/backend/lib/payload/src/main/java/co/airy/payload/response/EmptyResponsePayload.java b/lib/java/payload/src/main/java/co/airy/payload/response/EmptyResponsePayload.java similarity index 100% rename from backend/lib/payload/src/main/java/co/airy/payload/response/EmptyResponsePayload.java rename to lib/java/payload/src/main/java/co/airy/payload/response/EmptyResponsePayload.java diff --git a/backend/lib/payload/src/main/java/co/airy/payload/response/RequestErrorResponsePayload.java b/lib/java/payload/src/main/java/co/airy/payload/response/RequestErrorResponsePayload.java similarity index 100% rename from backend/lib/payload/src/main/java/co/airy/payload/response/RequestErrorResponsePayload.java rename to lib/java/payload/src/main/java/co/airy/payload/response/RequestErrorResponsePayload.java diff --git a/backend/lib/spring/auth/BUILD b/lib/java/spring/auth/BUILD similarity index 88% rename from backend/lib/spring/auth/BUILD rename to lib/java/spring/auth/BUILD index 43661eebaa..f2f86f5d0e 100644 --- a/backend/lib/spring/auth/BUILD +++ b/lib/java/spring/auth/BUILD @@ -7,8 +7,9 @@ lib_deps = [ "//:lombok", "//:spring", "//:springboot_security", - "//backend/lib/log", - "//backend/lib/spring/core:spring-core", + "//lib/java/log", + "//lib/java/spring/core:spring-core", + "//lib/java/spring/jwt:airy-jwt", "@maven//:javax_servlet_javax_servlet_api", "@maven//:javax_xml_bind_jaxb_api", ] @@ -18,6 +19,7 @@ custom_java_library( srcs = glob(["src/main/java/co/airy/spring/auth/**/*.java"]), visibility = ["//visibility:public"], exports = [ + "//lib/java/spring/jwt:airy-jwt", "@maven//:org_springframework_security_spring_security_core", ], deps = lib_deps, diff --git a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java similarity index 89% rename from backend/lib/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java rename to lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java index 45a619e2d6..4c244e22a9 100644 --- a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java +++ b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java @@ -1,7 +1,9 @@ package co.airy.spring.auth; +import co.airy.spring.jwt.Jwt; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -48,10 +50,11 @@ protected void configure(final HttpSecurity http) throws Exception { } @Bean - CorsConfigurationSource corsConfigurationSource() { + CorsConfigurationSource corsConfigurationSource(final Environment environment) { + final String allowed = environment.getProperty("ALLOWED_ORIGINS", ""); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOrigin("*"); // TODO should come from env + config.addAllowedOrigin(allowed); config.addAllowedHeader("*"); config.setAllowedMethods(List.of("GET", "POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); diff --git a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java similarity index 86% rename from backend/lib/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java rename to lib/java/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java index e334151085..811f4380ca 100644 --- a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java +++ b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/IgnoreAuthPattern.java @@ -12,5 +12,5 @@ public class IgnoreAuthPattern { public IgnoreAuthPattern(String... patterns) { this.ignorePattern = Arrays.asList(patterns); } - private List ignorePattern; + private final List ignorePattern; } diff --git a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java similarity index 98% rename from backend/lib/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java rename to lib/java/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java index f5e1aeece0..0b6bf242e2 100644 --- a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java +++ b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package co.airy.spring.auth; +import co.airy.spring.jwt.Jwt; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; diff --git a/backend/lib/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java b/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java similarity index 97% rename from backend/lib/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java rename to lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java index c7b3bef1be..81b10d1633 100644 --- a/backend/lib/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java +++ b/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java @@ -1,5 +1,6 @@ package co.airy.spring.auth; +import co.airy.spring.jwt.Jwt; import co.airy.spring.core.AirySpringBootApplication; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,7 +20,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(properties = { - "auth.jwt-secret=424242424242424242424242424242424242424242424242424242" + "auth.jwt-secret=424242424242424242424242424242424242424242424242424242", + "ALLOWED_ORIGINS=*" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) @AutoConfigureMockMvc @ExtendWith(SpringExtension.class) diff --git a/backend/lib/spring/auth/src/test/java/co/airy/spring/auth/TestApp.java b/lib/java/spring/auth/src/test/java/co/airy/spring/auth/TestApp.java similarity index 100% rename from backend/lib/spring/auth/src/test/java/co/airy/spring/auth/TestApp.java rename to lib/java/spring/auth/src/test/java/co/airy/spring/auth/TestApp.java diff --git a/backend/lib/spring/core/BUILD b/lib/java/spring/core/BUILD similarity index 94% rename from backend/lib/spring/core/BUILD rename to lib/java/spring/core/BUILD index e6cebc3fd6..8fb4df07af 100644 --- a/backend/lib/spring/core/BUILD +++ b/lib/java/spring/core/BUILD @@ -8,7 +8,7 @@ custom_java_library( deps = [ "//:jackson", "//:spring", - "//backend/lib/log", + "//lib/java/log", "@maven//:javax_xml_bind_jaxb_api", "@maven//:org_springframework_boot_spring_boot", "@maven//:org_springframework_boot_spring_boot_autoconfigure", diff --git a/backend/lib/spring/core/src/main/java/co/airy/spring/core/AirySpringBootApplication.java b/lib/java/spring/core/src/main/java/co/airy/spring/core/AirySpringBootApplication.java similarity index 100% rename from backend/lib/spring/core/src/main/java/co/airy/spring/core/AirySpringBootApplication.java rename to lib/java/spring/core/src/main/java/co/airy/spring/core/AirySpringBootApplication.java diff --git a/backend/lib/spring/core/src/main/java/co/airy/spring/core/mappers/AiryObjectMapperConfig.java b/lib/java/spring/core/src/main/java/co/airy/spring/core/mappers/AiryObjectMapperConfig.java similarity index 100% rename from backend/lib/spring/core/src/main/java/co/airy/spring/core/mappers/AiryObjectMapperConfig.java rename to lib/java/spring/core/src/main/java/co/airy/spring/core/mappers/AiryObjectMapperConfig.java diff --git a/backend/lib/spring/core/src/main/resources/default.properties b/lib/java/spring/core/src/main/resources/default.properties similarity index 100% rename from backend/lib/spring/core/src/main/resources/default.properties rename to lib/java/spring/core/src/main/resources/default.properties diff --git a/lib/java/spring/jwt/BUILD b/lib/java/spring/jwt/BUILD new file mode 100644 index 0000000000..37a431d05a --- /dev/null +++ b/lib/java/spring/jwt/BUILD @@ -0,0 +1,20 @@ +load("//tools/build:java_library.bzl", "custom_java_library") + +lib_deps = [ + "//:jackson", + "//:jwt", + "//:lombok", + "//:spring", + "//:springboot_security", + "//lib/java/log", + "//lib/java/spring/core:spring-core", + "@maven//:javax_servlet_javax_servlet_api", + "@maven//:javax_xml_bind_jaxb_api", +] + +custom_java_library( + name = "airy-jwt", + srcs = glob(["src/main/java/co/airy/spring/jwt/**/*.java"]), + visibility = ["//visibility:public"], + deps = lib_deps, +) diff --git a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/Jwt.java b/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java similarity index 92% rename from backend/lib/spring/auth/src/main/java/co/airy/spring/auth/Jwt.java rename to lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java index 4c1f58acd8..5dc3c2c698 100644 --- a/backend/lib/spring/auth/src/main/java/co/airy/spring/auth/Jwt.java +++ b/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java @@ -1,4 +1,4 @@ -package co.airy.spring.auth; +package co.airy.spring.jwt; import co.airy.log.AiryLoggerFactory; import io.jsonwebtoken.Claims; @@ -8,12 +8,9 @@ import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpClientErrorException; import javax.crypto.spec.SecretKeySpec; -import javax.servlet.ServletException; import javax.xml.bind.DatatypeConverter; -import java.nio.charset.Charset; import java.security.Key; import java.time.Duration; import java.time.Instant; @@ -21,8 +18,6 @@ import java.util.HashMap; import java.util.Map; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; - @Component public class Jwt { private static final Logger log = AiryLoggerFactory.getLogger(Jwt.class); @@ -94,3 +89,4 @@ private Claims extractClaims(String token) { return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody(); } } + diff --git a/backend/lib/spring/kafka/core/BUILD b/lib/java/spring/kafka/core/BUILD similarity index 67% rename from backend/lib/spring/kafka/core/BUILD rename to lib/java/spring/kafka/core/BUILD index 29b7f80d76..adee20eebd 100644 --- a/backend/lib/spring/kafka/core/BUILD +++ b/lib/java/spring/kafka/core/BUILD @@ -5,11 +5,11 @@ custom_java_library( srcs = glob(["src/main/java/co/airy/spring/kafka/core/**/*.java"]), visibility = ["//visibility:public"], exports = [ - "//backend/lib/kafka/core:kafka-core", + "//lib/java/kafka/core:kafka-core", ], deps = [ "//:spring", - "//backend/lib/kafka/core:kafka-core", - "//backend/lib/spring/core:spring-core", + "//lib/java/kafka/core:kafka-core", + "//lib/java/spring/core:spring-core", ], ) diff --git a/backend/lib/spring/kafka/core/src/main/java/co/airy/spring/kafka/core/KafkaCoreConfig.java b/lib/java/spring/kafka/core/src/main/java/co/airy/spring/kafka/core/KafkaCoreConfig.java similarity index 100% rename from backend/lib/spring/kafka/core/src/main/java/co/airy/spring/kafka/core/KafkaCoreConfig.java rename to lib/java/spring/kafka/core/src/main/java/co/airy/spring/kafka/core/KafkaCoreConfig.java diff --git a/backend/lib/spring/kafka/healthcheck/BUILD b/lib/java/spring/kafka/healthcheck/BUILD similarity index 79% rename from backend/lib/spring/kafka/healthcheck/BUILD rename to lib/java/spring/kafka/healthcheck/BUILD index 8dcf94bbef..c44a432d72 100644 --- a/backend/lib/spring/kafka/healthcheck/BUILD +++ b/lib/java/spring/kafka/healthcheck/BUILD @@ -12,8 +12,8 @@ custom_java_library( visibility = ["//visibility:public"], deps = [ ":healthcheck_schema", - "//backend/lib/kafka/core:kafka-core", - "//backend/lib/kafka/schema:ops-application-health", + "//lib/java/kafka/core:kafka-core", + "//lib/java/kafka/schema:ops-application-health", "@maven//:org_springframework_spring_context", ], ) diff --git a/backend/lib/spring/kafka/healthcheck/healtcheck.avsc b/lib/java/spring/kafka/healthcheck/healtcheck.avsc similarity index 100% rename from backend/lib/spring/kafka/healthcheck/healtcheck.avsc rename to lib/java/spring/kafka/healthcheck/healtcheck.avsc diff --git a/backend/lib/spring/kafka/healthcheck/src/main/java/co/airy/spring/kafka/healthcheck/ProducerHealthCheck.java b/lib/java/spring/kafka/healthcheck/src/main/java/co/airy/spring/kafka/healthcheck/ProducerHealthCheck.java similarity index 100% rename from backend/lib/spring/kafka/healthcheck/src/main/java/co/airy/spring/kafka/healthcheck/ProducerHealthCheck.java rename to lib/java/spring/kafka/healthcheck/src/main/java/co/airy/spring/kafka/healthcheck/ProducerHealthCheck.java diff --git a/backend/lib/spring/kafka/streams/BUILD b/lib/java/spring/kafka/streams/BUILD similarity index 59% rename from backend/lib/spring/kafka/streams/BUILD rename to lib/java/spring/kafka/streams/BUILD index 9335b9b03b..0073add7d6 100644 --- a/backend/lib/spring/kafka/streams/BUILD +++ b/lib/java/spring/kafka/streams/BUILD @@ -5,12 +5,12 @@ custom_java_library( srcs = glob(["src/main/java/co/airy/spring/kafka/streams/**/*.java"]), visibility = ["//visibility:public"], exports = [ - "//backend/lib/kafka/streams:kafka-streams", + "//lib/java/kafka/streams:kafka-streams", ], deps = [ "//:spring", - "//backend/lib/kafka/core:kafka-core", - "//backend/lib/kafka/streams:kafka-streams", - "//backend/lib/spring/core:spring-core", + "//lib/java/kafka/core:kafka-core", + "//lib/java/kafka/streams:kafka-streams", + "//lib/java/spring/core:spring-core", ], ) diff --git a/backend/lib/spring/kafka/streams/src/main/java/co/airy/spring/kafka/streams/KafkaStreamsConfig.java b/lib/java/spring/kafka/streams/src/main/java/co/airy/spring/kafka/streams/KafkaStreamsConfig.java similarity index 100% rename from backend/lib/spring/kafka/streams/src/main/java/co/airy/spring/kafka/streams/KafkaStreamsConfig.java rename to lib/java/spring/kafka/streams/src/main/java/co/airy/spring/kafka/streams/KafkaStreamsConfig.java diff --git a/backend/lib/spring/test/BUILD b/lib/java/spring/test/BUILD similarity index 75% rename from backend/lib/spring/test/BUILD rename to lib/java/spring/test/BUILD index d5d9f192d7..864adbde07 100644 --- a/backend/lib/spring/test/BUILD +++ b/lib/java/spring/test/BUILD @@ -9,8 +9,8 @@ custom_java_library( "//:lombok", "//:spring", "//:springboot_test", - "//backend/lib/log", - "//backend/lib/spring/auth:spring-auth", - "//backend/lib/test", + "//lib/java/log", + "//lib/java/spring/jwt:airy-jwt", + "//lib/java/test", ], ) diff --git a/backend/lib/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java b/lib/java/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java similarity index 98% rename from backend/lib/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java rename to lib/java/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java index 0ebd259cbe..9cca7f2182 100644 --- a/backend/lib/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java +++ b/lib/java/spring/test/src/main/java/co/airy/spring/test/WebTestHelper.java @@ -1,7 +1,7 @@ package co.airy.spring.test; -import co.airy.spring.auth.Jwt; +import co.airy.spring.jwt.Jwt; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; diff --git a/backend/lib/spring/web/BUILD b/lib/java/spring/web/BUILD similarity index 86% rename from backend/lib/spring/web/BUILD rename to lib/java/spring/web/BUILD index 614cf00f46..15e49670de 100644 --- a/backend/lib/spring/web/BUILD +++ b/lib/java/spring/web/BUILD @@ -9,8 +9,8 @@ custom_java_library( "//:jwt", "//:lombok", "//:spring", - "//backend/lib/log", - "//backend/lib/spring/core:spring-core", + "//lib/java/log", + "//lib/java/spring/core:spring-core", "@maven//:javax_servlet_javax_servlet_api", "@maven//:javax_xml_bind_jaxb_api", "@maven//:org_springframework_spring_web", diff --git a/backend/lib/spring/web/src/main/java/co/airy/spring/web/filters/RequestLoggingFilter.java b/lib/java/spring/web/src/main/java/co/airy/spring/web/filters/RequestLoggingFilter.java similarity index 100% rename from backend/lib/spring/web/src/main/java/co/airy/spring/web/filters/RequestLoggingFilter.java rename to lib/java/spring/web/src/main/java/co/airy/spring/web/filters/RequestLoggingFilter.java diff --git a/backend/lib/test/BUILD b/lib/java/test/BUILD similarity index 88% rename from backend/lib/test/BUILD rename to lib/java/test/BUILD index 5f0db09d57..f39ec5de1c 100644 --- a/backend/lib/test/BUILD +++ b/lib/java/test/BUILD @@ -5,6 +5,6 @@ custom_java_library( srcs = glob(["src/main/java/co/airy/test/**/*.java"]), visibility = ["//visibility:public"], deps = [ - "//backend/lib/log", + "//lib/java/log", ], ) diff --git a/backend/lib/test/src/main/java/co/airy/test/FileHelper.java b/lib/java/test/src/main/java/co/airy/test/FileHelper.java similarity index 100% rename from backend/lib/test/src/main/java/co/airy/test/FileHelper.java rename to lib/java/test/src/main/java/co/airy/test/FileHelper.java diff --git a/backend/lib/test/src/main/java/co/airy/test/RunnableTest.java b/lib/java/test/src/main/java/co/airy/test/RunnableTest.java similarity index 100% rename from backend/lib/test/src/main/java/co/airy/test/RunnableTest.java rename to lib/java/test/src/main/java/co/airy/test/RunnableTest.java diff --git a/backend/lib/test/src/main/java/co/airy/test/Timing.java b/lib/java/test/src/main/java/co/airy/test/Timing.java similarity index 100% rename from backend/lib/test/src/main/java/co/airy/test/Timing.java rename to lib/java/test/src/main/java/co/airy/test/Timing.java diff --git a/backend/lib/uuid/BUILD b/lib/java/uuid/BUILD similarity index 100% rename from backend/lib/uuid/BUILD rename to lib/java/uuid/BUILD diff --git a/backend/lib/uuid/src/main/java/co/airy/uuid/UUIDv5.java b/lib/java/uuid/src/main/java/co/airy/uuid/UUIDv5.java similarity index 100% rename from backend/lib/uuid/src/main/java/co/airy/uuid/UUIDv5.java rename to lib/java/uuid/src/main/java/co/airy/uuid/UUIDv5.java diff --git a/frontend/types/BUILD b/lib/typescript/types/BUILD similarity index 65% rename from frontend/types/BUILD rename to lib/typescript/types/BUILD index 8e2f228774..7fed076ef6 100644 --- a/frontend/types/BUILD +++ b/lib/typescript/types/BUILD @@ -1,4 +1,4 @@ -load("//tools/build/web:typescript.bzl", "ts_library") +load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_library") package(default_visibility = ["//visibility:public"]) diff --git a/lib/typescript/types/content.ts b/lib/typescript/types/content.ts new file mode 100644 index 0000000000..d3d788072c --- /dev/null +++ b/lib/typescript/types/content.ts @@ -0,0 +1,14 @@ +/* tslint:disable */ +/* eslint-disable */ +// Generated using typescript-generator version 2.26.723 on 2020-12-02 10:41:15. + +export interface Content { + type: 'text'; +} + +export interface Text extends Content { + type: 'text'; + text: string; +} + +export type ContentUnion = Text; diff --git a/frontend/types/global.d.ts b/lib/typescript/types/global.d.ts similarity index 100% rename from frontend/types/global.d.ts rename to lib/typescript/types/global.d.ts diff --git a/maven_install.json b/maven_install.json index 0838403136..edd7cea957 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,6 +1,6 @@ { "dependency_tree": { - "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": -123319287, + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": -1613073853, "conflict_resolution": { "com.fasterxml.jackson.core:jackson-annotations:2.10.0": "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.fasterxml.jackson.core:jackson-core:2.10.0": "com.fasterxml.jackson.core:jackson-core:2.11.2", @@ -8,7 +8,6 @@ "io.jsonwebtoken:jjwt-api:0.10.5": "io.jsonwebtoken:jjwt-api:0.10.7", "io.jsonwebtoken:jjwt-impl:0.10.5": "io.jsonwebtoken:jjwt-impl:0.10.7", "io.jsonwebtoken:jjwt-jackson:0.10.5": "io.jsonwebtoken:jjwt-jackson:0.10.7", - "org.apache.avro:avro:1.9.1": "org.apache.avro:avro:1.9.2", "org.apache.kafka:kafka-clients:2.5.1": "org.apache.kafka:kafka-clients:5.5.1-ccs", "org.apache.kafka:kafka_2.12:2.5.1": "org.apache.kafka:kafka_2.12:5.5.1-ccs", "org.hamcrest:hamcrest:2.1": "org.hamcrest:hamcrest:2.2", @@ -659,7 +658,7 @@ "url": "https://jitpack.io/com/github/everit-org/json-schema/org.everit.json.schema/1.12.1/org.everit.json.schema-1.12.1.jar" }, { - "coord": "com.github.luben:zstd-jni:1.4.4-7", + "coord": "com.github.luben:zstd-jni:1.4.5-2", "dependencies": [], "directDependencies": [], "exclusions": [ @@ -668,15 +667,15 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar", - "https://oss.sonatype.org/content/repositories/snapshots/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar", - "https://repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar", - "https://jitpack.io/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar" + "https://packages.confluent.io/maven/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar", + "https://oss.sonatype.org/content/repositories/snapshots/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar", + "https://repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar", + "https://jitpack.io/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar" ], - "sha256": "24ff5dbe06bb7ed31f4087df7d824d7b843880f3c953d608b4867f3ffd16ba7e", - "url": "https://repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar" + "sha256": "973431c14b4d09a86e23b7184116fcac2d85501eb4a7430f7d185cce1af46050", + "url": "https://repo1.maven.org/maven2/com/github/luben/zstd-jni/1.4.5-2/zstd-jni-1.4.5-2.jar" }, { "coord": "com.google.auth:google-auth-library-credentials:0.20.0", @@ -2002,28 +2001,28 @@ "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", "io.confluent:kafka-schema-serializer:5.5.1", "org.apache.kafka:kafka-clients:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", + "org.apache.commons:commons-compress:1.20", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "jakarta.annotation:jakarta.annotation-api:1.3.5", "javax.ws.rs:javax.ws.rs-api:2.1.1", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.sun.activation:jakarta.activation:1.2.1", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "io.confluent:kafka-schema-registry-client:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.glassfish.jersey.core:jersey-common:2.30" ], "directDependencies": [ "io.confluent:kafka-schema-serializer:5.5.1", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro:1.10.0", "io.confluent:common-utils:5.5.1", "io.confluent:common-config:5.5.1", "io.confluent:kafka-schema-registry-client:5.5.1" @@ -2060,19 +2059,20 @@ "com.google.j2objc:j2objc-annotations:1.3", "commons-collections:commons-collections:3.2.2", "commons-logging:commons-logging:1.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "com.google.code.findbugs:jsr305:3.0.2", "com.fasterxml.jackson.datatype:jackson-datatype-joda:2.10.2", - "org.apache.avro:avro:1.9.2", "org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.3.50", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", "org.json:json:20190722", + "org.apache.commons:commons-compress:1.20", "org.jetbrains.kotlin:kotlin-stdlib:1.3.72", "org.lz4:lz4-java:1.7.1", "com.google.re2j:re2j:1.3", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "org.jetbrains.kotlin:kotlin-reflect:1.3.72", "com.google.errorprone:error_prone_annotations:2.3.4", "jakarta.annotation:jakarta.annotation-api:1.3.5", @@ -2087,13 +2087,12 @@ "com.sun.activation:jakarta.activation:1.2.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "org.scala-lang:scala-library:2.12.10", "com.google.guava:failureaccess:1.0.1", "com.google.guava:guava:29.0-jre", "io.confluent:kafka-schema-registry-client:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.module:jackson-module-parameter-names:2.11.0", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", @@ -2139,12 +2138,13 @@ "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", "org.apache.kafka:kafka-clients:5.5.1-ccs", "com.google.j2objc:j2objc-annotations:1.3", + "org.xerial.snappy:snappy-java:1.1.7.5", "com.google.code.findbugs:jsr305:3.0.2", - "org.apache.avro:avro:1.9.2", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "com.squareup.wire:wire-schema:3.2.2", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", + "org.apache.commons:commons-compress:1.20", "com.google.protobuf:protobuf-java:3.11.4", "com.squareup.wire:wire-runtime:3.2.2", "com.squareup.okio:okio:2.5.0", @@ -2153,7 +2153,7 @@ "com.google.code.gson:gson:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.71", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "com.google.errorprone:error_prone_annotations:2.3.4", "jakarta.annotation:jakarta.annotation-api:1.3.5", "javax.ws.rs:javax.ws.rs-api:2.1.1", @@ -2161,12 +2161,11 @@ "com.sun.activation:jakarta.activation:1.2.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "com.google.guava:failureaccess:1.0.1", "com.google.guava:guava:29.0-jre", "io.confluent:kafka-schema-registry-client:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.glassfish.jersey.core:jersey-common:2.30", @@ -2203,27 +2202,27 @@ "com.fasterxml.jackson.core:jackson-core:2.11.2", "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", "org.apache.kafka:kafka-clients:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", + "org.apache.commons:commons-compress:1.20", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "jakarta.annotation:jakarta.annotation-api:1.3.5", "javax.ws.rs:javax.ws.rs-api:2.1.1", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.sun.activation:jakarta.activation:1.2.1", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", + "com.github.luben:zstd-jni:1.4.5-2", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.glassfish.jersey.core:jersey-common:2.30" ], "directDependencies": [ "org.apache.kafka:kafka-clients:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro:1.10.0", "javax.ws.rs:javax.ws.rs-api:2.1.1", "io.confluent:common-utils:5.5.1", "io.confluent:common-config:5.5.1", @@ -2270,11 +2269,11 @@ "commons-logging:commons-logging:1.2", "com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.2", "org.yaml:snakeyaml:1.26", + "org.xerial.snappy:snappy-java:1.1.7.5", "com.google.code.findbugs:jsr305:3.0.2", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", "org.apache.kafka:kafka_2.12:5.5.1-ccs", "com.fasterxml.jackson.datatype:jackson-datatype-joda:2.10.2", - "org.apache.avro:avro:1.9.2", "org.glassfish.jersey.inject:jersey-hk2:2.30", "javax.activation:javax.activation-api:1.2.0", "org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.3.50", @@ -2295,8 +2294,8 @@ "org.eclipse.jetty.websocket:websocket-client:9.4.29.v20200521", "org.eclipse.jetty:jetty-webapp:9.4.29.v20200521", "org.eclipse.jetty.websocket:javax-websocket-server-impl:9.4.29.v20200521", + "org.apache.commons:commons-compress:1.20", "com.google.protobuf:protobuf-java:3.11.4", - "org.apache.commons:commons-lang3:3.8.1", "com.squareup.wire:wire-runtime:3.2.2", "com.squareup.okio:okio:2.5.0", "org.jetbrains.kotlin:kotlin-stdlib:1.3.72", @@ -2317,8 +2316,8 @@ "org.glassfish.jersey.core:jersey-server:2.30", "io.netty:netty-codec:4.1.51.Final", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.71", - "com.github.luben:zstd-jni:1.4.4-7", "org.glassfish.hk2:hk2-utils:2.6.1", + "org.apache.avro:avro:1.10.0", "org.glassfish.jersey.containers:jersey-container-servlet-core:2.30", "org.glassfish.jersey.core:jersey-client:2.30", "org.jetbrains.kotlin:kotlin-reflect:1.3.72", @@ -2359,10 +2358,8 @@ "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72", "io.confluent:common-utils:5.5.1", "org.ow2.asm:asm-analysis:7.3.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "javax.annotation:javax.annotation-api:1.3.2", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "org.eclipse.jetty.websocket:websocket-common:9.4.29.v20200521", "org.javassist:javassist:3.26.0-GA", "org.jboss.logging:jboss-logging:3.3.2.Final", @@ -2381,12 +2378,14 @@ "jakarta.activation:jakarta.activation-api:1.2.2", "javax.servlet:javax.servlet-api:3.1.0", "io.confluent:rest-utils:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "commons-cli:commons-cli:1.4", "com.fasterxml.jackson.module:jackson-module-parameter-names:2.11.0", "io.swagger:swagger-annotations:1.6.0", "org.scala-lang.modules:scala-collection-compat_2.12:2.1.3", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.eclipse.jetty:jetty-security:9.4.29.v20200521", + "org.apache.commons:commons-lang3:3.9", "javax.validation:validation-api:2.0.1.Final", "jakarta.el:jakarta.el-api:3.0.3", "io.netty:netty-transport-native-unix-common:4.1.48.Final", @@ -2408,9 +2407,9 @@ "directDependencies": [ "org.apache.kafka:kafka-clients:5.5.1-ccs", "org.apache.kafka:kafka_2.12:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", "org.apache.zookeeper:zookeeper:3.5.8", "io.confluent:kafka-protobuf-provider:5.5.1", + "org.apache.avro:avro:1.10.0", "org.glassfish.jersey.ext:jersey-bean-validation:2.30", "com.101tec:zkclient:0.11", "io.confluent:common-utils:5.5.1", @@ -2443,21 +2442,21 @@ "com.fasterxml.jackson.core:jackson-core:2.11.2", "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", "org.apache.kafka:kafka-clients:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", + "org.apache.commons:commons-compress:1.20", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "jakarta.annotation:jakarta.annotation-api:1.3.5", "javax.ws.rs:javax.ws.rs-api:2.1.1", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.sun.activation:jakarta.activation:1.2.1", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "io.confluent:kafka-schema-registry-client:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.glassfish.jersey.core:jersey-common:2.30" @@ -2490,22 +2489,22 @@ "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", "io.confluent:kafka-schema-serializer:5.5.1", "org.apache.kafka:kafka-clients:5.5.1-ccs", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "io.confluent:kafka-avro-serializer:5.5.1", "org.glassfish.hk2.external:jakarta.inject:2.6.1", "org.slf4j:slf4j-api:1.7.30", "org.glassfish.hk2:osgi-resource-locator:1.0.3", + "org.apache.commons:commons-compress:1.20", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", + "org.apache.avro:avro:1.10.0", "jakarta.annotation:jakarta.annotation-api:1.3.5", "javax.ws.rs:javax.ws.rs-api:2.1.1", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.sun.activation:jakarta.activation:1.2.1", "io.confluent:common-utils:5.5.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "io.confluent:common-config:5.5.1", - "org.apache.commons:commons-compress:1.19", "io.confluent:kafka-schema-registry-client:5.5.1", + "com.github.luben:zstd-jni:1.4.5-2", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.glassfish.jersey.core:jersey-common:2.30" @@ -2514,7 +2513,7 @@ "io.confluent:common-utils:5.5.1", "io.confluent:kafka-avro-serializer:5.5.1", "io.confluent:kafka-schema-registry-client:5.5.1", - "org.apache.avro:avro:1.9.2" + "org.apache.avro:avro:1.10.0" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -2542,6 +2541,7 @@ "javax.activation:activation:1.1.1", "org.apache.kafka:kafka-clients:5.5.1-ccs", "org.eclipse.jetty.websocket:websocket-server:9.4.29.v20200521", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", "org.glassfish.jersey.inject:jersey-hk2:2.30", "javax.activation:javax.activation-api:1.2.0", @@ -2564,7 +2564,6 @@ "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.11.2", "org.eclipse.jetty:jetty-client:9.4.29.v20200521", "org.glassfish.jersey.core:jersey-server:2.30", - "com.github.luben:zstd-jni:1.4.4-7", "org.glassfish.hk2:hk2-utils:2.6.1", "org.glassfish.jersey.containers:jersey-container-servlet-core:2.30", "org.glassfish.jersey.core:jersey-client:2.30", @@ -2587,7 +2586,6 @@ "org.hibernate.validator:hibernate-validator:6.0.17.Final", "io.confluent:common-utils:5.5.1", "org.ow2.asm:asm-analysis:7.3.1", - "org.xerial.snappy:snappy-java:1.1.7.3", "javax.annotation:javax.annotation-api:1.3.2", "io.confluent:common-config:5.5.1", "org.eclipse.jetty.websocket:websocket-common:9.4.29.v20200521", @@ -2602,6 +2600,7 @@ "org.glassfish:jakarta.el:3.0.3", "jakarta.activation:jakarta.activation-api:1.2.2", "javax.servlet:javax.servlet-api:3.1.0", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.eclipse.jetty:jetty-security:9.4.29.v20200521", "jakarta.el:jakarta.el-api:3.0.3", @@ -3194,7 +3193,6 @@ "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", "io.swagger:swagger-models:1.6.0", "com.google.errorprone:error_prone_annotations:2.3.4", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", @@ -3202,16 +3200,17 @@ "com.google.guava:guava:29.0-jre", "io.swagger:swagger-annotations:1.6.0", "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "org.apache.commons:commons-lang3:3.9", "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.10.1", "org.checkerframework:checker-qual:2.11.1" ], "directDependencies": [ "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", "io.swagger:swagger-models:1.6.0", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "com.google.guava:guava:29.0-jre", "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "org.apache.commons:commons-lang3:3.9", "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.10.1" ], "exclusions": [ @@ -3377,7 +3376,7 @@ "org.springframework:spring-tx:5.2.8.RELEASE", "org.slf4j:slf4j-api:1.7.30", "org.springframework:spring-beans:5.2.8.RELEASE", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", "commons-codec:commons-codec:1.11", "org.springframework:spring-jdbc:5.2.7.RELEASE", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64:10.10.0", @@ -3391,10 +3390,10 @@ "org.tukaani:xz:1.8", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64-alpine:10.10.0", "org.springframework:spring-aop:5.2.8.RELEASE", - "org.apache.commons:commons-compress:1.19", "com.google.guava:failureaccess:1.0.1", "com.google.guava:guava:29.0-jre", "org.springframework:spring-context:5.2.8.RELEASE", + "org.apache.commons:commons-lang3:3.9", "io.zonky.test:embedded-database-spring-test-autoconfigure:1.5.1", "org.checkerframework:checker-qual:2.11.1" ], @@ -3432,26 +3431,26 @@ "io.zonky.test.postgres:embedded-postgres-binaries-darwin-amd64:10.10.0", "commons-io:commons-io:2.6", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", "commons-codec:commons-codec:1.11", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64:10.10.0", "org.postgresql:postgresql:42.2.5", "org.tukaani:xz:1.8", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64-alpine:10.10.0", - "org.apache.commons:commons-compress:1.19" + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ "io.zonky.test.postgres:embedded-postgres-binaries-windows-amd64:10.10.0", "io.zonky.test.postgres:embedded-postgres-binaries-darwin-amd64:10.10.0", "commons-io:commons-io:2.6", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", "commons-codec:commons-codec:1.11", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64:10.10.0", "org.postgresql:postgresql:42.2.5", "org.tukaani:xz:1.8", "io.zonky.test.postgres:embedded-postgres-binaries-linux-amd64-alpine:10.10.0", - "org.apache.commons:commons-compress:1.19" + "org.apache.commons:commons-lang3:3.9" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -4053,25 +4052,23 @@ "url": "https://repo1.maven.org/maven2/org/antlr/antlr4-runtime/4.8-1/antlr4-runtime-4.8-1.jar" }, { - "coord": "org.apache.avro:avro-compiler:1.9.1", + "coord": "org.apache.avro:avro-compiler:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:avro:1.9.2", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", - "joda-time:joda-time:2.10.2", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.velocity:velocity-engine-core:2.0", - "com.fasterxml.jackson.core:jackson-databind:2.11.2" + "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ - "org.apache.avro:avro:1.9.2", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", - "joda-time:joda-time:2.10.2", - "org.apache.velocity:velocity-engine-core:2.0", - "com.fasterxml.jackson.core:jackson-databind:2.11.2" + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", + "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "org.apache.commons:commons-lang3:3.9" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -4079,43 +4076,46 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar", - "https://jitpack.io/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar" ], - "sha256": "044692c6920362a3ec13105fe8c3306f43aa593498161d649a4360301987b0d7", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.9.1/avro-compiler-1.9.1.jar" + "sha256": "c42cc542474934b7d903a0bf1fa38b45e7de817430e7de5de112a8217da04849", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-compiler/1.10.0/avro-compiler-1.10.0.jar" }, { - "coord": "org.apache.avro:avro-ipc-jetty:1.9.1", + "coord": "org.apache.avro:avro-ipc-jetty:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", "org.eclipse.jetty:jetty-io:9.4.29.v20200521", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.avro:avro-ipc:1.9.1", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521", - "org.apache.velocity:velocity-engine-core:2.0", "javax.servlet:javax.servlet-api:3.1.0", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "org.eclipse.jetty:jetty-security:9.4.29.v20200521" + "org.eclipse.jetty:jetty-security:9.4.29.v20200521", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", + "org.apache.avro:avro:1.10.0", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", - "org.apache.avro:avro-ipc:1.9.1", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521" ], "exclusions": [ @@ -4124,35 +4124,41 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar", - "https://jitpack.io/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar" ], - "sha256": "24a2442b71ceb7d1e8695612a823b688961e92bf983bbef9b0db5e68af1c8205", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.9.1/avro-ipc-jetty-1.9.1.jar" + "sha256": "1b96597915799cba5e3eb0ac811734c39ba91848efd7c8076499cba18536dc32", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc-jetty/1.10.0/avro-ipc-jetty-1.10.0.jar" }, { - "coord": "org.apache.avro:avro-ipc:1.9.1", + "coord": "org.apache.avro:avro-ipc:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.velocity:velocity-engine-core:2.0", - "com.fasterxml.jackson.core:jackson-databind:2.11.2" + "com.github.luben:zstd-jni:1.4.5-2", + "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.velocity:velocity-engine-core:2.0", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2" ], "exclusions": [ @@ -4161,42 +4167,45 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar", - "https://jitpack.io/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar" ], - "sha256": "be691e14e12c2075b5c78db316908af0c80c17d9c5e55766b71e4e945ed411b1", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.9.1/avro-ipc-1.9.1.jar" + "sha256": "257c9f34335a1c85f37de1fe065a0f97479686f3237091aae8d66a4c75937db0", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-ipc/1.10.0/avro-ipc-1.10.0.jar" }, { - "coord": "org.apache.avro:avro-mapred:1.9.1", + "coord": "org.apache.avro:avro-mapred:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", + "org.apache.avro:avro-ipc-jetty:1.10.0", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", "org.eclipse.jetty:jetty-io:9.4.29.v20200521", - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.avro:avro-ipc-jetty:1.9.1", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.avro:avro-ipc:1.9.1", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521", - "org.apache.velocity:velocity-engine-core:2.0", "javax.servlet:javax.servlet-api:3.1.0", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "org.eclipse.jetty:jetty-security:9.4.29.v20200521" + "org.eclipse.jetty:jetty-security:9.4.29.v20200521", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:avro-ipc:1.9.1", - "org.apache.avro:avro-ipc-jetty:1.9.1", + "org.apache.avro:avro-ipc:1.10.0", + "org.apache.avro:avro-ipc-jetty:1.10.0", "org.slf4j:slf4j-api:1.7.30" ], "exclusions": [ @@ -4205,63 +4214,64 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar", - "https://jitpack.io/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar" ], - "sha256": "a5c583c075cbfc514a5694c4f74b2f7ec1bfc3becaad2fbd6d1f6510a03e0da7", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.9.1/avro-mapred-1.9.1.jar" + "sha256": "7128e1393e04da8a81496dc55fe0c40117c7d48bb87b0c48a8fc7c0713f1af05", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.10.0/avro-mapred-1.10.0.jar" }, { - "coord": "org.apache.avro:avro-tools:1.9.1", + "coord": "org.apache.avro:avro-tools:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:trevni-avro:jar:tests:1.9.1", + "org.apache.avro:avro-ipc-jetty:1.10.0", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", "org.eclipse.jetty:jetty-io:9.4.29.v20200521", - "org.apache.commons:commons-lang3:3.8.1", - "org.apache.avro:avro-compiler:1.9.1", - "org.apache.avro:trevni-core:1.9.1", - "org.apache.avro:trevni-avro:1.9.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:trevni-core:1.10.0", + "org.apache.avro:avro:1.10.0", "org.tukaani:xz:1.8", - "joda-time:joda-time:2.10.2", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.avro:avro-ipc-jetty:1.9.1", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", - "org.xerial.snappy:snappy-java:1.1.7.3", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.avro:avro-ipc:1.9.1", + "org.apache.avro:trevni-core:jar:tests:1.10.0", + "org.apache.avro:avro-mapred:1.10.0", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521", - "org.apache.velocity:velocity-engine-core:2.0", "javax.servlet:javax.servlet-api:3.1.0", - "org.apache.avro:avro-mapred:1.9.1", + "org.apache.avro:avro-compiler:1.10.0", + "com.github.luben:zstd-jni:1.4.5-2", "commons-cli:commons-cli:1.4", "com.fasterxml.jackson.core:jackson-databind:2.11.2", "org.eclipse.jetty:jetty-security:9.4.29.v20200521", - "org.apache.avro:trevni-core:jar:tests:1.9.1", + "org.apache.commons:commons-lang3:3.9", + "org.apache.avro:trevni-avro:jar:tests:1.10.0", + "org.apache.avro:trevni-avro:1.10.0", "net.sf.jopt-simple:jopt-simple:5.0.4" ], "directDependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", - "org.apache.avro:trevni-avro:jar:tests:1.9.1", - "org.apache.avro:avro:1.9.2", + "org.xerial.snappy:snappy-java:1.1.7.5", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", - "org.apache.avro:avro-compiler:1.9.1", - "org.apache.avro:trevni-core:1.9.1", - "org.apache.avro:trevni-avro:1.9.1", + "org.apache.avro:trevni-core:1.10.0", + "org.apache.avro:avro:1.10.0", "org.tukaani:xz:1.8", - "org.xerial.snappy:snappy-java:1.1.7.3", - "org.apache.avro:avro-ipc:1.9.1", - "org.apache.avro:avro-mapred:1.9.1", + "org.apache.avro:trevni-core:jar:tests:1.10.0", + "org.apache.avro:avro-mapred:1.10.0", + "org.apache.avro:avro-compiler:1.10.0", + "com.github.luben:zstd-jni:1.4.5-2", "commons-cli:commons-cli:1.4", - "org.apache.avro:trevni-core:jar:tests:1.9.1", + "org.apache.avro:trevni-avro:jar:tests:1.10.0", + "org.apache.avro:trevni-avro:1.10.0", "net.sf.jopt-simple:jopt-simple:5.0.4" ], "exclusions": [ @@ -4270,29 +4280,29 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar", - "https://jitpack.io/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar" ], - "sha256": "9aa4a624d3719dd3962699b70b52cd2521c2762bf471a95f25eaa6d2ddc2e334", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-tools/1.9.1/avro-tools-1.9.1.jar" + "sha256": "60988679ab55bd145387551293718d380d9d89de5a7c8f31e1cf0d3ee622bbb4", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro-tools/1.10.0/avro-tools-1.10.0.jar" }, { - "coord": "org.apache.avro:avro:1.9.2", + "coord": "org.apache.avro:avro:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", "org.slf4j:slf4j-api:1.7.30", + "org.apache.commons:commons-compress:1.20", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.commons:commons-compress:1.19", "com.fasterxml.jackson.core:jackson-databind:2.11.2" ], "directDependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "org.apache.commons:commons-compress:1.19", + "org.apache.commons:commons-compress:1.20", "org.slf4j:slf4j-api:1.7.30" ], "exclusions": [ @@ -4301,45 +4311,47 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro/1.9.2/avro-1.9.2.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/avro/1.10.0/avro-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/avro/1.9.2/avro-1.9.2.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro/1.9.2/avro-1.9.2.jar", - "https://repo1.maven.org/maven2/org/apache/avro/avro/1.9.2/avro-1.9.2.jar", - "https://jitpack.io/org/apache/avro/avro/1.9.2/avro-1.9.2.jar" + "https://packages.confluent.io/maven/org/apache/avro/avro/1.10.0/avro-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/avro/1.10.0/avro-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/avro/1.10.0/avro-1.10.0.jar", + "https://jitpack.io/org/apache/avro/avro/1.10.0/avro-1.10.0.jar" ], - "sha256": "9d8f65504604b5fcdffb96793a9a32ca7b10bc0a469425d1d1fe4aa490c31c02", - "url": "https://repo1.maven.org/maven2/org/apache/avro/avro/1.9.2/avro-1.9.2.jar" + "sha256": "51f91407456e1aeab0ac70008c13cfa84b7262c4bb16656f9897e57e2b1057f3", + "url": "https://repo1.maven.org/maven2/org/apache/avro/avro/1.10.0/avro-1.10.0.jar" }, { - "coord": "org.apache.avro:trevni-avro:1.9.1", + "coord": "org.apache.avro:trevni-avro:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", + "org.apache.avro:avro-ipc-jetty:1.10.0", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", "org.eclipse.jetty:jetty-io:9.4.29.v20200521", - "org.apache.commons:commons-lang3:3.8.1", - "org.apache.avro:trevni-core:1.9.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:trevni-core:1.10.0", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.avro:avro-ipc-jetty:1.9.1", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", - "org.xerial.snappy:snappy-java:1.1.7.3", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.avro:avro-ipc:1.9.1", + "org.apache.avro:avro-mapred:1.10.0", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521", - "org.apache.velocity:velocity-engine-core:2.0", "javax.servlet:javax.servlet-api:3.1.0", - "org.apache.avro:avro-mapred:1.9.1", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "org.eclipse.jetty:jetty-security:9.4.29.v20200521" + "org.eclipse.jetty:jetty-security:9.4.29.v20200521", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ - "org.apache.avro:avro:1.9.2", - "org.apache.avro:avro-mapred:1.9.1", - "org.apache.avro:trevni-core:1.9.1", + "org.apache.avro:avro:1.10.0", + "org.apache.avro:avro-mapred:1.10.0", + "org.apache.avro:trevni-core:1.10.0", "org.slf4j:slf4j-api:1.7.30" ], "exclusions": [ @@ -4348,45 +4360,47 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar", - "https://jitpack.io/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar", + "https://jitpack.io/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar" ], - "sha256": "0c66fdfe0642b22f7a9dc20808704c332a052abc682ad7b8813217dfd2061483", - "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1.jar" + "sha256": "59478590d0551e7db6b92b77503a5e299e459938d5fbff92523f19857626fc41", + "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0.jar" }, { - "coord": "org.apache.avro:trevni-avro:jar:tests:1.9.1", + "coord": "org.apache.avro:trevni-avro:jar:tests:1.10.0", "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", + "org.apache.avro:avro-ipc-jetty:1.10.0", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.eclipse.jetty:jetty-http:9.4.29.v20200521", - "org.apache.avro:avro:1.9.2", + "org.apache.avro:avro-ipc:1.10.0", "org.slf4j:slf4j-api:1.7.30", "org.eclipse.jetty:jetty-io:9.4.29.v20200521", - "org.apache.commons:commons-lang3:3.8.1", - "org.apache.avro:trevni-core:1.9.1", + "org.apache.commons:commons-compress:1.20", + "org.apache.velocity:velocity-engine-core:2.2", + "org.apache.avro:trevni-core:1.10.0", + "org.apache.avro:avro:1.10.0", + "org.tukaani:xz:1.8", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.apache.avro:avro-ipc-jetty:1.9.1", "org.eclipse.jetty:jetty-util:9.4.29.v20200521", "org.eclipse.jetty:jetty-server:9.4.29.v20200521", - "org.xerial.snappy:snappy-java:1.1.7.3", "javax.annotation:javax.annotation-api:1.3.2", - "org.apache.commons:commons-compress:1.19", - "org.apache.avro:avro-ipc:1.9.1", + "org.apache.avro:avro-mapred:1.10.0", "org.eclipse.jetty:jetty-servlet:9.4.29.v20200521", - "org.apache.velocity:velocity-engine-core:2.0", "javax.servlet:javax.servlet-api:3.1.0", - "org.apache.avro:avro-mapred:1.9.1", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "org.eclipse.jetty:jetty-security:9.4.29.v20200521" + "org.eclipse.jetty:jetty-security:9.4.29.v20200521", + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ - "org.apache.avro:avro:1.9.2", - "org.apache.avro:avro-mapred:1.9.1", - "org.apache.avro:trevni-core:1.9.1", + "org.apache.avro:avro:1.10.0", + "org.apache.avro:avro-mapred:1.10.0", + "org.apache.avro:trevni-core:1.10.0", "org.slf4j:slf4j-api:1.7.30" ], "exclusions": [ @@ -4395,27 +4409,27 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar", - "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar", - "https://jitpack.io/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar" + "https://packages.confluent.io/maven/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar", + "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar", + "https://jitpack.io/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar" ], - "sha256": "2606f98e910296b1aa3b62f532a9d6f029c12a0b38f8f58b2d3df6421160ff5c", - "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.9.1/trevni-avro-1.9.1-tests.jar" + "sha256": "5082dbe3430969450b59d10235676a4cabb10987ed99299537d01b02d605aa2f", + "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-avro/1.10.0/trevni-avro-1.10.0-tests.jar" }, { - "coord": "org.apache.avro:trevni-core:1.9.1", + "coord": "org.apache.avro:trevni-core:1.10.0", "dependencies": [ + "org.apache.commons:commons-compress:1.20", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3", - "org.apache.commons:commons-compress:1.19" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "directDependencies": [ - "org.apache.commons:commons-compress:1.19", + "org.apache.commons:commons-compress:1.20", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -4423,27 +4437,27 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar", - "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar", - "https://jitpack.io/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar" + "https://packages.confluent.io/maven/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar", + "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar", + "https://jitpack.io/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar" ], - "sha256": "c611fb4ec8be60e1e50219bd6a5fe20cb60cf98b6ec3ea1136fbbb9c28576499", - "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1.jar" + "sha256": "50d0aaeb9365cf5a6de9f9c54272be5d1c4875e393412a7498cfa7dcb490831c", + "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0.jar" }, { - "coord": "org.apache.avro:trevni-core:jar:tests:1.9.1", + "coord": "org.apache.avro:trevni-core:jar:tests:1.10.0", "dependencies": [ + "org.apache.commons:commons-compress:1.20", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3", - "org.apache.commons:commons-compress:1.19" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "directDependencies": [ - "org.apache.commons:commons-compress:1.19", + "org.apache.commons:commons-compress:1.20", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -4451,18 +4465,18 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar", - "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar", - "https://jitpack.io/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar" + "https://packages.confluent.io/maven/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar", + "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar", + "https://jitpack.io/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar" ], - "sha256": "2d520b2323436d6dc3b0f545e2705704602e21709b546dd3dd5fcd7e9ec6a0f9", - "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.9.1/trevni-core-1.9.1-tests.jar" + "sha256": "7e65e54b34f66f134b15b9239464d470c1db4bb3fb1a9fbc18eedbbbad5648b7", + "url": "https://repo1.maven.org/maven2/org/apache/avro/trevni-core/1.10.0/trevni-core-1.10.0-tests.jar" }, { - "coord": "org.apache.commons:commons-compress:1.19", + "coord": "org.apache.commons:commons-compress:1.20", "dependencies": [], "directDependencies": [], "exclusions": [ @@ -4471,18 +4485,18 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar", - "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar", - "https://jitpack.io/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar" + "https://packages.confluent.io/maven/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://jitpack.io/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar" ], - "sha256": "ff2d59fad74e867630fbc7daab14c432654712ac624dbee468d220677b124dd5", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar" + "sha256": "0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar" }, { - "coord": "org.apache.commons:commons-lang3:3.8.1", + "coord": "org.apache.commons:commons-lang3:3.9", "dependencies": [], "directDependencies": [], "exclusions": [ @@ -4491,15 +4505,15 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar", - "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar", - "https://jitpack.io/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar" + "https://packages.confluent.io/maven/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar", + "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar", + "https://jitpack.io/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar" ], - "sha256": "dac807f65b07698ff39b1b07bfef3d87ae3fd46d91bbf8a2bc02b2a831616f68", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar" + "sha256": "de2e1dcdcf3ef917a8ce858661a06726a9a944f28e33ad7f9e08bea44dc3c230", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar" }, { "coord": "org.apache.curator:curator-test:4.2.0", @@ -4596,11 +4610,11 @@ "coord": "org.apache.kafka:connect-api:2.5.1", "dependencies": [ "org.apache.kafka:kafka-clients:5.5.1-ccs", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", "javax.ws.rs:javax.ws.rs-api:2.1.1", - "org.xerial.snappy:snappy-java:1.1.7.3" + "com.github.luben:zstd-jni:1.4.5-2" ], "directDependencies": [ "javax.ws.rs:javax.ws.rs-api:2.1.1", @@ -4628,13 +4642,13 @@ "dependencies": [ "com.fasterxml.jackson.core:jackson-core:2.11.2", "org.apache.kafka:kafka-clients:5.5.1-ccs", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", "org.apache.kafka:connect-api:2.5.1", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.0", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.xerial.snappy:snappy-java:1.1.7.3", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2" ], "directDependencies": [ @@ -4664,12 +4678,12 @@ "coord": "org.apache.kafka:connect-transforms:2.5.1", "dependencies": [ "org.apache.kafka:kafka-clients:5.5.1-ccs", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", "org.apache.kafka:connect-api:2.5.1", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", "javax.ws.rs:javax.ws.rs-api:2.1.1", - "org.xerial.snappy:snappy-java:1.1.7.3" + "com.github.luben:zstd-jni:1.4.5-2" ], "directDependencies": [ "org.apache.kafka:connect-api:2.5.1", @@ -4694,16 +4708,16 @@ { "coord": "org.apache.kafka:kafka-clients:5.5.1-ccs", "dependencies": [ + "com.github.luben:zstd-jni:1.4.5-2", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3", - "com.github.luben:zstd-jni:1.4.4-7", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.lz4:lz4-java:1.7.1" ], "directDependencies": [ - "com.github.luben:zstd-jni:1.4.4-7", + "com.github.luben:zstd-jni:1.4.5-2", "org.lz4:lz4-java:1.7.1", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "exclusions": [ "org.slf4j:slf4j-log4j12", @@ -4730,16 +4744,16 @@ { "coord": "org.apache.kafka:kafka-clients:jar:test:5.5.1-ccs", "dependencies": [ + "com.github.luben:zstd-jni:1.4.5-2", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3", - "com.github.luben:zstd-jni:1.4.4-7", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.lz4:lz4-java:1.7.1" ], "directDependencies": [ - "com.github.luben:zstd-jni:1.4.4-7", + "com.github.luben:zstd-jni:1.4.5-2", "org.lz4:lz4-java:1.7.1", "org.slf4j:slf4j-api:1.7.30", - "org.xerial.snappy:snappy-java:1.1.7.3" + "org.xerial.snappy:snappy-java:1.1.7.5" ], "exclusions": [ "ch.qos.logback:logback-classic", @@ -4763,14 +4777,14 @@ "com.fasterxml.jackson.core:jackson-core:2.11.2", "org.apache.kafka:kafka-clients:5.5.1-ccs", "org.apache.kafka:connect-json:2.5.1", + "org.xerial.snappy:snappy-java:1.1.7.5", "org.slf4j:slf4j-api:1.7.30", "org.apache.kafka:connect-api:2.5.1", "org.lz4:lz4-java:1.7.1", - "com.github.luben:zstd-jni:1.4.4-7", "org.rocksdb:rocksdbjni:5.18.3", "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.0", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.xerial.snappy:snappy-java:1.1.7.3", + "com.github.luben:zstd-jni:1.4.5-2", "com.fasterxml.jackson.core:jackson-databind:2.11.2" ], "directDependencies": [ @@ -4802,6 +4816,7 @@ "org.apache.kafka:kafka-clients:5.5.1-ccs", "com.thoughtworks.paranamer:paranamer:2.8", "com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.2", + "org.xerial.snappy:snappy-java:1.1.7.5", "com.fasterxml.jackson.module:jackson-module-scala_2.12:2.10.2", "org.slf4j:slf4j-api:1.7.30", "com.typesafe.scala-logging:scala-logging_2.12:3.9.2", @@ -4810,7 +4825,6 @@ "io.netty:netty-buffer:4.1.51.Final", "io.netty:netty-transport-native-epoll:4.1.48.Final", "io.netty:netty-codec:4.1.51.Final", - "com.github.luben:zstd-jni:1.4.4-7", "io.netty:netty-resolver:4.1.51.Final", "org.scala-lang.modules:scala-java8-compat_2.12:0.9.0", "io.netty:netty-handler:4.1.51.Final", @@ -4821,9 +4835,9 @@ "com.yammer.metrics:metrics-core:2.2.0", "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.0", "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.xerial.snappy:snappy-java:1.1.7.3", "org.scala-lang:scala-library:2.12.10", "io.netty:netty-common:4.1.51.Final", + "com.github.luben:zstd-jni:1.4.5-2", "commons-cli:commons-cli:1.4", "org.scala-lang.modules:scala-collection-compat_2.12:2.1.3", "com.fasterxml.jackson.core:jackson-databind:2.11.2", @@ -4937,13 +4951,127 @@ "url": "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-slf4j-impl/2.12.1/log4j-slf4j-impl-2.12.1.jar" }, { - "coord": "org.apache.velocity:velocity-engine-core:2.0", + "coord": "org.apache.lucene:lucene-analyzers-common:8.7.0", + "dependencies": [ + "org.apache.lucene:lucene-core:8.7.0" + ], + "directDependencies": [ + "org.apache.lucene:lucene-core:8.7.0" + ], + "exclusions": [ + "ch.qos.logback:logback-classic", + "org.springframework.boot:spring-boot-starter-tomcat", + "org.springframework.boot:spring-boot-starter-logging", + "org.slf4j:slf4j-log4j12" + ], + "file": "v1/https/repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar", + "mirror_urls": [ + "https://packages.confluent.io/maven/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar", + "https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar", + "https://jitpack.io/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar" + ], + "sha256": "ad01765985f1571e9000db068e4a923e9a029db44689ea5ae271b489cb8f3cfd", + "url": "https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common/8.7.0/lucene-analyzers-common-8.7.0.jar" + }, + { + "coord": "org.apache.lucene:lucene-core:8.7.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "ch.qos.logback:logback-classic", + "org.springframework.boot:spring-boot-starter-tomcat", + "org.springframework.boot:spring-boot-starter-logging", + "org.slf4j:slf4j-log4j12" + ], + "file": "v1/https/repo1.maven.org/maven2/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar", + "mirror_urls": [ + "https://packages.confluent.io/maven/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar", + "https://repo1.maven.org/maven2/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar", + "https://jitpack.io/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar" + ], + "sha256": "8f2678fa42ffd71e5b54be3badc4e641fb4f54b0c777ef5c7f023114f847e4ef", + "url": "https://repo1.maven.org/maven2/org/apache/lucene/lucene-core/8.7.0/lucene-core-8.7.0.jar" + }, + { + "coord": "org.apache.lucene:lucene-queries:8.7.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "org.slf4j:slf4j-log4j12", + "org.springframework.boot:spring-boot-starter-tomcat", + "org.apache.lucene:lucene-core", + "ch.qos.logback:logback-classic", + "org.springframework.boot:spring-boot-starter-logging" + ], + "file": "v1/https/repo1.maven.org/maven2/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar", + "mirror_urls": [ + "https://packages.confluent.io/maven/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar", + "https://repo1.maven.org/maven2/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar", + "https://jitpack.io/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar" + ], + "sha256": "e40d87b496499d74c95286baed28ad97455fb45b7b32b9e2b8b2ba978ab91d7a", + "url": "https://repo1.maven.org/maven2/org/apache/lucene/lucene-queries/8.7.0/lucene-queries-8.7.0.jar" + }, + { + "coord": "org.apache.lucene:lucene-queryparser:8.7.0", + "dependencies": [ + "org.apache.lucene:lucene-sandbox:8.7.0", + "org.apache.lucene:lucene-core:8.7.0", + "org.apache.lucene:lucene-queries:8.7.0" + ], + "directDependencies": [ + "org.apache.lucene:lucene-core:8.7.0", + "org.apache.lucene:lucene-queries:8.7.0", + "org.apache.lucene:lucene-sandbox:8.7.0" + ], + "exclusions": [ + "ch.qos.logback:logback-classic", + "org.springframework.boot:spring-boot-starter-tomcat", + "org.springframework.boot:spring-boot-starter-logging", + "org.slf4j:slf4j-log4j12" + ], + "file": "v1/https/repo1.maven.org/maven2/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar", + "mirror_urls": [ + "https://packages.confluent.io/maven/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar", + "https://repo1.maven.org/maven2/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar", + "https://jitpack.io/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar" + ], + "sha256": "033530e8cdb47c05f7c777abf59699e3fd251bb119b7840b335b2832af9dd636", + "url": "https://repo1.maven.org/maven2/org/apache/lucene/lucene-queryparser/8.7.0/lucene-queryparser-8.7.0.jar" + }, + { + "coord": "org.apache.lucene:lucene-sandbox:8.7.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "org.slf4j:slf4j-log4j12", + "org.springframework.boot:spring-boot-starter-tomcat", + "org.apache.lucene:lucene-core", + "ch.qos.logback:logback-classic", + "org.springframework.boot:spring-boot-starter-logging" + ], + "file": "v1/https/repo1.maven.org/maven2/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar", + "mirror_urls": [ + "https://packages.confluent.io/maven/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar", + "https://repo1.maven.org/maven2/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar", + "https://jitpack.io/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar" + ], + "sha256": "073381087a3014cc66859779141a4627e239b2d019676d9800411172b2c0bee6", + "url": "https://repo1.maven.org/maven2/org/apache/lucene/lucene-sandbox/8.7.0/lucene-sandbox-8.7.0.jar" + }, + { + "coord": "org.apache.velocity:velocity-engine-core:2.2", "dependencies": [ "org.slf4j:slf4j-api:1.7.30", - "org.apache.commons:commons-lang3:3.8.1" + "org.apache.commons:commons-lang3:3.9" ], "directDependencies": [ - "org.apache.commons:commons-lang3:3.8.1", + "org.apache.commons:commons-lang3:3.9", "org.slf4j:slf4j-api:1.7.30" ], "exclusions": [ @@ -4952,15 +5080,15 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar", - "https://repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar", - "https://jitpack.io/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar" + "https://packages.confluent.io/maven/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar", + "https://repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar", + "https://jitpack.io/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar" ], - "sha256": "d5c2ce1e8ea0bec8f0755966df931983d2cdbad28300aeaf0c85ce5e75663165", - "url": "https://repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.0/velocity-engine-core-2.0.jar" + "sha256": "5167f8cf2dbc003b632a49b672161d8d96c8c6f03056d29bfd540a8a789d715e", + "url": "https://repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.2/velocity-engine-core-2.2.jar" }, { "coord": "org.apache.yetus:audience-annotations:0.5.0", @@ -9529,7 +9657,7 @@ "url": "https://repo1.maven.org/maven2/org/tukaani/xz/1.8/xz-1.8.jar" }, { - "coord": "org.xerial.snappy:snappy-java:1.1.7.3", + "coord": "org.xerial.snappy:snappy-java:1.1.7.5", "dependencies": [], "directDependencies": [], "exclusions": [ @@ -9538,15 +9666,15 @@ "org.springframework.boot:spring-boot-starter-logging", "org.slf4j:slf4j-log4j12" ], - "file": "v1/https/repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar", + "file": "v1/https/repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar", "mirror_urls": [ - "https://packages.confluent.io/maven/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar", - "https://oss.sonatype.org/content/repositories/snapshots/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar", - "https://repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar", - "https://jitpack.io/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar" + "https://packages.confluent.io/maven/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar", + "https://oss.sonatype.org/content/repositories/snapshots/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar", + "https://repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar", + "https://jitpack.io/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar" ], - "sha256": "7eea31c0a25d35cd092d8aec08bed04f22152409b58d63d43839074a9ab7ab97", - "url": "https://repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar" + "sha256": "5be9642ebb9851b8ce6a272bace492b5c1da2fac53605f172aafc39a33df3862", + "url": "https://repo1.maven.org/maven2/org/xerial/snappy/snappy-java/1.1.7.5/snappy-java-1.1.7.5.jar" }, { "coord": "org.xmlunit:xmlunit-core:2.7.0", diff --git a/package.json b/package.json index e4bb184834..078c2e7644 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,25 @@ "dependencies": { "@airyhq/components": "latest", "@stomp/stompjs": "^6.0.0", + "@types/facebook-js-sdk": "^3.3.0", "@types/node": "12.11.1", - "@types/react": "16.9.34", "@types/react-dom": "16.9.2", "@types/react-redux": "7.1.3", "@types/react-router-dom": "^5.1.0", + "@types/react": "16.9.34", "core-js": "3", "emoji-mart": "^3.0.0", "linkifyjs": "^2.1.9", - "preact": "^10.5.7", + "lodash-es": "4.17.15", "preact-router": "^3.2.1", - "react": "16.12.0", + "preact": "^10.5.7", "react-dom": "16.12.0", + "react-facebook-login": "^4.1.1", "react-redux": "7.1.3", "react-router-dom": "5.1.2", - "redux": "^4.0.5", + "react": "16.12.0", "redux-starter-kit": "^0.8.1", + "redux": "^4.0.5", "regenerator-runtime": "^0.13.5", "typesafe-actions": "^4.4.2" }, @@ -41,6 +44,7 @@ "html-webpack-plugin": "^4.2.0", "minimist": "^1.2.5", "node-sass": "^4.14.0", + "reselect": "4.0.0", "prettier": "^1.19.1", "react-hot-loader": "^4.12.20", "sass-loader": "^8.0.2", diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 269e141a82..68c6a67a82 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -6,7 +6,6 @@ ARCH=$(uname -m) OS=$(uname) SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -AIRY_VERSION=${AIRY_VERSION:-latest} infra_path="" infra_path+=$( dirname $SCRIPT_PATH ) infra_path+="/infrastructure" @@ -124,4 +123,23 @@ fi cd $infra_path vagrant destroy -f + +if [ -z ${AIRY_VERSION+x} ]; then + branch_name="$(git symbolic-ref HEAD 2>/dev/null)" || + branch_name="(unnamed branch)" # detached HEAD + + branch_name=${branch_name##refs/heads/} + case "$branch_name" in + develop ) + AIRY_VERSION=beta + ;; + release* ) + AIRY_VERSION=release + ;; + * ) + AIRY_VERSION=latest + ;; + esac +fi + AIRY_VERSION=${AIRY_VERSION} vagrant up diff --git a/scripts/lint.sh b/scripts/lint.sh index f3f61d4e23..63eed4e6c2 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -1,6 +1,9 @@ -#!/usr/bin/env bash +#!/bin/bash +set -eo pipefail +IFS=$'\n\t' + echo "Running Bazel lint" -bazel run //tools/code-format:check_buildifier +bazel run @com_github_airyhq_bazel_tools//code-format:check_buildifier echo echo "Running Prettier and Java tests" bazel test --test_tag_filters=lint //... diff --git a/tools/build/java_library.bzl b/tools/build/java_library.bzl index b68552bca3..bdf10ef467 100644 --- a/tools/build/java_library.bzl +++ b/tools/build/java_library.bzl @@ -1,5 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") -load("//tools/code-format:checkstyle.bzl", "check_pkg") +load("@com_github_airyhq_bazel_tools//code-format:checkstyle.bzl", "check_pkg") def custom_java_library(**kwargs): check_pkg() diff --git a/tools/build/springboot.bzl b/tools/build/springboot.bzl index 5451fb2a27..7d58c89cb8 100644 --- a/tools/build/springboot.bzl +++ b/tools/build/springboot.bzl @@ -1,6 +1,6 @@ load("@rules_java//java:defs.bzl", "java_binary") load("@io_bazel_rules_docker//container:container.bzl", "container_image") -load("//tools/code-format:checkstyle.bzl", "check_pkg") +load("@com_github_airyhq_bazel_tools//code-format:checkstyle.bzl", "check_pkg") # Spring Boot Executable JAR Layout specification # reverse engineered from the Spring Boot maven plugin diff --git a/tools/build/status.sh b/tools/build/status.sh new file mode 100755 index 0000000000..6c9a26691d --- /dev/null +++ b/tools/build/status.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo STABLE_GIT_COMMIT $(git rev-parse HEAD) + +echo STABLE_VERSION $(cat ./VERSION) \ No newline at end of file diff --git a/tools/build/web/BUILD b/tools/build/web/BUILD deleted file mode 100644 index e966c1792c..0000000000 --- a/tools/build/web/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -exports_files([ - "webpack.library.config.js", - "webpack.prod.config.js", - "webpack.dev.config.js", - "setupTests.js", - "runJest.js", - "runWebpackDevServer.js", -]) diff --git a/tools/build/web/files.bzl b/tools/build/web/files.bzl deleted file mode 100644 index ff35409c96..0000000000 --- a/tools/build/web/files.bzl +++ /dev/null @@ -1,35 +0,0 @@ -def _copy_filegroup_impl(ctx): - build_path = ctx.build_file_path.replace("BUILD", "") - - all_input_files = [] - for group in ctx.attr.input_groups: - all_input_files += group.files.to_list() - - all_outputs = [] - for f in all_input_files: - path = f.path.replace(build_path, "") - out = ctx.actions.declare_file(path) - all_outputs.append(out) - ctx.actions.run_shell( - outputs = [out], - inputs = depset([f]), - arguments = [f.path, out.path], - command = "cp $1 $2", - ) - - return [ - DefaultInfo( - files = depset(all_outputs), - runfiles = ctx.runfiles(files = all_outputs), - ), - ] - -# Copy file groups to bazel-out to make them accessible as generated files in web builds -copy_filegroups = rule( - implementation = _copy_filegroup_impl, - attrs = { - "input_groups": attr.label_list( - allow_files = True, - ), - }, -) diff --git a/tools/build/web/runWebpackDevServer.js b/tools/build/web/runWebpackDevServer.js deleted file mode 100644 index 97617b5a0f..0000000000 --- a/tools/build/web/runWebpackDevServer.js +++ /dev/null @@ -1,58 +0,0 @@ -const path = require('path'); - -const argv = require('minimist')(process.argv.slice(2)); - -const configGenerator = require(path.resolve(argv.config)); -console.log('cwd', process.cwd()); - -const config = configGenerator(process.env, argv); - -const webpack = require('webpack'); - -const port = 8080; - -const middleware = require('webpack-dev-middleware'); -const compiler = webpack(config); -const express = require('express'); -const app = express(); - -const instance = middleware(compiler, { - publicPath: config.output.publicPath, - noInfo: true, - stats: {colors: true}, -}); - -app.use( - require('connect-history-api-fallback')({ - rewrites: { - from: new RegExp('/[^.]*$'), - to: `/index.html`, - }, - }) -); - -app.use(instance); - -app.use(require('webpack-hot-middleware')(compiler)); - -// After the first build, we immediately stop the built-in middleware watcher -// as we will manually retrigger builds using ibazel -instance.waitUntilValid(() => { - instance.close(); -}); - -// Listen for ibazel commands on stdin -process.stdin.setEncoding('utf8'); - -process.stdin.on('readable', () => { - let chunk; - // Use a loop to make sure we read all available data. - while ((chunk = process.stdin.read()) !== null) { - if (chunk.includes('IBAZEL_BUILD_COMPLETED SUCCESS')) { - console.log('IBAZEL triggered an invalidation'); - instance.invalidate(); - } - } -}); - -app.listen(port, () => console.log('Webpack app listening on port ' + port)); diff --git a/tools/build/web/typescript.bzl b/tools/build/web/typescript.bzl deleted file mode 100644 index 94aac5cc64..0000000000 --- a/tools/build/web/typescript.bzl +++ /dev/null @@ -1,89 +0,0 @@ -load("//tools/build/web:files.bzl", "copy_filegroups") -load("//tools/code-format:prettier.bzl", "check_pkg") -load("@npm_bazel_typescript//:index.bzl", lib_ts = "ts_library") - -""" -Usage - -ts_library( - name = "mylib", - srcs = "index.ts", - deps = [ - "@npm//react", - "@npm//@types/react", - ], - data = ["assets/logo.svg"] -) - -parameters: - -name - Unique name of the rule. Will also be used as the js module name so that you can import it like so - `import {someFunction} from 'mylib'` -srcs - (optional) Your components source files. By default we glob all .ts and .tsx files, so for most use cases you can avoid this -deps - (optional) Node module dependencies required to build the library -data - (optional) Files needed as imports to your typescript files. By default we glob a typical web file extensions. -tsconfig - (optional) It's possible to extend tsconfigs! Give it a try, if - it fits your use case (https://www.npmjs.com/package/@bazel/typescript#ts_config) - -""" - -ASSETS_SUFFIX = "_assets" -DEFAULT_DEP_NAME = "types" -DEFAULT_DEP = "//frontend/" + DEFAULT_DEP_NAME - -def ts_library(name, srcs = None, deps = None, data = None, tsconfig = None): - tsconfig = "//:tsconfig.json" if not tsconfig else tsconfig - deps = [] if not deps else deps - srcs = native.glob(["**/*.tsx", "**/*.ts"]) if not srcs else srcs - - if DEFAULT_DEP not in deps and name != DEFAULT_DEP_NAME: - deps = [DEFAULT_DEP] + deps - - default_data_glob = native.glob([ - "**/*.scss", - "**/*.css", - "**/*.json", - "**/*.png", - "**/*.svg", - "**/*.json", - ]) - - check_pkg() - - data = default_data_glob if not data else data - - native.filegroup( - name = name + "_asset_files", - srcs = data, - ) - - copy_filegroups( - name = name + ASSETS_SUFFIX, - input_groups = [ - name + "_asset_files", - ], - ) - - lib_ts( - name = name, - module_name = name, - srcs = srcs, - devmode_module = "esnext", - devmode_target = "esnext", - prodmode_module = "esnext", - prodmode_target = "esnext", - tsconfig = tsconfig, - deps = deps, - ) - -# Helper function to get asset target from a ts_library target -def get_assets_label(lib): - # Fully qualified name e.g. //package/lib:lib - if ":" in lib: - return lib + ASSETS_SUFFIX - - # Shorthand e.g. //package/lib - folders = lib.split("/") - last_folder = folders[-1] - - return lib + ":" + last_folder + ASSETS_SUFFIX diff --git a/tools/build/web/web_library.bzl b/tools/build/web/web_library.bzl deleted file mode 100644 index fc15876e93..0000000000 --- a/tools/build/web/web_library.bzl +++ /dev/null @@ -1,77 +0,0 @@ -load("//tools/build/web:typescript.bzl", "get_assets_label") -load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") - -""" -Usage - -web_library( - name = "bundle", - app_lib = ":app", - entry = "ui/web/widget/src/index.js", - module_deps = module_deps -) - -parameters: - -name - Unique name of the build rule. The dev server rule will be called name_server -app_lib - Label of the ts_library to run the tests on -entry - Relative path to your compiled index.js -output - Dictionary that gets applied to the webpack output https://webpack.js.org/configuration/output/ -externals - (optional) Dependencies that should not be bundled, see https://webpack.js.org/guides/author-libraries/#externalize-lodash -module_deps - (optional) app_lib dependencies on our own typescript libraries (TODO infer this) -""" - -def web_library( - name, - app_lib, - entry, - output, - externals = {}, - module_deps = []): - ts_transpiled_sources = name + "_ts_transpiled" - - ts_srcs = [app_lib] + module_deps - ts_srcs_assets = [get_assets_label(src) for src in ts_srcs] - - native.filegroup( - name = ts_transpiled_sources, - srcs = ts_srcs, - output_group = "es5_sources", - ) - - webpack_config = "//tools/build/web:webpack.library.config.js" - - ts_config = app_lib + "_tsconfig.json" - - args = [ - "$(GENDIR)/" + entry, - "--config", - "$(execpath " + webpack_config + ")", - "--tsconfig", - "$(location " + ts_config + ")", - "--outputDict", - encode_dict(output), - "--externalDict", - encode_dict(externals), - "--path", - "$(@D)", - ] - - webpack( - name = name, - output_dir = True, - args = args, - data = [ - ":" + ts_transpiled_sources, - webpack_config, - ts_config, - "@npm//:node_modules", - ] + - ts_srcs_assets, - ) - -def encode_dict(output): - return "|".join([ - setting[0] + "=" + setting[1] - for setting in output.items() - ]) diff --git a/tools/build/web/webapp.bzl b/tools/build/web/webapp.bzl deleted file mode 100644 index 1764bbe01f..0000000000 --- a/tools/build/web/webapp.bzl +++ /dev/null @@ -1,110 +0,0 @@ -load("//tools/build/web:typescript.bzl", "get_assets_label") -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") - -""" -Usage - -webapp( - name = "bundle", - app_lib = ":app", - static_assets = "//ui/web/inbox/public", - entry = "ui/web/inbox/src/index.js", - index = ":index.html", - dev_index = ":dev_index.html", - module_deps = module_deps, - webpack_prod_config = ":webpack.prod.config.js" - webpack_dev_config = ":webpack.dev.config.js" -) - -parameters: - -name - Unique name of the build rule. The dev server rule will be called name_server -app_lib - Label of the ts_library to run the tests on -static_assets - (optional) Filegroup (list of files) that should be copied "as is" to the webroot. - Files need to be in a folder called 'public' so that we can implicitly infer their purpose -entry - Relative path to your compiled index.js -index - index.html file used for the build -dev_index - (optional) index.html file used for the devserver (defaults to bundle index) -module_deps - (optional) app_lib dependencies on our own typescript libraries (TODO infer this) - -(optional) webpack_prod_config and webpack_dev_config can be used to supply custom dev and prod rules -""" - -def webapp( - name, - app_lib, - entry, - index, - static_assets = None, - module_deps = [], - dev_index = None, - webpack_prod_config = None, - webpack_dev_config = None): - static_assets = [static_assets] if static_assets else [] - ts_transpiled_sources = name + "_ts_transpiled" - - ts_srcs = [app_lib] + module_deps - ts_srcs_assets = [get_assets_label(src) for src in ts_srcs] - - native.filegroup( - name = ts_transpiled_sources, - srcs = ts_srcs, - output_group = "es5_sources", - ) - - webpack_prod_config = "//tools/build/web:webpack.prod.config.js" if not webpack_prod_config else webpack_prod_config - webpack_dev_config = "//tools/build/web:webpack.dev.config.js" if not webpack_dev_config else webpack_dev_config - - ts_config = app_lib + "_tsconfig.json" - - webpack( - name = name, - output_dir = True, - args = [ - "$(GENDIR)/" + entry, - "--config", - "$(execpath " + webpack_prod_config + ")", - "--tsconfig", - "$(location " + ts_config + ")", - "--index", - "$(location " + index + ")", - "--path", - "$(@D)", - ], - data = [ - ts_transpiled_sources, - webpack_prod_config, - index, - ts_config, - "@npm//:node_modules", - ] + ts_srcs_assets + static_assets, - ) - - dev_index = index if not dev_index else dev_index - - nodejs_binary( - name = name + "_server", - entry_point = "//tools/build/web:runWebpackDevServer.js", - args = [ - "--entry", - entry, - "--config", - "$(execpath " + webpack_dev_config + ")", - "--tsconfig", - "$(location " + ts_config + ")", - "--index", - "$(location " + dev_index + ")", - ], - install_source_map_support = False, - data = [ - ts_transpiled_sources, - webpack_dev_config, - dev_index, - ts_config, - "@npm//:node_modules", - ] + ts_srcs_assets + static_assets, - tags = [ - "ibazel_notify_changes", - ], - ) diff --git a/tools/build/web/webpack.dev.config.js b/tools/build/web/webpack.dev.config.js deleted file mode 100644 index 9414f3b746..0000000000 --- a/tools/build/web/webpack.dev.config.js +++ /dev/null @@ -1,152 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); - -// For dev the tsconfig-paths-webpack-plugin doesn't cut it, as it links to file locations -// that are only available to build rules -function resolveTsconfigPathsToAlias({tsconfigPath, basePath}) { - const {paths} = require(tsconfigPath).compilerOptions; - - const stripGlobs = path => path.replace('/*', ''); - - return Object.keys(paths).reduce((aliases, moduleMappingKey) => { - const key = stripGlobs(moduleMappingKey); - const value = path.resolve(basePath, stripGlobs(paths[moduleMappingKey][0]).replace('*', '')); - - return { - ...aliases, - [key]: value, - }; - }, {}); -} - -module.exports = (env, argv) => ({ - mode: 'development', - target: 'web', - bail: false, - - entry: ['react-hot-loader/patch', 'webpack-hot-middleware/client', path.resolve(argv.entry)], - - output: { - publicPath: '/', - }, - - optimization: { - minimize: false, - }, - - resolve: { - alias: resolveTsconfigPathsToAlias({ - tsconfigPath: path.resolve(argv.tsconfig), - basePath: process.cwd(), - }), - }, - - devtool: 'cheap-module-eval-source-map', - - module: { - rules: [ - { - test: /\.(mjs|js)$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - cacheDirectory: true, - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: 3, - modules: false, - targets: ['>0.2%', 'not dead', 'not op_mini all'], - }, - ], - ], - }, - }, - { - test: /\.(scss|css)$/, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - importLoaders: 1, - modules: { - auto: true, - localIdentName: '[name]_[local]-[hash:base64:5]', - }, - }, - }, - 'sass-loader', - ], - }, - { - test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/, - loader: 'file-loader', - options: { - name: 'media/[name].[hash:8].[ext]', - }, - }, - { - test: /\.svg$/, - use: [ - { - loader: '@svgr/webpack', - options: { - titleProp: true, - svgoConfig: { - plugins: { - removeViewBox: false, - }, - }, - template: ({template}, opts, {imports, interfaces, componentName, props, jsx, exports}) => { - const plugins = ['jsx']; - if (opts.typescript) { - plugins.push('typescript'); - } - const typeScriptTpl = template.smart({plugins}); - return typeScriptTpl.ast` - ${imports} - ${interfaces} - function ${componentName}(${props}) { - props = { title: '', ...props }; - return ${jsx}; - } - ${exports} - `; - }, - }, - }, - // Use url-loader to be able to inject into img src - // https://www.npmjs.com/package/@svgr/webpack#using-with-url-loader-or-file-loader - 'url-loader', - ], - }, - ], - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': "'development'", - }), - new CopyWebpackPlugin([ - { - from: '**/public/**/*', - ignore: ['**/node_modules/**'], - transformPath(targetPath) { - const splits = targetPath.split('public/'); - return splits[1]; - }, - }, - ]), - new HtmlWebpackPlugin({ - template: '!!ejs-compiled-loader!' + path.resolve(argv.index), - inject: true, - filename: 'index.html', - }), - new webpack.optimize.OccurrenceOrderPlugin(), - new webpack.HotModuleReplacementPlugin(), - ], -}); diff --git a/tools/build/web/webpack.library.config.js b/tools/build/web/webpack.library.config.js deleted file mode 100644 index 18457219c4..0000000000 --- a/tools/build/web/webpack.library.config.js +++ /dev/null @@ -1,148 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); - -function resolveTsconfigPathsToAlias({tsconfigPath, basePath}) { - const {paths} = require(tsconfigPath).compilerOptions; - const stripGlobs = path => path.replace('/*', ''); - - return Object.keys(paths).reduce((aliases, moduleMappingKey) => { - const key = stripGlobs(moduleMappingKey); - const value = path.resolve(basePath, stripGlobs(paths[moduleMappingKey][1]).replace('*', '')); - - return { - ...aliases, - [key]: value, - }; - }, {}); -} - -const parseBazelDict = output => { - if (!output) { - return {}; - } - - return output.split('|').reduce((acc, it) => { - const keyValue = it.split('='); - return { - ...acc, - [keyValue[0]]: keyValue[1], - }; - }, {}); -}; - -module.exports = (env, argv) => ({ - mode: 'production', - target: 'web', - bail: true, // stop compilation on first error - resolve: { - alias: resolveTsconfigPathsToAlias({ - tsconfigPath: path.resolve(argv.tsconfig), - basePath: process.cwd(), - }), - }, - output: { - path: path.resolve(argv.path), - ...parseBazelDict(argv.outputDict), - }, - - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - - devtool: 'none', - - externals: { - ...parseBazelDict(argv.externalDict), - }, - - module: { - rules: [ - { - test: /\.(mjs|js)$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - cacheDirectory: false, - presets: [ - [ - '@babel/preset-env', - { - modules: 'auto', - }, - ], - ], - }, - }, - { - test: /\.(scss|css)$/, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - modules: { - auto: true, - localIdentName: '[name]_[local]-[hash:base64:5]', - }, - }, - }, - 'sass-loader', - ], - }, - { - test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/, - loader: 'url-loader', - }, - { - test: /\.svg$/, - use: [ - { - loader: '@svgr/webpack', - options: { - titleProp: true, - svgoConfig: { - plugins: { - removeViewBox: false, - }, - }, - - // adapted from the default template - // https://github.com/gregberge/svgr/blob/master/packages/babel-plugin-transform-svg-component/src/index.js - template: ({template}, opts, {imports, interfaces, componentName, props, jsx, exports}) => { - const plugins = ['jsx']; - if (opts.typescript) { - plugins.push('typescript'); - } - const typeScriptTpl = template.smart({plugins}); - return typeScriptTpl.ast` - ${imports} - ${interfaces} - function ${componentName}(${props}) { - props = { title: '', ...props }; - return ${jsx}; - } - ${exports} - `; - }, - }, - }, - // Use url-loader to be able to inject into img src - // https://www.npmjs.com/package/@svgr/webpack#using-with-url-loader-or-file-loader - 'url-loader', - ], - }, - ], - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': "'production'", - }), - /* Uncomment to get bundle report - new require('webpack-bundle-analyzer').BundleAnalyzerPlugin({ - analyzerMode: 'static', - openAnalyzer: false, - }),*/ - ], -}); diff --git a/tools/build/web/webpack.prod.config.js b/tools/build/web/webpack.prod.config.js deleted file mode 100644 index 52f116cc90..0000000000 --- a/tools/build/web/webpack.prod.config.js +++ /dev/null @@ -1,188 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); - -function resolveTsconfigPathsToAlias({tsconfigPath, basePath}) { - const {paths} = require(tsconfigPath).compilerOptions; - const stripGlobs = path => path.replace('/*', ''); - - return Object.keys(paths).reduce((aliases, moduleMappingKey) => { - const key = stripGlobs(moduleMappingKey); - const value = path.resolve(basePath, stripGlobs(paths[moduleMappingKey][1]).replace('*', '')); - - return { - ...aliases, - [key]: value, - }; - }, {}); -} - -module.exports = (env, argv) => ({ - mode: 'production', - target: 'web', - bail: true, // stop compilation on first error - resolve: { - alias: resolveTsconfigPathsToAlias({ - tsconfigPath: path.resolve(argv.tsconfig), - basePath: process.cwd(), - }), - }, - output: { - path: path.resolve(argv.path), - publicPath: '/', - filename: 'js/[name].[chunkhash:8].js', - }, - - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - sourceMap: true, - }), - ], - }, - - devtool: 'source-map', - - module: { - rules: [ - { - test: /\.(mjs|js)$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - cacheDirectory: false, - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: 3, - modules: false, - targets: ['>0.2%', 'not dead', 'not op_mini all'], - }, - ], - ], - }, - }, - { - test: /\.(mjs|js)$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - cacheDirectory: true, - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: 3, - modules: false, - targets: ['>0.2%', 'not dead', 'not op_mini all'], - }, - ], - ], - }, - }, - { - test: /\.module\.scss$/, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - importLoaders: 1, - modules: { - localIdentName: '[name]__[local]--[hash:base64:5]', - }, - }, - }, - 'sass-loader', - ], - }, - { - test: /(? { - const plugins = ['jsx']; - if (opts.typescript) { - plugins.push('typescript'); - } - const typeScriptTpl = template.smart({plugins}); - return typeScriptTpl.ast` - ${imports} - ${interfaces} - function ${componentName}(${props}) { - props = { title: '', ...props }; - return ${jsx}; - } - ${exports} - `; - }, - }, - }, - // Use url-loader to be able to inject into img src - // https://www.npmjs.com/package/@svgr/webpack#using-with-url-loader-or-file-loader - 'url-loader', - ], - }, - ], - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': "'production'", - }), - new CopyWebpackPlugin([ - { - from: '**/public/**/*', - ignore: ['**/node_modules/**'], - transformPath(targetPath) { - const splits = targetPath.split('public/'); - return splits[1]; - }, - }, - ]), - new HtmlWebpackPlugin({ - template: '!!ejs-compiled-loader!' + path.resolve(argv.index), - inject: true, - filename: 'index.html', - minify: {removeComments: true, collapseWhitespace: true}, - }), - /* Uncomment to get bundle report - new require('webpack-bundle-analyzer').BundleAnalyzerPlugin({ - analyzerMode: 'static', - openAnalyzer: false, - }),*/ - ], -}); diff --git a/tools/code-format/BUILD b/tools/code-format/BUILD deleted file mode 100644 index 0d51be20df..0000000000 --- a/tools/code-format/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier") -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -load("@com_github_atlassian_bazel_tools//multirun:def.bzl", "multirun") - -buildifier( - name = "check_buildifier", - mode = "diff", -) - -multirun( - name = "fix", - commands = [ - "fix_prettier", - "fix_buildifier", - ], - visibility = ["//visibility:public"], -) - -nodejs_binary( - name = "fix_prettier", - data = [ - "chdir.js", - "@npm//prettier", - ], - entry_point = "@npm//:node_modules/prettier/bin-prettier.js", - install_source_map_support = False, - templated_args = [ - # This is a little trick we need to do in order to run prettier in the workspace root - # The check_prettier.sh executable can not be used in other executable rules - "--node_options=--require=$$(rlocation $(rootpath chdir.js))", - "'**/*.{css,scss,ts,tsx,js}'", - "--write", - ], -) - -buildifier( - name = "fix_buildifier", - lint_mode = "fix", - mode = "fix", -) diff --git a/tools/code-format/chdir.js b/tools/code-format/chdir.js deleted file mode 100644 index fda77f3cd6..0000000000 --- a/tools/code-format/chdir.js +++ /dev/null @@ -1 +0,0 @@ -process.chdir(process.env.BUILD_WORKSPACE_DIRECTORY); diff --git a/tools/code-format/checkstyle.bzl b/tools/code-format/checkstyle.bzl deleted file mode 100644 index 2d0dfea221..0000000000 --- a/tools/code-format/checkstyle.bzl +++ /dev/null @@ -1,26 +0,0 @@ -load("@rules_java//java:defs.bzl", "java_test") - -def check_style(srcs): - java_test( - name = "checkstyle", - args = [ - "-c $(location //:checkstyle.xml)", - "./", - ], - size = "small", - use_testrunner = False, - main_class = "com.puppycrawl.tools.checkstyle.Main", - data = [ - "//:checkstyle.xml", - ] + srcs, - runtime_deps = [ - "@maven//:com_puppycrawl_tools_checkstyle", - ], - tags = ["lint"], - ) - -# Add code style checking to all java files in package if not already defined -def check_pkg(): - existing_rules = native.existing_rules().keys() - if "checkstyle" not in existing_rules: - check_style(native.glob(["**/*.java"])) diff --git a/tools/code-format/prettier.bzl b/tools/code-format/prettier.bzl deleted file mode 100644 index f77e5b64b3..0000000000 --- a/tools/code-format/prettier.bzl +++ /dev/null @@ -1,26 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") - -def prettier(srcs): - nodejs_test( - name = "prettier", - data = [ - "//:.prettierrc.json", - "@npm//prettier", - ] + srcs, - entry_point = "@npm//:node_modules/prettier/bin-prettier.js", - install_source_map_support = False, - templated_args = [ - "--check", - "--config $(rootpath //:.prettierrc.json)", - ] + [ - "$(rootpath " + src + ")" - for src in srcs - ], - tags = ["lint"], - ) - -# Add code style checking to all web files in package if not already defined -def check_pkg(): - existing_rules = native.existing_rules().keys() - if "prettier" not in existing_rules: - prettier(native.glob(["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.scss", "**/*.css"])) diff --git a/yarn.lock b/yarn.lock index 3858258111..a94caa34f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,9 +3,9 @@ "@airyhq/components@latest": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@airyhq/components/-/components-0.4.4.tgz#10a6461eeecf63e9f47a0701b29efefaaf4ef10f" - integrity sha512-zgb2uYtffZiD4/gzPmfbe0kHT2cGyTrK7jcgXIFQg23zHmZaIy0zCcBkCP29yuTr60w3Y0J+RPTruqLFfP6QPA== + version "0.4.8" + resolved "https://registry.yarnpkg.com/@airyhq/components/-/components-0.4.8.tgz#5fca05ebbcd1195d70075b35c1f48cc32b3b0056" + integrity sha512-qxdNLgMukxHq5cFacdOTPRC/Km8zHrJf0y2UoE0KcCfPMc7dzh1ziM5EFoHtxsIQJkhPlzcYyNaZxA6IpTdBLQ== dependencies: "@crello/react-lottie" "^0.0.9" emoji-mart "^3.0.0" @@ -1793,6 +1793,11 @@ resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@types/facebook-js-sdk@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/facebook-js-sdk/-/facebook-js-sdk-3.3.1.tgz#1aec89b8530b9b313f73d9efe088c9d378a716d3" + integrity sha512-jRVPdOu237QxDDoBjc9/xzGsDz75FmdvcwVZdCEg1AjHAQxGmXoHfACUyUVtz7DSWA4E+jgj5MQME4snjGwOng== + "@types/history@*": version "4.7.5" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" @@ -2468,9 +2473,9 @@ bluebird@^3.5.5: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + version "4.11.9" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" + integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== body-parser@1.19.0: version "1.19.0" @@ -3482,9 +3487,9 @@ electron-to-chromium@^1.3.571: integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww== elliptic@^6.0.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" - integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -4522,9 +4527,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.3.4, ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== interpret@1.2.0: version "1.2.0" @@ -5010,6 +5015,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" + integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== + lodash@^4.0.0, lodash@^4.17.13, lodash@^4.17.15, lodash@~4.17.12: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -6283,6 +6293,11 @@ react-dom@16.12.0: prop-types "^15.6.2" scheduler "^0.18.0" +react-facebook-login@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/react-facebook-login/-/react-facebook-login-4.1.1.tgz#005121236a6ac0dee02099976fb1a3265f9d633e" + integrity sha512-COnHEHlYGTKipz4963safFAK9PaNTcCiXfPXMS/yxo8El+/AJL5ye8kMJf23lKSSGGPgqFQuInskIHVqGqTvSw== + react-hot-loader@^4.12.20: version "4.12.20" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.20.tgz#c2c42362a7578e5c30357a5ff7afa680aa0bef8a" @@ -6682,7 +6697,7 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -reselect@^4.0.0: +reselect@4.0.0, reselect@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==