diff --git a/.env b/.env
index cbfabfcf57..72cd4e62f7 100644
--- a/.env
+++ b/.env
@@ -54,6 +54,12 @@ CART_SERVICE_PORT=7070
CART_SERVICE_ADDR=cartservice:${CART_SERVICE_PORT}
CART_SERVICE_DOCKERFILE=./src/cartservice/src/Dockerfile
+# Chat Service
+CHAT_SERVICE_PORT=4000
+CHAT_SERVICE_HOST=chatservice
+CHAT_SERVICE_DOCKERFILE=./src/chatservice/Dockerfile
+
+
# Checkout Service
CHECKOUT_SERVICE_PORT=5050
CHECKOUT_SERVICE_ADDR=checkoutservice:${CHECKOUT_SERVICE_PORT}
diff --git a/.gitignore b/.gitignore
index 8327d1d2cc..eddfe4582d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ docker-compose.override.yml
.settings
bin/
+!src/chatservice/rel/overlays/bin
obj/
.vs/
.vscode
diff --git a/docker-compose.yml b/docker-compose.yml
index 39ec207aa9..80fb7fd448 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -110,6 +110,33 @@ services:
condition: service_started
logging: *logging
+ # Cart service
+ chatservice:
+ image: ${IMAGE_NAME}:${DEMO_VERSION}-chatservice
+ container_name: chat-service
+ build:
+ context: ./
+ dockerfile: ${CHAT_SERVICE_DOCKERFILE}
+ cache_from:
+ - ${IMAGE_NAME}:${IMAGE_VERSION}-chatservice
+ deploy:
+ resources:
+ limits:
+ memory: 360M
+ restart: unless-stopped
+ ports:
+ - "${CHAT_SERVICE_PORT}:${CHAT_SERVICE_PORT}"
+ environment:
+ - CHAT_SERVICE_PORT
+ - OTEL_EXPORTER_OTLP_ENDPOINT=http://${OTEL_COLLECTOR_HOST}:${OTEL_COLLECTOR_PORT_HTTP}
+ - OTEL_RESOURCE_ATTRIBUTES
+ - OTEL_SERVICE_NAME=chatservice
+ - DATABASE_URL=ecto://chat:chat@chat-db:5432/chats
+ depends_on:
+ otelcol:
+ condition: service_started
+ logging: *logging
+
# Checkout service
checkoutservice:
image: ${IMAGE_NAME}:${DEMO_VERSION}-checkoutservice
@@ -323,25 +350,27 @@ services:
- "${ENVOY_PORT}:${ENVOY_PORT}"
- 10000:10000
environment:
- - FRONTEND_PORT
+ - CHAT_SERVICE_HOST
+ - CHAT_SERVICE_PORT
+ - ENVOY_PORT
+ - FLAGD_HOST
+ - FLAGD_PORT
+ - FLAGD_UI_HOST
+ - FLAGD_UI_PORT
- FRONTEND_HOST
- - LOCUST_WEB_HOST
- - LOCUST_WEB_PORT
- - GRAFANA_SERVICE_PORT
+ - FRONTEND_PORT
- GRAFANA_SERVICE_HOST
- - JAEGER_SERVICE_PORT
- - JAEGER_SERVICE_HOST
- - OTEL_COLLECTOR_HOST
+ - GRAFANA_SERVICE_PORT
- IMAGE_PROVIDER_HOST
- IMAGE_PROVIDER_PORT
+ - JAEGER_SERVICE_HOST
+ - JAEGER_SERVICE_PORT
+ - LOCUST_WEB_HOST
+ - LOCUST_WEB_PORT
+ - OTEL_COLLECTOR_HOST
- OTEL_COLLECTOR_PORT_GRPC
- OTEL_COLLECTOR_PORT_HTTP
- OTEL_RESOURCE_ATTRIBUTES
- - ENVOY_PORT
- - FLAGD_HOST
- - FLAGD_PORT
- - FLAGD_UI_HOST
- - FLAGD_UI_PORT
depends_on:
frontend:
condition: service_started
@@ -628,6 +657,29 @@ services:
volumes:
- ./src/flagd:/app/data
+ # Postgres used by chat service
+ chat_postgres:
+ image: ${POSTGRES_IMAGE}
+ container_name: chat-db
+ user: postgres
+ deploy:
+ resources:
+ limits:
+ memory: 120M
+ restart: unless-stopped
+ environment:
+ - POSTGRES_USER=chat
+ - POSTGRES_DB=chats
+ - POSTGRES_PASSWORD=chat
+ volumes:
+ - ./src/chat_postgres/10-chat_schema.sql:/docker-entrypoint-initdb.d/10-chat_schema.sql
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -d chats -U chat"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ logging: *logging
+
# Kafka used by Checkout, Accounting, and Fraud Detection services
kafka:
image: ${IMAGE_NAME}:${DEMO_VERSION}-kafka
diff --git a/src/chat_postgres/10-chat_schema.sql b/src/chat_postgres/10-chat_schema.sql
new file mode 100644
index 0000000000..2e2ec87665
--- /dev/null
+++ b/src/chat_postgres/10-chat_schema.sql
@@ -0,0 +1,12 @@
+-- Copyright The OpenTelemetry Authors
+-- SPDX-License-Identifier: Apache-2.0
+
+CREATE TABLE public.messages (
+ topic character varying(255),
+ name character varying(255),
+ message character varying(255),
+ sent_at timestamp(0) without time zone,
+ inserted_at timestamp(0) without time zone NOT NULL,
+ updated_at timestamp(0) without time zone NOT NULL
+);
+
diff --git a/src/chatservice/.formatter.exs b/src/chatservice/.formatter.exs
new file mode 100644
index 0000000000..903fc70c4b
--- /dev/null
+++ b/src/chatservice/.formatter.exs
@@ -0,0 +1,21 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+[
+ import_deps: [:ecto, :ecto_sql, :phoenix],
+ subdirectories: ["priv/*/migrations"],
+ plugins: [Phoenix.LiveView.HTMLFormatter],
+ inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
+]
diff --git a/src/chatservice/.gitignore b/src/chatservice/.gitignore
new file mode 100644
index 0000000000..a29c925ee7
--- /dev/null
+++ b/src/chatservice/.gitignore
@@ -0,0 +1,40 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where 3rd-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Temporary files, for example, from tests.
+/tmp/
+
+# Ignore package tarball (built via "mix hex.build").
+chatservice-*.tar
+
+# Ignore assets that are produced by build tools.
+/priv/static/assets/
+
+# Ignore digested assets cache.
+/priv/static/cache_manifest.json
+
+# In case you use Node.js/npm, you want to ignore these.
+npm-debug.log
+/assets/node_modules/
+
+.nix-*
+
+.elixir_ls
diff --git a/src/chatservice/Dockerfile b/src/chatservice/Dockerfile
new file mode 100644
index 0000000000..77bd8cdafc
--- /dev/null
+++ b/src/chatservice/Dockerfile
@@ -0,0 +1,107 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+
+# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of
+# Alpine to avoid DNS resolution issues in production.
+#
+# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
+# https://hub.docker.com/_/ubuntu?tab=tags
+#
+#
+# This file is based on these images:
+#
+# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
+# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
+# - https://pkgs.org/ - resource for finding needed packages
+# - Ex: hexpm/elixir:1.13.3-erlang-25.0-debian-bullseye-20210902-slim
+#
+ARG ELIXIR_VERSION=1.14.5
+ARG OTP_VERSION=23.0
+ARG DEBIAN_VERSION=bullseye-20210902-slim
+
+ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
+ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
+
+FROM ${BUILDER_IMAGE} as builder
+
+# install build dependencies
+RUN apt-get update -y && apt-get install -y build-essential git \
+ && apt-get clean && rm -f /var/lib/apt/lists/*_*
+
+# prepare build dir
+WORKDIR /app
+
+# install hex + rebar
+RUN mix local.hex --force --verbose
+RUN mix local.rebar --force --verbose
+
+# set build ENV
+ENV MIX_ENV="prod"
+
+# install mix dependencies
+COPY ./src/chatservice/mix.exs ./src/chatservice/mix.lock ./
+RUN mix deps.get --only $MIX_ENV
+RUN mkdir config
+
+# copy compile-time config files before we compile dependencies
+# to ensure any relevant config change will trigger the dependencies
+# to be re-compiled.
+COPY ./src/chatservice/config/config.exs ./src/chatservice/config/${MIX_ENV}.exs config/
+RUN mix deps.compile
+
+COPY ./src/chatservice/lib lib
+
+COPY ./src/chatservice/assets assets
+
+# Compile assets
+RUN mix assets.deploy
+
+# Compile the release
+RUN mix compile
+
+# Changes to config/runtime.exs don't require recompiling the code
+COPY ./src/chatservice/config/runtime.exs config/
+
+COPY ./src/chatservice/rel rel
+RUN mix release
+
+# start a new build stage so that the final image will only contain
+# the compiled release and other runtime necessities
+FROM ${RUNNER_IMAGE}
+
+RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
+ && apt-get clean && rm -f /var/lib/apt/lists/*_*
+
+# Set the locale
+RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
+
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+WORKDIR "/app"
+RUN chown nobody /app
+
+# set runner ENV
+ENV MIX_ENV="prod"
+ENV SECRET_KEY_BASE="mNhoOKKxgyvBIwbtw0P23waQcvUOmusb2U1moG2I7JQ3Bt6+MlGb5ZTrHwqbqy7j"
+
+# Only copy the final release from the build stage
+COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/chatservice ./
+
+USER nobody
+
+ENTRYPOINT ["/app/bin/server"]
diff --git a/src/chatservice/README.md b/src/chatservice/README.md
new file mode 100644
index 0000000000..8b0804c266
--- /dev/null
+++ b/src/chatservice/README.md
@@ -0,0 +1,18 @@
+# ChatService
+
+To start your Phoenix server:
+
+* Run `mix setup` to install and setup dependencies
+* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
+
+Now you can visit `http://localhost:4000` from your browser.
+
+Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
+
+## Learn more
+
+* Official website:
+* Guides:
+* Docs:
+* Forum:
+* Source:
diff --git a/src/chatservice/assets/css/app.css b/src/chatservice/assets/css/app.css
new file mode 100644
index 0000000000..c2867f0513
--- /dev/null
+++ b/src/chatservice/assets/css/app.css
@@ -0,0 +1,17 @@
+/****
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+****/
diff --git a/src/chatservice/assets/js/app.js b/src/chatservice/assets/js/app.js
new file mode 100644
index 0000000000..c62ee2a5c4
--- /dev/null
+++ b/src/chatservice/assets/js/app.js
@@ -0,0 +1,118 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021 Google LLC
+//
+// Licensed 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.
+
+import socket from "./user_socket.js";
+
+const form = document.getElementById('login-form');
+const loginContainer = document.getElementById('login-container');
+const chatContainer = document.getElementById('chat-container');
+const chatMessages = document.getElementById('chat-messages');
+const chatInput = document.getElementById('chat-input');
+const nameInput = document.getElementById('name-input');
+
+let chatInputEventHandler;
+let channelEventHandler;
+
+const addEventHandlers = (channel) => {
+ removeEventHandlers(channel);
+
+ chatInputEventHandler = (event) => {
+ if (event.key === 'Enter') {
+ const message = chatInput.value.trim();
+ if (message) {
+ sendMessage(channel, nameInput.value, message);
+ chatInput.value = '';
+ }
+ }
+ };
+ chatInput.addEventListener('keydown', chatInputEventHandler);
+
+ channelEventHandler = (payload) => {
+ renderMessage(payload);
+ };
+ channel.on('shout', channelEventHandler);
+};
+
+const removeEventHandlers = (channel) => {
+ if (chatInputEventHandler) {
+ chatInput.removeEventListener('keydown', chatInputEventHandler);
+ chatInputEventHandler = null;
+ }
+
+ if (channelEventHandler) {
+ channel.off('shout', channelEventHandler);
+ channelEventHandler = null;
+ }
+};
+
+form.addEventListener('submit', (event) => {
+ console.log("form submitted");
+ event.preventDefault();
+
+ const name = nameInput.value;
+
+ // Sanitize the username
+ const sanitizedName = name.replace(/[^a-zA-Z0-9]/g, '');
+ const urlParams = new URLSearchParams(window.location.search);
+ const channelName = urlParams.get('channel') || sanitizedName;
+ console.log(channelName);
+ const channel = socket.channel(`chat:${channelName}`, {});
+ channel.join()
+ .receive("ok", resp => {
+ console.log("Joined successfully", resp);
+
+ // Hide the login form and show the chat window
+ loginContainer.style.display = 'none';
+ chatContainer.style.display = 'block';
+
+ addEventHandlers(channel);
+ })
+ .receive("error", resp => {
+ console.log("Unable to join", resp);
+ });
+});
+
+function sendMessage(channel, name, message) {
+ channel.push('shout', {
+ name: name,
+ message: message,
+ inserted_at: new Date()
+ })
+}
+
+function renderMessage(payload) {
+ console.log(payload);
+ const messageContainer = document.createElement('div');
+ messageContainer.classList.add('message');
+
+ const nameElement = document.createElement('span');
+ nameElement.classList.add('name');
+ nameElement.textContent = payload.name;
+
+ const messageElement = document.createElement('p');
+ messageElement.textContent = payload.message;
+
+ const timestampElement = document.createElement('small');
+ timestampElement.classList.add('timestamp');
+ timestampElement.textContent = new Date(payload.inserted_at).toLocaleString();
+
+ messageContainer.appendChild(nameElement);
+ messageContainer.appendChild(messageElement);
+ messageContainer.appendChild(timestampElement);
+
+ chatMessages.appendChild(messageContainer);
+}
+
diff --git a/src/chatservice/assets/js/user_socket.js b/src/chatservice/assets/js/user_socket.js
new file mode 100644
index 0000000000..830fd6e94e
--- /dev/null
+++ b/src/chatservice/assets/js/user_socket.js
@@ -0,0 +1,20 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021 Google LLC
+//
+// Licensed 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.
+import {Socket} from "phoenix"
+
+let socket = new Socket("/socket", {params: {token: window.personToken}})
+socket.connect()
+export default socket
diff --git a/src/chatservice/config/config.exs b/src/chatservice/config/config.exs
new file mode 100644
index 0000000000..4aad195077
--- /dev/null
+++ b/src/chatservice/config/config.exs
@@ -0,0 +1,63 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+
+# This file is responsible for configuring your application
+# and its dependencies with the aid of the Config module.
+#
+# This configuration file is loaded before any dependency and
+# is restricted to this project.
+
+# General application configuration
+import Config
+
+config :chatservice,
+ ecto_repos: [ChatService.Repo],
+ generators: [timestamp_type: :utc_datetime]
+
+# Configures the endpoint
+config :chatservice, ChatServiceWeb.Endpoint,
+ url: [host: "localhost"],
+ adapter: Bandit.PhoenixAdapter,
+ pubsub_server: ChatService.PubSub,
+ live_view: [signing_salt: "vhPzRN9o"],
+ render_errors: [accepts: ~w(html json), layout: false]
+
+# Configure esbuild (the version is required)
+config :esbuild,
+ version: "0.17.11",
+ chatservice: [
+ args:
+ ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
+ cd: Path.expand("../assets", __DIR__),
+ env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
+ ]
+
+# Configures Elixir's Logger
+config :logger, :console,
+ format: "$time $metadata[$level] $message\n",
+ metadata: [:request_id]
+
+# Use Jason for JSON parsing in Phoenix
+config :phoenix, :json_library, Jason
+
+# Configure the OpenTelemetry SDK & Exporter
+config :opentelemetry,
+ span_processor: :batch,
+ traces_exporter: :otlp
+
+# Import environment specific config. This must remain at the bottom
+# of this file so it overrides the configuration defined above.
+import_config "#{config_env()}.exs"
diff --git a/src/chatservice/config/dev.exs b/src/chatservice/config/dev.exs
new file mode 100644
index 0000000000..512a4eb3e0
--- /dev/null
+++ b/src/chatservice/config/dev.exs
@@ -0,0 +1,93 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+import Config
+
+# Configure your database
+config :chatservice, ChatService.Repo,
+ username: "postgres",
+ password: "postgres",
+ hostname: "localhost",
+ database: "chatservice_dev",
+ stacktrace: true,
+ show_sensitive_data_on_connection_error: true,
+ pool_size: 10
+
+# For development, we disable any cache and enable
+# debugging and code reloading.
+#
+# The watchers configuration can be used to run external
+# watchers to your application. For example, we can use it
+# to bundle .js and .css sources.
+config :chatservice, ChatServiceWeb.Endpoint,
+ # Binding to loopback ipv4 address prevents access from other machines.
+ # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
+ http: [ip: {0, 0, 0, 0}, port: 4000],
+ check_origin: false,
+ code_reloader: true,
+ debug_errors: true,
+ secret_key_base: "n+w1XD8A1cQBvipOZZpvch1nKpb1enMm4Jhqn/uNPr+ypQ/PA8JxiztV6VPElipy",
+ watchers: [
+ esbuild: {Esbuild, :install_and_run, [:chatservice, ~w(--sourcemap=inline --watch)]}
+ ]
+
+# ## SSL Support
+#
+# In order to use HTTPS in development, a self-signed
+# certificate can be generated by running the following
+# Mix task:
+#
+# mix phx.gen.cert
+#
+# Run `mix help phx.gen.cert` for more information.
+#
+# The `http:` config above can be replaced with:
+#
+# https: [
+# port: 4001,
+# cipher_suite: :strong,
+# keyfile: "priv/cert/selfsigned_key.pem",
+# certfile: "priv/cert/selfsigned.pem"
+# ],
+#
+# If desired, both `http:` and `https:` keys can be
+# configured to run both http and https servers on
+# different ports.
+
+# Watch static and templates for browser reloading.
+config :chatservice, ChatServiceWeb.Endpoint,
+ live_reload: [
+ patterns: [
+ ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
+ ~r"priv/gettext/.*(po)$",
+ ~r"lib/chatservice_web/(controllers|live|components)/.*(ex|heex)$"
+ ]
+ ]
+
+# Enable dev routes for dashboard and mailbox
+config :chatservice, dev_routes: true
+
+# Do not include metadata nor timestamps in development logs
+config :logger, :console, format: "[$level] $message\n"
+
+# Set a higher stacktrace during development. Avoid configuring such
+# in production as building large stacktraces may be expensive.
+config :phoenix, :stacktrace_depth, 20
+
+# Initialize plugs at runtime for faster development compilation
+config :phoenix, :plug_init_mode, :runtime
+
+# Include HEEx debug annotations as HTML comments in rendered markup
+config :phoenix_live_view, :debug_heex_annotations, true
diff --git a/src/chatservice/config/prod.exs b/src/chatservice/config/prod.exs
new file mode 100644
index 0000000000..2d41153345
--- /dev/null
+++ b/src/chatservice/config/prod.exs
@@ -0,0 +1,30 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+import Config
+
+# Note we also include the path to a cache manifest
+# containing the digested version of static files. This
+# manifest is generated by the `mix assets.deploy` task,
+# which you should run after static files are built and
+# before starting your production server.
+config :chatservice, ChatServiceWeb.Endpoint,
+ cache_static_manifest: "priv/static/cache_manifest.json"
+
+# Do not print debug messages in production
+config :logger, level: :info
+
+# Runtime production configuration, including reading
+# of environment variables, is done on config/runtime.exs.
diff --git a/src/chatservice/config/runtime.exs b/src/chatservice/config/runtime.exs
new file mode 100644
index 0000000000..574bfd44b1
--- /dev/null
+++ b/src/chatservice/config/runtime.exs
@@ -0,0 +1,117 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+import Config
+
+# config/runtime.exs is executed for all environments, including
+# during releases. It is executed after compilation and before the
+# system starts, so it is typically used to load production configuration
+# and secrets from environment variables or elsewhere. Do not define
+# any compile-time configuration in here, as it won't be applied.
+# The block below contains prod specific runtime configuration.
+
+# ## Using releases
+#
+# If you use `mix release`, you need to explicitly enable the server
+# by passing the PHX_SERVER=true when you start it:
+#
+# PHX_SERVER=true bin/chatservice start
+#
+# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
+# script that automatically sets the env var above.
+if System.get_env("PHX_SERVER") do
+ config :chatservice, ChatServiceWeb.Endpoint, server: true
+end
+
+if config_env() == :prod do
+ config :chatservice, ChatServiceWeb.Endpoint,
+ static_url: [path: "/chat"]
+
+ database_url =
+ System.get_env("DATABASE_URL") ||
+ raise """
+ environment variable DATABASE_URL is missing.
+ For example: ecto://USER:PASS@HOST/DATABASE
+ """
+
+ maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
+
+ config :chatservice, ChatService.Repo,
+ # ssl: true,
+ url: database_url,
+ pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
+ socket_options: maybe_ipv6
+
+ # The secret key base is used to sign/encrypt cookies and other secrets.
+ # A default value is used in config/dev.exs and config/test.exs but you
+ # want to use a different value for prod and you most likely don't want
+ # to check this value into version control, so we use an environment
+ # variable instead.
+ secret_key_base =
+ System.get_env("SECRET_KEY_BASE") ||
+ raise """
+ environment variable SECRET_KEY_BASE is missing.
+ You can generate one by calling: mix phx.gen.secret
+ """
+
+ host = System.get_env("CHAT_SERVICE_HOST") || "localhost"
+ port = String.to_integer(System.get_env("CHAT_SERVICE_PORT") || "4000")
+
+ config :chatservice, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
+
+ config :chatservice, ChatServiceWeb.Endpoint,
+ url: [host: host, port: 443, scheme: "https"],
+ http: [
+ # Enable IPv6 and bind on all interfaces.
+ # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
+ # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0
+ # for details about using IPv6 vs IPv4 and loopback vs public addresses.
+ ip: {0, 0, 0, 0, 0, 0, 0, 0},
+ port: port
+ ],
+ secret_key_base: secret_key_base
+
+ # ## SSL Support
+ #
+ # To get SSL working, you will need to add the `https` key
+ # to your endpoint configuration:
+ #
+ # config :chatservice, ChatServiceWeb.Endpoint,
+ # https: [
+ # ...,
+ # port: 443,
+ # cipher_suite: :strong,
+ # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
+ # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
+ # ]
+ #
+ # The `cipher_suite` is set to `:strong` to support only the
+ # latest and more secure SSL ciphers. This means old browsers
+ # and clients may not be supported. You can set it to
+ # `:compatible` for wider support.
+ #
+ # `:keyfile` and `:certfile` expect an absolute path to the key
+ # and cert in disk or a relative path inside priv, for example
+ # "priv/ssl/server.key". For all supported SSL configuration
+ # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
+ #
+ # We also recommend setting `force_ssl` in your config/prod.exs,
+ # ensuring no data is ever sent via http, always redirecting to https:
+ #
+ # config :chatservice, ChatServiceWeb.Endpoint,
+ # force_ssl: [hsts: true]
+ #
+ # Check `Plug.SSL` for all available options in `force_ssl`.
+end
diff --git a/src/chatservice/config/test.exs b/src/chatservice/config/test.exs
new file mode 100644
index 0000000000..9c3d0bb76c
--- /dev/null
+++ b/src/chatservice/config/test.exs
@@ -0,0 +1,42 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+import Config
+
+# Configure your database
+#
+# The MIX_TEST_PARTITION environment variable can be used
+# to provide built-in test partitioning in CI environment.
+# Run `mix help test` for more information.
+config :chatservice, ChatService.Repo,
+ username: "postgres",
+ password: "postgres",
+ hostname: "localhost",
+ database: "chatservice_test#{System.get_env("MIX_TEST_PARTITION")}",
+ pool: Ecto.Adapters.SQL.Sandbox,
+ pool_size: System.schedulers_online() * 2
+
+# We don't run a server during test. If one is required,
+# you can enable the server option below.
+config :chatservice, ChatServiceWeb.Endpoint,
+ http: [ip: {127, 0, 0, 1}, port: 4002],
+ secret_key_base: "cfHSrMhdqQLdzdAiLRZazXjUBlnd12ZuG3ilwKigBsbA58cOWzW0Rm2cUa5oF8ts",
+ server: false
+
+# Print only warnings and errors during test
+config :logger, level: :warning
+
+# Initialize plugs at runtime for faster test compilation
+config :phoenix, :plug_init_mode, :runtime
diff --git a/src/chatservice/docker-compose.yml b/src/chatservice/docker-compose.yml
new file mode 100644
index 0000000000..266b9c57fb
--- /dev/null
+++ b/src/chatservice/docker-compose.yml
@@ -0,0 +1,24 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+services:
+ postgres:
+ image: postgres:16.1
+ container_name: postgres
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=postgres
+ ports:
+ - "5432:5432"
diff --git a/src/chatservice/flake.nix b/src/chatservice/flake.nix
new file mode 100644
index 0000000000..2f33f39676
--- /dev/null
+++ b/src/chatservice/flake.nix
@@ -0,0 +1,54 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+
+{
+ description = "Development environment";
+
+ inputs = {
+ flake-utils = { url = "github:numtide/flake-utils"; };
+ };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ inherit (nixpkgs.lib) optional;
+ pkgs = import nixpkgs { inherit system; };
+
+ elixir = pkgs.beam.packages.erlang.elixir;
+ elixir-ls = pkgs.beam.packages.erlang.elixir_ls;
+ locales = pkgs.glibcLocales;
+
+ hooks = ''
+ mkdir -p .nix-mix
+ mkdir -p .nix-hex
+ export MIX_HOME=$PWD/.nix-mix
+ export HEX_HOME=$PWD/.nix-hex
+ export PATH=$MIX_HOME/bin:$PATH
+ export PATH=$HEX_HOME/bin:$PATH
+ export LANG=en_US.UTF-8
+ '';
+ in
+ {
+ devShell = pkgs.mkShell {
+ buildInputs = [
+ elixir
+ locales
+ ];
+
+ shellHook = hooks;
+ };
+ });
+}
diff --git a/src/chatservice/lib/chatservice.ex b/src/chatservice/lib/chatservice.ex
new file mode 100644
index 0000000000..c05008f20d
--- /dev/null
+++ b/src/chatservice/lib/chatservice.ex
@@ -0,0 +1,21 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService do
+ @moduledoc """
+ The ChatService supervision tree is in chatservice/application.ex
+
+ """
+end
diff --git a/src/chatservice/lib/chatservice/application.ex b/src/chatservice/lib/chatservice/application.ex
new file mode 100644
index 0000000000..873542a582
--- /dev/null
+++ b/src/chatservice/lib/chatservice/application.ex
@@ -0,0 +1,50 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.Application do
+ # See https://hexdocs.pm/elixir/Application.html
+ # for more information on OTP Applications
+ @moduledoc false
+
+ use Application
+
+ @impl true
+ def start(_type, _args) do
+ # Set up OpenTelemetry instrumentation
+ OpentelemetryPhoenix.setup()
+ OpentelemetryEcto.setup([:chat_service, :repo])
+
+ children = [
+ ChatServiceWeb.Telemetry,
+ ChatService.Repo,
+ {Phoenix.PubSub, name: ChatService.PubSub},
+ {Registry, keys: :unique, name: ChatService.Registry},
+ ChatServiceWeb.Endpoint
+ ]
+
+ # See https://hexdocs.pm/elixir/Supervisor.html
+ # for other strategies and supported options
+ opts = [strategy: :one_for_one, name: ChatService.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+
+ # Tell Phoenix to update the endpoint configuration
+ # whenever the application is updated.
+ @impl true
+ def config_change(changed, _new, removed) do
+ ChatServiceWeb.Endpoint.config_change(changed, removed)
+ :ok
+ end
+end
diff --git a/src/chatservice/lib/chatservice/chat_context.ex b/src/chatservice/lib/chatservice/chat_context.ex
new file mode 100644
index 0000000000..0f05944327
--- /dev/null
+++ b/src/chatservice/lib/chatservice/chat_context.ex
@@ -0,0 +1,37 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.ChatContext do
+ @moduledoc """
+ The database/persistence context
+ """
+
+ import Ecto.Query, warn: false
+ alias ChatService.Repo
+
+ alias ChatService.ChatContext.Message
+
+ def list_messages(topic) do
+ Message
+ |> where(topic: ^topic)
+ |> Repo.all()
+ end
+
+ def create_message(attrs \\ %{}) do
+ %Message{}
+ |> Message.changeset(attrs)
+ |> Repo.insert()
+ end
+end
diff --git a/src/chatservice/lib/chatservice/chat_context/message.ex b/src/chatservice/lib/chatservice/chat_context/message.ex
new file mode 100644
index 0000000000..22e92a454a
--- /dev/null
+++ b/src/chatservice/lib/chatservice/chat_context/message.ex
@@ -0,0 +1,36 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.ChatContext.Message do
+ @derive {Jason.Encoder, only: [:name, :message, :inserted_at, :topic]}
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @primary_key false
+ schema "messages" do
+ field :topic, :string
+ field :name, :string
+ field :message, :string
+
+ timestamps(type: :utc_datetime)
+ end
+
+ @doc false
+ def changeset(message, attrs) do
+ message
+ |> cast(attrs, [:topic, :name, :message])
+ |> validate_required([:topic, :name, :message])
+ end
+end
diff --git a/src/chatservice/lib/chatservice/chat_server.ex b/src/chatservice/lib/chatservice/chat_server.ex
new file mode 100644
index 0000000000..d8c848a9c8
--- /dev/null
+++ b/src/chatservice/lib/chatservice/chat_server.ex
@@ -0,0 +1,72 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.ChatServer do
+ use GenServer
+ alias ChatService.ChatContext
+ require OpenTelemetry.Tracer
+
+ def start_chat(topic) do
+ case Registry.lookup(ChatService.Registry, topic) do
+ [] -> start_link(topic)
+ [first | _] -> first
+ end
+ end
+
+ def list_topics() do
+ ChatService.Registry
+ |> Registry.select([{{:"$1", :_, :_}, [], [{{:"$1"}}]}])
+ |> Enum.map(fn {key} -> key end)
+ end
+
+ def send_message(topic, message) do
+ OpenTelemetry.Tracer.with_span "ChatServer.send_message" do
+ current_ctx = OpenTelemetry.Ctx.get_current()
+ GenServer.call(via_tuple(topic), {:send_message, Map.put(message, "topic", topic), current_ctx})
+ end
+ end
+
+ def get_messages(topic) do
+ GenServer.call(via_tuple(topic), :get_messages)
+ end
+
+ def start_link(topic) do
+ GenServer.start_link(__MODULE__, topic, name: via_tuple(topic))
+ end
+
+ @impl true
+ def init(topic) do
+ messages = ChatContext.list_messages(topic)
+ {:ok, messages}
+ end
+
+ @impl true
+ def handle_call({:send_message, message, parent_ctx}, _from, state) do
+ OpenTelemetry.Ctx.attach(parent_ctx)
+ OpenTelemetry.Tracer.with_span :handle_call do
+ saved = ChatContext.create_message(message)
+ {:reply, saved, [saved | state]}
+ end
+ end
+
+ @impl true
+ def handle_call(:get_messages, _from, state) do
+ {:reply, Enum.reverse(state), state}
+ end
+
+ defp via_tuple(topic) do
+ {:via, Registry, {ChatService.Registry, topic}}
+ end
+end
diff --git a/src/chatservice/lib/chatservice/repo.ex b/src/chatservice/lib/chatservice/repo.ex
new file mode 100644
index 0000000000..4cd9f3696e
--- /dev/null
+++ b/src/chatservice/lib/chatservice/repo.ex
@@ -0,0 +1,20 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.Repo do
+ use Ecto.Repo,
+ otp_app: :chatservice,
+ adapter: Ecto.Adapters.Postgres
+end
diff --git a/src/chatservice/lib/chatservice_web.ex b/src/chatservice/lib/chatservice_web.ex
new file mode 100644
index 0000000000..570dd67a36
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web.ex
@@ -0,0 +1,101 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb do
+ @moduledoc """
+ The entrypoint for defining your web interface, such
+ as controllers, components, channels, and so on.
+
+ This can be used in your application as:
+
+ use ChatServiceWeb, :controller
+ use ChatServiceWeb, :html
+
+ The definitions below will be executed for every controller,
+ component, etc, so keep them short and clean, focused
+ on imports, uses and aliases.
+
+ Do NOT define functions inside the quoted expressions
+ below. Instead, define additional modules and import
+ those modules here.
+ """
+
+ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
+
+ def router do
+ quote do
+ use Phoenix.Router, helpers: false
+
+ # Import common connection and controller functions to use in pipelines
+ import Plug.Conn
+ import Phoenix.Controller
+ import Phoenix.LiveView.Router
+ end
+ end
+
+ def channel do
+ quote do
+ use Phoenix.Channel
+ end
+ end
+
+ def controller do
+ quote do
+ use Phoenix.Controller,
+ formats: [:html, :json],
+ layouts: [html: ChatServiceWeb.Layouts]
+
+ import Plug.Conn
+
+ unquote(verified_routes())
+ end
+ end
+
+ def html do
+ quote do
+ use Phoenix.Component
+
+ # Import convenience functions from controllers
+ import Phoenix.Controller,
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
+
+ # Include general helpers for rendering HTML
+ unquote(html_helpers())
+ end
+ end
+
+ defp html_helpers do
+ quote do
+ # Routes generation with the ~p sigil
+ unquote(verified_routes())
+ end
+ end
+
+ def verified_routes do
+ quote do
+ use Phoenix.VerifiedRoutes,
+ endpoint: ChatServiceWeb.Endpoint,
+ router: ChatServiceWeb.Router,
+ statics: ChatServiceWeb.static_paths()
+ end
+ end
+
+ @doc """
+ When used, dispatch to the appropriate controller/view/etc.
+ """
+ defmacro __using__(which) when is_atom(which) do
+ apply(__MODULE__, which, [])
+ end
+end
diff --git a/src/chatservice/lib/chatservice_web/channels/chat_channel.ex b/src/chatservice/lib/chatservice_web/channels/chat_channel.ex
new file mode 100644
index 0000000000..10fa330b85
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/channels/chat_channel.ex
@@ -0,0 +1,52 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.ChatChannel do
+ use ChatServiceWeb, :channel
+ require Logger
+ require OpenTelemetry.Tracer
+ alias ChatService.ChatServer
+
+ @impl true
+ def join(topic, _payload, socket) do
+ OpenTelemetry.Tracer.with_span :join do
+ ChatServer.start_chat(topic)
+ send(self(), :after_join)
+ {:ok, assign(socket, :topic, topic)}
+ end
+ end
+
+ @impl true
+ def handle_in("shout", payload, socket) do
+ OpenTelemetry.Tracer.with_span :shout, %{kind: :consumer} do
+ OpenTelemetry.Tracer.set_attributes(%{
+ "messaging.operation.name": :shout,
+ "messaging.destination.name": socket.assigns.topic,
+ "messaging.message.body.size": byte_size(:erlang.term_to_binary(payload))
+ })
+ ChatServer.send_message(socket.assigns.topic, payload)
+ broadcast(socket, "shout", payload)
+ {:noreply, socket}
+ end
+ end
+
+ @impl true
+ def handle_info(:after_join, socket) do
+ ChatServer.get_messages(socket.assigns.topic)
+ |> Enum.each(fn msg -> push(socket, "shout", msg) end)
+
+ {:noreply, socket}
+ end
+end
diff --git a/src/chatservice/lib/chatservice_web/channels/user_socket.ex b/src/chatservice/lib/chatservice_web/channels/user_socket.ex
new file mode 100644
index 0000000000..7f94754a9a
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/channels/user_socket.ex
@@ -0,0 +1,63 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.UserSocket do
+ require Logger
+ use Phoenix.Socket
+
+ # A Socket handler
+
+ ## Channels
+
+ channel("chat:*", ChatServiceWeb.ChatChannel)
+
+ # Socket params are passed from the client and can
+ # be used to verify and authenticate a user. After
+ # verification, you can put default assigns into
+ # the socket that will be set for all channels, ie
+ #
+ # {:ok, assign(socket, :user_id, verified_user_id)}
+ #
+ # To deny connection, return `:error` or `{:error, term}`. To control the
+ # response the client receives in that case, [define an error handler in the
+ # websocket
+ # configuration](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration).
+ #
+ # See `Phoenix.Token` documentation for examples in
+ # performing token verification on connect.
+ @impl true
+ def connect(params, socket, _connect_info) do
+ # In a real application we might want to verify
+ # this session ID but since this is a demo we will
+ # trust the front-end.
+
+ # This session ID will provide a way to identify users
+ # across different chats.
+ {:ok, assign(socket, :session_id, params["session_id"])}
+ end
+
+ # Socket IDs are topics that allow you to identify all sockets for a given user:
+ #
+ # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
+ #
+ # Would allow you to broadcast a "disconnect" event and terminate
+ # all active sockets and channels for a given user:
+ #
+ # Elixir.ChatServiceWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
+ #
+ # Returning `nil` makes this socket anonymous.
+ @impl true
+ def id(socket), do: "user_socket:#{socket.assigns.session_id}"
+end
diff --git a/src/chatservice/lib/chatservice_web/components/layouts.ex b/src/chatservice/lib/chatservice_web/components/layouts.ex
new file mode 100644
index 0000000000..2d488c9b73
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/components/layouts.ex
@@ -0,0 +1,20 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.Layouts do
+ use ChatServiceWeb, :html
+
+ embed_templates "layouts/*"
+end
diff --git a/src/chatservice/lib/chatservice_web/components/layouts/app.html.heex b/src/chatservice/lib/chatservice_web/components/layouts/app.html.heex
new file mode 100644
index 0000000000..05433985bf
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/components/layouts/app.html.heex
@@ -0,0 +1 @@
+<%= @inner_content %>
diff --git a/src/chatservice/lib/chatservice_web/components/layouts/root.html.heex b/src/chatservice/lib/chatservice_web/components/layouts/root.html.heex
new file mode 100644
index 0000000000..9dc2d7e287
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/components/layouts/root.html.heex
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ <.live_title suffix=" ยท Phoenix Framework">
+ <%= assigns[:page_title] || "ChatService" %>
+
+
+
+
+
+ <%= @inner_content %>
+
+
diff --git a/src/chatservice/lib/chatservice_web/controllers/page_controller.ex b/src/chatservice/lib/chatservice_web/controllers/page_controller.ex
new file mode 100644
index 0000000000..8808580f1c
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/controllers/page_controller.ex
@@ -0,0 +1,28 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.PageController do
+ use ChatServiceWeb, :controller
+
+ def home(conn, _params) do
+ topics = ChatService.ChatServer.list_topics()
+
+ assigns = [
+ topics: topics
+ ]
+
+ render(conn, :home, assigns)
+ end
+end
diff --git a/src/chatservice/lib/chatservice_web/controllers/page_html.ex b/src/chatservice/lib/chatservice_web/controllers/page_html.ex
new file mode 100644
index 0000000000..5f2f72b098
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/controllers/page_html.ex
@@ -0,0 +1,20 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.PageHTML do
+ use ChatServiceWeb, :html
+
+ embed_templates "page_html/*"
+end
diff --git a/src/chatservice/lib/chatservice_web/controllers/page_html/home.html.heex b/src/chatservice/lib/chatservice_web/controllers/page_html/home.html.heex
new file mode 100644
index 0000000000..daf1e5689c
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/controllers/page_html/home.html.heex
@@ -0,0 +1,13 @@
+
Astronomy Store Chat
+
+
+
+
+
+
+
+
diff --git a/src/chatservice/lib/chatservice_web/endpoint.ex b/src/chatservice/lib/chatservice_web/endpoint.ex
new file mode 100644
index 0000000000..04655aa840
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/endpoint.ex
@@ -0,0 +1,65 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.Endpoint do
+ use Phoenix.Endpoint, otp_app: :chatservice
+
+ # The session will be stored in the cookie and signed,
+ # this means its contents can be read but not tampered with.
+ # Set :encryption_salt if you would also like to encrypt it.
+ @session_options [
+ store: :cookie,
+ key: "_chatservice_key",
+ signing_salt: "7zpyAVGL",
+ same_site: "Lax"
+ ]
+
+ # add socket created by mix.phx.gen.channel
+ socket "/socket", ChatServiceWeb.UserSocket,
+ websocket: true,
+ longpoll: false
+
+ # Serve at "/" the static files from "priv/static" directory.
+ #
+ # You should set gzip to true if you are running phx.digest
+ # when deploying your static files in production.
+ plug Plug.Static,
+ at: "/",
+ from: :chatservice,
+ gzip: false,
+ only: ChatServiceWeb.static_paths()
+
+ # Code reloading can be explicitly enabled under the
+ # :code_reloader configuration of your endpoint.
+ if code_reloading? do
+ socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
+ plug Phoenix.LiveReloader
+ plug Phoenix.CodeReloader
+ plug Phoenix.Ecto.CheckRepoStatus, otp_app: :chatservice
+ end
+
+ plug Plug.RequestId
+ plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
+
+ plug Plug.Parsers,
+ parsers: [:urlencoded, :multipart, :json],
+ pass: ["*/*"],
+ json_decoder: Phoenix.json_library()
+
+ plug Plug.MethodOverride
+ plug Plug.Head
+ plug Plug.Session, @session_options
+ plug ChatServiceWeb.Router
+end
diff --git a/src/chatservice/lib/chatservice_web/router.ex b/src/chatservice/lib/chatservice_web/router.ex
new file mode 100644
index 0000000000..5cd019243a
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/router.ex
@@ -0,0 +1,36 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.Router do
+ use ChatServiceWeb, :router
+
+ pipeline :browser do
+ plug :accepts, ["html"]
+ plug :fetch_session
+ plug :put_root_layout, html: {ChatServiceWeb.Layouts, :root}
+ plug :protect_from_forgery
+ plug :put_secure_browser_headers
+ end
+
+ # pipeline :api do
+ # plug :accepts, ["json"]
+ # end
+
+ scope "/", ChatServiceWeb do
+ pipe_through :browser
+
+ get "/", PageController, :home
+ end
+end
diff --git a/src/chatservice/lib/chatservice_web/telemetry.ex b/src/chatservice/lib/chatservice_web/telemetry.ex
new file mode 100644
index 0000000000..d3aa91044b
--- /dev/null
+++ b/src/chatservice/lib/chatservice_web/telemetry.ex
@@ -0,0 +1,107 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.Telemetry do
+ use Supervisor
+ import Telemetry.Metrics
+
+ def start_link(arg) do
+ Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_arg) do
+ children = [
+ # Telemetry poller will execute the given period measurements
+ # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
+ {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
+ # Add reporters as children of your supervision tree.
+ # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ def metrics do
+ [
+ # Phoenix Metrics
+ summary("phoenix.endpoint.start.system_time",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.endpoint.stop.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.start.system_time",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.exception.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.stop.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.socket_connected.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.channel_joined.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.channel_handled_in.duration",
+ tags: [:event],
+ unit: {:native, :millisecond}
+ ),
+
+ # Database Metrics
+ summary("chatservice.repo.query.total_time",
+ unit: {:native, :millisecond},
+ description: "The sum of the other measurements"
+ ),
+ summary("chatservice.repo.query.decode_time",
+ unit: {:native, :millisecond},
+ description: "The time spent decoding the data received from the database"
+ ),
+ summary("chatservice.repo.query.query_time",
+ unit: {:native, :millisecond},
+ description: "The time spent executing the query"
+ ),
+ summary("chatservice.repo.query.queue_time",
+ unit: {:native, :millisecond},
+ description: "The time spent waiting for a database connection"
+ ),
+ summary("chatservice.repo.query.idle_time",
+ unit: {:native, :millisecond},
+ description:
+ "The time the connection spent waiting before being checked out for the query"
+ ),
+
+ # VM Metrics
+ summary("vm.memory.total", unit: {:byte, :kilobyte}),
+ summary("vm.total_run_queue_lengths.total"),
+ summary("vm.total_run_queue_lengths.cpu"),
+ summary("vm.total_run_queue_lengths.io")
+ ]
+ end
+
+ defp periodic_measurements do
+ [
+ # A module, function and arguments to be invoked periodically.
+ # This function must call :telemetry.execute/3 and a metric must be added above.
+ # {ChatServiceWeb, :count_users, []}
+ ]
+ end
+end
diff --git a/src/chatservice/mix.exs b/src/chatservice/mix.exs
new file mode 100644
index 0000000000..787204dca3
--- /dev/null
+++ b/src/chatservice/mix.exs
@@ -0,0 +1,95 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :chatservice,
+ version: "0.1.0",
+ elixir: "~> 1.14",
+ elixirc_paths: elixirc_paths(Mix.env()),
+ start_permanent: Mix.env() == :prod,
+ aliases: aliases(),
+ deps: deps()
+ ]
+ end
+
+ # Configuration for the OTP application.
+ #
+ # Type `mix help compile.app` for more information.
+ def application do
+ [
+ mod: {ChatService.Application, []},
+ extra_applications: [:logger, :runtime_tools]
+ ]
+ end
+
+ # Specifies which paths to compile per environment.
+ defp elixirc_paths(:test), do: ["lib", "test/support"]
+ defp elixirc_paths(_), do: ["lib"]
+
+ # Specifies your project dependencies.
+ #
+ # Type `mix help deps` for examples and options.
+ defp deps do
+ [
+ {:phoenix, "~> 1.7.11"},
+ {:phoenix_ecto, "~> 4.4"},
+ {:ecto_sql, "~> 3.10"},
+ {:postgrex, ">= 0.0.0"},
+ {:phoenix_html, "~> 4.0"},
+ {:floki, ">= 0.30.0", only: :test},
+ {:phoenix_live_reload, "~> 1.2", only: :dev},
+ {:phoenix_live_view, "~> 0.20.2"},
+ {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
+ {:telemetry_metrics, "~> 0.6"},
+ {:telemetry_poller, "~> 1.0"},
+ {:gettext, "~> 0.20"},
+ {:jason, "~> 1.2"},
+ {:bandit, "~> 1.2"},
+
+ # OpenTelemetry
+ {:opentelemetry, "~> 1.4"},
+ {:opentelemetry_api, "~> 1.3"},
+ {:opentelemetry_exporter, "~> 1.6"},
+ {:opentelemetry_bandit, "~> 0.1"},
+ {:opentelemetry_ecto, "~> 1.2"},
+ {:opentelemetry_phoenix, "~> 1.2"}
+ ]
+ end
+
+ # Aliases are shortcuts or tasks specific to the current project.
+ # For example, to install project dependencies and perform other setup tasks, run:
+ #
+ # $ mix setup
+ #
+ # See the documentation for `Mix` for more info on aliases.
+ defp aliases do
+ [
+ setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
+ "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
+ "ecto.reset": ["ecto.drop", "ecto.setup"],
+ test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
+ "assets.setup": ["esbuild.install --if-missing"],
+ "assets.build": ["esbuild chatservice"],
+ "assets.deploy": [
+ "esbuild chatservice --minify",
+ "phx.digest"
+ ]
+ ]
+ end
+end
diff --git a/src/chatservice/mix.lock b/src/chatservice/mix.lock
new file mode 100644
index 0000000000..aa1ce1d366
--- /dev/null
+++ b/src/chatservice/mix.lock
@@ -0,0 +1,62 @@
+%{
+ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"},
+ "bandit": {:hex, :bandit, "1.5.2", "ed0a41c43a9e529c670d0fd48371db4027e7b80d43b1942893e17deb8bed0540", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "35ddbdce7e8a2a3c6b5093f7299d70832a43ed2f4a1852885a61d334cab1b4ad"},
+ "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
+ "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"},
+ "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
+ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
+ "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
+ "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
+ "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
+ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
+ "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
+ "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
+ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
+ "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
+ "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"},
+ "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
+ "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
+ "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
+ "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
+ "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"},
+ "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"},
+ "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]},
+ "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"},
+ "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
+ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
+ "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
+ "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"},
+ "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
+ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
+ "opentelemetry": {:hex, :opentelemetry, "1.4.0", "f928923ed80adb5eb7894bac22e9a198478e6a8f04020ae1d6f289fdcad0b498", [:rebar3], [{:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "50b32ce127413e5d87b092b4d210a3449ea80cd8224090fe68d73d576a3faa15"},
+ "opentelemetry_api": {:hex, :opentelemetry_api, "1.3.0", "03e2177f28dd8d11aaa88e8522c81c2f6a788170fe52f7a65262340961e663f9", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "b9e5ff775fd064fa098dba3c398490b77649a352b40b0b730a6b7dc0bdd68858"},
+ "opentelemetry_cowboy": {:hex, :opentelemetry_cowboy, "0.3.0", "0144b211fa6cda0e6211c340cebd1bbd9158e350099ea3bf3d838f993cb4b90e", [:rebar3], [{:cowboy_telemetry, "~> 0.4", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.0", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4f44537b4c7430018198d480f55bc88a40f7d0582c3ad927a5bab4ceb39e80ea"},
+ "opentelemetry_ecto": {:hex, :opentelemetry_ecto, "1.2.0", "2382cb47ddc231f953d3b8263ed029d87fbf217915a1da82f49159d122b64865", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "70dfa2e79932e86f209df00e36c980b17a32f82d175f0068bf7ef9a96cf080cf"},
+ "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.7.0", "dec4e90c0667cf11a3642f7fe71982dbc0c6bfbb8725a0b13766830718cf0d98", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.4.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "d0f25f6439ec43f2561537c3fabbe177b38547cddaa3a692cbb8f4770dbefc1e"},
+ "opentelemetry_phoenix": {:hex, :opentelemetry_phoenix, "1.2.0", "b8a53ee595b24970571a7d2fcaef3e4e1a021c68e97cac163ca5d9875fad5e9f", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.0", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:plug, ">= 1.11.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "acab991d14ed3efc3f780c5a20cabba27149cf731005b1cc6454c160859debe5"},
+ "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"},
+ "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"},
+ "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"},
+ "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
+ "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
+ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
+ "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
+ "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
+ "swoosh": {:hex, :swoosh, "1.16.5", "5742f24c4d081671ebe87d8e7f6595cf75205d7f808cc5d55b09e4598b583413", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.1.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2324cf696b09ee52e5e1049dcc77880a11fe618a381e2df1c5ca5d69c380eb0"},
+ "tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"},
+ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
+ "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
+ "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
+ "tls_certificate_check": {:hex, :tls_certificate_check, "1.22.1", "0f450cc1568a67a65ce5e15df53c53f9a098c3da081c5f126199a72505858dc1", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3092be0babdc0e14c2e900542351e066c0fa5a9cf4b3597559ad1e67f07938c0"},
+ "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
+ "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
+}
diff --git a/src/chatservice/rel/overlays/bin/server b/src/chatservice/rel/overlays/bin/server
new file mode 100755
index 0000000000..00ff113f8c
--- /dev/null
+++ b/src/chatservice/rel/overlays/bin/server
@@ -0,0 +1,4 @@
+#!/bin/sh
+cd -P -- "$(dirname -- "$0")"
+
+PHX_SERVER=true exec ./chatservice start
diff --git a/src/chatservice/rel/overlays/bin/server.bat b/src/chatservice/rel/overlays/bin/server.bat
new file mode 100755
index 0000000000..61f3977774
--- /dev/null
+++ b/src/chatservice/rel/overlays/bin/server.bat
@@ -0,0 +1,2 @@
+set PHX_SERVER=true
+call "%~dp0\chatservice" start
diff --git a/src/chatservice/test/chatservice/chats_test.exs b/src/chatservice/test/chatservice/chats_test.exs
new file mode 100644
index 0000000000..ae42809527
--- /dev/null
+++ b/src/chatservice/test/chatservice/chats_test.exs
@@ -0,0 +1,51 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.ChatsTest do
+ use ChatService.DataCase
+
+ alias ChatService.ChatContext
+
+ describe "messages" do
+ alias ChatService.ChatContext.Message
+
+ import ChatService.ChatsFixtures
+
+ @invalid_attrs %{topic: nil, message: nil, name: nil, sent_at: nil}
+
+ test "list_messages/0 returns all messages for topic" do
+ message = message_fixture()
+ assert ChatContext.list_messages(message.topic) == [message]
+ end
+
+ test "create_message/1 with valid data creates a message" do
+ valid_attrs = %{
+ topic: "josh",
+ message: "some message",
+ name: "some name",
+ sent_at: ~U[2024-07-05 18:28:00Z]
+ }
+
+ assert {:ok, %Message{} = message} = ChatContext.create_message(valid_attrs)
+ assert message.message == "some message"
+ assert message.name == "some name"
+ assert message.sent_at == ~U[2024-07-05 18:28:00Z]
+ end
+
+ test "create_message/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = ChatContext.create_message(@invalid_attrs)
+ end
+ end
+end
diff --git a/src/chatservice/test/chatservice_web/channels/chat_channel_test.exs b/src/chatservice/test/chatservice_web/channels/chat_channel_test.exs
new file mode 100644
index 0000000000..1a4dfbe7a6
--- /dev/null
+++ b/src/chatservice/test/chatservice_web/channels/chat_channel_test.exs
@@ -0,0 +1,37 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.ChatChannelTest do
+ use ChatServiceWeb.ChannelCase
+
+ setup do
+ {:ok, _, socket} =
+ ChatServiceWeb.UserSocket
+ |> socket("user_id", %{some: :assign})
+ |> subscribe_and_join(ChatServiceWeb.ChatChannel, "chat:lobby")
+
+ %{socket: socket}
+ end
+
+ test "shout broadcasts to chat:lobby", %{socket: socket} do
+ push(socket, "shout", %{"hello" => "all"})
+ assert_broadcast "shout", %{"hello" => "all"}
+ end
+
+ test "broadcasts are pushed to the client", %{socket: socket} do
+ broadcast_from!(socket, "broadcast", %{"some" => "data"})
+ assert_push "broadcast", %{"some" => "data"}
+ end
+end
diff --git a/src/chatservice/test/chatservice_web/controllers/page_controller_test.exs b/src/chatservice/test/chatservice_web/controllers/page_controller_test.exs
new file mode 100644
index 0000000000..bbf32bbc78
--- /dev/null
+++ b/src/chatservice/test/chatservice_web/controllers/page_controller_test.exs
@@ -0,0 +1,23 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.PageControllerTest do
+ use ChatServiceWeb.ConnCase
+
+ test "GET /", %{conn: conn} do
+ conn = get(conn, ~p"/")
+ assert html_response(conn, 200) =~ "Astronomy Store Chat"
+ end
+end
diff --git a/src/chatservice/test/support/channel_case.ex b/src/chatservice/test/support/channel_case.ex
new file mode 100644
index 0000000000..9c48510649
--- /dev/null
+++ b/src/chatservice/test/support/channel_case.ex
@@ -0,0 +1,50 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.ChannelCase do
+ @moduledoc """
+ This module defines the test case to be used by
+ channel tests.
+
+ Such tests rely on `Phoenix.ChannelTest` and also
+ import other functionality to make it easier
+ to build common data structures and query the data layer.
+
+ Finally, if the test case interacts with the database,
+ we enable the SQL sandbox, so changes done to the database
+ are reverted at the end of every test. If you are using
+ PostgreSQL, you can even run database tests asynchronously
+ by setting `use ChatServiceWeb.ChannelCase, async: true`, although
+ this option is not recommended for other databases.
+ """
+
+ use ExUnit.CaseTemplate
+
+ using do
+ quote do
+ # Import conveniences for testing with channels
+ import Phoenix.ChannelTest
+ import ChatServiceWeb.ChannelCase
+
+ # The default endpoint for testing
+ @endpoint ChatServiceWeb.Endpoint
+ end
+ end
+
+ setup tags do
+ ChatService.DataCase.setup_sandbox(tags)
+ :ok
+ end
+end
diff --git a/src/chatservice/test/support/conn_case.ex b/src/chatservice/test/support/conn_case.ex
new file mode 100644
index 0000000000..98dfc3e166
--- /dev/null
+++ b/src/chatservice/test/support/conn_case.ex
@@ -0,0 +1,53 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatServiceWeb.ConnCase do
+ @moduledoc """
+ This module defines the test case to be used by
+ tests that require setting up a connection.
+
+ Such tests rely on `Phoenix.ConnTest` and also
+ import other functionality to make it easier
+ to build common data structures and query the data layer.
+
+ Finally, if the test case interacts with the database,
+ we enable the SQL sandbox, so changes done to the database
+ are reverted at the end of every test. If you are using
+ PostgreSQL, you can even run database tests asynchronously
+ by setting `use ChatServiceWeb.ConnCase, async: true`, although
+ this option is not recommended for other databases.
+ """
+
+ use ExUnit.CaseTemplate
+
+ using do
+ quote do
+ # The default endpoint for testing
+ @endpoint ChatServiceWeb.Endpoint
+
+ use ChatServiceWeb, :verified_routes
+
+ # Import conveniences for testing with connections
+ import Plug.Conn
+ import Phoenix.ConnTest
+ import ChatServiceWeb.ConnCase
+ end
+ end
+
+ setup tags do
+ ChatService.DataCase.setup_sandbox(tags)
+ {:ok, conn: Phoenix.ConnTest.build_conn()}
+ end
+end
diff --git a/src/chatservice/test/support/data_case.ex b/src/chatservice/test/support/data_case.ex
new file mode 100644
index 0000000000..ba9d8179f9
--- /dev/null
+++ b/src/chatservice/test/support/data_case.ex
@@ -0,0 +1,73 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.DataCase do
+ @moduledoc """
+ This module defines the setup for tests requiring
+ access to the application's data layer.
+
+ You may define functions here to be used as helpers in
+ your tests.
+
+ Finally, if the test case interacts with the database,
+ we enable the SQL sandbox, so changes done to the database
+ are reverted at the end of every test. If you are using
+ PostgreSQL, you can even run database tests asynchronously
+ by setting `use ChatService.DataCase, async: true`, although
+ this option is not recommended for other databases.
+ """
+
+ use ExUnit.CaseTemplate
+
+ using do
+ quote do
+ alias ChatService.Repo
+
+ import Ecto
+ import Ecto.Changeset
+ import Ecto.Query
+ import ChatService.DataCase
+ end
+ end
+
+ setup tags do
+ ChatService.DataCase.setup_sandbox(tags)
+ :ok
+ end
+
+ @doc """
+ Sets up the sandbox based on the test tags.
+ """
+ def setup_sandbox(tags) do
+ pid = Ecto.Adapters.SQL.Sandbox.start_owner!(ChatService.Repo, shared: not tags[:async])
+ on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
+ end
+
+ @doc """
+ A helper that transforms changeset errors into a map of messages.
+
+ assert {:error, changeset} = Accounts.create_user(%{password: "short"})
+ assert "password is too short" in errors_on(changeset).password
+ assert %{password: ["password is too short"]} = errors_on(changeset)
+
+ """
+ def errors_on(changeset) do
+ Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
+ Regex.replace(~r"%{(\w+)}", message, fn _, key ->
+ opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
+ end)
+ end)
+ end
+end
diff --git a/src/chatservice/test/support/fixtures/chats_fixtures.ex b/src/chatservice/test/support/fixtures/chats_fixtures.ex
new file mode 100644
index 0000000000..ea0d0adfd3
--- /dev/null
+++ b/src/chatservice/test/support/fixtures/chats_fixtures.ex
@@ -0,0 +1,38 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+defmodule ChatService.ChatsFixtures do
+ @moduledoc """
+ This module defines test helpers for creating
+ entities via the `Chatservice.Chats` context.
+ """
+
+ @doc """
+ Generate a message.
+ """
+ def message_fixture(attrs \\ %{}) do
+ {:ok, message} =
+ attrs
+ |> Enum.into(%{
+ topic: "test",
+ message: "some message",
+ name: "some name",
+ sent_at: ~U[2024-07-05 18:28:00Z]
+ })
+ |> ChatService.ChatContext.create_message()
+
+ message
+ end
+end
diff --git a/src/chatservice/test/test_helper.exs b/src/chatservice/test/test_helper.exs
new file mode 100644
index 0000000000..b6677800e7
--- /dev/null
+++ b/src/chatservice/test/test_helper.exs
@@ -0,0 +1,17 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 Google LLC
+#
+# Licensed 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.
+ExUnit.start()
+Ecto.Adapters.SQL.Sandbox.mode(ChatService.Repo, :manual)
diff --git a/src/frontendproxy/envoy.tmpl.yaml b/src/frontendproxy/envoy.tmpl.yaml
index dd19fc4bc8..a7060a96f9 100644
--- a/src/frontendproxy/envoy.tmpl.yaml
+++ b/src/frontendproxy/envoy.tmpl.yaml
@@ -51,6 +51,8 @@ static_resources:
route: { cluster: flagservice, prefix_rewrite: "/", timeout: 0s }
- match: { prefix: "/feature" }
route: { cluster: flagd-ui }
+ - match: { prefix: "/chat" }
+ route: { cluster: chat, prefix_rewrite: "/" }
- match: { prefix: "/" }
route: { cluster: frontend }
http_filters:
@@ -67,6 +69,18 @@ static_resources:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
+ - name: chat
+ type: STRICT_DNS
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: chat
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: ${CHAT_SERVICE_HOST}
+ port_value: ${CHAT_SERVICE_PORT}
- name: opentelemetry_collector_grpc
type: STRICT_DNS
lb_policy: ROUND_ROBIN