diff --git a/README.md b/README.md
index 890e970..55fcd27 100644
--- a/README.md
+++ b/README.md
@@ -83,12 +83,15 @@ which are all part of the "***Real World***"
of building and running apps;
so those are topics we **_will_ cover** to "_fill in the gaps_".
-We wrote _this_ tutorial to be **_easiest_ way to learn `Phoenix`**,
-`Ecto` and `Channels` with a **_practical_ example _anyone_ can follow**.
+We wrote _this_ tutorial
+to be **_easiest_ way to learn `Phoenix`**,
+`Ecto` and `Channels` with a **_practical_ example
+_anyone_ can follow**. 🔰
This is the example/tutorial we _wished_ we had
when we were learning `Elixir`, `Phoenix` ...
-If you find it useful, please ⭐ 🙏 Thanks!
+If you find it useful,
+**_please_ star** on **`GitHub`** ⭐ 🙏 Thanks!
## What?
@@ -214,7 +217,7 @@ mix phx.new -v
You should see:
```sh
-Phoenix installer v1.7.0-rc.2
+Phoenix installer v1.8.5
```
> **Note**: if your `Phoenix` version is _newer_,
@@ -222,24 +225,6 @@ Phoenix installer v1.7.0-rc.2
> We try our best to keep it updated ...
> but _your_ contributions are always welcome!
-> In this tutorial,
-> we are using
-> [Phoenix 1.7-rc2](https://github.com/phoenixframework/phoenix/blob/master/CHANGELOG.md#170-rc2-2023-01-13),
-> the second release candidate
-> for `Phoenix 1.7`.
-> At the time of writing,
-> if you install Phoenix,
-> the *latest stable version* is not `v1.7`.
-> To use this version,
-> follow the official guide (don't worry, it's just running one command!)
-> -> https://www.phoenixframework.org/blog/phoenix-1.7-released
->
-> However, if you are reading this after its release,
-> `v1.7` will be installed for you,
-> and you should see
-> `Phoenix installer v1.7.0`
-> in your terminal.
-
_Confirm_ **PostgreSQL** is running (_so the App can store chat messages_)
run the following command:
@@ -252,11 +237,12 @@ You should see output _similar_ to the following:
```sh
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
-postgres 529 Nelson 5u IPv6 0xbc5d729e529f062b 0t0 TCP localhost:postgresql (LISTEN)
-postgres 529 Nelson 6u IPv4 0xbc5d729e55a89a13 0t0 TCP localhost:postgresql (LISTEN)
+postgres 529 Alex 5u IPv6 0xbc5d729e529f062b 0t0 TCP localhost:postgresql (LISTEN)
+postgres 529 Alex 6u IPv4 0xbc5d729e55a89a13 0t0 TCP localhost:postgresql (LISTEN)
```
-This tells us that PostgreSQL is "_listening_" on TCP Port `5432`
+This tells us that PostgreSQL
+is "_listening_" on TCP Port `5432`
(_the default port_)
If the `lsof` command does not yield any result
diff --git a/assets/css/app.css b/assets/css/app.css
deleted file mode 100644
index 378c8f9..0000000
--- a/assets/css/app.css
+++ /dev/null
@@ -1,5 +0,0 @@
-@import "tailwindcss/base";
-@import "tailwindcss/components";
-@import "tailwindcss/utilities";
-
-/* This file is for your main application CSS */
diff --git a/assets/js/app.js b/assets/js/app.js
deleted file mode 100644
index aa6580c..0000000
--- a/assets/js/app.js
+++ /dev/null
@@ -1,190 +0,0 @@
-import socket from "./user_socket.js"
-
-// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
-import "phoenix_html"
-// Establish Phoenix Socket and LiveView configuration.
-import {Socket} from "phoenix"
-import {LiveSocket} from "phoenix_live_view"
-import topbar from "../vendor/topbar"
-
-let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
-let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
-
-// Show progress bar on live navigation and form submits
-topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
-window.addEventListener("phx:page-loading-start", info => topbar.delayedShow(200))
-window.addEventListener("phx:page-loading-stop", info => topbar.hide())
-
-// connect if there are any LiveViews on the page
-liveSocket.connect()
-
-// expose liveSocket on window for web console debug logs and latency simulation:
-// >> liveSocket.enableDebug()
-// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
-// >> liveSocket.disableLatencySim()
-window.liveSocket = liveSocket
-
-
-/* INITIAL SETUP OF VARIABLES AND JOINING CHANNEL -------------- */
-const ul = document.getElementById('msg-list'); // list of messages.
-const name = document.getElementById('name'); // name of message sender
-const msg = document.getElementById('msg'); // message input field
-const send = document.getElementById('send'); // send button
-const peopleListMobile = document.getElementById('people_online-list-mobile'); // online people list mobile
-const peopleListDesktop = document.getElementById('people_online-list-desktop'); // online people list desktop
-
-const channel = socket.channel('room:lobby', {}); // connect to chat "room"
-channel.join(); // join the channel.
-
-
-
-/* ONLINE people / PRESENCE FUNCTIONS -------------- */
-
-
-// This function will be probably caught when the person first enters the page
-channel.on('presence_state', function (payload) {
- // Array of objects with id and username
- const currentlyOnlinePeople = Object.entries(payload).map(elem => ({username: elem[0], id: elem[1].metas[0].phx_ref}))
-
- updateOnlinePeopleList(currentlyOnlinePeople)
-})
-
-// Listening to presence events whenever a person leaves or joins
-channel.on('presence_diff', function (payload) {
- if(payload.joins && payload.leaves) {
- // Array of objects with id and username
- const currentlyOnlinePeople = Object.entries(payload.joins).map(elem => ({username: elem[0], id: elem[1].metas[0].phx_ref}))
- const peopleThatLeft = Object.entries(payload.leaves).map(elem => ({username: elem[0], id: elem[1].metas[0].phx_ref}))
-
- updateOnlinePeopleList(currentlyOnlinePeople)
- removePeopleThatLeft(peopleThatLeft)
- }
-});
-
-function updateOnlinePeopleList(currentlyOnlinePeople) {
- // Add joined people
- for (var i = currentlyOnlinePeople.length - 1; i >= 0; i--) {
- const name = currentlyOnlinePeople[i].username
- const id = name + "-" + currentlyOnlinePeople[i].id
-
- if (document.getElementById(name) == null) {
- var liMobile = document.createElement("li"); // create new person list item DOM element for mobile
- var liDesktop = document.createElement("li"); // create new person list item DOM element for desktop
-
- liMobile.id = id + '_mobile'
- liDesktop.id = id + '_desktop'
- liMobile.innerHTML = `
${sanitizeString(name)}
`
- liDesktop.innerHTML = `
${sanitizeString(name)}
`
-
- peopleListMobile.appendChild(liMobile); // append to people list
- peopleListDesktop.appendChild(liDesktop); // append to people list
- }
- }
-}
-
-function removePeopleThatLeft(peopleThatLeft) {
- // Remove people that left
- for (var i = peopleThatLeft.length - 1; i >= 0; i--) {
- const name = peopleThatLeft[i].name
- const id = name + "-" + peopleThatLeft[i].id
-
- const personThatLeftMobile = document.getElementById(id + '_mobile')
- const personThatLeftDesktop = document.getElementById(id + '_desktop')
-
- if (personThatLeftMobile != null && personThatLeftDesktop != null) {
- peopleListMobile.removeChild(personThatLeftMobile); // remove the person from list mobile
- peopleListDesktop.removeChild(personThatLeftDesktop); // remove the person from list desktop
- }
- }
-}
-
-/* SENDING MESSAGES FUNCTIONS ------------- */
-
-// Listening to 'shout' events
-channel.on('shout', function (payload) {
- render_message(payload)
-});
-
-// Send the message to the server on "shout" channel
-function sendMessage() {
-
- channel.push('shout', {
- name: name.value || "guest", // get value of "name" of person sending the message. Set guest as default
- message: msg.value, // get message text (value) from msg input field.
- inserted_at: new Date() // date + time of when the message was sent
- });
-
- msg.value = ''; // reset the message input field for next message.
-}
-
-// The page does not automatically scroll to show the latest message
-// So invoke this after rendering messages to ensure the last one is in view:
-function scroll_latest_message_into_view() {
- window.scrollTo(0, document.documentElement.scrollHeight) // desktop
- ul.scrollTo(0, ul.scrollHeight) // mobile
-}
-
-// Render the message with Tailwind styles
-function render_message(payload) {
-
- const li = document.createElement("li"); // create new list item DOM element
-
- // Message HTML with Tailwind CSS Classes for layout/style:
- li.innerHTML = `
-
- `
- // Append to list
- ul.appendChild(li);
- scroll_latest_message_into_view();
-}
-
-// Listen for the [Enter] keypress event to send a message:
-msg.addEventListener('keypress', function (event) {
- if (event.keyCode == 13 && msg.value.length > 0) { // don't sent empty msg.
- sendMessage()
- }
-});
-
-// On "Send" button press
-send.addEventListener('click', function (event) {
- if (msg.value.length > 0) { // don't sent empty msg.
- sendMessage()
- }
-});
-
-
-/* UTILS ------------ */
-
-// Date formatting
-function formatDate(datetime) {
- const m = new Date(datetime);
- return m.getUTCFullYear() + "/"
- + ("0" + (m.getUTCMonth()+1)).slice(-2) + "/"
- + ("0" + m.getUTCDate()).slice(-2);
-}
-
-// Time formatting
-function formatTime(datetime) {
- const m = new Date(datetime);
- return ("0" + m.getUTCHours()).slice(-2) + ":"
- + ("0" + m.getUTCMinutes()).slice(-2) + ":"
- + ("0" + m.getUTCSeconds()).slice(-2);
-}
-
-// Sanitize string input borrowed from:
-// stackoverflow.com/questions/23187013/sanitize-javascript-string
-function sanitizeString(str){
- str = str.replace(/[^a-z0-9áéíóúñü \.,_-]/gim,"");
- return str.trim();
-}
\ No newline at end of file
diff --git a/assets/js/user_socket.js b/assets/js/user_socket.js
deleted file mode 100644
index 5b40269..0000000
--- a/assets/js/user_socket.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// NOTE: The contents of this file will only be executed if
-// you uncomment its entry in "assets/js/app.js".
-
-// Bring in Phoenix channels client library:
-import {Socket} from "phoenix"
-
-// And connect to the path in "lib/chat_web/endpoint.ex". We pass the
-// token for authentication. Read below how it should be used.
-let socket = new Socket("/socket", {params: {token: window.personToken}})
-
-// Connect to the socket:
-socket.connect()
-export default socket
diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js
deleted file mode 100644
index e3bf241..0000000
--- a/assets/tailwind.config.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// See the Tailwind configuration guide for advanced usage
-// https://tailwindcss.com/docs/configuration
-
-const plugin = require("tailwindcss/plugin")
-
-module.exports = {
- content: [
- "./js/**/*.js",
- "../lib/*_web.ex",
- "../lib/*_web/**/*.*ex"
- ],
- theme: {
- extend: {
- colors: {
- brand: "#FD4F00",
- }
- },
- },
- plugins: [
- require("@tailwindcss/forms"),
- plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
- plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
- plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
- plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"]))
- ]
-}
diff --git a/assets/vendor/topbar.js b/assets/vendor/topbar.js
deleted file mode 100644
index 4176ede..0000000
--- a/assets/vendor/topbar.js
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * @license MIT
- * topbar 1.0.0, 2021-01-06
- * Modifications:
- * - add delayedShow(time) (2022-09-21)
- * http://buunguyen.github.io/topbar
- * Copyright (c) 2021 Buu Nguyen
- */
-(function (window, document) {
- "use strict";
-
- // https://gist.github.com/paulirish/1579671
- (function () {
- var lastTime = 0;
- var vendors = ["ms", "moz", "webkit", "o"];
- for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
- window.requestAnimationFrame =
- window[vendors[x] + "RequestAnimationFrame"];
- window.cancelAnimationFrame =
- window[vendors[x] + "CancelAnimationFrame"] ||
- window[vendors[x] + "CancelRequestAnimationFrame"];
- }
- if (!window.requestAnimationFrame)
- window.requestAnimationFrame = function (callback, element) {
- var currTime = new Date().getTime();
- var timeToCall = Math.max(0, 16 - (currTime - lastTime));
- var id = window.setTimeout(function () {
- callback(currTime + timeToCall);
- }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- if (!window.cancelAnimationFrame)
- window.cancelAnimationFrame = function (id) {
- clearTimeout(id);
- };
- })();
-
- var canvas,
- currentProgress,
- showing,
- progressTimerId = null,
- fadeTimerId = null,
- delayTimerId = null,
- addEvent = function (elem, type, handler) {
- if (elem.addEventListener) elem.addEventListener(type, handler, false);
- else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
- else elem["on" + type] = handler;
- },
- options = {
- autoRun: true,
- barThickness: 3,
- barColors: {
- 0: "rgba(26, 188, 156, .9)",
- ".25": "rgba(52, 152, 219, .9)",
- ".50": "rgba(241, 196, 15, .9)",
- ".75": "rgba(230, 126, 34, .9)",
- "1.0": "rgba(211, 84, 0, .9)",
- },
- shadowBlur: 10,
- shadowColor: "rgba(0, 0, 0, .6)",
- className: null,
- },
- repaint = function () {
- canvas.width = window.innerWidth;
- canvas.height = options.barThickness * 5; // need space for shadow
-
- var ctx = canvas.getContext("2d");
- ctx.shadowBlur = options.shadowBlur;
- ctx.shadowColor = options.shadowColor;
-
- var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
- for (var stop in options.barColors)
- lineGradient.addColorStop(stop, options.barColors[stop]);
- ctx.lineWidth = options.barThickness;
- ctx.beginPath();
- ctx.moveTo(0, options.barThickness / 2);
- ctx.lineTo(
- Math.ceil(currentProgress * canvas.width),
- options.barThickness / 2
- );
- ctx.strokeStyle = lineGradient;
- ctx.stroke();
- },
- createCanvas = function () {
- canvas = document.createElement("canvas");
- var style = canvas.style;
- style.position = "fixed";
- style.top = style.left = style.right = style.margin = style.padding = 0;
- style.zIndex = 100001;
- style.display = "none";
- if (options.className) canvas.classList.add(options.className);
- document.body.appendChild(canvas);
- addEvent(window, "resize", repaint);
- },
- topbar = {
- config: function (opts) {
- for (var key in opts)
- if (options.hasOwnProperty(key)) options[key] = opts[key];
- },
- delayedShow: function(time) {
- if (showing) return;
- if (delayTimerId) return;
- delayTimerId = setTimeout(() => topbar.show(), time);
- },
- show: function () {
- if (showing) return;
- showing = true;
- if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
- if (!canvas) createCanvas();
- canvas.style.opacity = 1;
- canvas.style.display = "block";
- topbar.progress(0);
- if (options.autoRun) {
- (function loop() {
- progressTimerId = window.requestAnimationFrame(loop);
- topbar.progress(
- "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
- );
- })();
- }
- },
- progress: function (to) {
- if (typeof to === "undefined") return currentProgress;
- if (typeof to === "string") {
- to =
- (to.indexOf("+") >= 0 || to.indexOf("-") >= 0
- ? currentProgress
- : 0) + parseFloat(to);
- }
- currentProgress = to > 1 ? 1 : to;
- repaint();
- return currentProgress;
- },
- hide: function () {
- clearTimeout(delayTimerId);
- delayTimerId = null;
- if (!showing) return;
- showing = false;
- if (progressTimerId != null) {
- window.cancelAnimationFrame(progressTimerId);
- progressTimerId = null;
- }
- (function loop() {
- if (topbar.progress("+.1") >= 1) {
- canvas.style.opacity -= 0.05;
- if (canvas.style.opacity <= 0.05) {
- canvas.style.display = "none";
- fadeTimerId = null;
- return;
- }
- }
- fadeTimerId = window.requestAnimationFrame(loop);
- })();
- },
- };
-
- if (typeof module === "object" && typeof module.exports === "object") {
- module.exports = topbar;
- } else if (typeof define === "function" && define.amd) {
- define(function () {
- return topbar;
- });
- } else {
- this.topbar = topbar;
- }
-}.call(this, window, document));
diff --git a/config/config.exs b/config/config.exs
deleted file mode 100644
index 566b770..0000000
--- a/config/config.exs
+++ /dev/null
@@ -1,55 +0,0 @@
-# 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 :chat,
- ecto_repos: [Chat.Repo]
-
-# Configures the endpoint
-config :chat, ChatWeb.Endpoint,
- url: [host: "localhost"],
- render_errors: [
- formats: [html: ChatWeb.ErrorHTML, json: ChatWeb.ErrorJSON],
- layout: false
- ],
- pubsub_server: Chat.PubSub,
- live_view: [signing_salt: "lvvIz0Oa"]
-
-# Configure esbuild (the version is required)
-config :esbuild,
- version: "0.14.41",
- default: [
- 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__)}
- ]
-
-# Configure tailwind (the version is required)
-config :tailwind,
- version: "3.2.4",
- default: [
- args: ~w(
- --config=tailwind.config.js
- --input=css/app.css
- --output=../priv/static/assets/app.css
- ),
- cd: Path.expand("../assets", __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
-
-# 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/config/dev.exs b/config/dev.exs
deleted file mode 100644
index a97061f..0000000
--- a/config/dev.exs
+++ /dev/null
@@ -1,75 +0,0 @@
-import Config
-
-# Configure your database
-config :chat, Chat.Repo,
- username: "postgres",
- password: "postgres",
- hostname: "localhost",
- database: "chat_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 use it
-# with esbuild to bundle .js and .css sources.
-config :chat, ChatWeb.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: {127, 0, 0, 1}, port: 4000],
- check_origin: false,
- code_reloader: true,
- debug_errors: true,
- secret_key_base: "odMGQmHhvRQ8crBIH3IN31hahkw6hD0RJwvmr5i3+Up96Hs4//+tkgzNdg3HSxLI",
- watchers: [
- esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
- tailwind: {Tailwind, :install_and_run, [:default, ~w(--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 :chat, ChatWeb.Endpoint,
- live_reload: [
- patterns: [
- ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
- ~r"lib/chat_web/(controllers|live|components)/.*(ex|heex)$"
- ]
- ]
-
-# Enable dev routes for dashboard and mailbox
-config :chat, 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
diff --git a/config/prod.exs b/config/prod.exs
deleted file mode 100644
index 044a1da..0000000
--- a/config/prod.exs
+++ /dev/null
@@ -1,18 +0,0 @@
-import Config
-
-# For production, don't forget to configure the url host
-# to something meaningful, Phoenix uses this information
-# when generating URLs.
-
-# Note we also include the path to a cache manifest
-# containing the digested version of static files. This
-# manifest is generated by the `mix phx.digest` task,
-# which you should run after static files are built and
-# before starting your production server.
-config :chat, ChatWeb.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/config/runtime.exs b/config/runtime.exs
deleted file mode 100644
index 3e18c1c..0000000
--- a/config/runtime.exs
+++ /dev/null
@@ -1,97 +0,0 @@
-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/chat 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 :chat, ChatWeb.Endpoint, server: true
-end
-
-if config_env() == :prod do
- 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"), do: [:inet6], else: []
-
- config :chat, Chat.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("PHX_HOST") || "example.com"
- port = String.to_integer(System.get_env("PORT") || "4000")
-
- config :chat, ChatWeb.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/plug_cowboy/Plug.Cowboy.html
- # 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 :chat, ChatWeb.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 endpoint, ensuring
- # no data is ever sent via http, always redirecting to https:
- #
- # config :chat, ChatWeb.Endpoint,
- # force_ssl: [hsts: true]
- #
- # Check `Plug.SSL` for all available options in `force_ssl`.
-end
diff --git a/config/test.exs b/config/test.exs
deleted file mode 100644
index 74009ba..0000000
--- a/config/test.exs
+++ /dev/null
@@ -1,27 +0,0 @@
-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 :chat, Chat.Repo,
- username: "postgres",
- password: "postgres",
- hostname: "localhost",
- database: "chat_test#{System.get_env("MIX_TEST_PARTITION")}",
- pool: Ecto.Adapters.SQL.Sandbox,
- pool_size: 10
-
-# We don't run a server during test. If one is required,
-# you can enable the server option below.
-config :chat, ChatWeb.Endpoint,
- http: [ip: {127, 0, 0, 1}, port: 4002],
- secret_key_base: "Q1WJjPpcI6Vs2ieJQMcs1DjL4JPZ0HafQ3afIzEUMx7fN/K0HO0+85e02OhUQS9n",
- 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/lib/chat.ex b/lib/chat.ex
deleted file mode 100644
index 6442a14..0000000
--- a/lib/chat.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Chat do
- @moduledoc """
- Chat keeps the contexts that define your domain
- and business logic.
-
- Contexts are also responsible for managing your data, regardless
- if it comes from the database, an external API or others.
- """
-end
diff --git a/lib/chat/application.ex b/lib/chat/application.ex
deleted file mode 100644
index 1923c94..0000000
--- a/lib/chat/application.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule Chat.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
- children = [
- # Start the Telemetry supervisor
- ChatWeb.Telemetry,
- # Start the Ecto repository
- Chat.Repo,
- # Start the PubSub system
- {Phoenix.PubSub, name: Chat.PubSub},
- ChatWeb.Presence,
- # Start the Endpoint (http/https)
- ChatWeb.Endpoint
- # Start a worker by calling: Chat.Worker.start_link(arg)
- # {Chat.Worker, arg}
- ]
-
- # See https://hexdocs.pm/elixir/Supervisor.html
- # for other strategies and supported options
- opts = [strategy: :one_for_one, name: Chat.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
- ChatWeb.Endpoint.config_change(changed, removed)
- :ok
- end
-end
diff --git a/lib/chat/message.ex b/lib/chat/message.ex
deleted file mode 100644
index 54321a7..0000000
--- a/lib/chat/message.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Chat.Message do
- use Ecto.Schema
- import Ecto.Changeset
- import Ecto.Query
-
- schema "messages" do
- field :message, :string
- field :name, :string
-
- timestamps()
- end
-
- @doc false
- def changeset(message, attrs) do
- message
- |> cast(attrs, [:name, :message])
- |> validate_required([:name, :message])
- end
-
- def get_messages(limit \\ 20) do
- Chat.Message
- |> limit(^limit)
- |> order_by(desc: :inserted_at)
- |> Chat.Repo.all()
- end
-end
diff --git a/lib/chat/release.ex b/lib/chat/release.ex
deleted file mode 100644
index 04b7809..0000000
--- a/lib/chat/release.ex
+++ /dev/null
@@ -1,28 +0,0 @@
-defmodule Chat.Release do
- @moduledoc """
- Used for executing DB release tasks when run in production without Mix
- installed.
- """
- @app :chat
-
- def migrate do
- load_app()
-
- for repo <- repos() do
- {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
- end
- end
-
- def rollback(repo, version) do
- load_app()
- {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
- end
-
- defp repos do
- Application.fetch_env!(@app, :ecto_repos)
- end
-
- defp load_app do
- Application.load(@app)
- end
-end
diff --git a/lib/chat/repo.ex b/lib/chat/repo.ex
deleted file mode 100644
index 43b4153..0000000
--- a/lib/chat/repo.ex
+++ /dev/null
@@ -1,5 +0,0 @@
-defmodule Chat.Repo do
- use Ecto.Repo,
- otp_app: :chat,
- adapter: Ecto.Adapters.Postgres
-end
diff --git a/lib/chat_web.ex b/lib/chat_web.ex
deleted file mode 100644
index 5052cc5..0000000
--- a/lib/chat_web.ex
+++ /dev/null
@@ -1,111 +0,0 @@
-defmodule ChatWeb 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 ChatWeb, :controller
- use ChatWeb, :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: ChatWeb.Layouts]
-
- import Plug.Conn
-
- unquote(verified_routes())
- end
- end
-
- def live_view do
- quote do
- use Phoenix.LiveView,
- layout: {ChatWeb.Layouts, :app}
-
- unquote(html_helpers())
- end
- end
-
- def live_component do
- quote do
- use Phoenix.LiveComponent
-
- unquote(html_helpers())
- 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
- # HTML escaping functionality
- import Phoenix.HTML
- # Core UI components and translation
- import ChatWeb.CoreComponents
-
- # Shortcut for generating JS commands
- alias Phoenix.LiveView.JS
-
- # Routes generation with the ~p sigil
- unquote(verified_routes())
- end
- end
-
- def verified_routes do
- quote do
- use Phoenix.VerifiedRoutes,
- endpoint: ChatWeb.Endpoint,
- router: ChatWeb.Router,
- statics: ChatWeb.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/lib/chat_web/channels/room_channel.ex b/lib/chat_web/channels/room_channel.ex
deleted file mode 100644
index 7caa72b..0000000
--- a/lib/chat_web/channels/room_channel.ex
+++ /dev/null
@@ -1,74 +0,0 @@
-defmodule ChatWeb.RoomChannel do
- use ChatWeb, :channel
- alias ChatWeb.Presence
-
- @impl true
- def join("room:lobby", payload, socket) do
- if authorized?(payload) do
- send(self(), :after_join)
- {:ok, socket}
- else
- {:error, %{reason: "unauthorized"}}
- end
- end
-
- # Channels can be used in a request/response fashion
- # by sending replies to requests from the client
- @impl true
- def handle_in("ping", payload, socket) do
- {:reply, {:ok, payload}, socket}
- end
-
- # It is also common to receive messages from the client and
- # broadcast to everyone in the current topic (room:lobby).
- @impl true
- def handle_in("shout", payload, socket) do
- # Insert message in database
- {:ok, msg} = Chat.Message.changeset(%Chat.Message{}, payload) |> Chat.Repo.insert()
-
- # Assigning name to socket assigns and tracking presence
- socket
- |> assign(:username, msg.name)
- |> track_presence()
- |> broadcast("shout", Map.put_new(payload, :id, msg.id))
-
- {:noreply, socket}
- end
-
- @impl true
- def handle_info(:after_join, socket) do
- # Get messages and list them
- Chat.Message.get_messages()
- # reverts the enum to display the latest message at the bottom of the page
- |> Enum.reverse()
- |> Enum.each(fn msg ->
- push(socket, "shout", %{
- name: msg.name,
- message: msg.message,
- inserted_at: msg.inserted_at
- })
- end)
-
- # Send currently online people in lobby
- push(socket, "presence_state", Presence.list("room:lobby"))
-
- {:noreply, socket}
- end
-
- # Add authorization logic here as required.
- defp authorized?(_payload) do
- true
- end
-
- # This creates a Presence track when the person joins the channel.
- # Normally this is done when joining the channel,
- # but the socket doesn't know the name so we wait for a message to be sent
- # with the name to begin tracking.
- defp track_presence(%{assigns: %{username: username}} = socket) do
- Presence.track(socket, username, %{
- online_at: inspect(System.system_time(:second))
- })
-
- socket
- end
-end
diff --git a/lib/chat_web/channels/user_socket.ex b/lib/chat_web/channels/user_socket.ex
deleted file mode 100644
index 459a251..0000000
--- a/lib/chat_web/channels/user_socket.ex
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule ChatWeb.UserSocket do
- use Phoenix.Socket
-
- # A Socket handler
- #
- # It's possible to control the websocket connection and
- # assign values that can be accessed by your channel topics.
-
- ## Channels
-
- channel "room:lobby", ChatWeb.RoomChannel
-
- # 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
- {:ok, socket}
- end
-
- # Socket id's 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.ChatWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
- #
- # Returning `nil` makes this socket anonymous.
- @impl true
- def id(_socket), do: nil
-end
diff --git a/lib/chat_web/components/core_components.ex b/lib/chat_web/components/core_components.ex
deleted file mode 100644
index 1f0b584..0000000
--- a/lib/chat_web/components/core_components.ex
+++ /dev/null
@@ -1,617 +0,0 @@
-defmodule ChatWeb.CoreComponents do
- @moduledoc """
- Provides core UI components.
-
- The components in this module use Tailwind CSS, a utility-first CSS framework.
- See the [Tailwind CSS documentation](https://tailwindcss.com) to learn how to
- customize the generated components in this module.
-
- Icons are provided by [heroicons](https://heroicons.com), using the
- [heroicons_elixir](https://github.com/mveytsman/heroicons_elixir) project.
- """
- use Phoenix.Component
-
- alias Phoenix.LiveView.JS
-
- @doc """
- Renders a modal.
-
- ## Examples
-
- <.modal id="confirm-modal">
- Are you sure?
- <:confirm>OK
- <:cancel>Cancel
-
-
- JS commands may be passed to the `:on_cancel` and `on_confirm` attributes
- for the caller to react to each button press, for example:
-
- <.modal id="confirm" on_confirm={JS.push("delete")} on_cancel={JS.navigate(~p"/posts")}>
- Are you sure you?
- <:confirm>OK
- <:cancel>Cancel
-
- """
- attr :id, :string, required: true
- attr :show, :boolean, default: false
- attr :on_cancel, JS, default: %JS{}
- attr :on_confirm, JS, default: %JS{}
-
- slot :inner_block, required: true
- slot :title
- slot :subtitle
- slot :confirm
- slot :cancel
-
- def modal(assigns) do
- ~H"""
-