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