diff --git a/services/bright/.dockerignore b/services/bright/.dockerignore new file mode 100644 index 0000000..61a7393 --- /dev/null +++ b/services/bright/.dockerignore @@ -0,0 +1,45 @@ +# This file excludes paths from the Docker build context. +# +# By default, Docker's build context includes all files (and folders) in the +# current directory. Even if a file isn't copied into the container it is still sent to +# the Docker daemon. +# +# There are multiple reasons to exclude files from the build context: +# +# 1. Prevent nested folders from being copied into the container (ex: exclude +# /assets/node_modules when copying /assets) +# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc) +# 3. Avoid sending files containing sensitive information +# +# More information on using .dockerignore is available here: +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +.dockerignore + +# Ignore git, but keep git HEAD and refs to access current commit hash if needed: +# +# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat +# d0b8727759e1e0e7aa3d41707d12376e373d5ecc +.git +!.git/HEAD +!.git/refs + +# Common development/test artifacts +/cover/ +/doc/ +/test/ +/tmp/ +.elixir_ls + +# Mix artifacts +/_build/ +/deps/ +*.ez + +# Generated on crash by the VM +erl_crash.dump + +# Static artifacts - These should be fetched and built inside the Docker image +/assets/node_modules/ +/priv/static/assets/ +/priv/static/cache_manifest.json diff --git a/services/bright/.formatter.exs b/services/bright/.formatter.exs new file mode 100644 index 0000000..ef8840c --- /dev/null +++ b/services/bright/.formatter.exs @@ -0,0 +1,6 @@ +[ + 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/services/bright/.gitignore b/services/bright/.gitignore new file mode 100644 index 0000000..0c9c530 --- /dev/null +++ b/services/bright/.gitignore @@ -0,0 +1,37 @@ +# 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"). +bright-*.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/ + diff --git a/services/bright/README.md b/services/bright/README.md new file mode 100644 index 0000000..2f472de --- /dev/null +++ b/services/bright/README.md @@ -0,0 +1,18 @@ +# Bright + +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 [`localhost:4000`](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: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/services/bright/assets/css/app.css b/services/bright/assets/css/app.css new file mode 100644 index 0000000..433a06e --- /dev/null +++ b/services/bright/assets/css/app.css @@ -0,0 +1,7 @@ +/* @import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; */ + +@import "./bulma.min.css"; + +/* This file is for your main application CSS */ diff --git a/services/bright/assets/css/app.scss b/services/bright/assets/css/app.scss new file mode 100644 index 0000000..4e8cb6b --- /dev/null +++ b/services/bright/assets/css/app.scss @@ -0,0 +1,4 @@ +/* This file is for your main application CSS */ +// @import "./phoenix.css"; +@import "bulma"; + diff --git a/services/bright/assets/js/app.js b/services/bright/assets/js/app.js new file mode 100644 index 0000000..d5e278a --- /dev/null +++ b/services/bright/assets/js/app.js @@ -0,0 +1,44 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// 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, { + longPollFallbackMs: 2500, + 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.show(300)) +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 + diff --git a/services/bright/assets/tailwind.config.js b/services/bright/assets/tailwind.config.js new file mode 100644 index 0000000..df37d6d --- /dev/null +++ b/services/bright/assets/tailwind.config.js @@ -0,0 +1,74 @@ +// See the Tailwind configuration guide for advanced usage +// https://tailwindcss.com/docs/configuration + +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = { + content: [ + "./js/**/*.js", + "../lib/bright_web.ex", + "../lib/bright_web/**/*.*ex" + ], + theme: { + extend: { + colors: { + brand: "#FD4F00", + } + }, + }, + plugins: [ + require("@tailwindcss/forms"), + // Allows prefixing tailwind classes with LiveView classes to add rules + // only when LiveView classes are applied, for example: + // + //
+ // + 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 &"])), + + // Embeds Heroicons (https://heroicons.com) into your app.css bundle + // See your `CoreComponents.icon/1` for more information. + // + plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + let size = theme("spacing.6") + if (name.endsWith("-mini")) { + size = theme("spacing.5") + } else if (name.endsWith("-micro")) { + size = theme("spacing.4") + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": size, + "height": size + } + } + }, {values}) + }) + ] +} diff --git a/services/bright/assets/vendor/topbar.js b/services/bright/assets/vendor/topbar.js new file mode 100644 index 0000000..4195727 --- /dev/null +++ b/services/bright/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://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]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + 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/services/bright/config/config.exs b/services/bright/config/config.exs new file mode 100644 index 0000000..f1d068c --- /dev/null +++ b/services/bright/config/config.exs @@ -0,0 +1,79 @@ +# 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 :bright, + ecto_repos: [Bright.Repo], + generators: [timestamp_type: :utc_datetime] + +# Configures the endpoint +config :bright, BrightWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: BrightWeb.ErrorHTML, json: BrightWeb.ErrorJSON], + layout: false + ], + pubsub_server: Bright.PubSub, + live_view: [signing_salt: "JGNufzrG"] + + + + + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :bright, Bright.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.17.11", + bright: [ + 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 dart_sass, used for bulma + config :dart_sass, + version: "1.61.0", + default: [ + args: ~w(--load-path=../deps/bulma css:../priv/static/assets), + cd: Path.expand("../assets", __DIR__) + ] + + +# # Configure tailwind (the version is required) +# config :tailwind, +# version: "3.4.3", +# bright: [ +# 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/services/bright/config/dev.exs b/services/bright/config/dev.exs new file mode 100644 index 0000000..5585617 --- /dev/null +++ b/services/bright/config/dev.exs @@ -0,0 +1,87 @@ +import Config + +# Configure the database +config :bright, Bright.Repo, + url: "#{System.get_env("DATABASE_URL")}", + 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 :bright, BrightWeb.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: "#{System.get_env("PORT")}"], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "#{System.get_env("SECRET_KEY_BASE")}", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:bright, ~w(--sourcemap=inline --watch)]}, + # tailwind: {Tailwind, :install_and_run, [:bright, ~w(--watch)]}, + sass: { + DartSass, + :install_and_run, + [:default, ~w(--embed-source-map --source-map-urls=absolute --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 :bright, BrightWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/bright_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :bright, 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 + +config :phoenix_live_view, + # Include HEEx debug annotations as HTML comments in rendered markup + debug_heex_annotations: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/services/bright/config/prod.exs b/services/bright/config/prod.exs new file mode 100644 index 0000000..1e8548c --- /dev/null +++ b/services/bright/config/prod.exs @@ -0,0 +1,20 @@ +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 :bright, BrightWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Configures Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Bright.Finch + +# Disable Swoosh Local Memory Storage +config :swoosh, local: false + +# 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/services/bright/config/runtime.exs b/services/bright/config/runtime.exs new file mode 100644 index 0000000..427306b --- /dev/null +++ b/services/bright/config/runtime.exs @@ -0,0 +1,117 @@ +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/bright 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 :bright, BrightWeb.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") in ~w(true 1), do: [:inet6], else: [] + + config :bright, Bright.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 :bright, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :bright, BrightWeb.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 :bright, BrightWeb.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 :bright, BrightWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :bright, Bright.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/services/bright/config/test.exs b/services/bright/config/test.exs new file mode 100644 index 0000000..8935c25 --- /dev/null +++ b/services/bright/config/test.exs @@ -0,0 +1,35 @@ +import Config + +# Configure the 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 :bright, Bright.Repo, + url: "#{System.get_env("DATABASE_URL")}", + database: "bright_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 :bright, BrightWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "#{System.get_env("SECRET_KEY_BASE")}", + server: false + +# In test we don't send emails +config :bright, Bright.Mailer, adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters +config :swoosh, :api_client, 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 + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true diff --git a/services/bright/lib/bright.ex b/services/bright/lib/bright.ex new file mode 100644 index 0000000..55d586e --- /dev/null +++ b/services/bright/lib/bright.ex @@ -0,0 +1,9 @@ +defmodule Bright do + @moduledoc """ + Bright 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/services/bright/lib/bright/application.ex b/services/bright/lib/bright/application.ex new file mode 100644 index 0000000..cae4975 --- /dev/null +++ b/services/bright/lib/bright/application.ex @@ -0,0 +1,36 @@ +defmodule Bright.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 = [ + BrightWeb.Telemetry, + Bright.Repo, + {DNSCluster, query: Application.get_env(:bright, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: Bright.PubSub}, + # Start the Finch HTTP client for sending emails + {Finch, name: Bright.Finch}, + # Start a worker by calling: Bright.Worker.start_link(arg) + # {Bright.Worker, arg}, + # Start to serve requests, typically the last entry + BrightWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Bright.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 + BrightWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/services/bright/lib/bright/blog.ex b/services/bright/lib/bright/blog.ex new file mode 100644 index 0000000..2e891fb --- /dev/null +++ b/services/bright/lib/bright/blog.ex @@ -0,0 +1,104 @@ +defmodule Bright.Blog do + @moduledoc """ + The Blog context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Blog.Post + + @doc """ + Returns the list of posts. + + ## Examples + + iex> list_posts() + [%Post{}, ...] + + """ + def list_posts do + Repo.all(Post) + end + + @doc """ + Gets a single post. + + Raises `Ecto.NoResultsError` if the Post does not exist. + + ## Examples + + iex> get_post!(123) + %Post{} + + iex> get_post!(456) + ** (Ecto.NoResultsError) + + """ + def get_post!(id), do: Repo.get!(Post, id) + + @doc """ + Creates a post. + + ## Examples + + iex> create_post(%{field: value}) + {:ok, %Post{}} + + iex> create_post(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_post(attrs \\ %{}) do + %Post{} + |> Post.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a post. + + ## Examples + + iex> update_post(post, %{field: new_value}) + {:ok, %Post{}} + + iex> update_post(post, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_post(%Post{} = post, attrs) do + post + |> Post.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a post. + + ## Examples + + iex> delete_post(post) + {:ok, %Post{}} + + iex> delete_post(post) + {:error, %Ecto.Changeset{}} + + """ + def delete_post(%Post{} = post) do + Repo.delete(post) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking post changes. + + ## Examples + + iex> change_post(post) + %Ecto.Changeset{data: %Post{}} + + """ + def change_post(%Post{} = post, attrs \\ %{}) do + Post.changeset(post, attrs) + end +end diff --git a/services/bright/lib/bright/blog/post.ex b/services/bright/lib/bright/blog/post.ex new file mode 100644 index 0000000..e5bc353 --- /dev/null +++ b/services/bright/lib/bright/blog/post.ex @@ -0,0 +1,18 @@ +defmodule Bright.Blog.Post do + use Ecto.Schema + import Ecto.Changeset + + schema "posts" do + field :title, :string + field :body, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(post, attrs) do + post + |> cast(attrs, [:title, :body]) + |> validate_required([:title, :body]) + end +end diff --git a/services/bright/lib/bright/catalog.ex b/services/bright/lib/bright/catalog.ex new file mode 100644 index 0000000..5ced820 --- /dev/null +++ b/services/bright/lib/bright/catalog.ex @@ -0,0 +1,214 @@ +defmodule Bright.Catalog do + @moduledoc """ + The Catalog context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Catalog.Product + alias Bright.Catalog.Category + + @doc """ + Returns the list of products. + + ## Examples + + iex> list_products() + [%Product{}, ...] + + """ + def list_products do + Repo.all(Product) + end + + @doc """ + Gets a single product. + + Raises `Ecto.NoResultsError` if the Product does not exist. + + ## Examples + + iex> get_product!(123) + %Product{} + + iex> get_product!(456) + ** (Ecto.NoResultsError) + + """ + def get_product!(id) do + Product + |> Repo.get!(id) + |> Repo.preload(:categories) + end + + @doc """ + Creates a product. + + ## Examples + + iex> create_product(%{field: value}) + {:ok, %Product{}} + + iex> create_product(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_product(attrs \\ %{}) do + %Product{} + |> change_product(attrs) + |> Repo.insert() + end + + @doc """ + Updates a product. + + ## Examples + + iex> update_product(product, %{field: new_value}) + {:ok, %Product{}} + + iex> update_product(product, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_product(%Product{} = product, attrs) do + product + |> change_product(attrs) + |> Repo.update() + end + + @doc """ + Deletes a product. + + ## Examples + + iex> delete_product(product) + {:ok, %Product{}} + + iex> delete_product(product) + {:error, %Ecto.Changeset{}} + + """ + def delete_product(%Product{} = product) do + Repo.delete(product) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking product changes. + + ## Examples + + iex> change_product(product) + %Ecto.Changeset{data: %Product{}} + + """ + def change_product(%Product{} = product, attrs \\ %{}) do + categories = list_categories_by_id(attrs["category_ids"]) + product + |> Repo.preload(:categories) + |> Product.changeset(attrs) + |> Ecto.Changeset.put_assoc(:categories, categories) + end + + + @doc """ + Returns the list of categories. + + ## Examples + + iex> list_categories() + [%Category{}, ...] + + """ + def list_categories do + Repo.all(Category) + end + + + @doc """ + Gets a single category. + + Raises `Ecto.NoResultsError` if the Category does not exist. + + ## Examples + + iex> get_category!(123) + %Category{} + + iex> get_category!(456) + ** (Ecto.NoResultsError) + + """ + def get_category!(id), do: Repo.get!(Category, id) + + @doc """ + Creates a category. + + ## Examples + + iex> create_category(%{field: value}) + {:ok, %Category{}} + + iex> create_category(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_category(attrs \\ %{}) do + %Category{} + |> Category.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a category. + + ## Examples + + iex> update_category(category, %{field: new_value}) + {:ok, %Category{}} + + iex> update_category(category, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_category(%Category{} = category, attrs) do + category + |> Category.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a category. + + ## Examples + + iex> delete_category(category) + {:ok, %Category{}} + + iex> delete_category(category) + {:error, %Ecto.Changeset{}} + + """ + def delete_category(%Category{} = category) do + Repo.delete(category) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking category changes. + + ## Examples + + iex> change_category(category) + %Ecto.Changeset{data: %Category{}} + + """ + def change_category(%Category{} = category, attrs \\ %{}) do + Category.changeset(category, attrs) + end + + def list_categories_by_id(nil), do: [] + def list_categories_by_id(category_ids) do + Repo.all(from c in Category, where: c.id in ^category_ids) + end +end diff --git a/services/bright/lib/bright/catalog/category.ex b/services/bright/lib/bright/catalog/category.ex new file mode 100644 index 0000000..50ac10a --- /dev/null +++ b/services/bright/lib/bright/catalog/category.ex @@ -0,0 +1,18 @@ +defmodule Bright.Catalog.Category do + use Ecto.Schema + import Ecto.Changeset + + schema "categories" do + field :title, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(category, attrs) do + category + |> cast(attrs, [:title]) + |> validate_required([:title]) + |> unique_constraint(:title) + end +end diff --git a/services/bright/lib/bright/catalog/product.ex b/services/bright/lib/bright/catalog/product.ex new file mode 100644 index 0000000..cee7218 --- /dev/null +++ b/services/bright/lib/bright/catalog/product.ex @@ -0,0 +1,23 @@ +defmodule Bright.Catalog.Product do + use Ecto.Schema + import Ecto.Changeset + alias Bright.Catalog.Category + + schema "products" do + field :description, :string + field :title, :string + field :price, :decimal + field :views, :integer + + many_to_many :categories, Category, join_through: "product_categories", on_replace: :delete + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(product, attrs) do + product + |> cast(attrs, [:title, :description, :price, :views]) + |> validate_required([:title, :description, :price]) + end +end diff --git a/services/bright/lib/bright/mailer.ex b/services/bright/lib/bright/mailer.ex new file mode 100644 index 0000000..1c82853 --- /dev/null +++ b/services/bright/lib/bright/mailer.ex @@ -0,0 +1,3 @@ +defmodule Bright.Mailer do + use Swoosh.Mailer, otp_app: :bright +end diff --git a/services/bright/lib/bright/orders.ex b/services/bright/lib/bright/orders.ex new file mode 100644 index 0000000..458506b --- /dev/null +++ b/services/bright/lib/bright/orders.ex @@ -0,0 +1,231 @@ +defmodule Bright.Orders do + @moduledoc """ + The Orders context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Orders.{Order,LineItem} + alias Bright.ShoppingCart + + @doc """ + Returns the list of orders. + + ## Examples + + iex> list_orders() + [%Order{}, ...] + + """ + def list_orders do + Repo.all(Order) + end + + @doc """ + Gets a single order. + + Raises `Ecto.NoResultsError` if the Order does not exist. + + ## Examples + + iex> get_order!(123) + %Order{} + + iex> get_order!(456) + ** (Ecto.NoResultsError) + + """ + # def get_order!(id), do: Repo.get!(Order, id) + def get_order!(user_uuid, id) do + Order + |> Repo.get_by!(id: id, user_uuid: user_uuid) + |> Repo.preload([line_items: [:product]]) + end + + @doc """ + Creates a order. + + ## Examples + + iex> create_order(%{field: value}) + {:ok, %Order{}} + + iex> create_order(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_order(attrs \\ %{}) do + %Order{} + |> Order.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a order. + + ## Examples + + iex> update_order(order, %{field: new_value}) + {:ok, %Order{}} + + iex> update_order(order, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_order(%Order{} = order, attrs) do + order + |> Order.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a order. + + ## Examples + + iex> delete_order(order) + {:ok, %Order{}} + + iex> delete_order(order) + {:error, %Ecto.Changeset{}} + + """ + def delete_order(%Order{} = order) do + Repo.delete(order) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking order changes. + + ## Examples + + iex> change_order(order) + %Ecto.Changeset{data: %Order{}} + + """ + def change_order(%Order{} = order, attrs \\ %{}) do + Order.changeset(order, attrs) + end + + alias Bright.Orders.LineItem + + @doc """ + Returns the list of order_line_items. + + ## Examples + + iex> list_order_line_items() + [%LineItem{}, ...] + + """ + def list_order_line_items do + Repo.all(LineItem) + end + + @doc """ + Gets a single line_item. + + Raises `Ecto.NoResultsError` if the Line item does not exist. + + ## Examples + + iex> get_line_item!(123) + %LineItem{} + + iex> get_line_item!(456) + ** (Ecto.NoResultsError) + + """ + def get_line_item!(id), do: Repo.get!(LineItem, id) + + @doc """ + Creates a line_item. + + ## Examples + + iex> create_line_item(%{field: value}) + {:ok, %LineItem{}} + + iex> create_line_item(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_line_item(attrs \\ %{}) do + %LineItem{} + |> LineItem.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a line_item. + + ## Examples + + iex> update_line_item(line_item, %{field: new_value}) + {:ok, %LineItem{}} + + iex> update_line_item(line_item, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_line_item(%LineItem{} = line_item, attrs) do + line_item + |> LineItem.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a line_item. + + ## Examples + + iex> delete_line_item(line_item) + {:ok, %LineItem{}} + + iex> delete_line_item(line_item) + {:error, %Ecto.Changeset{}} + + """ + def delete_line_item(%LineItem{} = line_item) do + Repo.delete(line_item) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking line_item changes. + + ## Examples + + iex> change_line_item(line_item) + %Ecto.Changeset{data: %LineItem{}} + + """ + def change_line_item(%LineItem{} = line_item, attrs \\ %{}) do + LineItem.changeset(line_item, attrs) + end + + def complete_order(%ShoppingCart.Cart{} = cart) do + line_items = + Enum.map(cart.items, fn item -> + %{product_id: item.product_id, price: item.product.price, quantity: item.quantity} + end) + + order = + Ecto.Changeset.change(%Order{}, + user_uuid: cart.user_uuid, + total_price: ShoppingCart.total_cart_price(cart), + line_items: line_items + ) + + Ecto.Multi.new() + |> Ecto.Multi.insert(:order, order) + |> Ecto.Multi.run(:prune_cart, fn _repo, _changes -> + ShoppingCart.prune_cart_items(cart) + end) + |> Repo.transaction() + |> case do + {:ok, %{order: order}} -> {:ok, order} + {:error, name, value, _changes_so_far} -> {:error, {name, value}} + end + end +end diff --git a/services/bright/lib/bright/orders/line_item.ex b/services/bright/lib/bright/orders/line_item.ex new file mode 100644 index 0000000..a94009f --- /dev/null +++ b/services/bright/lib/bright/orders/line_item.ex @@ -0,0 +1,21 @@ +defmodule Bright.Orders.LineItem do + use Ecto.Schema + import Ecto.Changeset + + schema "order_line_items" do + field :price, :decimal + field :quantity, :integer + + belongs_to :order, Bright.Orders.Order + belongs_to :product, Bright.Catalog.Product + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(line_item, attrs) do + line_item + |> cast(attrs, [:price, :quantity]) + |> validate_required([:price, :quantity]) + end +end diff --git a/services/bright/lib/bright/orders/order.ex b/services/bright/lib/bright/orders/order.ex new file mode 100644 index 0000000..44c5ba7 --- /dev/null +++ b/services/bright/lib/bright/orders/order.ex @@ -0,0 +1,21 @@ +defmodule Bright.Orders.Order do + use Ecto.Schema + import Ecto.Changeset + + schema "orders" do + field :user_uuid, Ecto.UUID + field :total_price, :decimal + + has_many :line_items, Bright.Orders.LineItem + has_many :products, through: [:line_items, :product] + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(order, attrs) do + order + |> cast(attrs, [:user_uuid, :total_price]) + |> validate_required([:user_uuid, :total_price]) + end +end diff --git a/services/bright/lib/bright/patrons.ex b/services/bright/lib/bright/patrons.ex new file mode 100644 index 0000000..a7f4aeb --- /dev/null +++ b/services/bright/lib/bright/patrons.ex @@ -0,0 +1,104 @@ +defmodule Bright.Patrons do + @moduledoc """ + The Patrons context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Patrons.Patron + + @doc """ + Returns the list of patrons. + + ## Examples + + iex> list_patrons() + [%Patron{}, ...] + + """ + def list_patrons do + Repo.all(Patron) + end + + @doc """ + Gets a single patron. + + Raises `Ecto.NoResultsError` if the Patron does not exist. + + ## Examples + + iex> get_patron!(123) + %Patron{} + + iex> get_patron!(456) + ** (Ecto.NoResultsError) + + """ + def get_patron!(id), do: Repo.get!(Patron, id) + + @doc """ + Creates a patron. + + ## Examples + + iex> create_patron(%{field: value}) + {:ok, %Patron{}} + + iex> create_patron(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_patron(attrs \\ %{}) do + %Patron{} + |> Patron.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a patron. + + ## Examples + + iex> update_patron(patron, %{field: new_value}) + {:ok, %Patron{}} + + iex> update_patron(patron, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_patron(%Patron{} = patron, attrs) do + patron + |> Patron.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a patron. + + ## Examples + + iex> delete_patron(patron) + {:ok, %Patron{}} + + iex> delete_patron(patron) + {:error, %Ecto.Changeset{}} + + """ + def delete_patron(%Patron{} = patron) do + Repo.delete(patron) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking patron changes. + + ## Examples + + iex> change_patron(patron) + %Ecto.Changeset{data: %Patron{}} + + """ + def change_patron(%Patron{} = patron, attrs \\ %{}) do + Patron.changeset(patron, attrs) + end +end diff --git a/services/bright/lib/bright/patrons/patron.ex b/services/bright/lib/bright/patrons/patron.ex new file mode 100644 index 0000000..98a714a --- /dev/null +++ b/services/bright/lib/bright/patrons/patron.ex @@ -0,0 +1,19 @@ +defmodule Bright.Patrons.Patron do + use Ecto.Schema + import Ecto.Changeset + + schema "patrons" do + field :name, :string + field :public, :boolean, default: false + field :lifetime_support_cents, :integer + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(patron, attrs) do + patron + |> cast(attrs, [:name, :lifetime_support_cents, :public]) + |> validate_required([:name, :lifetime_support_cents, :public]) + end +end diff --git a/services/bright/lib/bright/platforms.ex b/services/bright/lib/bright/platforms.ex new file mode 100644 index 0000000..0b99102 --- /dev/null +++ b/services/bright/lib/bright/platforms.ex @@ -0,0 +1,104 @@ +defmodule Bright.Platforms do + @moduledoc """ + The Platforms context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Platforms.Platform + + @doc """ + Returns the list of platforms. + + ## Examples + + iex> list_platforms() + [%Platform{}, ...] + + """ + def list_platforms do + Repo.all(Platform) + end + + @doc """ + Gets a single platform. + + Raises `Ecto.NoResultsError` if the Platform does not exist. + + ## Examples + + iex> get_platform!(123) + %Platform{} + + iex> get_platform!(456) + ** (Ecto.NoResultsError) + + """ + def get_platform!(id), do: Repo.get!(Platform, id) + + @doc """ + Creates a platform. + + ## Examples + + iex> create_platform(%{field: value}) + {:ok, %Platform{}} + + iex> create_platform(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_platform(attrs \\ %{}) do + %Platform{} + |> Platform.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a platform. + + ## Examples + + iex> update_platform(platform, %{field: new_value}) + {:ok, %Platform{}} + + iex> update_platform(platform, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_platform(%Platform{} = platform, attrs) do + platform + |> Platform.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a platform. + + ## Examples + + iex> delete_platform(platform) + {:ok, %Platform{}} + + iex> delete_platform(platform) + {:error, %Ecto.Changeset{}} + + """ + def delete_platform(%Platform{} = platform) do + Repo.delete(platform) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking platform changes. + + ## Examples + + iex> change_platform(platform) + %Ecto.Changeset{data: %Platform{}} + + """ + def change_platform(%Platform{} = platform, attrs \\ %{}) do + Platform.changeset(platform, attrs) + end +end diff --git a/services/bright/lib/bright/platforms/platform.ex b/services/bright/lib/bright/platforms/platform.ex new file mode 100644 index 0000000..dcc915f --- /dev/null +++ b/services/bright/lib/bright/platforms/platform.ex @@ -0,0 +1,30 @@ +defmodule Bright.Platforms.Platform do + use Ecto.Schema + import Ecto.Changeset + + schema "platforms" do + field :name, :string + field :url, :string + field :icon, :string + + many_to_many :streams, Bright.Streams.Stream, join_through: "streams_platforms" + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(platform, attrs) do + platform + |> cast(attrs, [:name, :url, :icon]) + |> validate_required([:name, :url, :icon]) + end +end + + + +defimpl Phoenix.HTML.Safe, for: Bright.Platforms.Platform do + def to_iodata(platform) do + # platform.icon + platform.name + end +end diff --git a/services/bright/lib/bright/release.ex b/services/bright/lib/bright/release.ex new file mode 100644 index 0000000..f27fcaa --- /dev/null +++ b/services/bright/lib/bright/release.ex @@ -0,0 +1,28 @@ +defmodule Bright.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :bright + + 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/services/bright/lib/bright/repo.ex b/services/bright/lib/bright/repo.ex new file mode 100644 index 0000000..54b41a2 --- /dev/null +++ b/services/bright/lib/bright/repo.ex @@ -0,0 +1,5 @@ +defmodule Bright.Repo do + use Ecto.Repo, + otp_app: :bright, + adapter: Ecto.Adapters.Postgres +end diff --git a/services/bright/lib/bright/shopping_cart.ex b/services/bright/lib/bright/shopping_cart.ex new file mode 100644 index 0000000..d2ecdb1 --- /dev/null +++ b/services/bright/lib/bright/shopping_cart.ex @@ -0,0 +1,272 @@ +defmodule Bright.ShoppingCart do + @moduledoc """ + The ShoppingCart context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + alias Bright.Catalog + alias Bright.ShoppingCart.{Cart, CartItem} + + @doc """ + Returns the list of carts. + + ## Examples + + iex> list_carts() + [%Cart{}, ...] + + """ + def list_carts do + Repo.all(Cart) + end + + @doc """ + Gets a single cart. + + Raises `Ecto.NoResultsError` if the Cart does not exist. + + ## Examples + + iex> get_cart!(123) + %Cart{} + + iex> get_cart!(456) + ** (Ecto.NoResultsError) + + """ + def get_cart!(id), do: Repo.get!(Cart, id) + + @doc """ + Creates a cart. + + ## Examples + + iex> create_cart(%{field: value}) + {:ok, %Cart{}} + + iex> create_cart(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_cart(user_uuid) do + %Cart{user_uuid: user_uuid} + |> Cart.changeset(%{}) + |> Repo.insert() + |> case do + {:ok, cart} -> {:ok, reload_cart(cart)} + {:error, changeset} -> {:error, changeset} + end + end + + def reload_cart(%Cart{} = cart), do: get_cart_by_user_uuid(cart.user_uuid) + + def add_item_to_cart(%Cart{} = cart, product_id) do + product = Catalog.get_product!(product_id) + %CartItem{quantity: 1, price_when_carted: product.price} + |> CartItem.changeset(%{}) + |> Ecto.Changeset.put_assoc(:cart, cart) + |> Ecto.Changeset.put_assoc(:product, product) + |> Repo.insert( + on_conflict: [inc: [quantity: 1]], + conflict_target: [:cart_id, :product_id] + ) + end + + def remove_item_from_cart(%Cart{} = cart, product_id) do + {1, _} = + Repo.delete_all( + from(i in CartItem, + where: i.cart_id == ^cart.id, + where: i.product_id == ^product_id + ) + ) + + {:ok, reload_cart(cart)} + end + + @doc """ + Updates a cart. + + ## Examples + + iex> update_cart(cart, %{field: new_value}) + {:ok, %Cart{}} + + iex> update_cart(cart, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_cart(%Cart{} = cart, attrs) do + changeset = + cart + |> Cart.changeset(attrs) + |> Ecto.Changeset.cast_assoc(:items, with: &CartItem.changeset/2) + + Ecto.Multi.new() + |> Ecto.Multi.update(:cart, changeset) + |> Ecto.Multi.delete_all(:discarded_items, fn %{cart: cart} -> + from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0) + end) + |> Repo.transaction() + |> case do + {:ok, %{cart: cart}} -> {:ok, cart} + {:error, :cart, changeset, _changes_so_far} -> {:error, changeset} + end + end + + @doc """ + Deletes a cart. + + ## Examples + + iex> delete_cart(cart) + {:ok, %Cart{}} + + iex> delete_cart(cart) + {:error, %Ecto.Changeset{}} + + """ + def delete_cart(%Cart{} = cart) do + Repo.delete(cart) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking cart changes. + + ## Examples + + iex> change_cart(cart) + %Ecto.Changeset{data: %Cart{}} + + """ + def change_cart(%Cart{} = cart, attrs \\ %{}) do + Cart.changeset(cart, attrs) + end + + alias Bright.ShoppingCart.CartItem + + @doc """ + Returns the list of cart_items. + + ## Examples + + iex> list_cart_items() + [%CartItem{}, ...] + + """ + def list_cart_items do + Repo.all(CartItem) + end + + @doc """ + Gets a single cart_item. + + Raises `Ecto.NoResultsError` if the Cart item does not exist. + + ## Examples + + iex> get_cart_item!(123) + %CartItem{} + + iex> get_cart_item!(456) + ** (Ecto.NoResultsError) + + """ + def get_cart_item!(id), do: Repo.get!(CartItem, id) + + @doc """ + Creates a cart_item. + + ## Examples + + iex> create_cart_item(%{field: value}) + {:ok, %CartItem{}} + + iex> create_cart_item(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_cart_item(attrs \\ %{}) do + %CartItem{} + |> CartItem.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a cart_item. + + ## Examples + + iex> update_cart_item(cart_item, %{field: new_value}) + {:ok, %CartItem{}} + + iex> update_cart_item(cart_item, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_cart_item(%CartItem{} = cart_item, attrs) do + cart_item + |> CartItem.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a cart_item. + + ## Examples + + iex> delete_cart_item(cart_item) + {:ok, %CartItem{}} + + iex> delete_cart_item(cart_item) + {:error, %Ecto.Changeset{}} + + """ + def delete_cart_item(%CartItem{} = cart_item) do + Repo.delete(cart_item) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking cart_item changes. + + ## Examples + + iex> change_cart_item(cart_item) + %Ecto.Changeset{data: %CartItem{}} + + """ + def change_cart_item(%CartItem{} = cart_item, attrs \\ %{}) do + CartItem.changeset(cart_item, attrs) + end + + def get_cart_by_user_uuid(user_uuid) do + Repo.one( + from(c in Cart, + where: c.user_uuid == ^user_uuid, + left_join: i in assoc(c, :items), + left_join: p in assoc(i, :product), + order_by: [asc: i.inserted_at], + preload: [items: {i, product: p}] + ) + ) + end + + def total_item_price(%CartItem{} = item) do + Decimal.mult(item.product.price, item.quantity) + end + + def total_cart_price(%Cart{} = cart) do + Enum.reduce(cart.items, 0, fn item, acc -> + item + |> total_item_price() + |> Decimal.add(acc) + end) + end + + def prune_cart_items(%Cart{} = cart) do + {_, _} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id)) + {:ok, reload_cart(cart)} + end + +end diff --git a/services/bright/lib/bright/shopping_cart/cart.ex b/services/bright/lib/bright/shopping_cart/cart.ex new file mode 100644 index 0000000..61bb811 --- /dev/null +++ b/services/bright/lib/bright/shopping_cart/cart.ex @@ -0,0 +1,19 @@ +defmodule Bright.ShoppingCart.Cart do + use Ecto.Schema + import Ecto.Changeset + + schema "carts" do + field :user_uuid, Ecto.UUID + has_many :items, Bright.ShoppingCart.CartItem + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(cart, attrs) do + cart + |> cast(attrs, [:user_uuid]) + |> validate_required([:user_uuid]) + |> unique_constraint(:user_uuid) + end +end diff --git a/services/bright/lib/bright/shopping_cart/cart_item.ex b/services/bright/lib/bright/shopping_cart/cart_item.ex new file mode 100644 index 0000000..2e49d54 --- /dev/null +++ b/services/bright/lib/bright/shopping_cart/cart_item.ex @@ -0,0 +1,21 @@ +defmodule Bright.ShoppingCart.CartItem do + use Ecto.Schema + import Ecto.Changeset + + schema "cart_items" do + field :price_when_carted, :decimal + field :quantity, :integer + belongs_to :cart, Bright.ShoppingCart.Cart + belongs_to :product, Bright.Catalog.Product + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(cart_item, attrs) do + cart_item + |> cast(attrs, [:price_when_carted, :quantity]) + |> validate_required([:price_when_carted, :quantity]) + |> validate_number(:quantity, greater_than_or_equal_to: 0, less_than: 100) + end +end diff --git a/services/bright/lib/bright/streams.ex b/services/bright/lib/bright/streams.ex new file mode 100644 index 0000000..b38503b --- /dev/null +++ b/services/bright/lib/bright/streams.ex @@ -0,0 +1,247 @@ +defmodule Bright.Streams do + @moduledoc """ + The Streams context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Streams.{Stream,Vod} + alias Bright.Vtubers.Vtuber + alias Bright.Tags.Tag + alias Bright.Platforms.Platform + + @doc """ + Returns the list of streams. + + ## Examples + + iex> list_streams() + [%Stream{}, ...] + + """ + def list_streams do + Stream + |> Repo.all() + |> Repo.preload([:tags, :vods, :vtubers, :platforms]) + end + + @doc """ + Gets a single stream. + + Raises `Ecto.NoResultsError` if the Stream does not exist. + + ## Examples + + iex> get_stream!(123) + %Stream{} + + iex> get_stream!(456) + ** (Ecto.NoResultsError) + + """ + def get_stream!(id) do + Stream + |> Repo.get!(id) + |> Repo.preload([:tags, :vods, :vtubers, :platforms]) + end + + @doc """ + Creates a stream. + + ## Examples + + iex> create_stream(%{field: value}) + {:ok, %Stream{}} + + iex> create_stream(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_stream(attrs \\ %{}) do + %Stream{} + # |> Stream.changeset(attrs) + |> change_stream(attrs) + |> Repo.insert() + end + + @doc """ + Updates a stream. + + ## Examples + + iex> update_stream(stream, %{field: new_value}) + {:ok, %Stream{}} + + iex> update_stream(stream, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_stream(%Stream{} = stream, attrs) do + stream + |> change_stream(attrs) + |> Repo.update() + end + + @doc """ + Deletes a stream. + + ## Examples + + iex> delete_stream(stream) + {:ok, %Stream{}} + + iex> delete_stream(stream) + {:error, %Ecto.Changeset{}} + + """ + def delete_stream(%Stream{} = stream) do + Repo.delete(stream) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking stream changes. + + ## Examples + + iex> change_stream(stream) + %Ecto.Changeset{data: %Stream{}} + + """ + def change_stream(%Stream{} = stream, attrs \\ %{}) do + tags = list_tags_by_id(attrs["tag_ids"]) + vods = list_vods_by_id(attrs["vod_ids"]) + vtubers = list_vtubers_by_id(attrs["vtuber_ids"]) + platforms = list_platforms_by_id(attrs["platform_ids"]) + stream + |> Repo.preload([:tags, :vods, :vtubers]) + |> Stream.changeset(attrs) + |> Ecto.Changeset.put_assoc(:tags, tags) + |> Ecto.Changeset.put_assoc(:vods, vods) + |> Ecto.Changeset.put_assoc(:vtubers, vtubers) + |> Ecto.Changeset.put_assoc(:platforms, platforms) + end + + def inc_page_views(%Stream{} = stream) do + {1, [%Stream{views: views}]} = + from(s in Stream, where: s.id == ^stream.id, select: [:views]) + |> Repo.update_all(inc: [views: 1]) + put_in(stream.views, views) + end + + def list_tags_by_id(nil), do: [] + def list_tags_by_id(tag_ids) do + Repo.all(from t in Tag, where: t.id in ^tag_ids) + end + + def list_vods_by_id(nil), do: [] + def list_vods_by_id(vod_ids) do + Repo.all(from v in Vod, where: v.id in ^vod_ids) + end + + def list_vtubers_by_id(nil), do: [] + def list_vtubers_by_id(vtuber_ids) do + Repo.all(from v in Vtuber, where: v.id in ^vtuber_ids) + end + + def list_platforms_by_id(nil), do: [] + def list_platforms_by_id(platform_ids) do + Repo.all(from p in Platform, where: p.id in ^platform_ids) + end + + alias Bright.Streams.Vod + + @doc """ + Returns the list of vods. + + ## Examples + + iex> list_vods() + [%Vod{}, ...] + + """ + def list_vods do + Repo.all(Vod) + end + + @doc """ + Gets a single vod. + + Raises `Ecto.NoResultsError` if the Vod does not exist. + + ## Examples + + iex> get_vod!(123) + %Vod{} + + iex> get_vod!(456) + ** (Ecto.NoResultsError) + + """ + def get_vod!(id), do: Repo.get!(Vod, id) + + @doc """ + Creates a vod. + + ## Examples + + iex> create_vod(%{field: value}) + {:ok, %Vod{}} + + iex> create_vod(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_vod(attrs \\ %{}) do + %Vod{} + |> Vod.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a vod. + + ## Examples + + iex> update_vod(vod, %{field: new_value}) + {:ok, %Vod{}} + + iex> update_vod(vod, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_vod(%Vod{} = vod, attrs) do + vod + |> Vod.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a vod. + + ## Examples + + iex> delete_vod(vod) + {:ok, %Vod{}} + + iex> delete_vod(vod) + {:error, %Ecto.Changeset{}} + + """ + def delete_vod(%Vod{} = vod) do + Repo.delete(vod) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking vod changes. + + ## Examples + + iex> change_vod(vod) + %Ecto.Changeset{data: %Vod{}} + + """ + def change_vod(%Vod{} = vod, attrs \\ %{}) do + Vod.changeset(vod, attrs) + end +end diff --git a/services/bright/lib/bright/streams/stream.ex b/services/bright/lib/bright/streams/stream.ex new file mode 100644 index 0000000..5395456 --- /dev/null +++ b/services/bright/lib/bright/streams/stream.ex @@ -0,0 +1,30 @@ +defmodule Bright.Streams.Stream do + use Ecto.Schema + import Ecto.Changeset + alias Bright.Tags.Tag + alias Bright.Vtubers.Vtuber + alias Bright.Platforms.Platform + + schema "streams" do + field :date, :utc_datetime + field :title, :string + field :notes, :string + field :views, :integer + + + many_to_many :tags, Tag, join_through: "streams_tags", on_replace: :delete + many_to_many :vtubers, Vtuber, join_through: "streams_vtubers", on_replace: :delete + many_to_many :platforms, Platform, join_through: "streams_platforms", on_replace: :delete + + has_many :vods, Bright.Streams.Vod, on_replace: :delete + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(stream, attrs) do + stream + |> cast(attrs, [:title, :notes, :date]) + |> validate_required([:title, :date]) + end +end diff --git a/services/bright/lib/bright/streams/vod.ex b/services/bright/lib/bright/streams/vod.ex new file mode 100644 index 0000000..9222f66 --- /dev/null +++ b/services/bright/lib/bright/streams/vod.ex @@ -0,0 +1,29 @@ +defmodule Bright.Streams.Vod do + use Ecto.Schema + import Ecto.Changeset + + schema "vods" do + field :s3_cdn_url, :string + field :s3_upload_id, :string + field :s3_key, :string + field :s3_bucket, :string + field :mux_asset_id, :string + field :mux_playback_id, :string + field :ipfs_cid, :string + field :torrent, :string + field :notes, :string + + belongs_to :stream, Bright.Streams.Stream + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(vod, attrs) do + vod + |> cast(attrs, [:s3_cdn_url, :s3_upload_id, :s3_key, :s3_bucket, :mux_asset_id, :mux_playback_id, :ipfs_cid, :torrent, :stream_id]) + |> validate_required([:stream_id]) + end + + +end diff --git a/services/bright/lib/bright/tags.ex b/services/bright/lib/bright/tags.ex new file mode 100644 index 0000000..de364a4 --- /dev/null +++ b/services/bright/lib/bright/tags.ex @@ -0,0 +1,104 @@ +defmodule Bright.Tags do + @moduledoc """ + The Tags context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Tags.Tag + + @doc """ + Returns the list of tags. + + ## Examples + + iex> list_tags() + [%Tag{}, ...] + + """ + def list_tags do + Repo.all(Tag) + end + + @doc """ + Gets a single tag. + + Raises `Ecto.NoResultsError` if the Tag does not exist. + + ## Examples + + iex> get_tag!(123) + %Tag{} + + iex> get_tag!(456) + ** (Ecto.NoResultsError) + + """ + def get_tag!(id), do: Repo.get!(Tag, id) + + @doc """ + Creates a tag. + + ## Examples + + iex> create_tag(%{field: value}) + {:ok, %Tag{}} + + iex> create_tag(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_tag(attrs \\ %{}) do + %Tag{} + |> Tag.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a tag. + + ## Examples + + iex> update_tag(tag, %{field: new_value}) + {:ok, %Tag{}} + + iex> update_tag(tag, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_tag(%Tag{} = tag, attrs) do + tag + |> Tag.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a tag. + + ## Examples + + iex> delete_tag(tag) + {:ok, %Tag{}} + + iex> delete_tag(tag) + {:error, %Ecto.Changeset{}} + + """ + def delete_tag(%Tag{} = tag) do + Repo.delete(tag) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking tag changes. + + ## Examples + + iex> change_tag(tag) + %Ecto.Changeset{data: %Tag{}} + + """ + def change_tag(%Tag{} = tag, attrs \\ %{}) do + Tag.changeset(tag, attrs) + end +end diff --git a/services/bright/lib/bright/tags/stream_tag.ex b/services/bright/lib/bright/tags/stream_tag.ex new file mode 100644 index 0000000..a047f34 --- /dev/null +++ b/services/bright/lib/bright/tags/stream_tag.ex @@ -0,0 +1,19 @@ +defmodule Bright.Tags.StreamTag do + use Ecto.Schema + import Ecto.Changeset + + schema "streams_tags" do + + field :stream_id, :id + field :tag_id, :id + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(stream_tag, attrs) do + stream_tag + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/services/bright/lib/bright/tags/tag.ex b/services/bright/lib/bright/tags/tag.ex new file mode 100644 index 0000000..a06429d --- /dev/null +++ b/services/bright/lib/bright/tags/tag.ex @@ -0,0 +1,18 @@ +defmodule Bright.Tags.Tag do + use Ecto.Schema + import Ecto.Changeset + + schema "tags" do + field :name, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(tag, attrs) do + tag + |> cast(attrs, [:name]) + |> validate_required([:name]) + |> unique_constraint(:name) + end +end diff --git a/services/bright/lib/bright/urls.ex b/services/bright/lib/bright/urls.ex new file mode 100644 index 0000000..eb68b53 --- /dev/null +++ b/services/bright/lib/bright/urls.ex @@ -0,0 +1,104 @@ +defmodule Bright.Urls do + @moduledoc """ + The Urls context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Urls.Url + + @doc """ + Returns the list of urls. + + ## Examples + + iex> list_urls() + [%Url{}, ...] + + """ + def list_urls do + Repo.all(Url) + end + + @doc """ + Gets a single url. + + Raises `Ecto.NoResultsError` if the Url does not exist. + + ## Examples + + iex> get_url!(123) + %Url{} + + iex> get_url!(456) + ** (Ecto.NoResultsError) + + """ + def get_url!(id), do: Repo.get!(Url, id) + + @doc """ + Creates a url. + + ## Examples + + iex> create_url(%{field: value}) + {:ok, %Url{}} + + iex> create_url(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_url(attrs \\ %{}) do + %Url{} + |> Url.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a url. + + ## Examples + + iex> update_url(url, %{field: new_value}) + {:ok, %Url{}} + + iex> update_url(url, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_url(%Url{} = url, attrs) do + url + |> Url.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a url. + + ## Examples + + iex> delete_url(url) + {:ok, %Url{}} + + iex> delete_url(url) + {:error, %Ecto.Changeset{}} + + """ + def delete_url(%Url{} = url) do + Repo.delete(url) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking url changes. + + ## Examples + + iex> change_url(url) + %Ecto.Changeset{data: %Url{}} + + """ + def change_url(%Url{} = url, attrs \\ %{}) do + Url.changeset(url, attrs) + end +end diff --git a/services/bright/lib/bright/urls/url.ex b/services/bright/lib/bright/urls/url.ex new file mode 100644 index 0000000..22b3e14 --- /dev/null +++ b/services/bright/lib/bright/urls/url.ex @@ -0,0 +1,18 @@ +defmodule Bright.Urls.Url do + use Ecto.Schema + import Ecto.Changeset + + schema "urls" do + field :link, :string + field :title, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(url, attrs) do + url + |> cast(attrs, [:link, :title]) + |> validate_required([:link, :title]) + end +end diff --git a/services/bright/lib/bright/user.ex b/services/bright/lib/bright/user.ex new file mode 100644 index 0000000..15d1e9e --- /dev/null +++ b/services/bright/lib/bright/user.ex @@ -0,0 +1,20 @@ +defmodule Bright.User do + use Ecto.Schema + import Ecto.Changeset + + schema "users" do + field :name, :string + field :email, :string + field :bio, :string + field :number_of_pets, :integer + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(user, attrs) do + user + |> cast(attrs, [:name, :email, :bio, :number_of_pets]) + |> validate_required([:name, :email, :bio, :number_of_pets]) + end +end diff --git a/services/bright/lib/bright/vtubers.ex b/services/bright/lib/bright/vtubers.ex new file mode 100644 index 0000000..2202b8f --- /dev/null +++ b/services/bright/lib/bright/vtubers.ex @@ -0,0 +1,105 @@ +defmodule Bright.Vtubers do + @moduledoc """ + The Vtubers context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Vtubers.Vtuber + + @doc """ + Returns the list of vtubers. + + ## Examples + + iex> list_vtubers() + [%Vtuber{}, ...] + + """ + def list_vtubers do + Repo.all(Vtuber) + end + + @doc """ + Gets a single vtuber. + + Raises `Ecto.NoResultsError` if the Vtuber does not exist. + + ## Examples + + iex> get_vtuber!(123) + %Vtuber{} + + iex> get_vtuber!(456) + ** (Ecto.NoResultsError) + + """ + def get_vtuber!(id), do: Repo.get!(Vtuber, id) + + @doc """ + Creates a vtuber. + + ## Examples + + iex> create_vtuber(%{field: value}) + {:ok, %Vtuber{}} + + iex> create_vtuber(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_vtuber(attrs \\ %{}) do + %Vtuber{} + |> Vtuber.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a vtuber. + + ## Examples + + iex> update_vtuber(vtuber, %{field: new_value}) + {:ok, %Vtuber{}} + + iex> update_vtuber(vtuber, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_vtuber(%Vtuber{} = vtuber, attrs) do + vtuber + |> Vtuber.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a vtuber. + + ## Examples + + iex> delete_vtuber(vtuber) + {:ok, %Vtuber{}} + + iex> delete_vtuber(vtuber) + {:error, %Ecto.Changeset{}} + + """ + def delete_vtuber(%Vtuber{} = vtuber) do + Repo.delete(vtuber) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking vtuber changes. + + ## Examples + + iex> change_vtuber(vtuber) + %Ecto.Changeset{data: %Vtuber{}} + + """ + def change_vtuber(%Vtuber{} = vtuber, attrs \\ %{}) do + Vtuber.changeset(vtuber, attrs) + end + +end diff --git a/services/bright/lib/bright/vtubers/vtuber.ex b/services/bright/lib/bright/vtubers/vtuber.ex new file mode 100644 index 0000000..644b853 --- /dev/null +++ b/services/bright/lib/bright/vtubers/vtuber.ex @@ -0,0 +1,42 @@ +defmodule Bright.Vtubers.Vtuber do + use Ecto.Schema + import Ecto.Changeset + + schema "vtubers" do + field :image, :string + field :slug, :string + field :display_name, :string + field :chaturbate, :string + field :twitter, :string + field :patreon, :string + field :twitch, :string + field :tiktok, :string + field :onlyfans, :string + field :youtube, :string + field :linktree, :string + field :carrd, :string + field :fansly, :string + field :pornhub, :string + field :discord, :string + field :reddit, :string + field :throne, :string + field :instagram, :string + field :facebook, :string + field :merch, :string + field :description_1, :string + field :description_2, :string + field :theme_color, :string + field :fansly_id, :string + field :chaturbate_id, :string + field :twitter_id, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(vtuber, attrs) do + vtuber + |> cast(attrs, [:slug, :display_name, :chaturbate, :twitter, :patreon, :twitch, :tiktok, :onlyfans, :youtube, :linktree, :carrd, :fansly, :pornhub, :discord, :reddit, :throne, :instagram, :facebook, :merch, :description_1, :description_2, :image, :theme_color, :fansly_id, :chaturbate_id, :twitter_id]) + |> validate_required([:slug, :display_name, :image, :theme_color]) + end +end diff --git a/services/bright/lib/bright_web.ex b/services/bright/lib/bright_web.ex new file mode 100644 index 0000000..f3e83d4 --- /dev/null +++ b/services/bright/lib/bright_web.ex @@ -0,0 +1,114 @@ +defmodule BrightWeb 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 BrightWeb, :controller + use BrightWeb, :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: BrightWeb.Layouts] + + import Plug.Conn + import BrightWeb.Gettext + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {BrightWeb.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 BrightWeb.CoreComponents + import BrightWeb.Gettext + import BrightWeb.NavigationComponents + + # 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: BrightWeb.Endpoint, + router: BrightWeb.Router, + statics: BrightWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/services/bright/lib/bright_web/components/core_components.ex b/services/bright/lib/bright_web/components/core_components.ex new file mode 100644 index 0000000..b047d8a --- /dev/null +++ b/services/bright/lib/bright_web/components/core_components.ex @@ -0,0 +1,686 @@ +defmodule BrightWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as modals, tables, and + forms. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The default components use Tailwind CSS, a utility-first CSS framework. + See the [Tailwind CSS documentation](https://tailwindcss.com) to learn + how to customize them or feel free to swap in another framework altogether. + + Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. + """ + use Phoenix.Component + + alias Phoenix.LiveView.JS + import BrightWeb.Gettext + + @doc """ + Renders a modal. + + ## Examples + + <.modal id="confirm-modal"> + This is a modal. + + + JS commands may be passed to the `:on_cancel` to configure + the closing/cancel event, for example: + + <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> + This is another modal. + + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + slot :inner_block, required: true + + def modal(assigns) do + ~H""" + + """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ <.label for={@id}>{@label} +
+
+ + <.error :for={msg <- @errors}><%= msg %> +
+
+
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ <.label for={@id}><%= @label %> + + <.error :for={msg <- @errors}><%= msg %> +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ <.label for={@id}><%= @label %> + + <.error :for={msg <- @errors}><%= msg %> +
+ """ + end + + @doc """ + Renders a label. + """ + attr :for, :string, default: nil + slot :inner_block, required: true + + def label(assigns) do + ~H""" + + """ + end + + @doc """ + Generates a generic error message. + """ + slot :inner_block, required: true + + def error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> + <%= render_slot(@inner_block) %> +

+ """ + end + + @doc """ + Renders a header with title. + """ + attr :class, :string, default: nil + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ <%= render_slot(@inner_block) %> +

+

+ <%= render_slot(@subtitle) %> +

+
+
+ <%= render_slot(@actions) %> +
+
+ """ + end + + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id"><%= user.id %> + <:col :let={user} label="username"><%= user.username %> + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" +
+ + + + + + + + + + + + + +
<%= col[:label] %> + <%= gettext("Actions") %> +
+
+ + + <%= render_slot(col, @row_item.(row)) %> + +
+
+
+ + + <%= render_slot(action, @row_item.(row)) %> + +
+
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title"><%= @post.title %> + <:item title="Views"><%= @post.views %> + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
+
+
+
<%= item.title %>
+
<%= render_slot(item) %>
+
+
+
+ """ + end + + + @doc """ + Renders a back navigation link. + + ## Examples + + <.back navigate={~p"/posts"}>Back to posts + """ + attr :navigate, :any, required: true + slot :inner_block, required: true + + def back(assigns) do + ~H""" +
+ <.link + navigate={@navigate} + class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" + > + <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> + <%= render_slot(@inner_block) %> + +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in your `assets/tailwind.config.js`. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all transform ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all transform ease-in duration-200", + "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + def show_modal(js \\ %JS{}, id) when is_binary(id) do + js + |> JS.show(to: "##{id}") + |> JS.show( + to: "##{id}-bg", + time: 300, + transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} + ) + |> show("##{id}-container") + |> JS.add_class("overflow-hidden", to: "body") + |> JS.focus_first(to: "##{id}-content") + end + + def hide_modal(js \\ %JS{}, id) do + js + |> JS.hide( + to: "##{id}-bg", + transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} + ) + |> hide("##{id}-container") + |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) + |> JS.remove_class("overflow-hidden", to: "body") + |> JS.pop_focus() + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(BrightWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(BrightWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/services/bright/lib/bright_web/components/layouts.ex b/services/bright/lib/bright_web/components/layouts.ex new file mode 100644 index 0000000..f1083ec --- /dev/null +++ b/services/bright/lib/bright_web/components/layouts.ex @@ -0,0 +1,14 @@ +defmodule BrightWeb.Layouts do + @moduledoc """ + This module holds different layouts used by your application. + + See the `layouts` directory for all templates available. + The "root" layout is a skeleton rendered as part of the + application router. The "app" layout is set as the default + layout on both `use BrightWeb, :controller` and + `use BrightWeb, :live_view`. + """ + use BrightWeb, :html + + embed_templates "layouts/*" +end diff --git a/services/bright/lib/bright_web/components/layouts/app.html.heex b/services/bright/lib/bright_web/components/layouts/app.html.heex new file mode 100644 index 0000000..fdfd902 --- /dev/null +++ b/services/bright/lib/bright_web/components/layouts/app.html.heex @@ -0,0 +1,6 @@ +<.navbar> + +
+ <.flash_group flash={@flash} /> + <%= @inner_content %> +
diff --git a/services/bright/lib/bright_web/components/layouts/root.html.heex b/services/bright/lib/bright_web/components/layouts/root.html.heex new file mode 100644 index 0000000..26f6807 --- /dev/null +++ b/services/bright/lib/bright_web/components/layouts/root.html.heex @@ -0,0 +1,17 @@ + + + + + + + <.live_title suffix=" · Phoenix Framework"> + <%= assigns[:page_title] || "Bright" %> + + + + + + <%= @inner_content %> + + diff --git a/services/bright/lib/bright_web/components/navigation_components.ex b/services/bright/lib/bright_web/components/navigation_components.ex new file mode 100644 index 0000000..8a72aca --- /dev/null +++ b/services/bright/lib/bright_web/components/navigation_components.ex @@ -0,0 +1,114 @@ +defmodule BrightWeb.NavigationComponents do + + @moduledoc """ + Components for user navigation + """ + + use Phoenix.Component + use Phoenix.VerifiedRoutes, + endpoint: BrightWeb.Endpoint, + router: BrightWeb.Router, + statics: BrightWeb.static_paths() + + alias Phoenix.LiveView.JS + + + @doc """ + Renders a Bulma navbar component. + + ## Examples + + <.navbar brand="MyApp"> + <:start> + Home + About + + <:end> + Login + Sign Up + + + """ + attr :rest, :global, doc: "any additional attributes for the navbar element" + + slot :start_slot, doc: "slot for navbar items aligned to the start" + slot :end_slot, doc: "slot for navbar items aligned to the end" + + def navbar(assigns) do + ~H""" + + """ + end + +end diff --git a/services/bright/lib/bright_web/controllers/cart_controller.ex b/services/bright/lib/bright_web/controllers/cart_controller.ex new file mode 100644 index 0000000..9f52a2d --- /dev/null +++ b/services/bright/lib/bright_web/controllers/cart_controller.ex @@ -0,0 +1,32 @@ +defmodule BrightWeb.CartController do + use BrightWeb, :controller + alias Bright.ShoppingCart + def show(conn, _params) do + render(conn, :show, changeset: ShoppingCart.change_cart(conn.assigns.cart)) + # render(conn, :show) + end + def update(conn, %{"cart" => cart_params}) do + case ShoppingCart.update_cart(conn.assigns.cart, cart_params) do + {:ok, _cart} -> + redirect(conn, to: ~p"/cart") + + {:error, _changeset} -> + conn + |> put_flash(:error, "There was an error updating your cart") + |> redirect(to: ~p"/cart") + end + end +end +# def show(conn, %{"id" => id}) do +# product = Catalog.get_product!(id) +# render(conn, :show, product: product) +# end + +# def show(conn, %{"id" => id}) do +# stream = +# id +# |> Streams.get_stream!() +# |> Streams.inc_page_views() + +# render(conn, :show, stream: stream) +# end diff --git a/services/bright/lib/bright_web/controllers/cart_html.ex b/services/bright/lib/bright_web/controllers/cart_html.ex new file mode 100644 index 0000000..c4e25d5 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/cart_html.ex @@ -0,0 +1,10 @@ +defmodule BrightWeb.CartHTML do + use BrightWeb, :html + + # this alias is for the html.heex templates + alias Bright.ShoppingCart + + embed_templates "cart_html/*" + + def currency_to_str(%Decimal{} = val), do: "$#{Decimal.round(val, 2)}" +end diff --git a/services/bright/lib/bright_web/controllers/cart_html/show.html.heex b/services/bright/lib/bright_web/controllers/cart_html/show.html.heex new file mode 100644 index 0000000..46e9ccf --- /dev/null +++ b/services/bright/lib/bright_web/controllers/cart_html/show.html.heex @@ -0,0 +1,26 @@ + + +<.header> + My Cart + <:subtitle :if={@cart.items == []}>Your cart is empty + <:actions> + <.link href={~p"/orders"} method="post"> + <.button>Complete order + + + + +
+ <.simple_form :let={f} for={@changeset} action={~p"/cart"}> + <.inputs_for :let={%{data: item} = item_form} field={f[:items]}> + <.input field={item_form[:quantity]} type="number" label={item.product.title} /> + {currency_to_str(ShoppingCart.total_item_price(item))} + + <:actions> + <.button>Update cart + + + Total: {currency_to_str(ShoppingCart.total_cart_price(@cart))} +
+ +<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/cart_item_controller.ex b/services/bright/lib/bright_web/controllers/cart_item_controller.ex new file mode 100644 index 0000000..3372521 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/cart_item_controller.ex @@ -0,0 +1,22 @@ +defmodule BrightWeb.CartItemController do + use BrightWeb, :controller + alias Bright.ShoppingCart + + def create(conn, %{"product_id" => product_id}) do + case ShoppingCart.add_item_to_cart(conn.assigns.cart, product_id) do + {:ok, _item} -> + conn + |> put_flash(:info, "Item added to your cart") + |> redirect(to: ~p"/cart") + {:error, _changeset} -> + conn + |> put_flash(:eerror, "There was an error adding the item to your cart") + |> redirect(to: ~p"/cart") + end + end + + def delete(conn, %{"id" => product_id}) do + {:ok, _cart} = ShoppingCart.remove_item_from_cart(conn.assigns.cart, product_id) + redirect(conn, to: ~p"/cart") + end +end diff --git a/services/bright/lib/bright_web/controllers/changeset_json.ex b/services/bright/lib/bright_web/controllers/changeset_json.ex new file mode 100644 index 0000000..c2ece57 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/changeset_json.ex @@ -0,0 +1,25 @@ +defmodule BrightWeb.ChangesetJSON do + @doc """ + Renders changeset errors. + """ + def error(%{changeset: changeset}) do + # When encoded, the changeset returns its errors + # as a JSON object. So we just pass it forward. + %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)} + end + + defp translate_error({msg, opts}) do + # You can make use of gettext to translate error messages by + # uncommenting and adjusting the following code: + + # if count = opts[:count] do + # Gettext.dngettext(BrightWeb.Gettext, "errors", msg, msg, count, opts) + # else + # Gettext.dgettext(BrightWeb.Gettext, "errors", msg, opts) + # end + + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) + end) + end +end diff --git a/services/bright/lib/bright_web/controllers/error_html.ex b/services/bright/lib/bright_web/controllers/error_html.ex new file mode 100644 index 0000000..f300433 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/error_html.ex @@ -0,0 +1,24 @@ +defmodule BrightWeb.ErrorHTML do + @moduledoc """ + This module is invoked by your endpoint in case of errors on HTML requests. + + See config/config.exs. + """ + use BrightWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/bright_web/controllers/error_html/404.html.heex + # * lib/bright_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/services/bright/lib/bright_web/controllers/error_json.ex b/services/bright/lib/bright_web/controllers/error_json.ex new file mode 100644 index 0000000..b17be0b --- /dev/null +++ b/services/bright/lib/bright_web/controllers/error_json.ex @@ -0,0 +1,21 @@ +defmodule BrightWeb.ErrorJSON do + @moduledoc """ + This module is invoked by your endpoint in case of errors on JSON requests. + + See config/config.exs. + """ + + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/services/bright/lib/bright_web/controllers/fallback_controller.ex b/services/bright/lib/bright_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..18a987f --- /dev/null +++ b/services/bright/lib/bright_web/controllers/fallback_controller.ex @@ -0,0 +1,24 @@ +defmodule BrightWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use BrightWeb, :controller + + # This clause handles errors returned by Ecto's insert/update/delete. + def call(conn, {:error, %Ecto.Changeset{} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(json: BrightWeb.ChangesetJSON) + |> render(:error, changeset: changeset) + end + + # This clause is an example of how to handle resources that cannot be found. + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> put_view(html: BrightWeb.ErrorHTML, json: BrightWeb.ErrorJSON) + |> render(:"404") + end +end diff --git a/services/bright/lib/bright_web/controllers/hello_controller.ex b/services/bright/lib/bright_web/controllers/hello_controller.ex new file mode 100644 index 0000000..1d3a3ef --- /dev/null +++ b/services/bright/lib/bright_web/controllers/hello_controller.ex @@ -0,0 +1,17 @@ +defmodule BrightWeb.HelloController do + use BrightWeb, :controller + + # plug :put_view, html: HelloWeb.PageHTML, json: HelloWeb.PageJSON + + def index(conn, _params) do + render(conn, :index) + end + + + def show(conn, %{"messenger" => messenger}) do + conn + |> assign(:messenger, messenger) + |> assign(:receiver, "Dweezil") + |> render(:show) + end +end diff --git a/services/bright/lib/bright_web/controllers/hello_html.ex b/services/bright/lib/bright_web/controllers/hello_html.ex new file mode 100644 index 0000000..40a235d --- /dev/null +++ b/services/bright/lib/bright_web/controllers/hello_html.ex @@ -0,0 +1,13 @@ +defmodule BrightWeb.HelloHTML do + use BrightWeb, :html + + embed_templates "hello_html/*" + + attr :messenger, :string, required: true + + def greet(assigns) do + ~H""" +

Hello World, from {@messenger}!

+ """ + end +end diff --git a/services/bright/lib/bright_web/controllers/hello_html/index.html.heex b/services/bright/lib/bright_web/controllers/hello_html/index.html.heex new file mode 100644 index 0000000..b01c4a2 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/hello_html/index.html.heex @@ -0,0 +1,3 @@ +
+

Hello World, from Phoenix!~

+
\ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/hello_html/show.html.heex b/services/bright/lib/bright_web/controllers/hello_html/show.html.heex new file mode 100644 index 0000000..5cc1584 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/hello_html/show.html.heex @@ -0,0 +1,3 @@ +
+ <.greet messenger={@messenger} /> +
\ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/order_controller.ex b/services/bright/lib/bright_web/controllers/order_controller.ex new file mode 100644 index 0000000..88a8440 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/order_controller.ex @@ -0,0 +1,21 @@ +defmodule BrightWeb.OrderController do + use BrightWeb, :controller + alias Bright.Orders + def create(conn, _) do + case Orders.complete_order(conn.assigns.cart) do + {:ok, order} -> + conn + |> put_flash(:info, "Order created successfully.") + |> redirect(to: ~p"/orders/#{order}") + + {:error, _reason} -> + conn + |> put_flash(:error, "There was an error processing your order") + |> redirect(to: ~p"/cart") + end + end + def show(conn, %{"id" => id}) do + order = Orders.get_order!(conn.assigns.current_uuid, id) + render(conn, :show, order: order) + end +end diff --git a/services/bright/lib/bright_web/controllers/order_html.ex b/services/bright/lib/bright_web/controllers/order_html.ex new file mode 100644 index 0000000..5813054 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/order_html.ex @@ -0,0 +1,4 @@ +defmodule BrightWeb.OrderHTML do + use BrightWeb, :html + embed_templates "order_html/*" +end diff --git a/services/bright/lib/bright_web/controllers/order_html/show.html.heex b/services/bright/lib/bright_web/controllers/order_html/show.html.heex new file mode 100644 index 0000000..df41e43 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/order_html/show.html.heex @@ -0,0 +1,20 @@ +<.header> + Thank you for your order! + <:subtitle> + User uuid: {@order.user_uuid} + + + + +<.table id="items" rows={@order.line_items}> + <:col :let={item} label="Title">{item.product.title} + <:col :let={item} label="Quantity">{item.quantity} + <:col :let={item} label="Price"> + {BrightWeb.CartHTML.currency_to_str(item.price)} + + + +Total price: +{BrightWeb.CartHTML.currency_to_str(@order.total_price)} + +<.back navigate={~p"/products"}>Back to products \ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/page_controller.ex b/services/bright/lib/bright_web/controllers/page_controller.ex new file mode 100644 index 0000000..12a72e3 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_controller.ex @@ -0,0 +1,30 @@ +defmodule BrightWeb.PageController do + use BrightWeb, :controller + + def home(conn, _params) do + # The home page is often custom made, + # so skip the default app layout. + # render(conn, :home, layout: false) + + # send_resp(conn, 201, "") + conn + |> put_flash(:info, "You are beautiful!") + |> put_flash(:error, "Test error!") + |> put_status(202) + |> render(:home, layout: false) + # redirect(conn, to: ~p"/redirect_test") + # redirect(conn, external: "https://elixir-lang.org/") + end + + def about(conn, _params) do + render(conn, :about, layout: false) + end + + def api(conn, _params) do + render(conn, :api, layout: false) + end + + def redirect_test(conn, _params) do + render(conn, :home, layout: false) + end +end diff --git a/services/bright/lib/bright_web/controllers/page_html.ex b/services/bright/lib/bright_web/controllers/page_html.ex new file mode 100644 index 0000000..fb841f2 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_html.ex @@ -0,0 +1,10 @@ +defmodule BrightWeb.PageHTML do + @moduledoc """ + This module contains pages rendered by PageController. + + See the `page_html` directory for all templates available. + """ + use BrightWeb, :html + + embed_templates "page_html/*" +end diff --git a/services/bright/lib/bright_web/controllers/page_html/about.html.heex b/services/bright/lib/bright_web/controllers/page_html/about.html.heex new file mode 100644 index 0000000..7bfe4eb --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_html/about.html.heex @@ -0,0 +1,7 @@ +<.flash_group flash={@flash} /> + +<.navbar /> + +
+

About

+
diff --git a/services/bright/lib/bright_web/controllers/page_html/api.html.heex b/services/bright/lib/bright_web/controllers/page_html/api.html.heex new file mode 100644 index 0000000..cf9a853 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_html/api.html.heex @@ -0,0 +1,16 @@ +<.flash_group flash={@flash} /> + +<.navbar /> + +
+

API

+

For Developers and Power Users

+ + +
+ + + +
+

@todo

+
diff --git a/services/bright/lib/bright_web/controllers/page_html/home.html.heex b/services/bright/lib/bright_web/controllers/page_html/home.html.heex new file mode 100644 index 0000000..9735504 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_html/home.html.heex @@ -0,0 +1,43 @@ +<.navbar /> + +
+ <.flash_group flash={@flash} /> +
+ + + +

+ The Galaxy's Best VTuber Hentai Site +

+

+ For adults only (NSFW) +

+ + +
+

Latest VODs

+ <%= for number <- 1..10 do %> + + <%= number %> + <%= number * number %> + + <% end %> +
+ + + +
+ +
+ + + Hello {@current_uuid} + + products + cart + Archive + + <.back navigate={~p"/posts"}>Back + +
+
diff --git a/services/bright/lib/bright_web/controllers/page_json.ex b/services/bright/lib/bright_web/controllers/page_json.ex new file mode 100644 index 0000000..8e9a9d8 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_json.ex @@ -0,0 +1,5 @@ +defmodule BrightWeb.PageJSON do + def home(_assigns) do + %{message: "this is some JSON"} + end +end diff --git a/services/bright/lib/bright_web/controllers/patron_controller.ex b/services/bright/lib/bright_web/controllers/patron_controller.ex new file mode 100644 index 0000000..62d3f59 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/patron_controller.ex @@ -0,0 +1,10 @@ +defmodule BrightWeb.PatronController do + use BrightWeb, :controller + + alias Bright.Patrons + + def index(conn, _params) do + patrons = Patrons.list_patrons() + render(conn, :index, patrons: patrons) + end +end diff --git a/services/bright/lib/bright_web/controllers/patron_html.ex b/services/bright/lib/bright_web/controllers/patron_html.ex new file mode 100644 index 0000000..70b8318 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/patron_html.ex @@ -0,0 +1,4 @@ +defmodule BrightWeb.PatronHTML do + use BrightWeb, :html + embed_templates "patron_html/*" +end diff --git a/services/bright/lib/bright_web/controllers/patron_html/index.html.heex b/services/bright/lib/bright_web/controllers/patron_html/index.html.heex new file mode 100644 index 0000000..85e724c --- /dev/null +++ b/services/bright/lib/bright_web/controllers/patron_html/index.html.heex @@ -0,0 +1,14 @@ +<.header> + Listing Patrons + + +<.table id="patrons" rows={@patrons}> + <:col :let={patron} label="Name">{patron.name} + + +<%# +<% for patron <- @patrons do +
+ +
+end %> diff --git a/services/bright/lib/bright_web/controllers/platform_controller.ex b/services/bright/lib/bright_web/controllers/platform_controller.ex new file mode 100644 index 0000000..f935748 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_controller.ex @@ -0,0 +1,62 @@ +defmodule BrightWeb.PlatformController do + use BrightWeb, :controller + + alias Bright.Platforms + alias Bright.Platforms.Platform + + def index(conn, _params) do + platforms = Platforms.list_platforms() + render(conn, :index, platforms: platforms) + end + + def new(conn, _params) do + changeset = Platforms.change_platform(%Platform{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"platform" => platform_params}) do + case Platforms.create_platform(platform_params) do + {:ok, platform} -> + conn + |> put_flash(:info, "Platform created successfully.") + |> redirect(to: ~p"/platforms/#{platform}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + platform = Platforms.get_platform!(id) + render(conn, :show, platform: platform) + end + + def edit(conn, %{"id" => id}) do + platform = Platforms.get_platform!(id) + changeset = Platforms.change_platform(platform) + render(conn, :edit, platform: platform, changeset: changeset) + end + + def update(conn, %{"id" => id, "platform" => platform_params}) do + platform = Platforms.get_platform!(id) + + case Platforms.update_platform(platform, platform_params) do + {:ok, platform} -> + conn + |> put_flash(:info, "Platform updated successfully.") + |> redirect(to: ~p"/platforms/#{platform}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, platform: platform, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + platform = Platforms.get_platform!(id) + {:ok, _platform} = Platforms.delete_platform(platform) + + conn + |> put_flash(:info, "Platform deleted successfully.") + |> redirect(to: ~p"/platforms") + end +end diff --git a/services/bright/lib/bright_web/controllers/platform_html.ex b/services/bright/lib/bright_web/controllers/platform_html.ex new file mode 100644 index 0000000..99798bc --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html.ex @@ -0,0 +1,13 @@ +defmodule BrightWeb.PlatformHTML do + use BrightWeb, :html + + embed_templates "platform_html/*" + + @doc """ + Renders a platform form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def platform_form(assigns) +end diff --git a/services/bright/lib/bright_web/controllers/platform_html/edit.html.heex b/services/bright/lib/bright_web/controllers/platform_html/edit.html.heex new file mode 100644 index 0000000..a0efd2e --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Platform {@platform.id} + <:subtitle>Use this form to manage platform records in the database. + + +<.platform_form changeset={@changeset} action={~p"/platforms/#{@platform}"} /> + +<.back navigate={~p"/platforms"}>Back to platforms diff --git a/services/bright/lib/bright_web/controllers/platform_html/index.html.heex b/services/bright/lib/bright_web/controllers/platform_html/index.html.heex new file mode 100644 index 0000000..3d643c4 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html/index.html.heex @@ -0,0 +1,25 @@ +<.header> + Listing Platforms + <:actions> + <.link href={~p"/platforms/new"}> + <.button>New Platform + + + + +<.table id="platforms" rows={@platforms} row_click={&JS.navigate(~p"/platforms/#{&1}")}> + <:col :let={platform} label="Name">{platform.name} + <:col :let={platform} label="Url">{platform.url} + <:col :let={platform} label="Icon">{raw(platform.icon)} + <:action :let={platform}> +
+ <.link navigate={~p"/platforms/#{platform}"}>Show +
+ <.link navigate={~p"/platforms/#{platform}/edit"}>Edit + + <:action :let={platform}> + <.link href={~p"/platforms/#{platform}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/platform_html/new.html.heex b/services/bright/lib/bright_web/controllers/platform_html/new.html.heex new file mode 100644 index 0000000..5d6aaf9 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Platform + <:subtitle>Use this form to manage platform records in the database. + + +<.platform_form changeset={@changeset} action={~p"/platforms"} /> + +<.back navigate={~p"/platforms"}>Back to platforms diff --git a/services/bright/lib/bright_web/controllers/platform_html/platform_form.html.heex b/services/bright/lib/bright_web/controllers/platform_html/platform_form.html.heex new file mode 100644 index 0000000..e3478ff --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html/platform_form.html.heex @@ -0,0 +1,11 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:name]} type="text" label="Name" /> + <.input field={f[:url]} type="text" label="Url" /> + <.input field={f[:icon]} type="text" label="Icon" /> + <:actions> + <.button>Save Platform + + diff --git a/services/bright/lib/bright_web/controllers/platform_html/show.html.heex b/services/bright/lib/bright_web/controllers/platform_html/show.html.heex new file mode 100644 index 0000000..c94efa7 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/platform_html/show.html.heex @@ -0,0 +1,17 @@ +<.header> + Platform {@platform.id} + <:subtitle>This is a platform record from the database. + <:actions> + <.link href={~p"/platforms/#{@platform}/edit"}> + <.button>Edit platform + + + + +<.list> + <:item title="Name">{@platform.name} + <:item title="Url">{@platform.url} + <:item title="Icon">{raw(@platform.icon)} + + +<.back navigate={~p"/platforms"}>Back to platforms diff --git a/services/bright/lib/bright_web/controllers/product_controller.ex b/services/bright/lib/bright_web/controllers/product_controller.ex new file mode 100644 index 0000000..5984005 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_controller.ex @@ -0,0 +1,62 @@ +defmodule BrightWeb.ProductController do + use BrightWeb, :controller + + alias Bright.Catalog + alias Bright.Catalog.Product + + def index(conn, _params) do + products = Catalog.list_products() + render(conn, :index, products: products) + end + + def new(conn, _params) do + changeset = Catalog.change_product(%Product{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"product" => product_params}) do + case Catalog.create_product(product_params) do + {:ok, product} -> + conn + |> put_flash(:info, "Product created successfully.") + |> redirect(to: ~p"/products/#{product}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + product = Catalog.get_product!(id) + render(conn, :show, product: product) + end + + def edit(conn, %{"id" => id}) do + product = Catalog.get_product!(id) + changeset = Catalog.change_product(product) + render(conn, :edit, product: product, changeset: changeset) + end + + def update(conn, %{"id" => id, "product" => product_params}) do + product = Catalog.get_product!(id) + + case Catalog.update_product(product, product_params) do + {:ok, product} -> + conn + |> put_flash(:info, "Product updated successfully.") + |> redirect(to: ~p"/products/#{product}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, product: product, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + product = Catalog.get_product!(id) + {:ok, _product} = Catalog.delete_product(product) + + conn + |> put_flash(:info, "Product deleted successfully.") + |> redirect(to: ~p"/products") + end +end diff --git a/services/bright/lib/bright_web/controllers/product_html.ex b/services/bright/lib/bright_web/controllers/product_html.ex new file mode 100644 index 0000000..1ad749a --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html.ex @@ -0,0 +1,23 @@ +defmodule BrightWeb.ProductHTML do + use BrightWeb, :html + + embed_templates "product_html/*" + + @doc """ + Renders a product form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def product_form(assigns) + + def category_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:categories, []) + |> Enum.map(& &1.data.id) + + for cat <- Bright.Catalog.list_categories(), + do: [key: cat.title, value: cat.id, selected: cat.id in existing_ids] + end +end diff --git a/services/bright/lib/bright_web/controllers/product_html/edit.html.heex b/services/bright/lib/bright_web/controllers/product_html/edit.html.heex new file mode 100644 index 0000000..5179947 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Product {@product.id} + <:subtitle>Use this form to manage product records in the database. + + +<.product_form changeset={@changeset} action={~p"/products/#{@product}"} /> + +<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/product_html/index.html.heex b/services/bright/lib/bright_web/controllers/product_html/index.html.heex new file mode 100644 index 0000000..687b052 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html/index.html.heex @@ -0,0 +1,26 @@ +<.header> + Listing Products + <:actions> + <.link href={~p"/products/new"}> + <.button>New Product + + + + +<.table id="products" rows={@products} row_click={&JS.navigate(~p"/products/#{&1}")}> + <:col :let={product} label="Title">{product.title} + <:col :let={product} label="Description">{product.description} + <:col :let={product} label="Price">{product.price} + <:col :let={product} label="Views">{product.views} + <:action :let={product}> +
+ <.link navigate={~p"/products/#{product}"}>Show +
+ <.link navigate={~p"/products/#{product}/edit"}>Edit + + <:action :let={product}> + <.link href={~p"/products/#{product}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/product_html/new.html.heex b/services/bright/lib/bright_web/controllers/product_html/new.html.heex new file mode 100644 index 0000000..e64df6b --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Product + <:subtitle>Use this form to manage product records in the database. + + +<.product_form changeset={@changeset} action={~p"/products"} /> + +<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex b/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex new file mode 100644 index 0000000..d2dc1ba --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex @@ -0,0 +1,12 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:title]} type="text" label="Title" /> + <.input field={f[:description]} type="text" label="Description" /> + <.input field={f[:price]} type="number" label="Price" step="any" /> + <.input field={f[:category_ids]} type="select" multiple={true} options={category_opts(@changeset)} /> + <:actions> + <.button>Save Product + + diff --git a/services/bright/lib/bright_web/controllers/product_html/show.html.heex b/services/bright/lib/bright_web/controllers/product_html/show.html.heex new file mode 100644 index 0000000..a29e6f7 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/product_html/show.html.heex @@ -0,0 +1,26 @@ +<.header> + Product {@product.id} + <:subtitle>This is a product record from the database. + <:actions> + <.link href={~p"/products/#{@product}/edit"}> + <.button>Edit product + + <.link href={~p"/cart_items?product_id=#{@product.id}"} method="post"> + <.button>Add to cart + + + + +<.list> + <:item title="Title">{@product.title} + <:item title="Description">{@product.description} + <:item title="Price">{@product.price} + <:item title="Views">{@product.views} + <:item title="Categories"> + + + + +<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/stream_controller.ex b/services/bright/lib/bright_web/controllers/stream_controller.ex new file mode 100644 index 0000000..3426a5a --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_controller.ex @@ -0,0 +1,66 @@ +defmodule BrightWeb.StreamController do + use BrightWeb, :controller + + alias Bright.Streams + alias Bright.Streams.Stream + + def index(conn, _params) do + streams = Streams.list_streams() + render(conn, :index, streams: streams) + end + + def new(conn, _params) do + changeset = Streams.change_stream(%Stream{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"stream" => stream_params}) do + case Streams.create_stream(stream_params) do + {:ok, stream} -> + conn + |> put_flash(:info, "Stream created successfully.") + |> redirect(to: ~p"/streams/#{stream}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + stream = + id + |> Streams.get_stream!() + |> Streams.inc_page_views() + + render(conn, :show, stream: stream) + end + + def edit(conn, %{"id" => id}) do + stream = Streams.get_stream!(id) + changeset = Streams.change_stream(stream) + render(conn, :edit, stream: stream, changeset: changeset) + end + + def update(conn, %{"id" => id, "stream" => stream_params}) do + stream = Streams.get_stream!(id) + + case Streams.update_stream(stream, stream_params) do + {:ok, stream} -> + conn + |> put_flash(:info, "Stream updated successfully.") + |> redirect(to: ~p"/streams/#{stream}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, stream: stream, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + stream = Streams.get_stream!(id) + {:ok, _stream} = Streams.delete_stream(stream) + + conn + |> put_flash(:info, "Stream deleted successfully.") + |> redirect(to: ~p"/streams") + end +end diff --git a/services/bright/lib/bright_web/controllers/stream_html.ex b/services/bright/lib/bright_web/controllers/stream_html.ex new file mode 100644 index 0000000..cec2066 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html.ex @@ -0,0 +1,58 @@ +defmodule BrightWeb.StreamHTML do + use BrightWeb, :html + + embed_templates "stream_html/*" + + @doc """ + Renders a stream form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def stream_form(assigns) + + def tag_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:tags, []) + |> Enum.map(& &1.data.id) + + for tag <- Bright.Tags.list_tags(), + do: [key: tag.name, value: tag.id, selected: tag.id in existing_ids] + end + + def vod_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:vods, []) + |> Enum.map(& &1.data.id) + + for vod <- Bright.Streams.list_vods(), + do: [key: vod.id, value: vod.id, selected: vod.id in existing_ids] + end + + + def vtuber_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:vtubers, []) + |> Enum.map(& &1.data.id) + + for vtuber <- Bright.Vtubers.list_vtubers(), + do: [key: vtuber.display_name, value: vtuber.id, selected: vtuber.id in existing_ids] + end + + + def platform_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:vtubers, []) + |> Enum.map(& &1.data.id) + + for platform <- Bright.Platforms.list_platforms(), + do: [key: platform.name, value: platform.id, selected: platform.id in existing_ids] + end + + + +end diff --git a/services/bright/lib/bright_web/controllers/stream_html/edit.html.heex b/services/bright/lib/bright_web/controllers/stream_html/edit.html.heex new file mode 100644 index 0000000..935e120 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Stream {@stream.id} + <:subtitle>Use this form to manage stream records in the database. + + +<.stream_form changeset={@changeset} action={~p"/streams/#{@stream}"} /> + +<.back navigate={~p"/streams"}>Back to streams diff --git a/services/bright/lib/bright_web/controllers/stream_html/index.html.heex b/services/bright/lib/bright_web/controllers/stream_html/index.html.heex new file mode 100644 index 0000000..1637061 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html/index.html.heex @@ -0,0 +1,54 @@ +<.header> + Listing Streams + <:actions> + <.link href={~p"/streams/new"}> + <.button>New Stream + + + + +<.table id="streams" rows={@streams} row_click={&JS.navigate(~p"/streams/#{&1}")}> + <:col :let={stream} label="ID">{stream.id} + <:col :let={stream} label="Title">{stream.title} + <:col :let={stream} label="Date">{stream.date} + <:col :let={stream} label="Platforms"> +
+ <%= for platform <- stream.platforms do %> +
+ {raw(platform.icon)} +
+ <% end %> +
+ + <:col :let={stream} label="Vtubers"> +
+ <%= for vtuber <- stream.vtubers do %> +
+
+ {vtuber.display_name} +
+
+ <% end %> +
+ + <:col :let={stream} label="VODs"> +
+ <%= for vod <- stream.vods do %> +
+ <.link href={~p"/vods/#{vod.id}"}>#{vod.id} +
+ <% end %> +
+ + <:action :let={stream}> +
+ <.link navigate={~p"/streams/#{stream}"}>Show +
+ <.link navigate={~p"/streams/#{stream}/edit"}>Edit + + <:action :let={stream}> + <.link href={~p"/streams/#{stream}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/stream_html/new.html.heex b/services/bright/lib/bright_web/controllers/stream_html/new.html.heex new file mode 100644 index 0000000..8536ef9 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Stream + <:subtitle>Use this form to manage stream records in the database. + + +<.stream_form changeset={@changeset} action={~p"/streams"} /> + +<.back navigate={~p"/streams"}>Back to streams diff --git a/services/bright/lib/bright_web/controllers/stream_html/show.html.heex b/services/bright/lib/bright_web/controllers/stream_html/show.html.heex new file mode 100644 index 0000000..b2e27d3 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html/show.html.heex @@ -0,0 +1,36 @@ +<.header> + Stream {@stream.id} + <:subtitle>This is a stream record from the database. + <:actions> + <.link href={~p"/streams/#{@stream}/edit"}> + <.button>Edit stream + + + + +<.list> + <:item title="Title">{@stream.title} + <:item title="Notes">{@stream.notes} + <:item title="Date">{@stream.date} + + <:item title="Views">{@stream.views} + <:item title="Tags"> + + + <:item title="Vods"> + + + <:item title="Vtubers"> + + + + + + +<.back navigate={~p"/streams"}>Back to streams diff --git a/services/bright/lib/bright_web/controllers/stream_html/stream_form.html.heex b/services/bright/lib/bright_web/controllers/stream_html/stream_form.html.heex new file mode 100644 index 0000000..7719dc3 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/stream_html/stream_form.html.heex @@ -0,0 +1,17 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:title]} type="text" label="Title" /> + <.input field={f[:notes]} type="text" label="Notes" /> + <.input field={f[:date]} type="datetime-local" label="Date" /> + + <.input field={f[:tag_ids]} label="Tags" type="select" multiple={true} options={tag_opts(@changeset)} /> + <.input field={f[:vod_ids]} label="Vods" type="select" multiple={true} options={vod_opts(@changeset)} /> + <.input field={f[:platform_ids]} label="Platforms" type="select" multiple={true} options={platform_opts(@changeset)} /> + <.input field={f[:vtuber_ids]} label="Vtubers" type="select" multiple={true} options={vtuber_opts(@changeset)} /> + + <:actions> + <.button>Save Stream + + diff --git a/services/bright/lib/bright_web/controllers/tag_controller.ex b/services/bright/lib/bright_web/controllers/tag_controller.ex new file mode 100644 index 0000000..bb97f94 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_controller.ex @@ -0,0 +1,62 @@ +defmodule BrightWeb.TagController do + use BrightWeb, :controller + + alias Bright.Tags + alias Bright.Tags.Tag + + def index(conn, _params) do + tags = Tags.list_tags() + render(conn, :index, tags: tags) + end + + def new(conn, _params) do + changeset = Tags.change_tag(%Tag{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"tag" => tag_params}) do + case Tags.create_tag(tag_params) do + {:ok, tag} -> + conn + |> put_flash(:info, "Tag created successfully.") + |> redirect(to: ~p"/tags/#{tag}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + tag = Tags.get_tag!(id) + render(conn, :show, tag: tag) + end + + def edit(conn, %{"id" => id}) do + tag = Tags.get_tag!(id) + changeset = Tags.change_tag(tag) + render(conn, :edit, tag: tag, changeset: changeset) + end + + def update(conn, %{"id" => id, "tag" => tag_params}) do + tag = Tags.get_tag!(id) + + case Tags.update_tag(tag, tag_params) do + {:ok, tag} -> + conn + |> put_flash(:info, "Tag updated successfully.") + |> redirect(to: ~p"/tags/#{tag}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, tag: tag, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + tag = Tags.get_tag!(id) + {:ok, _tag} = Tags.delete_tag(tag) + + conn + |> put_flash(:info, "Tag deleted successfully.") + |> redirect(to: ~p"/tags") + end +end diff --git a/services/bright/lib/bright_web/controllers/tag_html.ex b/services/bright/lib/bright_web/controllers/tag_html.ex new file mode 100644 index 0000000..e57ea85 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html.ex @@ -0,0 +1,13 @@ +defmodule BrightWeb.TagHTML do + use BrightWeb, :html + + embed_templates "tag_html/*" + + @doc """ + Renders a tag form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def tag_form(assigns) +end diff --git a/services/bright/lib/bright_web/controllers/tag_html/edit.html.heex b/services/bright/lib/bright_web/controllers/tag_html/edit.html.heex new file mode 100644 index 0000000..f5aa01d --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Tag {@tag.id} + <:subtitle>Use this form to manage tag records in the database. + + +<.tag_form changeset={@changeset} action={~p"/tags/#{@tag}"} /> + +<.back navigate={~p"/tags"}>Back to tags diff --git a/services/bright/lib/bright_web/controllers/tag_html/index.html.heex b/services/bright/lib/bright_web/controllers/tag_html/index.html.heex new file mode 100644 index 0000000..e250f9b --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html/index.html.heex @@ -0,0 +1,23 @@ +<.header> + Listing Tags + <:actions> + <.link href={~p"/tags/new"}> + <.button>New Tag + + + + +<.table id="tags" rows={@tags} row_click={&JS.navigate(~p"/tags/#{&1}")}> + <:col :let={tag} label="Name">{tag.name} + <:action :let={tag}> +
+ <.link navigate={~p"/tags/#{tag}"}>Show +
+ <.link navigate={~p"/tags/#{tag}/edit"}>Edit + + <:action :let={tag}> + <.link href={~p"/tags/#{tag}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/tag_html/new.html.heex b/services/bright/lib/bright_web/controllers/tag_html/new.html.heex new file mode 100644 index 0000000..78703e6 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Tag + <:subtitle>Use this form to manage tag records in the database. + + +<.tag_form changeset={@changeset} action={~p"/tags"} /> + +<.back navigate={~p"/tags"}>Back to tags diff --git a/services/bright/lib/bright_web/controllers/tag_html/show.html.heex b/services/bright/lib/bright_web/controllers/tag_html/show.html.heex new file mode 100644 index 0000000..1b31d06 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html/show.html.heex @@ -0,0 +1,15 @@ +<.header> + Tag {@tag.id} + <:subtitle>This is a tag record from the database. + <:actions> + <.link href={~p"/tags/#{@tag}/edit"}> + <.button>Edit tag + + + + +<.list> + <:item title="Name">{@tag.name} + + +<.back navigate={~p"/tags"}>Back to tags diff --git a/services/bright/lib/bright_web/controllers/tag_html/tag_form.html.heex b/services/bright/lib/bright_web/controllers/tag_html/tag_form.html.heex new file mode 100644 index 0000000..3839a4b --- /dev/null +++ b/services/bright/lib/bright_web/controllers/tag_html/tag_form.html.heex @@ -0,0 +1,9 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:name]} type="text" label="Name" /> + <:actions> + <.button>Save Tag + + diff --git a/services/bright/lib/bright_web/controllers/url_controller.ex b/services/bright/lib/bright_web/controllers/url_controller.ex new file mode 100644 index 0000000..9e6dd36 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/url_controller.ex @@ -0,0 +1,43 @@ +defmodule BrightWeb.UrlController do + use BrightWeb, :controller + + alias Bright.Urls + alias Bright.Urls.Url + + action_fallback BrightWeb.FallbackController + + def index(conn, _params) do + urls = Urls.list_urls() + render(conn, :index, urls: urls) + end + + def create(conn, %{"url" => url_params}) do + with {:ok, %Url{} = url} <- Urls.create_url(url_params) do + conn + |> put_status(:created) + |> put_resp_header("location", ~p"/api/urls/#{url}") + |> render(:show, url: url) + end + end + + def show(conn, %{"id" => id}) do + url = Urls.get_url!(id) + render(conn, :show, url: url) + end + + def update(conn, %{"id" => id, "url" => url_params}) do + url = Urls.get_url!(id) + + with {:ok, %Url{} = url} <- Urls.update_url(url, url_params) do + render(conn, :show, url: url) + end + end + + def delete(conn, %{"id" => id}) do + url = Urls.get_url!(id) + + with {:ok, %Url{}} <- Urls.delete_url(url) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/services/bright/lib/bright_web/controllers/url_json.ex b/services/bright/lib/bright_web/controllers/url_json.ex new file mode 100644 index 0000000..9b56dc6 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/url_json.ex @@ -0,0 +1,25 @@ +defmodule BrightWeb.UrlJSON do + alias Bright.Urls.Url + + @doc """ + Renders a list of urls. + """ + def index(%{urls: urls}) do + %{data: for(url <- urls, do: data(url))} + end + + @doc """ + Renders a single url. + """ + def show(%{url: url}) do + %{data: data(url)} + end + + defp data(%Url{} = url) do + %{ + id: url.id, + link: url.link, + title: url.title + } + end +end diff --git a/services/bright/lib/bright_web/controllers/vod_controller.ex b/services/bright/lib/bright_web/controllers/vod_controller.ex new file mode 100644 index 0000000..9714258 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_controller.ex @@ -0,0 +1,62 @@ +defmodule BrightWeb.VodController do + use BrightWeb, :controller + + alias Bright.Streams + alias Bright.Streams.Vod + + def index(conn, _params) do + vods = Streams.list_vods() + render(conn, :index, vods: vods) + end + + def new(conn, _params) do + changeset = Streams.change_vod(%Vod{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"vod" => vod_params}) do + case Streams.create_vod(vod_params) do + {:ok, vod} -> + conn + |> put_flash(:info, "Vod created successfully.") + |> redirect(to: ~p"/vods/#{vod}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + vod = Streams.get_vod!(id) + render(conn, :show, vod: vod) + end + + def edit(conn, %{"id" => id}) do + vod = Streams.get_vod!(id) + changeset = Streams.change_vod(vod) + render(conn, :edit, vod: vod, changeset: changeset) + end + + def update(conn, %{"id" => id, "vod" => vod_params}) do + vod = Streams.get_vod!(id) + + case Streams.update_vod(vod, vod_params) do + {:ok, vod} -> + conn + |> put_flash(:info, "Vod updated successfully.") + |> redirect(to: ~p"/vods/#{vod}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, vod: vod, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + vod = Streams.get_vod!(id) + {:ok, _vod} = Streams.delete_vod(vod) + + conn + |> put_flash(:info, "Vod deleted successfully.") + |> redirect(to: ~p"/vods") + end +end diff --git a/services/bright/lib/bright_web/controllers/vod_html.ex b/services/bright/lib/bright_web/controllers/vod_html.ex new file mode 100644 index 0000000..dbea17c --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html.ex @@ -0,0 +1,23 @@ +defmodule BrightWeb.VodHTML do + use BrightWeb, :html + + embed_templates "vod_html/*" + + @doc """ + Renders a vod form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def vod_form(assigns) + + def stream_opts(changeset) do + existing_ids = + changeset + |> Ecto.Changeset.get_change(:stream, []) + |> Enum.map(& &1.data.id) + + for stream <- Bright.Streams.list_streams(), + do: [key: "#{stream.id} | #{stream.date} | #{stream.vtubers |> Enum.map(& &1.display_name) |> Enum.join(", ")}", value: stream.id, selected: stream.id in existing_ids] + end +end diff --git a/services/bright/lib/bright_web/controllers/vod_html/edit.html.heex b/services/bright/lib/bright_web/controllers/vod_html/edit.html.heex new file mode 100644 index 0000000..fa565c9 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Vod {@vod.id} + <:subtitle>Use this form to manage vod records in the database. + + +<.vod_form changeset={@changeset} action={~p"/vods/#{@vod}"} /> + +<.back navigate={~p"/vods"}>Back to vods diff --git a/services/bright/lib/bright_web/controllers/vod_html/index.html.heex b/services/bright/lib/bright_web/controllers/vod_html/index.html.heex new file mode 100644 index 0000000..2c35b47 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html/index.html.heex @@ -0,0 +1,31 @@ +<.header> + Listing Vods + <:actions> + <.link href={~p"/vods/new"}> + <.button>New Vod + + + + +<.table id="vods" rows={@vods} row_click={&JS.navigate(~p"/vods/#{&1}")}> + <:col :let={vod} label="S3 cdn url">{vod.s3_cdn_url} + <:col :let={vod} label="S3 upload">{vod.s3_upload_id} + <:col :let={vod} label="S3 key">{vod.s3_key} + <:col :let={vod} label="S3 bucket">{vod.s3_bucket} + <:col :let={vod} label="Mux asset">{vod.mux_asset_id} + <:col :let={vod} label="Mux playback">{vod.mux_playback_id} + <:col :let={vod} label="Ipfs cid">{vod.ipfs_cid} + <:col :let={vod} label="Torrent">{vod.torrent} + <:col :let={vod} label="Notes">{vod.notes} + <:action :let={vod}> +
+ <.link navigate={~p"/vods/#{vod}"}>Show +
+ <.link navigate={~p"/vods/#{vod}/edit"}>Edit + + <:action :let={vod}> + <.link href={~p"/vods/#{vod}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/vod_html/new.html.heex b/services/bright/lib/bright_web/controllers/vod_html/new.html.heex new file mode 100644 index 0000000..5b9d2a3 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Vod + <:subtitle>Use this form to manage vod records in the database. + + +<.vod_form changeset={@changeset} action={~p"/vods"} /> + +<.back navigate={~p"/vods"}>Back to vods diff --git a/services/bright/lib/bright_web/controllers/vod_html/show.html.heex b/services/bright/lib/bright_web/controllers/vod_html/show.html.heex new file mode 100644 index 0000000..5cfb0c4 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html/show.html.heex @@ -0,0 +1,23 @@ +<.header> + Vod {@vod.id} + <:subtitle>This is a vod record from the database. + <:actions> + <.link href={~p"/vods/#{@vod}/edit"}> + <.button>Edit vod + + + + +<.list> + <:item title="S3 CDN url">{@vod.s3_cdn_url} + <:item title="S3 upload">{@vod.s3_upload_id} + <:item title="S3 key">{@vod.s3_key} + <:item title="S3 bucket">{@vod.s3_bucket} + <:item title="Mux asset">{@vod.mux_asset_id} + <:item title="Mux playback">{@vod.mux_playback_id} + <:item title="Ipfs CID">{@vod.ipfs_cid} + <:item title="Torrent">{@vod.torrent} + + + +<.back navigate={~p"/vods"}>Back to vods diff --git a/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex b/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex new file mode 100644 index 0000000..69be539 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex @@ -0,0 +1,20 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:s3_cdn_url]} type="text" label="S3 cdn url" /> + <.input field={f[:s3_upload_id]} type="text" label="S3 upload" /> + <.input field={f[:s3_key]} type="text" label="S3 key" /> + <.input field={f[:s3_bucket]} type="text" label="S3 bucket" /> + <.input field={f[:mux_asset_id]} type="text" label="Mux asset" /> + <.input field={f[:mux_playback_id]} type="text" label="Mux playback" /> + <.input field={f[:ipfs_cid]} type="text" label="Ipfs cid" /> + <.input field={f[:torrent]} type="text" label="Torrent" /> + <.input field={f[:notes]} type="textarea" label="Notes" /> + <.input field={f[:stream_id]} type="select" label="Stream" multiple={false} options={stream_opts(@changeset)}/> + + + <:actions> + <.button>Save Vod + + diff --git a/services/bright/lib/bright_web/controllers/vtuber_controller.ex b/services/bright/lib/bright_web/controllers/vtuber_controller.ex new file mode 100644 index 0000000..37a4ab7 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_controller.ex @@ -0,0 +1,62 @@ +defmodule BrightWeb.VtuberController do + use BrightWeb, :controller + + alias Bright.Vtubers + alias Bright.Vtubers.Vtuber + + def index(conn, _params) do + vtubers = Vtubers.list_vtubers() + render(conn, :index, vtubers: vtubers) + end + + def new(conn, _params) do + changeset = Vtubers.change_vtuber(%Vtuber{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"vtuber" => vtuber_params}) do + case Vtubers.create_vtuber(vtuber_params) do + {:ok, vtuber} -> + conn + |> put_flash(:info, "Vtuber created successfully.") + |> redirect(to: ~p"/vtubers/#{vtuber}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + vtuber = Vtubers.get_vtuber!(id) + render(conn, :show, vtuber: vtuber) + end + + def edit(conn, %{"id" => id}) do + vtuber = Vtubers.get_vtuber!(id) + changeset = Vtubers.change_vtuber(vtuber) + render(conn, :edit, vtuber: vtuber, changeset: changeset) + end + + def update(conn, %{"id" => id, "vtuber" => vtuber_params}) do + vtuber = Vtubers.get_vtuber!(id) + + case Vtubers.update_vtuber(vtuber, vtuber_params) do + {:ok, vtuber} -> + conn + |> put_flash(:info, "Vtuber updated successfully.") + |> redirect(to: ~p"/vtubers/#{vtuber}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, vtuber: vtuber, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + vtuber = Vtubers.get_vtuber!(id) + {:ok, _vtuber} = Vtubers.delete_vtuber(vtuber) + + conn + |> put_flash(:info, "Vtuber deleted successfully.") + |> redirect(to: ~p"/vtubers") + end +end diff --git a/services/bright/lib/bright_web/controllers/vtuber_html.ex b/services/bright/lib/bright_web/controllers/vtuber_html.ex new file mode 100644 index 0000000..2fd9d44 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html.ex @@ -0,0 +1,13 @@ +defmodule BrightWeb.VtuberHTML do + use BrightWeb, :html + + embed_templates "vtuber_html/*" + + @doc """ + Renders a vtuber form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def vtuber_form(assigns) +end diff --git a/services/bright/lib/bright_web/controllers/vtuber_html/edit.html.heex b/services/bright/lib/bright_web/controllers/vtuber_html/edit.html.heex new file mode 100644 index 0000000..4d5946f --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Vtuber {@vtuber.id} + <:subtitle>Use this form to manage vtuber records in the database. + + +<.vtuber_form changeset={@changeset} action={~p"/vtubers/#{@vtuber}"} /> + +<.back navigate={~p"/vtubers"}>Back to vtubers diff --git a/services/bright/lib/bright_web/controllers/vtuber_html/index.html.heex b/services/bright/lib/bright_web/controllers/vtuber_html/index.html.heex new file mode 100644 index 0000000..5b97735 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html/index.html.heex @@ -0,0 +1,27 @@ +<.header> + Listing Vtubers + <:actions> + <.link href={~p"/vtubers/new"}> + <.button>New Vtuber + + + + +<.table id="vtubers" rows={@vtubers} row_click={&JS.navigate(~p"/vtubers/#{&1}")}> + <:col :let={vtuber} label="ID">{vtuber.id} + <:col :let={vtuber} label="Image">{vtuber.image} + <:col :let={vtuber} label="Slug">{vtuber.slug} + <:col :let={vtuber} label="Display Name">{vtuber.display_name} + + <:action :let={vtuber}> +
+ <.link navigate={~p"/vtubers/#{vtuber}"}>Show +
+ <.link navigate={~p"/vtubers/#{vtuber}/edit"}>Edit + + <:action :let={vtuber}> + <.link href={~p"/vtubers/#{vtuber}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/services/bright/lib/bright_web/controllers/vtuber_html/new.html.heex b/services/bright/lib/bright_web/controllers/vtuber_html/new.html.heex new file mode 100644 index 0000000..55ecc4b --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Vtuber + <:subtitle>Use this form to add a new vtuber to the database. + + +<.vtuber_form changeset={@changeset} action={~p"/vtubers"} /> + +<.back navigate={~p"/vtubers"}>Back to vtubers diff --git a/services/bright/lib/bright_web/controllers/vtuber_html/show.html.heex b/services/bright/lib/bright_web/controllers/vtuber_html/show.html.heex new file mode 100644 index 0000000..4c41e45 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html/show.html.heex @@ -0,0 +1,21 @@ +<.header> + Vtuber {@vtuber.id} + <:subtitle>This is a vtuber record in the database. + <:actions> + <.link href={~p"/vtubers/#{@vtuber}/edit"}> + <.button>Edit vtuber + + + + +<.list> + <:item title="ID">{@vtuber.id} + <:item title="Image">{@vtuber.image} + <:item title="Slug">{@vtuber.slug} + <:item title="Display Name">{@vtuber.display_name} + <:item title="Theme Color">{@vtuber.theme_color} + + + + +<.back navigate={~p"/vtubers"}>Back to vtubers diff --git a/services/bright/lib/bright_web/controllers/vtuber_html/vtuber_form.html.heex b/services/bright/lib/bright_web/controllers/vtuber_html/vtuber_form.html.heex new file mode 100644 index 0000000..65a1e82 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/vtuber_html/vtuber_form.html.heex @@ -0,0 +1,38 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + + <.input field={f[:slug]} type="text" label="Slug" /> + <.input field={f[:display_name]} type="text" label="Display Name" /> + <.input field={f[:image]} type="text" label="Image URL" /> + <.input field={f[:theme_color]} type="color" label="Theme Color" /> + + <.input field={f[:chaturbate]} type="text" label="Chaturbate" /> + <.input field={f[:twitter]} type="text" label="Twitter" /> + <.input field={f[:patreon]} type="text" label="Patreon" /> + <.input field={f[:twitch]} type="text" label="Twitch" /> + <.input field={f[:tiktok]} type="text" label="Tiktok" /> + <.input field={f[:onlyfans]} type="text" label="OnlyFans" /> + <.input field={f[:youtube]} type="text" label="YouTube" /> + <.input field={f[:linktree]} type="text" label="Linktree" /> + <.input field={f[:carrd]} type="text" label="Carrd" /> + <.input field={f[:fansly]} type="text" label="Fansly" /> + <.input field={f[:pornhub]} type="text" label="Pornhub" /> + <.input field={f[:discord]} type="text" label="Discord" /> + <.input field={f[:reddit]} type="text" label="Reddit" /> + <.input field={f[:throne]} type="text" label="Throne" /> + <.input field={f[:instagram]} type="text" label="Instagram" /> + <.input field={f[:facebook]} type="text" label="Facebook" /> + <.input field={f[:merch]} type="text" label="Merch" /> + <.input field={f[:description_1]} type="text" label="Description 1" /> + <.input field={f[:description_2]} type="text" label="Description 2" /> + + <.input field={f[:fansly_id]} type="text" label="Fansly ID" /> + <.input field={f[:chaturbate_id]} type="text" label="Chaturbate ID" /> + <.input field={f[:twitter_id]} type="text" label="Twitter ID" /> + + <:actions> + <.button>Save Vtuber + + diff --git a/services/bright/lib/bright_web/endpoint.ex b/services/bright/lib/bright_web/endpoint.ex new file mode 100644 index 0000000..4b04a9e --- /dev/null +++ b/services/bright/lib/bright_web/endpoint.ex @@ -0,0 +1,59 @@ +defmodule BrightWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :bright + + + + # 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: "_bright_key", + signing_salt: "K3qIiNna", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # 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: :bright, + gzip: false, + only: BrightWeb.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: :bright + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + 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 BrightWeb.Router + + + + +end diff --git a/services/bright/lib/bright_web/gettext.ex b/services/bright/lib/bright_web/gettext.ex new file mode 100644 index 0000000..c098c06 --- /dev/null +++ b/services/bright/lib/bright_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule BrightWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import BrightWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :bright +end diff --git a/services/bright/lib/bright_web/live/post_live/form_component.ex b/services/bright/lib/bright_web/live/post_live/form_component.ex new file mode 100644 index 0000000..94db4e6 --- /dev/null +++ b/services/bright/lib/bright_web/live/post_live/form_component.ex @@ -0,0 +1,83 @@ +defmodule BrightWeb.PostLive.FormComponent do + use BrightWeb, :live_component + + alias Bright.Blog + + @impl true + def render(assigns) do + ~H""" +
+ <.header> + {@title} + <:subtitle>Use this form to manage post records in the database. + + + <.simple_form + for={@form} + id="post-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <.input field={@form[:title]} type="text" label="Title" /> + <.input field={@form[:body]} type="text" label="Body" /> + <:actions> + <.button phx-disable-with="Saving...">Save Post + + +
+ """ + end + + @impl true + def update(%{post: post} = assigns, socket) do + {:ok, + socket + |> assign(assigns) + |> assign_new(:form, fn -> + to_form(Blog.change_post(post)) + end)} + end + + @impl true + def handle_event("validate", %{"post" => post_params}, socket) do + changeset = Blog.change_post(socket.assigns.post, post_params) + {:noreply, assign(socket, form: to_form(changeset, action: :validate))} + end + + def handle_event("save", %{"post" => post_params}, socket) do + save_post(socket, socket.assigns.action, post_params) + end + + defp save_post(socket, :edit, post_params) do + case Blog.update_post(socket.assigns.post, post_params) do + {:ok, post} -> + notify_parent({:saved, post}) + + {:noreply, + socket + |> put_flash(:info, "Post updated successfully") + |> push_patch(to: socket.assigns.patch)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, form: to_form(changeset))} + end + end + + defp save_post(socket, :new, post_params) do + case Blog.create_post(post_params) do + {:ok, post} -> + notify_parent({:saved, post}) + + {:noreply, + socket + |> put_flash(:info, "Post created successfully") + |> push_patch(to: socket.assigns.patch)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, form: to_form(changeset))} + end + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/services/bright/lib/bright_web/live/post_live/index.ex b/services/bright/lib/bright_web/live/post_live/index.ex new file mode 100644 index 0000000..a732076 --- /dev/null +++ b/services/bright/lib/bright_web/live/post_live/index.ex @@ -0,0 +1,47 @@ +defmodule BrightWeb.PostLive.Index do + use BrightWeb, :live_view + + alias Bright.Blog + alias Bright.Blog.Post + + @impl true + def mount(_params, _session, socket) do + {:ok, stream(socket, :posts, Blog.list_posts())} + end + + @impl true + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, "Edit Post") + |> assign(:post, Blog.get_post!(id)) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, "New Post") + |> assign(:post, %Post{}) + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Listing Posts") + |> assign(:post, nil) + end + + @impl true + def handle_info({BrightWeb.PostLive.FormComponent, {:saved, post}}, socket) do + {:noreply, stream_insert(socket, :posts, post)} + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + post = Blog.get_post!(id) + {:ok, _} = Blog.delete_post(post) + + {:noreply, stream_delete(socket, :posts, post)} + end +end diff --git a/services/bright/lib/bright_web/live/post_live/index.html.heex b/services/bright/lib/bright_web/live/post_live/index.html.heex new file mode 100644 index 0000000..d361b94 --- /dev/null +++ b/services/bright/lib/bright_web/live/post_live/index.html.heex @@ -0,0 +1,42 @@ +<.header> + Listing Posts + <:actions> + <.link patch={~p"/posts/new"}> + <.button>New Post + + + + +<.table + id="posts" + rows={@streams.posts} + row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end} +> + <:col :let={{_id, post}} label="Title">{post.title} + <:col :let={{_id, post}} label="Body">{post.body} + <:action :let={{_id, post}}> +
+ <.link navigate={~p"/posts/#{post}"}>Show +
+ <.link patch={~p"/posts/#{post}/edit"}>Edit + + <:action :let={{id, post}}> + <.link + phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")} + data-confirm="Are you sure?" + > + Delete + + + + +<.modal :if={@live_action in [:new, :edit]} id="post-modal" show on_cancel={JS.patch(~p"/posts")}> + <.live_component + module={BrightWeb.PostLive.FormComponent} + id={@post.id || :new} + title={@page_title} + action={@live_action} + post={@post} + patch={~p"/posts"} + /> + diff --git a/services/bright/lib/bright_web/live/post_live/show.ex b/services/bright/lib/bright_web/live/post_live/show.ex new file mode 100644 index 0000000..c974a2c --- /dev/null +++ b/services/bright/lib/bright_web/live/post_live/show.ex @@ -0,0 +1,21 @@ +defmodule BrightWeb.PostLive.Show do + use BrightWeb, :live_view + + alias Bright.Blog + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end + + @impl true + def handle_params(%{"id" => id}, _, socket) do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:post, Blog.get_post!(id))} + end + + defp page_title(:show), do: "Show Post" + defp page_title(:edit), do: "Edit Post" +end diff --git a/services/bright/lib/bright_web/live/post_live/show.html.heex b/services/bright/lib/bright_web/live/post_live/show.html.heex new file mode 100644 index 0000000..5b1ae57 --- /dev/null +++ b/services/bright/lib/bright_web/live/post_live/show.html.heex @@ -0,0 +1,27 @@ +<.header> + Post {@post.id} + <:subtitle>This is a post record from the database. + <:actions> + <.link patch={~p"/posts/#{@post}/show/edit"} phx-click={JS.push_focus()}> + <.button>Edit post + + + + +<.list> + <:item title="Title">{@post.title} + <:item title="Body">{@post.body} + + +<.back navigate={~p"/posts"}>Back to posts + +<.modal :if={@live_action == :edit} id="post-modal" show on_cancel={JS.patch(~p"/posts/#{@post}")}> + <.live_component + module={BrightWeb.PostLive.FormComponent} + id={@post.id} + title={@page_title} + action={@live_action} + post={@post} + patch={~p"/posts/#{@post}"} + /> + diff --git a/services/bright/lib/bright_web/live/thermostat_live.ex b/services/bright/lib/bright_web/live/thermostat_live.ex new file mode 100644 index 0000000..c9d5e53 --- /dev/null +++ b/services/bright/lib/bright_web/live/thermostat_live.ex @@ -0,0 +1,21 @@ +defmodule Bright.ThermostatLive do + use BrightWeb, :live_view + + def render(assigns) do + ~H""" + Current temperature: {@temperature}°F + + """ + end + + def mount(_params, _session, socket) do + temperature = 70 + {:ok, assign(socket, :temperature, temperature)} + end + + def handle_event("inc_temperature", _params, socket) do + {:noreply, update(socket, :temperature, &(&1 + 1))} + end +end diff --git a/services/bright/lib/bright_web/plugs/locale.ex b/services/bright/lib/bright_web/plugs/locale.ex new file mode 100644 index 0000000..521e9c0 --- /dev/null +++ b/services/bright/lib/bright_web/plugs/locale.ex @@ -0,0 +1,15 @@ +defmodule BrightWeb.Plugs.Locale do + import Plug.Conn + + @locales ["en", "jp"] + + def init(default), do: default + + def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do + assign(conn, :locale, loc) + end + + def call(conn, default) do + assign(conn, :locale, default) + end +end diff --git a/services/bright/lib/bright_web/router.ex b/services/bright/lib/bright_web/router.ex new file mode 100644 index 0000000..d9dd0e6 --- /dev/null +++ b/services/bright/lib/bright_web/router.ex @@ -0,0 +1,108 @@ +defmodule BrightWeb.Router do + use BrightWeb, :router + alias Bright.ShoppingCart + + pipeline :browser do + plug :accepts, ["html", "json"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {BrightWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + plug :fetch_current_user + plug :fetch_current_cart + end + + defp fetch_current_user(conn, _) do + if user_uuid = get_session(conn, :current_uuid) do + assign(conn, :current_uuid, user_uuid) + else + new_uuid = Ecto.UUID.generate() + conn + |> assign(:current_uuid, new_uuid) + |> put_session(:current_uuid, new_uuid) + end + end + + defp fetch_current_cart(conn, _opts) do + if cart = ShoppingCart.get_cart_by_user_uuid(conn.assigns.current_uuid) do + assign(conn, :cart, cart) + else + {:ok, new_cart} = ShoppingCart.create_cart(conn.assigns.current_uuid) + assign(conn, :cart, new_cart) + end + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", BrightWeb do + pipe_through :browser + + get "/", PageController, :home + resources "/products", ProductController + + get "/patrons", PatronController, :index + get "/about", PageController, :about + get "/api", PageController, :api + + resources "/cart_items", CartItemController, only: [:create, :delete] + get "/cart", CartController, :show + put "/cart", CartController, :update + + resources "/orders", OrderController, only: [:create, :show] + + resources "/archive", StreamController + resources "/streams", StreamController + + resources "/vods", VodController + + resources "/vtubers", VtuberController + resources "/vt", VtuberController do + resources "/vods", VodController + end + + + # resources "/users", UserController + resources "/tags", TagController + + ## @todo DANGER, platforms must only be writable by admins, (unless we implement SVG sanitizing) + resources "/platforms", PlatformController + + + get "/hello", HelloController, :index + get "/hello/:messenger", HelloController, :show + + + live "/posts", PostLive.Index, :index + live "/posts/new", PostLive.Index, :new + live "/posts/:id/edit", PostLive.Index, :edit + + live "/posts/:id", PostLive.Show, :show + live "/posts/:id/show/edit", PostLive.Show, :edit + end + + # Other scopes may use custom stacks. + scope "/api", BrightWeb do + pipe_through :api + resources "/urls", UrlController, except: [:new, :edit] + end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:bright, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: BrightWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/services/bright/lib/bright_web/telemetry.ex b/services/bright/lib/bright_web/telemetry.ex new file mode 100644 index 0000000..cfc7b10 --- /dev/null +++ b/services/bright/lib/bright_web/telemetry.ex @@ -0,0 +1,92 @@ +defmodule BrightWeb.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("bright.repo.query.total_time", + unit: {:native, :millisecond}, + description: "The sum of the other measurements" + ), + summary("bright.repo.query.decode_time", + unit: {:native, :millisecond}, + description: "The time spent decoding the data received from the database" + ), + summary("bright.repo.query.query_time", + unit: {:native, :millisecond}, + description: "The time spent executing the query" + ), + summary("bright.repo.query.queue_time", + unit: {:native, :millisecond}, + description: "The time spent waiting for a database connection" + ), + summary("bright.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. + # {BrightWeb, :count_users, []} + ] + end +end diff --git a/services/bright/mix.exs b/services/bright/mix.exs new file mode 100644 index 0000000..f21fb9b --- /dev/null +++ b/services/bright/mix.exs @@ -0,0 +1,87 @@ +defmodule Bright.MixProject do + use Mix.Project + + def project do + [ + app: :bright, + 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: {Bright.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.17"}, + {:phoenix_ecto, "~> 4.5"}, + {:ecto_sql, "~> 3.10"}, + {:postgrex, ">= 0.0.0"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.0.0"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.8.3"}, + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:dart_sass, "0.7.0", runtime: Mix.env() == :dev}, + {:bulma, "1.0.2"}, + {:heroicons, + github: "tailwindlabs/heroicons", + tag: "v2.1.1", + sparse: "optimized", + app: false, + compile: false, + depth: 1}, + {:swoosh, "~> 1.5"}, + {:finch, "~> 0.13"}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.20"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.1.1"}, + {:bandit, "~> 1.5"} + ] + 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": ["tailwind.install --if-missing", "esbuild.install --if-missing"], + "assets.build": ["tailwind bright", "esbuild bright"], + "assets.deploy": [ + "sass default --no-source-map --style=compressed", "esbuild default --minify", "phx.digest", + "esbuild bright --minify", + "phx.digest" + ] + ] + end +end diff --git a/services/bright/mix.lock b/services/bright/mix.lock new file mode 100644 index 0000000..c84d4f0 --- /dev/null +++ b/services/bright/mix.lock @@ -0,0 +1,43 @@ +%{ + "bandit": {:hex, :bandit, "1.6.1", "9e01b93d72ddc21d8c576a704949e86ee6cde7d11270a1d3073787876527a48f", [:mix], [{:hpax, "~> 1.0", [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", "5a904bf010ea24b67979835e0507688e31ac873d4ffc8ed0e5413e8d77455031"}, + "bulma": {:hex, :bulma, "1.0.2", "50dfffe8d28b0bd527418560223b407f9e80e990e187e1653b17eff818f8fcbe", [:mix], [], "hexpm", "27745727ff7f451d140a2438c0ca4448bc8ca73e0a6d2d4f24e1b5b9ced8a774"}, + "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, + "dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [: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", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [: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", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [: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]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, + "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.5", "d5f44d7dbd7cfacaa617b70c5a14b2b598d6f93b9caa8e350c51d56cd4350a9b", [: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", "1d73920515554d7d6c548aee0bf10a4780568b029d042eccb336db29ea0dad70"}, + "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, "1.0.1", "5389a30658176c0de816636ce276567478bffd063c082515a6e8368b8fc9a0db", [: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", "c0f517e6f290f10dbb94343ac22e0109437fb1fa6f0696e7c73967b789c1c285"}, + "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.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [: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", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "swoosh": {:hex, :swoosh, "1.17.5", "14910d267a2633d4335917b37846e376e2067815601592629366c39845dad145", [: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.2.3", [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.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "629113d477bc82c4c3bffd15a25e8becc1c7ccc0f0e67743b017caddebb06f04"}, + "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, + "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.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, +} diff --git a/services/bright/priv/gettext/en/LC_MESSAGES/errors.po b/services/bright/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..844c4f5 --- /dev/null +++ b/services/bright/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,112 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" + +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be %{count} byte(s)" +msgid_plural "should be %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} byte(s)" +msgid_plural "should be at least %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} byte(s)" +msgid_plural "should be at most %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/services/bright/priv/gettext/errors.pot b/services/bright/priv/gettext/errors.pot new file mode 100644 index 0000000..eef2de2 --- /dev/null +++ b/services/bright/priv/gettext/errors.pot @@ -0,0 +1,109 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be %{count} byte(s)" +msgid_plural "should be %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} byte(s)" +msgid_plural "should be at least %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} byte(s)" +msgid_plural "should be at most %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/services/bright/priv/repo/migrations/.formatter.exs b/services/bright/priv/repo/migrations/.formatter.exs new file mode 100644 index 0000000..49f9151 --- /dev/null +++ b/services/bright/priv/repo/migrations/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:ecto_sql], + inputs: ["*.exs"] +] diff --git a/services/bright/priv/repo/migrations/20241227185046_create_posts.exs b/services/bright/priv/repo/migrations/20241227185046_create_posts.exs new file mode 100644 index 0000000..082b920 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241227185046_create_posts.exs @@ -0,0 +1,12 @@ +defmodule Bright.Repo.Migrations.CreatePosts do + use Ecto.Migration + + def change do + create table(:posts) do + add :title, :string + add :body, :text + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229023428_create_users.exs b/services/bright/priv/repo/migrations/20241229023428_create_users.exs new file mode 100644 index 0000000..2d6f3fd --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229023428_create_users.exs @@ -0,0 +1,14 @@ +defmodule Bright.Repo.Migrations.CreateUsers do + use Ecto.Migration + + def change do + create table(:users) do + add :name, :string + add :email, :string + add :bio, :string + add :number_of_pets, :integer + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229033144_create_streams.exs b/services/bright/priv/repo/migrations/20241229033144_create_streams.exs new file mode 100644 index 0000000..c00d978 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229033144_create_streams.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreateStreams do + use Ecto.Migration + + def change do + create table(:streams) do + add :title, :string + add :notes, :string + add :date, :utc_datetime + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229033908_make_notes_nullable.exs b/services/bright/priv/repo/migrations/20241229033908_make_notes_nullable.exs new file mode 100644 index 0000000..b5a52bb --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229033908_make_notes_nullable.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.MakeNotesNullable do + use Ecto.Migration + + def change do + alter table(:streams) do + modify :notes, :string, null: true + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229042113_add_views_col_to_streams.exs b/services/bright/priv/repo/migrations/20241229042113_add_views_col_to_streams.exs new file mode 100644 index 0000000..e3045b8 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229042113_add_views_col_to_streams.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.AddViewsColToStreams do + use Ecto.Migration + + def change do + alter table(:streams) do + add :views, :integer, default: 0, null: false + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229044635_create_vods.exs b/services/bright/priv/repo/migrations/20241229044635_create_vods.exs new file mode 100644 index 0000000..c0a6f8a --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229044635_create_vods.exs @@ -0,0 +1,18 @@ +defmodule Bright.Repo.Migrations.CreateVods do + use Ecto.Migration + + def change do + create table(:vods) do + add :s3_cdn_url, :string + add :s3_upload_id, :string + add :s3_key, :string + add :s3_bucket, :string + add :mux_asset_id, :string + add :mux_playback_id, :string + add :ipfs_cid, :string + add :torrent, :string + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229052718_create_tags.exs b/services/bright/priv/repo/migrations/20241229052718_create_tags.exs new file mode 100644 index 0000000..7c8387a --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229052718_create_tags.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreateTags do + use Ecto.Migration + + def change do + create table(:tags) do + add :name, :string + + timestamps(type: :utc_datetime) + end + + create unique_index(:tags, [:name]) + end +end diff --git a/services/bright/priv/repo/migrations/20241229053017_create_streams_tags.exs b/services/bright/priv/repo/migrations/20241229053017_create_streams_tags.exs new file mode 100644 index 0000000..37ef568 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229053017_create_streams_tags.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreateStreamsTags do + use Ecto.Migration + + def change do + create table(:streams_tags, primary_key: false) do + add :stream_id, references(:streams, on_delete: :delete_all), primary_key: true + add :tag_id, references(:tags, on_delete: :delete_all), primary_key: true + end + + create index(:streams_tags, [:stream_id]) + create index(:streams_tags, [:tag_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20241229055702_create_vtubers.exs b/services/bright/priv/repo/migrations/20241229055702_create_vtubers.exs new file mode 100644 index 0000000..de8be03 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229055702_create_vtubers.exs @@ -0,0 +1,37 @@ +defmodule Bright.Repo.Migrations.CreateVtubers do + use Ecto.Migration + + def change do + create table(:vtubers) do + add :slug, :string + add :display_name, :string + add :chaturbate, :string + add :twitter, :string + add :patreon, :string + add :twitch, :string + add :tiktok, :string + add :onlyfans, :string + add :youtube, :string + add :linktree, :string + add :carrd, :string + add :fansly, :string + add :pornhub, :string + add :discord, :string + add :reddit, :string + add :throne, :string + add :instagram, :string + add :facebook, :string + add :merch, :string + add :description_1, :string + add :description_2, :string + add :image, :string + add :image_blur, :string + add :theme_color, :string + add :fansly_id, :string + add :chaturbate_id, :string + add :twitter_id, :string + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229213446_create_products.exs b/services/bright/priv/repo/migrations/20241229213446_create_products.exs new file mode 100644 index 0000000..80efa02 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229213446_create_products.exs @@ -0,0 +1,14 @@ +defmodule Bright.Repo.Migrations.CreateProducts do + use Ecto.Migration + + def change do + create table(:products) do + add :title, :string + add :description, :string + add :price, :decimal, precision: 15, scale: 6, null: false + add :views, :integer, default: 0, null: false + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241229214700_create_categories.exs b/services/bright/priv/repo/migrations/20241229214700_create_categories.exs new file mode 100644 index 0000000..ffecf90 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229214700_create_categories.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreateCategories do + use Ecto.Migration + + def change do + create table(:categories) do + add :title, :string + + timestamps(type: :utc_datetime) + end + + create unique_index(:categories, [:title]) + end +end diff --git a/services/bright/priv/repo/migrations/20241229214737_create_product_categories.exs b/services/bright/priv/repo/migrations/20241229214737_create_product_categories.exs new file mode 100644 index 0000000..b952fcc --- /dev/null +++ b/services/bright/priv/repo/migrations/20241229214737_create_product_categories.exs @@ -0,0 +1,12 @@ +defmodule Bright.Repo.Migrations.CreateProductCategories do + use Ecto.Migration + + def change do + create table(:product_categories, primary_key: false) do + add :product_id, references(:products, on_delete: :delete_all) + add :category_id, references(:categories, on_delete: :delete_all) + end + create index(:product_categories, [:product_id]) + create unique_index(:product_categories, [:category_id, :product_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20241230002702_create_carts.exs b/services/bright/priv/repo/migrations/20241230002702_create_carts.exs new file mode 100644 index 0000000..389d95d --- /dev/null +++ b/services/bright/priv/repo/migrations/20241230002702_create_carts.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreateCarts do + use Ecto.Migration + + def change do + create table(:carts) do + add :user_uuid, :uuid + + timestamps(type: :utc_datetime) + end + + create unique_index(:carts, [:user_uuid]) + end +end diff --git a/services/bright/priv/repo/migrations/20241230003000_create_cart_items.exs b/services/bright/priv/repo/migrations/20241230003000_create_cart_items.exs new file mode 100644 index 0000000..26a3124 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241230003000_create_cart_items.exs @@ -0,0 +1,17 @@ +defmodule Bright.Repo.Migrations.CreateCartItems do + use Ecto.Migration + + def change do + create table(:cart_items) do + add :price_when_carted, :decimal, precision: 15, scale: 6, null: false + add :quantity, :integer + add :cart_id, references(:carts, on_delete: :delete_all) + add :product_id, references(:products, on_delete: :delete_all) + + timestamps(type: :utc_datetime) + end + + create index(:cart_items, [:product_id]) + create unique_index(:cart_items, [:cart_id, :product_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20241230044441_create_orders.exs b/services/bright/priv/repo/migrations/20241230044441_create_orders.exs new file mode 100644 index 0000000..f7593f2 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241230044441_create_orders.exs @@ -0,0 +1,12 @@ +defmodule Bright.Repo.Migrations.CreateOrders do + use Ecto.Migration + + def change do + create table(:orders) do + add :user_uuid, :uuid + add :total_price, :decimal, precision: 15, scale: 6, null: false + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20241230044633_create_order_line_items.exs b/services/bright/priv/repo/migrations/20241230044633_create_order_line_items.exs new file mode 100644 index 0000000..fad1069 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241230044633_create_order_line_items.exs @@ -0,0 +1,17 @@ +defmodule Bright.Repo.Migrations.CreateOrderLineItems do + use Ecto.Migration + + def change do + create table(:order_line_items) do + add :price, :decimal, precision: 15, scale: 6, null: false + add :quantity, :integer + add :order_id, references(:orders, on_delete: :nothing) + add :product_id, references(:products, on_delete: :nothing) + + timestamps(type: :utc_datetime) + end + + create index(:order_line_items, [:order_id]) + create index(:order_line_items, [:product_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20241230055033_create_urls.exs b/services/bright/priv/repo/migrations/20241230055033_create_urls.exs new file mode 100644 index 0000000..50f9ee3 --- /dev/null +++ b/services/bright/priv/repo/migrations/20241230055033_create_urls.exs @@ -0,0 +1,12 @@ +defmodule Bright.Repo.Migrations.CreateUrls do + use Ecto.Migration + + def change do + create table(:urls) do + add :link, :string + add :title, :string + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20250102231141_remove_blur_from_vtubers.exs b/services/bright/priv/repo/migrations/20250102231141_remove_blur_from_vtubers.exs new file mode 100644 index 0000000..fed27c4 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250102231141_remove_blur_from_vtubers.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.RemoveBlurFromVtubers do + use Ecto.Migration + + def change do + alter table(:vtubers) do + remove :image_blur + end + end +end diff --git a/services/bright/priv/repo/migrations/20250102234644_create_streams_vtubers.exs b/services/bright/priv/repo/migrations/20250102234644_create_streams_vtubers.exs new file mode 100644 index 0000000..92f21d3 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250102234644_create_streams_vtubers.exs @@ -0,0 +1,12 @@ +defmodule Bright.Repo.Migrations.CreateStreamsVtubers do + use Ecto.Migration + + def change do + create table(:streams_vtubers, primary_key: false) do + add :stream_id, references(:streams, on_delete: :delete_all), null: false + add :vtuber_id, references(:vtubers, on_delete: :delete_all), null: false + end + + create unique_index(:streams_vtubers, [:stream_id, :vtuber_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20250103001216_add_stream_id_column_to_vods.exs b/services/bright/priv/repo/migrations/20250103001216_add_stream_id_column_to_vods.exs new file mode 100644 index 0000000..ed6cc1e --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103001216_add_stream_id_column_to_vods.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.AddStreamIdColumnToVods do + use Ecto.Migration + + def change do + alter table(:vods) do + add :stream_id, references(:streams, on_delete: :nothing), null: false + end + end +end diff --git a/services/bright/priv/repo/migrations/20250103030423_create_patrons.exs b/services/bright/priv/repo/migrations/20250103030423_create_patrons.exs new file mode 100644 index 0000000..ce0c532 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103030423_create_patrons.exs @@ -0,0 +1,13 @@ +defmodule Bright.Repo.Migrations.CreatePatrons do + use Ecto.Migration + + def change do + create table(:patrons) do + add :name, :text + add :lifetime_support_cents, :integer + add :public, :boolean, default: false, null: false + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/priv/repo/migrations/20250103070833_add_platforms.exs b/services/bright/priv/repo/migrations/20250103070833_add_platforms.exs new file mode 100644 index 0000000..37cd047 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103070833_add_platforms.exs @@ -0,0 +1,17 @@ +defmodule Bright.Repo.Migrations.AddPlatforms do + use Ecto.Migration + + def change do + + create table(:platforms) do + add :name, :string, null: false + add :url, :string + add :icon, :string + + timestamps(type: :utc_datetime) + end + + create unique_index(:platforms, [:name]) + + end +end diff --git a/services/bright/priv/repo/migrations/20250103071310_add_platform_to_stream.exs b/services/bright/priv/repo/migrations/20250103071310_add_platform_to_stream.exs new file mode 100644 index 0000000..db0c928 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103071310_add_platform_to_stream.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.AddStreamPlatformIdDefault do + use Ecto.Migration + + def change do + alter table(:streams) do + add :platform_id, references(:platforms, on_delete: :nothing), null: true + end + end +end diff --git a/services/bright/priv/repo/migrations/20250103071506_add_vod_notes.exs b/services/bright/priv/repo/migrations/20250103071506_add_vod_notes.exs new file mode 100644 index 0000000..b6decf3 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103071506_add_vod_notes.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.AddVodNotes do + use Ecto.Migration + + def change do + alter table(:vods) do + add :notes, :text + end + end +end diff --git a/services/bright/priv/repo/migrations/20250103072941_add_platform_id_default_to_stream.exs b/services/bright/priv/repo/migrations/20250103072941_add_platform_id_default_to_stream.exs new file mode 100644 index 0000000..a3ab444 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103072941_add_platform_id_default_to_stream.exs @@ -0,0 +1,20 @@ +defmodule Bright.Repo.Migrations.AddPlatformIdToStream do + use Ecto.Migration + + + + + def up do + # Insert a default platform + execute(""" + INSERT INTO platforms (id, name, inserted_at, updated_at) + VALUES (1, 'Unknown', NOW(), NOW()) + ON CONFLICT DO NOTHING + """) + + execute("UPDATE streams SET platform_id = 1 WHERE platform_id IS NULL") + end + + + +end diff --git a/services/bright/priv/repo/migrations/20250103085138_change_icon_to_text.exs b/services/bright/priv/repo/migrations/20250103085138_change_icon_to_text.exs new file mode 100644 index 0000000..0fc7143 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103085138_change_icon_to_text.exs @@ -0,0 +1,9 @@ +defmodule Bright.Repo.Migrations.ChangeIconToText do + use Ecto.Migration + + def change do + alter table(:platforms) do + modify :icon, :text + end + end +end diff --git a/services/bright/priv/repo/migrations/20250103102125_add_streams_platforms_join_table.exs b/services/bright/priv/repo/migrations/20250103102125_add_streams_platforms_join_table.exs new file mode 100644 index 0000000..ae206c9 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103102125_add_streams_platforms_join_table.exs @@ -0,0 +1,21 @@ +defmodule Bright.Repo.Migrations.AddStreamsPlatformsJoinTable do + use Ecto.Migration + + def change do + # Remove the existing foreign key and column + alter table(:streams) do + remove :platform_id + end + + # The join table for streams and platforms + create table(:streams_platforms) do + add :stream_id, references(:streams, on_delete: :delete_all), null: false + add :platform_id, references(:platforms, on_delete: :delete_all), null: false + + timestamps(type: :utc_datetime) + end + + # A unique index to prevent duplicate entries + create unique_index(:streams_platforms, [:stream_id, :platform_id]) + end +end diff --git a/services/bright/priv/repo/migrations/20250103112029_remove_timestamps_from_streams_platforms.exs b/services/bright/priv/repo/migrations/20250103112029_remove_timestamps_from_streams_platforms.exs new file mode 100644 index 0000000..dee4c7c --- /dev/null +++ b/services/bright/priv/repo/migrations/20250103112029_remove_timestamps_from_streams_platforms.exs @@ -0,0 +1,10 @@ +defmodule Bright.Repo.Migrations.RemoveTimestampsFromStreamsPlatforms do + use Ecto.Migration + + def change do + alter table(:streams_platforms) do + remove :inserted_at + remove :updated_at + end + end +end diff --git a/services/bright/priv/repo/seeds.exs b/services/bright/priv/repo/seeds.exs new file mode 100644 index 0000000..179d3c1 --- /dev/null +++ b/services/bright/priv/repo/seeds.exs @@ -0,0 +1,17 @@ +# Script for populating the database. You can run it as: +# +# mix run priv/repo/seeds.exs +# +# Inside the script, you can read and write to any of your +# repositories directly: +# +# Bright.Repo.insert!(%Bright.SomeSchema{}) +# +# We recommend using the bang functions (`insert!`, `update!` +# and so on) as they will fail if something goes wrong. + + + +for title <- ["Home Improvement", "Power Tools", "Gardening", "Books", "Education"] do + {:ok, _} = Bright.Catalog.create_category(%{title: title}) +end diff --git a/services/bright/priv/static/favicon.ico b/services/bright/priv/static/favicon.ico new file mode 100644 index 0000000..7f372bf Binary files /dev/null and b/services/bright/priv/static/favicon.ico differ diff --git a/services/bright/priv/static/images/logo.svg b/services/bright/priv/static/images/logo.svg new file mode 100644 index 0000000..9f26bab --- /dev/null +++ b/services/bright/priv/static/images/logo.svg @@ -0,0 +1,6 @@ + diff --git a/services/bright/priv/static/robots.txt b/services/bright/priv/static/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/services/bright/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/services/bright/rel/overlays/bin/migrate b/services/bright/rel/overlays/bin/migrate new file mode 100755 index 0000000..557f6ec --- /dev/null +++ b/services/bright/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./bright eval Bright.Release.migrate diff --git a/services/bright/rel/overlays/bin/migrate.bat b/services/bright/rel/overlays/bin/migrate.bat new file mode 100755 index 0000000..2d971ef --- /dev/null +++ b/services/bright/rel/overlays/bin/migrate.bat @@ -0,0 +1 @@ +call "%~dp0\bright" eval Bright.Release.migrate diff --git a/services/bright/rel/overlays/bin/server b/services/bright/rel/overlays/bin/server new file mode 100755 index 0000000..300e51a --- /dev/null +++ b/services/bright/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./bright start diff --git a/services/bright/rel/overlays/bin/server.bat b/services/bright/rel/overlays/bin/server.bat new file mode 100755 index 0000000..d962c28 --- /dev/null +++ b/services/bright/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\bright" start diff --git a/services/bright/test/bright/blog_test.exs b/services/bright/test/bright/blog_test.exs new file mode 100644 index 0000000..da1953b --- /dev/null +++ b/services/bright/test/bright/blog_test.exs @@ -0,0 +1,61 @@ +defmodule Bright.BlogTest do + use Bright.DataCase + + alias Bright.Blog + + describe "posts" do + alias Bright.Blog.Post + + import Bright.BlogFixtures + + @invalid_attrs %{title: nil, body: nil} + + test "list_posts/0 returns all posts" do + post = post_fixture() + assert Blog.list_posts() == [post] + end + + test "get_post!/1 returns the post with given id" do + post = post_fixture() + assert Blog.get_post!(post.id) == post + end + + test "create_post/1 with valid data creates a post" do + valid_attrs = %{title: "some title", body: "some body"} + + assert {:ok, %Post{} = post} = Blog.create_post(valid_attrs) + assert post.title == "some title" + assert post.body == "some body" + end + + test "create_post/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Blog.create_post(@invalid_attrs) + end + + test "update_post/2 with valid data updates the post" do + post = post_fixture() + update_attrs = %{title: "some updated title", body: "some updated body"} + + assert {:ok, %Post{} = post} = Blog.update_post(post, update_attrs) + assert post.title == "some updated title" + assert post.body == "some updated body" + end + + test "update_post/2 with invalid data returns error changeset" do + post = post_fixture() + assert {:error, %Ecto.Changeset{}} = Blog.update_post(post, @invalid_attrs) + assert post == Blog.get_post!(post.id) + end + + test "delete_post/1 deletes the post" do + post = post_fixture() + assert {:ok, %Post{}} = Blog.delete_post(post) + assert_raise Ecto.NoResultsError, fn -> Blog.get_post!(post.id) end + end + + test "change_post/1 returns a post changeset" do + post = post_fixture() + assert %Ecto.Changeset{} = Blog.change_post(post) + end + end +end diff --git a/services/bright/test/bright/catalog_test.exs b/services/bright/test/bright/catalog_test.exs new file mode 100644 index 0000000..a4e835b --- /dev/null +++ b/services/bright/test/bright/catalog_test.exs @@ -0,0 +1,119 @@ +defmodule Bright.CatalogTest do + use Bright.DataCase + + alias Bright.Catalog + + describe "products" do + alias Bright.Catalog.Product + + import Bright.CatalogFixtures + + @invalid_attrs %{description: nil, title: nil, price: nil, views: nil} + + test "list_products/0 returns all products" do + product = product_fixture() + assert Catalog.list_products() == [product] + end + + test "get_product!/1 returns the product with given id" do + product = product_fixture() + assert Catalog.get_product!(product.id) == product + end + + test "create_product/1 with valid data creates a product" do + valid_attrs = %{description: "some description", title: "some title", price: "120.5", views: 42} + + assert {:ok, %Product{} = product} = Catalog.create_product(valid_attrs) + assert product.description == "some description" + assert product.title == "some title" + assert product.price == Decimal.new("120.5") + assert product.views == 42 + end + + test "create_product/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Catalog.create_product(@invalid_attrs) + end + + test "update_product/2 with valid data updates the product" do + product = product_fixture() + update_attrs = %{description: "some updated description", title: "some updated title", price: "456.7", views: 43} + + assert {:ok, %Product{} = product} = Catalog.update_product(product, update_attrs) + assert product.description == "some updated description" + assert product.title == "some updated title" + assert product.price == Decimal.new("456.7") + assert product.views == 43 + end + + test "update_product/2 with invalid data returns error changeset" do + product = product_fixture() + assert {:error, %Ecto.Changeset{}} = Catalog.update_product(product, @invalid_attrs) + assert product == Catalog.get_product!(product.id) + end + + test "delete_product/1 deletes the product" do + product = product_fixture() + assert {:ok, %Product{}} = Catalog.delete_product(product) + assert_raise Ecto.NoResultsError, fn -> Catalog.get_product!(product.id) end + end + + test "change_product/1 returns a product changeset" do + product = product_fixture() + assert %Ecto.Changeset{} = Catalog.change_product(product) + end + end + + describe "categories" do + alias Bright.Catalog.Category + + import Bright.CatalogFixtures + + @invalid_attrs %{title: nil} + + test "list_categories/0 returns all categories" do + category = category_fixture() + assert Catalog.list_categories() == [category] + end + + test "get_category!/1 returns the category with given id" do + category = category_fixture() + assert Catalog.get_category!(category.id) == category + end + + test "create_category/1 with valid data creates a category" do + valid_attrs = %{title: "some title"} + + assert {:ok, %Category{} = category} = Catalog.create_category(valid_attrs) + assert category.title == "some title" + end + + test "create_category/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Catalog.create_category(@invalid_attrs) + end + + test "update_category/2 with valid data updates the category" do + category = category_fixture() + update_attrs = %{title: "some updated title"} + + assert {:ok, %Category{} = category} = Catalog.update_category(category, update_attrs) + assert category.title == "some updated title" + end + + test "update_category/2 with invalid data returns error changeset" do + category = category_fixture() + assert {:error, %Ecto.Changeset{}} = Catalog.update_category(category, @invalid_attrs) + assert category == Catalog.get_category!(category.id) + end + + test "delete_category/1 deletes the category" do + category = category_fixture() + assert {:ok, %Category{}} = Catalog.delete_category(category) + assert_raise Ecto.NoResultsError, fn -> Catalog.get_category!(category.id) end + end + + test "change_category/1 returns a category changeset" do + category = category_fixture() + assert %Ecto.Changeset{} = Catalog.change_category(category) + end + end +end diff --git a/services/bright/test/bright/orders_test.exs b/services/bright/test/bright/orders_test.exs new file mode 100644 index 0000000..9ada911 --- /dev/null +++ b/services/bright/test/bright/orders_test.exs @@ -0,0 +1,117 @@ +defmodule Bright.OrdersTest do + use Bright.DataCase + + alias Bright.Orders + + describe "orders" do + alias Bright.Orders.Order + + import Bright.OrdersFixtures + + @invalid_attrs %{user_uuid: nil, total_price: nil} + + test "list_orders/0 returns all orders" do + order = order_fixture() + assert Orders.list_orders() == [order] + end + + test "get_order!/1 returns the order with given id" do + order = order_fixture() + assert Orders.get_order!(order.id) == order + end + + test "create_order/1 with valid data creates a order" do + valid_attrs = %{user_uuid: "7488a646-e31f-11e4-aace-600308960662", total_price: "120.5"} + + assert {:ok, %Order{} = order} = Orders.create_order(valid_attrs) + assert order.user_uuid == "7488a646-e31f-11e4-aace-600308960662" + assert order.total_price == Decimal.new("120.5") + end + + test "create_order/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Orders.create_order(@invalid_attrs) + end + + test "update_order/2 with valid data updates the order" do + order = order_fixture() + update_attrs = %{user_uuid: "7488a646-e31f-11e4-aace-600308960668", total_price: "456.7"} + + assert {:ok, %Order{} = order} = Orders.update_order(order, update_attrs) + assert order.user_uuid == "7488a646-e31f-11e4-aace-600308960668" + assert order.total_price == Decimal.new("456.7") + end + + test "update_order/2 with invalid data returns error changeset" do + order = order_fixture() + assert {:error, %Ecto.Changeset{}} = Orders.update_order(order, @invalid_attrs) + assert order == Orders.get_order!(order.id) + end + + test "delete_order/1 deletes the order" do + order = order_fixture() + assert {:ok, %Order{}} = Orders.delete_order(order) + assert_raise Ecto.NoResultsError, fn -> Orders.get_order!(order.id) end + end + + test "change_order/1 returns a order changeset" do + order = order_fixture() + assert %Ecto.Changeset{} = Orders.change_order(order) + end + end + + describe "order_line_items" do + alias Bright.Orders.LineItem + + import Bright.OrdersFixtures + + @invalid_attrs %{price: nil, quantity: nil} + + test "list_order_line_items/0 returns all order_line_items" do + line_item = line_item_fixture() + assert Orders.list_order_line_items() == [line_item] + end + + test "get_line_item!/1 returns the line_item with given id" do + line_item = line_item_fixture() + assert Orders.get_line_item!(line_item.id) == line_item + end + + test "create_line_item/1 with valid data creates a line_item" do + valid_attrs = %{price: "120.5", quantity: 42} + + assert {:ok, %LineItem{} = line_item} = Orders.create_line_item(valid_attrs) + assert line_item.price == Decimal.new("120.5") + assert line_item.quantity == 42 + end + + test "create_line_item/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Orders.create_line_item(@invalid_attrs) + end + + test "update_line_item/2 with valid data updates the line_item" do + line_item = line_item_fixture() + update_attrs = %{price: "456.7", quantity: 43} + + assert {:ok, %LineItem{} = line_item} = Orders.update_line_item(line_item, update_attrs) + assert line_item.price == Decimal.new("456.7") + assert line_item.quantity == 43 + end + + test "update_line_item/2 with invalid data returns error changeset" do + line_item = line_item_fixture() + assert {:error, %Ecto.Changeset{}} = Orders.update_line_item(line_item, @invalid_attrs) + assert line_item == Orders.get_line_item!(line_item.id) + end + + test "delete_line_item/1 deletes the line_item" do + line_item = line_item_fixture() + assert {:ok, %LineItem{}} = Orders.delete_line_item(line_item) + assert_raise Ecto.NoResultsError, fn -> Orders.get_line_item!(line_item.id) end + end + + test "change_line_item/1 returns a line_item changeset" do + line_item = line_item_fixture() + assert %Ecto.Changeset{} = Orders.change_line_item(line_item) + end + end +end diff --git a/services/bright/test/bright/patrons_test.exs b/services/bright/test/bright/patrons_test.exs new file mode 100644 index 0000000..e4cedd7 --- /dev/null +++ b/services/bright/test/bright/patrons_test.exs @@ -0,0 +1,63 @@ +defmodule Bright.PatronsTest do + use Bright.DataCase + + alias Bright.Patrons + + describe "patrons" do + alias Bright.Patrons.Patron + + import Bright.PatronsFixtures + + @invalid_attrs %{name: nil, public: nil, lifetime_support_cents: nil} + + test "list_patrons/0 returns all patrons" do + patron = patron_fixture() + assert Patrons.list_patrons() == [patron] + end + + test "get_patron!/1 returns the patron with given id" do + patron = patron_fixture() + assert Patrons.get_patron!(patron.id) == patron + end + + test "create_patron/1 with valid data creates a patron" do + valid_attrs = %{name: "some name", public: true, lifetime_support_cents: 42} + + assert {:ok, %Patron{} = patron} = Patrons.create_patron(valid_attrs) + assert patron.name == "some name" + assert patron.public == true + assert patron.lifetime_support_cents == 42 + end + + test "create_patron/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Patrons.create_patron(@invalid_attrs) + end + + test "update_patron/2 with valid data updates the patron" do + patron = patron_fixture() + update_attrs = %{name: "some updated name", public: false, lifetime_support_cents: 43} + + assert {:ok, %Patron{} = patron} = Patrons.update_patron(patron, update_attrs) + assert patron.name == "some updated name" + assert patron.public == false + assert patron.lifetime_support_cents == 43 + end + + test "update_patron/2 with invalid data returns error changeset" do + patron = patron_fixture() + assert {:error, %Ecto.Changeset{}} = Patrons.update_patron(patron, @invalid_attrs) + assert patron == Patrons.get_patron!(patron.id) + end + + test "delete_patron/1 deletes the patron" do + patron = patron_fixture() + assert {:ok, %Patron{}} = Patrons.delete_patron(patron) + assert_raise Ecto.NoResultsError, fn -> Patrons.get_patron!(patron.id) end + end + + test "change_patron/1 returns a patron changeset" do + patron = patron_fixture() + assert %Ecto.Changeset{} = Patrons.change_patron(patron) + end + end +end diff --git a/services/bright/test/bright/platforms_test.exs b/services/bright/test/bright/platforms_test.exs new file mode 100644 index 0000000..c08a5ca --- /dev/null +++ b/services/bright/test/bright/platforms_test.exs @@ -0,0 +1,115 @@ +defmodule Bright.PlatformsTest do + use Bright.DataCase + + alias Bright.Platforms + + describe "platforms" do + alias Bright.Platforms.Platform + + import Bright.PlatformsFixtures + + @invalid_attrs %{} + + test "list_platforms/0 returns all platforms" do + platform = platform_fixture() + assert Platforms.list_platforms() == [platform] + end + + test "get_platform!/1 returns the platform with given id" do + platform = platform_fixture() + assert Platforms.get_platform!(platform.id) == platform + end + + test "create_platform/1 with valid data creates a platform" do + valid_attrs = %{} + + assert {:ok, %Platform{} = platform} = Platforms.create_platform(valid_attrs) + end + + test "create_platform/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Platforms.create_platform(@invalid_attrs) + end + + test "update_platform/2 with valid data updates the platform" do + platform = platform_fixture() + update_attrs = %{} + + assert {:ok, %Platform{} = platform} = Platforms.update_platform(platform, update_attrs) + end + + test "update_platform/2 with invalid data returns error changeset" do + platform = platform_fixture() + assert {:error, %Ecto.Changeset{}} = Platforms.update_platform(platform, @invalid_attrs) + assert platform == Platforms.get_platform!(platform.id) + end + + test "delete_platform/1 deletes the platform" do + platform = platform_fixture() + assert {:ok, %Platform{}} = Platforms.delete_platform(platform) + assert_raise Ecto.NoResultsError, fn -> Platforms.get_platform!(platform.id) end + end + + test "change_platform/1 returns a platform changeset" do + platform = platform_fixture() + assert %Ecto.Changeset{} = Platforms.change_platform(platform) + end + end + + describe "platforms" do + alias Bright.Platforms.Platform + + import Bright.PlatformsFixtures + + @invalid_attrs %{name: nil, url: nil, icon: nil} + + test "list_platforms/0 returns all platforms" do + platform = platform_fixture() + assert Platforms.list_platforms() == [platform] + end + + test "get_platform!/1 returns the platform with given id" do + platform = platform_fixture() + assert Platforms.get_platform!(platform.id) == platform + end + + test "create_platform/1 with valid data creates a platform" do + valid_attrs = %{name: "some name", url: "some url", icon: "some icon"} + + assert {:ok, %Platform{} = platform} = Platforms.create_platform(valid_attrs) + assert platform.name == "some name" + assert platform.url == "some url" + assert platform.icon == "some icon" + end + + test "create_platform/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Platforms.create_platform(@invalid_attrs) + end + + test "update_platform/2 with valid data updates the platform" do + platform = platform_fixture() + update_attrs = %{name: "some updated name", url: "some updated url", icon: "some updated icon"} + + assert {:ok, %Platform{} = platform} = Platforms.update_platform(platform, update_attrs) + assert platform.name == "some updated name" + assert platform.url == "some updated url" + assert platform.icon == "some updated icon" + end + + test "update_platform/2 with invalid data returns error changeset" do + platform = platform_fixture() + assert {:error, %Ecto.Changeset{}} = Platforms.update_platform(platform, @invalid_attrs) + assert platform == Platforms.get_platform!(platform.id) + end + + test "delete_platform/1 deletes the platform" do + platform = platform_fixture() + assert {:ok, %Platform{}} = Platforms.delete_platform(platform) + assert_raise Ecto.NoResultsError, fn -> Platforms.get_platform!(platform.id) end + end + + test "change_platform/1 returns a platform changeset" do + platform = platform_fixture() + assert %Ecto.Changeset{} = Platforms.change_platform(platform) + end + end +end diff --git a/services/bright/test/bright/shopping_cart_test.exs b/services/bright/test/bright/shopping_cart_test.exs new file mode 100644 index 0000000..d333c17 --- /dev/null +++ b/services/bright/test/bright/shopping_cart_test.exs @@ -0,0 +1,115 @@ +defmodule Bright.ShoppingCartTest do + use Bright.DataCase + + alias Bright.ShoppingCart + + describe "carts" do + alias Bright.ShoppingCart.Cart + + import Bright.ShoppingCartFixtures + + @invalid_attrs %{user_uuid: nil} + + test "list_carts/0 returns all carts" do + cart = cart_fixture() + assert ShoppingCart.list_carts() == [cart] + end + + test "get_cart!/1 returns the cart with given id" do + cart = cart_fixture() + assert ShoppingCart.get_cart!(cart.id) == cart + end + + test "create_cart/1 with valid data creates a cart" do + valid_attrs = %{user_uuid: "7488a646-e31f-11e4-aace-600308960662"} + + assert {:ok, %Cart{} = cart} = ShoppingCart.create_cart(valid_attrs) + assert cart.user_uuid == "7488a646-e31f-11e4-aace-600308960662" + end + + test "create_cart/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = ShoppingCart.create_cart(@invalid_attrs) + end + + test "update_cart/2 with valid data updates the cart" do + cart = cart_fixture() + update_attrs = %{user_uuid: "7488a646-e31f-11e4-aace-600308960668"} + + assert {:ok, %Cart{} = cart} = ShoppingCart.update_cart(cart, update_attrs) + assert cart.user_uuid == "7488a646-e31f-11e4-aace-600308960668" + end + + test "update_cart/2 with invalid data returns error changeset" do + cart = cart_fixture() + assert {:error, %Ecto.Changeset{}} = ShoppingCart.update_cart(cart, @invalid_attrs) + assert cart == ShoppingCart.get_cart!(cart.id) + end + + test "delete_cart/1 deletes the cart" do + cart = cart_fixture() + assert {:ok, %Cart{}} = ShoppingCart.delete_cart(cart) + assert_raise Ecto.NoResultsError, fn -> ShoppingCart.get_cart!(cart.id) end + end + + test "change_cart/1 returns a cart changeset" do + cart = cart_fixture() + assert %Ecto.Changeset{} = ShoppingCart.change_cart(cart) + end + end + + describe "cart_items" do + alias Bright.ShoppingCart.CartItem + + import Bright.ShoppingCartFixtures + + @invalid_attrs %{price_when_carted: nil, quantity: nil} + + test "list_cart_items/0 returns all cart_items" do + cart_item = cart_item_fixture() + assert ShoppingCart.list_cart_items() == [cart_item] + end + + test "get_cart_item!/1 returns the cart_item with given id" do + cart_item = cart_item_fixture() + assert ShoppingCart.get_cart_item!(cart_item.id) == cart_item + end + + test "create_cart_item/1 with valid data creates a cart_item" do + valid_attrs = %{price_when_carted: "120.5", quantity: 42} + + assert {:ok, %CartItem{} = cart_item} = ShoppingCart.create_cart_item(valid_attrs) + assert cart_item.price_when_carted == Decimal.new("120.5") + assert cart_item.quantity == 42 + end + + test "create_cart_item/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = ShoppingCart.create_cart_item(@invalid_attrs) + end + + test "update_cart_item/2 with valid data updates the cart_item" do + cart_item = cart_item_fixture() + update_attrs = %{price_when_carted: "456.7", quantity: 43} + + assert {:ok, %CartItem{} = cart_item} = ShoppingCart.update_cart_item(cart_item, update_attrs) + assert cart_item.price_when_carted == Decimal.new("456.7") + assert cart_item.quantity == 43 + end + + test "update_cart_item/2 with invalid data returns error changeset" do + cart_item = cart_item_fixture() + assert {:error, %Ecto.Changeset{}} = ShoppingCart.update_cart_item(cart_item, @invalid_attrs) + assert cart_item == ShoppingCart.get_cart_item!(cart_item.id) + end + + test "delete_cart_item/1 deletes the cart_item" do + cart_item = cart_item_fixture() + assert {:ok, %CartItem{}} = ShoppingCart.delete_cart_item(cart_item) + assert_raise Ecto.NoResultsError, fn -> ShoppingCart.get_cart_item!(cart_item.id) end + end + + test "change_cart_item/1 returns a cart_item changeset" do + cart_item = cart_item_fixture() + assert %Ecto.Changeset{} = ShoppingCart.change_cart_item(cart_item) + end + end +end diff --git a/services/bright/test/bright/streams_test.exs b/services/bright/test/bright/streams_test.exs new file mode 100644 index 0000000..bd8795d --- /dev/null +++ b/services/bright/test/bright/streams_test.exs @@ -0,0 +1,131 @@ +defmodule Bright.StreamsTest do + use Bright.DataCase + + alias Bright.Streams + + describe "streams" do + alias Bright.Streams.Stream + + import Bright.StreamsFixtures + + @invalid_attrs %{date: nil, title: nil, notes: nil} + + test "list_streams/0 returns all streams" do + stream = stream_fixture() + assert Streams.list_streams() == [stream] + end + + test "get_stream!/1 returns the stream with given id" do + stream = stream_fixture() + assert Streams.get_stream!(stream.id) == stream + end + + test "create_stream/1 with valid data creates a stream" do + valid_attrs = %{date: ~U[2024-12-28 03:31:00Z], title: "some title", notes: "some notes"} + + assert {:ok, %Stream{} = stream} = Streams.create_stream(valid_attrs) + assert stream.date == ~U[2024-12-28 03:31:00Z] + assert stream.title == "some title" + assert stream.notes == "some notes" + end + + test "create_stream/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Streams.create_stream(@invalid_attrs) + end + + test "update_stream/2 with valid data updates the stream" do + stream = stream_fixture() + update_attrs = %{date: ~U[2024-12-29 03:31:00Z], title: "some updated title", notes: "some updated notes"} + + assert {:ok, %Stream{} = stream} = Streams.update_stream(stream, update_attrs) + assert stream.date == ~U[2024-12-29 03:31:00Z] + assert stream.title == "some updated title" + assert stream.notes == "some updated notes" + end + + test "update_stream/2 with invalid data returns error changeset" do + stream = stream_fixture() + assert {:error, %Ecto.Changeset{}} = Streams.update_stream(stream, @invalid_attrs) + assert stream == Streams.get_stream!(stream.id) + end + + test "delete_stream/1 deletes the stream" do + stream = stream_fixture() + assert {:ok, %Stream{}} = Streams.delete_stream(stream) + assert_raise Ecto.NoResultsError, fn -> Streams.get_stream!(stream.id) end + end + + test "change_stream/1 returns a stream changeset" do + stream = stream_fixture() + assert %Ecto.Changeset{} = Streams.change_stream(stream) + end + end + + describe "vods" do + alias Bright.Streams.Vod + + import Bright.StreamsFixtures + + @invalid_attrs %{s3_cdn_url: nil, s3_upload_id: nil, s3_key: nil, s3_bucket: nil, mux_asset_id: nil, mux_playback_id: nil, ipfs_cid: nil, torrent: nil} + + test "list_vods/0 returns all vods" do + vod = vod_fixture() + assert Streams.list_vods() == [vod] + end + + test "get_vod!/1 returns the vod with given id" do + vod = vod_fixture() + assert Streams.get_vod!(vod.id) == vod + end + + test "create_vod/1 with valid data creates a vod" do + valid_attrs = %{s3_cdn_url: "some s3_cdn_url", s3_upload_id: "some s3_upload_id", s3_key: "some s3_key", s3_bucket: "some s3_bucket", mux_asset_id: "some mux_asset_id", mux_playback_id: "some mux_playback_id", ipfs_cid: "some ipfs_cid", torrent: "some torrent"} + + assert {:ok, %Vod{} = vod} = Streams.create_vod(valid_attrs) + assert vod.s3_cdn_url == "some s3_cdn_url" + assert vod.s3_upload_id == "some s3_upload_id" + assert vod.s3_key == "some s3_key" + assert vod.s3_bucket == "some s3_bucket" + assert vod.mux_asset_id == "some mux_asset_id" + assert vod.mux_playback_id == "some mux_playback_id" + assert vod.ipfs_cid == "some ipfs_cid" + assert vod.torrent == "some torrent" + end + + test "create_vod/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Streams.create_vod(@invalid_attrs) + end + + test "update_vod/2 with valid data updates the vod" do + vod = vod_fixture() + update_attrs = %{s3_cdn_url: "some updated s3_cdn_url", s3_upload_id: "some updated s3_upload_id", s3_key: "some updated s3_key", s3_bucket: "some updated s3_bucket", mux_asset_id: "some updated mux_asset_id", mux_playback_id: "some updated mux_playback_id", ipfs_cid: "some updated ipfs_cid", torrent: "some updated torrent"} + + assert {:ok, %Vod{} = vod} = Streams.update_vod(vod, update_attrs) + assert vod.s3_cdn_url == "some updated s3_cdn_url" + assert vod.s3_upload_id == "some updated s3_upload_id" + assert vod.s3_key == "some updated s3_key" + assert vod.s3_bucket == "some updated s3_bucket" + assert vod.mux_asset_id == "some updated mux_asset_id" + assert vod.mux_playback_id == "some updated mux_playback_id" + assert vod.ipfs_cid == "some updated ipfs_cid" + assert vod.torrent == "some updated torrent" + end + + test "update_vod/2 with invalid data returns error changeset" do + vod = vod_fixture() + assert {:error, %Ecto.Changeset{}} = Streams.update_vod(vod, @invalid_attrs) + assert vod == Streams.get_vod!(vod.id) + end + + test "delete_vod/1 deletes the vod" do + vod = vod_fixture() + assert {:ok, %Vod{}} = Streams.delete_vod(vod) + assert_raise Ecto.NoResultsError, fn -> Streams.get_vod!(vod.id) end + end + + test "change_vod/1 returns a vod changeset" do + vod = vod_fixture() + assert %Ecto.Changeset{} = Streams.change_vod(vod) + end + end +end diff --git a/services/bright/test/bright/tags_test.exs b/services/bright/test/bright/tags_test.exs new file mode 100644 index 0000000..efff959 --- /dev/null +++ b/services/bright/test/bright/tags_test.exs @@ -0,0 +1,113 @@ +defmodule Bright.TagsTest do + use Bright.DataCase + + alias Bright.Tags + + describe "tag" do + alias Bright.Tags.Tag + + import Bright.TagsFixtures + + @invalid_attrs %{name: nil} + + test "list_tag/0 returns all tag" do + tag = tag_fixture() + assert Tags.list_tag() == [tag] + end + + test "get_tag!/1 returns the tag with given id" do + tag = tag_fixture() + assert Tags.get_tag!(tag.id) == tag + end + + test "create_tag/1 with valid data creates a tag" do + valid_attrs = %{name: "some name"} + + assert {:ok, %Tag{} = tag} = Tags.create_tag(valid_attrs) + assert tag.name == "some name" + end + + test "create_tag/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Tags.create_tag(@invalid_attrs) + end + + test "update_tag/2 with valid data updates the tag" do + tag = tag_fixture() + update_attrs = %{name: "some updated name"} + + assert {:ok, %Tag{} = tag} = Tags.update_tag(tag, update_attrs) + assert tag.name == "some updated name" + end + + test "update_tag/2 with invalid data returns error changeset" do + tag = tag_fixture() + assert {:error, %Ecto.Changeset{}} = Tags.update_tag(tag, @invalid_attrs) + assert tag == Tags.get_tag!(tag.id) + end + + test "delete_tag/1 deletes the tag" do + tag = tag_fixture() + assert {:ok, %Tag{}} = Tags.delete_tag(tag) + assert_raise Ecto.NoResultsError, fn -> Tags.get_tag!(tag.id) end + end + + test "change_tag/1 returns a tag changeset" do + tag = tag_fixture() + assert %Ecto.Changeset{} = Tags.change_tag(tag) + end + end + + describe "tags" do + alias Bright.Tags.Tag + + import Bright.TagsFixtures + + @invalid_attrs %{name: nil} + + test "list_tags/0 returns all tags" do + tag = tag_fixture() + assert Tags.list_tags() == [tag] + end + + test "get_tag!/1 returns the tag with given id" do + tag = tag_fixture() + assert Tags.get_tag!(tag.id) == tag + end + + test "create_tag/1 with valid data creates a tag" do + valid_attrs = %{name: "some name"} + + assert {:ok, %Tag{} = tag} = Tags.create_tag(valid_attrs) + assert tag.name == "some name" + end + + test "create_tag/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Tags.create_tag(@invalid_attrs) + end + + test "update_tag/2 with valid data updates the tag" do + tag = tag_fixture() + update_attrs = %{name: "some updated name"} + + assert {:ok, %Tag{} = tag} = Tags.update_tag(tag, update_attrs) + assert tag.name == "some updated name" + end + + test "update_tag/2 with invalid data returns error changeset" do + tag = tag_fixture() + assert {:error, %Ecto.Changeset{}} = Tags.update_tag(tag, @invalid_attrs) + assert tag == Tags.get_tag!(tag.id) + end + + test "delete_tag/1 deletes the tag" do + tag = tag_fixture() + assert {:ok, %Tag{}} = Tags.delete_tag(tag) + assert_raise Ecto.NoResultsError, fn -> Tags.get_tag!(tag.id) end + end + + test "change_tag/1 returns a tag changeset" do + tag = tag_fixture() + assert %Ecto.Changeset{} = Tags.change_tag(tag) + end + end +end diff --git a/services/bright/test/bright/urls_test.exs b/services/bright/test/bright/urls_test.exs new file mode 100644 index 0000000..bf306c6 --- /dev/null +++ b/services/bright/test/bright/urls_test.exs @@ -0,0 +1,61 @@ +defmodule Bright.UrlsTest do + use Bright.DataCase + + alias Bright.Urls + + describe "urls" do + alias Bright.Urls.Url + + import Bright.UrlsFixtures + + @invalid_attrs %{link: nil, title: nil} + + test "list_urls/0 returns all urls" do + url = url_fixture() + assert Urls.list_urls() == [url] + end + + test "get_url!/1 returns the url with given id" do + url = url_fixture() + assert Urls.get_url!(url.id) == url + end + + test "create_url/1 with valid data creates a url" do + valid_attrs = %{link: "some link", title: "some title"} + + assert {:ok, %Url{} = url} = Urls.create_url(valid_attrs) + assert url.link == "some link" + assert url.title == "some title" + end + + test "create_url/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Urls.create_url(@invalid_attrs) + end + + test "update_url/2 with valid data updates the url" do + url = url_fixture() + update_attrs = %{link: "some updated link", title: "some updated title"} + + assert {:ok, %Url{} = url} = Urls.update_url(url, update_attrs) + assert url.link == "some updated link" + assert url.title == "some updated title" + end + + test "update_url/2 with invalid data returns error changeset" do + url = url_fixture() + assert {:error, %Ecto.Changeset{}} = Urls.update_url(url, @invalid_attrs) + assert url == Urls.get_url!(url.id) + end + + test "delete_url/1 deletes the url" do + url = url_fixture() + assert {:ok, %Url{}} = Urls.delete_url(url) + assert_raise Ecto.NoResultsError, fn -> Urls.get_url!(url.id) end + end + + test "change_url/1 returns a url changeset" do + url = url_fixture() + assert %Ecto.Changeset{} = Urls.change_url(url) + end + end +end diff --git a/services/bright/test/bright/vtubers_test.exs b/services/bright/test/bright/vtubers_test.exs new file mode 100644 index 0000000..4ad39d4 --- /dev/null +++ b/services/bright/test/bright/vtubers_test.exs @@ -0,0 +1,161 @@ +defmodule Bright.VtubersTest do + use Bright.DataCase + + alias Bright.Vtubers + + describe "vtubers" do + alias Bright.Vtubers.Vtuber + + import Bright.VtubersFixtures + + @invalid_attrs %{image: nil, slug: nil, display_name: nil, chaturbate: nil, twitter: nil, patreon: nil, twitch: nil, tiktok: nil, onlyfans: nil, youtube: nil, linktree: nil, carrd: nil, fansly: nil, pornhub: nil, discord: nil, reddit: nil, throne: nil, instagram: nil, facebook: nil, merch: nil, description_1: nil, description_2: nil, theme_color: nil, fansly_id: nil, chaturbate_id: nil, twitter_id: nil} + + test "list_vtubers/0 returns all vtubers" do + vtuber = vtuber_fixture() + assert Vtubers.list_vtubers() == [vtuber] + end + + test "get_vtuber!/1 returns the vtuber with given id" do + vtuber = vtuber_fixture() + assert Vtubers.get_vtuber!(vtuber.id) == vtuber + end + + test "create_vtuber/1 with valid data creates a vtuber" do + valid_attrs = %{image: "some image", slug: "some slug", display_name: "some display_name", chaturbate: "some chaturbate", twitter: "some twitter", patreon: "some patreon", twitch: "some twitch", tiktok: "some tiktok", onlyfans: "some onlyfans", youtube: "some youtube", linktree: "some linktree", carrd: "some carrd", fansly: "some fansly", pornhub: "some pornhub", discord: "some discord", reddit: "some reddit", throne: "some throne", instagram: "some instagram", facebook: "some facebook", merch: "some merch", description_1: "some description_1", description_2: "some description_2", theme_color: "some theme_color", fansly_id: "some fansly_id", chaturbate_id: "some chaturbate_id", twitter_id: "some twitter_id"} + + assert {:ok, %Vtuber{} = vtuber} = Vtubers.create_vtuber(valid_attrs) + assert vtuber.image == "some image" + assert vtuber.slug == "some slug" + assert vtuber.display_name == "some display_name" + assert vtuber.chaturbate == "some chaturbate" + assert vtuber.twitter == "some twitter" + assert vtuber.patreon == "some patreon" + assert vtuber.twitch == "some twitch" + assert vtuber.tiktok == "some tiktok" + assert vtuber.onlyfans == "some onlyfans" + assert vtuber.youtube == "some youtube" + assert vtuber.linktree == "some linktree" + assert vtuber.carrd == "some carrd" + assert vtuber.fansly == "some fansly" + assert vtuber.pornhub == "some pornhub" + assert vtuber.discord == "some discord" + assert vtuber.reddit == "some reddit" + assert vtuber.throne == "some throne" + assert vtuber.instagram == "some instagram" + assert vtuber.facebook == "some facebook" + assert vtuber.merch == "some merch" + assert vtuber.description_1 == "some description_1" + assert vtuber.description_2 == "some description_2" + assert vtuber.theme_color == "some theme_color" + assert vtuber.fansly_id == "some fansly_id" + assert vtuber.chaturbate_id == "some chaturbate_id" + assert vtuber.twitter_id == "some twitter_id" + end + + test "create_vtuber/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Vtubers.create_vtuber(@invalid_attrs) + end + + test "update_vtuber/2 with valid data updates the vtuber" do + vtuber = vtuber_fixture() + update_attrs = %{image: "some updated image", slug: "some updated slug", display_name: "some updated display_name", chaturbate: "some updated chaturbate", twitter: "some updated twitter", patreon: "some updated patreon", twitch: "some updated twitch", tiktok: "some updated tiktok", onlyfans: "some updated onlyfans", youtube: "some updated youtube", linktree: "some updated linktree", carrd: "some updated carrd", fansly: "some updated fansly", pornhub: "some updated pornhub", discord: "some updated discord", reddit: "some updated reddit", throne: "some updated throne", instagram: "some updated instagram", facebook: "some updated facebook", merch: "some updated merch", description_1: "some updated description_1", description_2: "some updated description_2", theme_color: "some updated theme_color", fansly_id: "some updated fansly_id", chaturbate_id: "some updated chaturbate_id", twitter_id: "some updated twitter_id"} + + assert {:ok, %Vtuber{} = vtuber} = Vtubers.update_vtuber(vtuber, update_attrs) + assert vtuber.image == "some updated image" + assert vtuber.slug == "some updated slug" + assert vtuber.display_name == "some updated display_name" + assert vtuber.chaturbate == "some updated chaturbate" + assert vtuber.twitter == "some updated twitter" + assert vtuber.patreon == "some updated patreon" + assert vtuber.twitch == "some updated twitch" + assert vtuber.tiktok == "some updated tiktok" + assert vtuber.onlyfans == "some updated onlyfans" + assert vtuber.youtube == "some updated youtube" + assert vtuber.linktree == "some updated linktree" + assert vtuber.carrd == "some updated carrd" + assert vtuber.fansly == "some updated fansly" + assert vtuber.pornhub == "some updated pornhub" + assert vtuber.discord == "some updated discord" + assert vtuber.reddit == "some updated reddit" + assert vtuber.throne == "some updated throne" + assert vtuber.instagram == "some updated instagram" + assert vtuber.facebook == "some updated facebook" + assert vtuber.merch == "some updated merch" + assert vtuber.description_1 == "some updated description_1" + assert vtuber.description_2 == "some updated description_2" + assert vtuber.theme_color == "some updated theme_color" + assert vtuber.fansly_id == "some updated fansly_id" + assert vtuber.chaturbate_id == "some updated chaturbate_id" + assert vtuber.twitter_id == "some updated twitter_id" + end + + test "update_vtuber/2 with invalid data returns error changeset" do + vtuber = vtuber_fixture() + assert {:error, %Ecto.Changeset{}} = Vtubers.update_vtuber(vtuber, @invalid_attrs) + assert vtuber == Vtubers.get_vtuber!(vtuber.id) + end + + test "delete_vtuber/1 deletes the vtuber" do + vtuber = vtuber_fixture() + assert {:ok, %Vtuber{}} = Vtubers.delete_vtuber(vtuber) + assert_raise Ecto.NoResultsError, fn -> Vtubers.get_vtuber!(vtuber.id) end + end + + test "change_vtuber/1 returns a vtuber changeset" do + vtuber = vtuber_fixture() + assert %Ecto.Changeset{} = Vtubers.change_vtuber(vtuber) + end + end + + describe "vtubers" do + alias Bright.Vtubers.Vtuber + + import Bright.VtubersFixtures + + @invalid_attrs %{} + + test "list_vtubers/0 returns all vtubers" do + vtuber = vtuber_fixture() + assert Vtubers.list_vtubers() == [vtuber] + end + + test "get_vtuber!/1 returns the vtuber with given id" do + vtuber = vtuber_fixture() + assert Vtubers.get_vtuber!(vtuber.id) == vtuber + end + + test "create_vtuber/1 with valid data creates a vtuber" do + valid_attrs = %{} + + assert {:ok, %Vtuber{} = vtuber} = Vtubers.create_vtuber(valid_attrs) + end + + test "create_vtuber/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Vtubers.create_vtuber(@invalid_attrs) + end + + test "update_vtuber/2 with valid data updates the vtuber" do + vtuber = vtuber_fixture() + update_attrs = %{} + + assert {:ok, %Vtuber{} = vtuber} = Vtubers.update_vtuber(vtuber, update_attrs) + end + + test "update_vtuber/2 with invalid data returns error changeset" do + vtuber = vtuber_fixture() + assert {:error, %Ecto.Changeset{}} = Vtubers.update_vtuber(vtuber, @invalid_attrs) + assert vtuber == Vtubers.get_vtuber!(vtuber.id) + end + + test "delete_vtuber/1 deletes the vtuber" do + vtuber = vtuber_fixture() + assert {:ok, %Vtuber{}} = Vtubers.delete_vtuber(vtuber) + assert_raise Ecto.NoResultsError, fn -> Vtubers.get_vtuber!(vtuber.id) end + end + + test "change_vtuber/1 returns a vtuber changeset" do + vtuber = vtuber_fixture() + assert %Ecto.Changeset{} = Vtubers.change_vtuber(vtuber) + end + end +end diff --git a/services/bright/test/bright_web/controllers/error_html_test.exs b/services/bright/test/bright_web/controllers/error_html_test.exs new file mode 100644 index 0000000..5788cdb --- /dev/null +++ b/services/bright/test/bright_web/controllers/error_html_test.exs @@ -0,0 +1,14 @@ +defmodule BrightWeb.ErrorHTMLTest do + use BrightWeb.ConnCase, async: true + + # Bring render_to_string/4 for testing custom views + import Phoenix.Template + + test "renders 404.html" do + assert render_to_string(BrightWeb.ErrorHTML, "404", "html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(BrightWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + end +end diff --git a/services/bright/test/bright_web/controllers/error_json_test.exs b/services/bright/test/bright_web/controllers/error_json_test.exs new file mode 100644 index 0000000..08c39e3 --- /dev/null +++ b/services/bright/test/bright_web/controllers/error_json_test.exs @@ -0,0 +1,12 @@ +defmodule BrightWeb.ErrorJSONTest do + use BrightWeb.ConnCase, async: true + + test "renders 404" do + assert BrightWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500" do + assert BrightWeb.ErrorJSON.render("500.json", %{}) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/services/bright/test/bright_web/controllers/page_controller_test.exs b/services/bright/test/bright_web/controllers/page_controller_test.exs new file mode 100644 index 0000000..3943091 --- /dev/null +++ b/services/bright/test/bright_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule BrightWeb.PageControllerTest do + use BrightWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, ~p"/") + assert html_response(conn, 200) =~ "Peace of mind from prototype to production" + end +end diff --git a/services/bright/test/bright_web/controllers/platform_controller_test.exs b/services/bright/test/bright_web/controllers/platform_controller_test.exs new file mode 100644 index 0000000..40ff8e9 --- /dev/null +++ b/services/bright/test/bright_web/controllers/platform_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.PlatformControllerTest do + use BrightWeb.ConnCase + + import Bright.PlatformsFixtures + + @create_attrs %{name: "some name", url: "some url", icon: "some icon"} + @update_attrs %{name: "some updated name", url: "some updated url", icon: "some updated icon"} + @invalid_attrs %{name: nil, url: nil, icon: nil} + + describe "index" do + test "lists all platforms", %{conn: conn} do + conn = get(conn, ~p"/platforms") + assert html_response(conn, 200) =~ "Listing Platforms" + end + end + + describe "new platform" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/platforms/new") + assert html_response(conn, 200) =~ "New Platform" + end + end + + describe "create platform" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/platforms", platform: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/platforms/#{id}" + + conn = get(conn, ~p"/platforms/#{id}") + assert html_response(conn, 200) =~ "Platform #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/platforms", platform: @invalid_attrs) + assert html_response(conn, 200) =~ "New Platform" + end + end + + describe "edit platform" do + setup [:create_platform] + + test "renders form for editing chosen platform", %{conn: conn, platform: platform} do + conn = get(conn, ~p"/platforms/#{platform}/edit") + assert html_response(conn, 200) =~ "Edit Platform" + end + end + + describe "update platform" do + setup [:create_platform] + + test "redirects when data is valid", %{conn: conn, platform: platform} do + conn = put(conn, ~p"/platforms/#{platform}", platform: @update_attrs) + assert redirected_to(conn) == ~p"/platforms/#{platform}" + + conn = get(conn, ~p"/platforms/#{platform}") + assert html_response(conn, 200) =~ "some updated name" + end + + test "renders errors when data is invalid", %{conn: conn, platform: platform} do + conn = put(conn, ~p"/platforms/#{platform}", platform: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Platform" + end + end + + describe "delete platform" do + setup [:create_platform] + + test "deletes chosen platform", %{conn: conn, platform: platform} do + conn = delete(conn, ~p"/platforms/#{platform}") + assert redirected_to(conn) == ~p"/platforms" + + assert_error_sent 404, fn -> + get(conn, ~p"/platforms/#{platform}") + end + end + end + + defp create_platform(_) do + platform = platform_fixture() + %{platform: platform} + end +end diff --git a/services/bright/test/bright_web/controllers/product_controller_test.exs b/services/bright/test/bright_web/controllers/product_controller_test.exs new file mode 100644 index 0000000..01a1f22 --- /dev/null +++ b/services/bright/test/bright_web/controllers/product_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.ProductControllerTest do + use BrightWeb.ConnCase + + import Bright.CatalogFixtures + + @create_attrs %{description: "some description", title: "some title", price: "120.5", views: 42} + @update_attrs %{description: "some updated description", title: "some updated title", price: "456.7", views: 43} + @invalid_attrs %{description: nil, title: nil, price: nil, views: nil} + + describe "index" do + test "lists all products", %{conn: conn} do + conn = get(conn, ~p"/products") + assert html_response(conn, 200) =~ "Listing Products" + end + end + + describe "new product" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/products/new") + assert html_response(conn, 200) =~ "New Product" + end + end + + describe "create product" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/products", product: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/products/#{id}" + + conn = get(conn, ~p"/products/#{id}") + assert html_response(conn, 200) =~ "Product #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/products", product: @invalid_attrs) + assert html_response(conn, 200) =~ "New Product" + end + end + + describe "edit product" do + setup [:create_product] + + test "renders form for editing chosen product", %{conn: conn, product: product} do + conn = get(conn, ~p"/products/#{product}/edit") + assert html_response(conn, 200) =~ "Edit Product" + end + end + + describe "update product" do + setup [:create_product] + + test "redirects when data is valid", %{conn: conn, product: product} do + conn = put(conn, ~p"/products/#{product}", product: @update_attrs) + assert redirected_to(conn) == ~p"/products/#{product}" + + conn = get(conn, ~p"/products/#{product}") + assert html_response(conn, 200) =~ "some updated description" + end + + test "renders errors when data is invalid", %{conn: conn, product: product} do + conn = put(conn, ~p"/products/#{product}", product: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Product" + end + end + + describe "delete product" do + setup [:create_product] + + test "deletes chosen product", %{conn: conn, product: product} do + conn = delete(conn, ~p"/products/#{product}") + assert redirected_to(conn) == ~p"/products" + + assert_error_sent 404, fn -> + get(conn, ~p"/products/#{product}") + end + end + end + + defp create_product(_) do + product = product_fixture() + %{product: product} + end +end diff --git a/services/bright/test/bright_web/controllers/stream_controller_test.exs b/services/bright/test/bright_web/controllers/stream_controller_test.exs new file mode 100644 index 0000000..a6e3855 --- /dev/null +++ b/services/bright/test/bright_web/controllers/stream_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.StreamControllerTest do + use BrightWeb.ConnCase + + import Bright.StreamsFixtures + + @create_attrs %{date: ~U[2024-12-28 03:31:00Z], title: "some title", notes: "some notes"} + @update_attrs %{date: ~U[2024-12-29 03:31:00Z], title: "some updated title", notes: "some updated notes"} + @invalid_attrs %{date: nil, title: nil, notes: nil} + + describe "index" do + test "lists all streams", %{conn: conn} do + conn = get(conn, ~p"/streams") + assert html_response(conn, 200) =~ "Listing Streams" + end + end + + describe "new stream" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/streams/new") + assert html_response(conn, 200) =~ "New Stream" + end + end + + describe "create stream" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/streams", stream: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/streams/#{id}" + + conn = get(conn, ~p"/streams/#{id}") + assert html_response(conn, 200) =~ "Stream #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/streams", stream: @invalid_attrs) + assert html_response(conn, 200) =~ "New Stream" + end + end + + describe "edit stream" do + setup [:create_stream] + + test "renders form for editing chosen stream", %{conn: conn, stream: stream} do + conn = get(conn, ~p"/streams/#{stream}/edit") + assert html_response(conn, 200) =~ "Edit Stream" + end + end + + describe "update stream" do + setup [:create_stream] + + test "redirects when data is valid", %{conn: conn, stream: stream} do + conn = put(conn, ~p"/streams/#{stream}", stream: @update_attrs) + assert redirected_to(conn) == ~p"/streams/#{stream}" + + conn = get(conn, ~p"/streams/#{stream}") + assert html_response(conn, 200) =~ "some updated title" + end + + test "renders errors when data is invalid", %{conn: conn, stream: stream} do + conn = put(conn, ~p"/streams/#{stream}", stream: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Stream" + end + end + + describe "delete stream" do + setup [:create_stream] + + test "deletes chosen stream", %{conn: conn, stream: stream} do + conn = delete(conn, ~p"/streams/#{stream}") + assert redirected_to(conn) == ~p"/streams" + + assert_error_sent 404, fn -> + get(conn, ~p"/streams/#{stream}") + end + end + end + + defp create_stream(_) do + stream = stream_fixture() + %{stream: stream} + end +end diff --git a/services/bright/test/bright_web/controllers/tag_controller_test.exs b/services/bright/test/bright_web/controllers/tag_controller_test.exs new file mode 100644 index 0000000..1536432 --- /dev/null +++ b/services/bright/test/bright_web/controllers/tag_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.TagControllerTest do + use BrightWeb.ConnCase + + import Bright.TagsFixtures + + @create_attrs %{name: "some name"} + @update_attrs %{name: "some updated name"} + @invalid_attrs %{name: nil} + + describe "index" do + test "lists all tags", %{conn: conn} do + conn = get(conn, ~p"/tags") + assert html_response(conn, 200) =~ "Listing Tags" + end + end + + describe "new tag" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/tags/new") + assert html_response(conn, 200) =~ "New Tag" + end + end + + describe "create tag" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/tags", tag: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/tags/#{id}" + + conn = get(conn, ~p"/tags/#{id}") + assert html_response(conn, 200) =~ "Tag #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/tags", tag: @invalid_attrs) + assert html_response(conn, 200) =~ "New Tag" + end + end + + describe "edit tag" do + setup [:create_tag] + + test "renders form for editing chosen tag", %{conn: conn, tag: tag} do + conn = get(conn, ~p"/tags/#{tag}/edit") + assert html_response(conn, 200) =~ "Edit Tag" + end + end + + describe "update tag" do + setup [:create_tag] + + test "redirects when data is valid", %{conn: conn, tag: tag} do + conn = put(conn, ~p"/tags/#{tag}", tag: @update_attrs) + assert redirected_to(conn) == ~p"/tags/#{tag}" + + conn = get(conn, ~p"/tags/#{tag}") + assert html_response(conn, 200) =~ "some updated name" + end + + test "renders errors when data is invalid", %{conn: conn, tag: tag} do + conn = put(conn, ~p"/tags/#{tag}", tag: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Tag" + end + end + + describe "delete tag" do + setup [:create_tag] + + test "deletes chosen tag", %{conn: conn, tag: tag} do + conn = delete(conn, ~p"/tags/#{tag}") + assert redirected_to(conn) == ~p"/tags" + + assert_error_sent 404, fn -> + get(conn, ~p"/tags/#{tag}") + end + end + end + + defp create_tag(_) do + tag = tag_fixture() + %{tag: tag} + end +end diff --git a/services/bright/test/bright_web/controllers/url_controller_test.exs b/services/bright/test/bright_web/controllers/url_controller_test.exs new file mode 100644 index 0000000..c86c135 --- /dev/null +++ b/services/bright/test/bright_web/controllers/url_controller_test.exs @@ -0,0 +1,88 @@ +defmodule BrightWeb.UrlControllerTest do + use BrightWeb.ConnCase + + import Bright.UrlsFixtures + + alias Bright.Urls.Url + + @create_attrs %{ + link: "some link", + title: "some title" + } + @update_attrs %{ + link: "some updated link", + title: "some updated title" + } + @invalid_attrs %{link: nil, title: nil} + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all urls", %{conn: conn} do + conn = get(conn, ~p"/api/urls") + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create url" do + test "renders url when data is valid", %{conn: conn} do + conn = post(conn, ~p"/api/urls", url: @create_attrs) + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get(conn, ~p"/api/urls/#{id}") + + assert %{ + "id" => ^id, + "link" => "some link", + "title" => "some title" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/api/urls", url: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update url" do + setup [:create_url] + + test "renders url when data is valid", %{conn: conn, url: %Url{id: id} = url} do + conn = put(conn, ~p"/api/urls/#{url}", url: @update_attrs) + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get(conn, ~p"/api/urls/#{id}") + + assert %{ + "id" => ^id, + "link" => "some updated link", + "title" => "some updated title" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn, url: url} do + conn = put(conn, ~p"/api/urls/#{url}", url: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete url" do + setup [:create_url] + + test "deletes chosen url", %{conn: conn, url: url} do + conn = delete(conn, ~p"/api/urls/#{url}") + assert response(conn, 204) + + assert_error_sent 404, fn -> + get(conn, ~p"/api/urls/#{url}") + end + end + end + + defp create_url(_) do + url = url_fixture() + %{url: url} + end +end diff --git a/services/bright/test/bright_web/controllers/vod_controller_test.exs b/services/bright/test/bright_web/controllers/vod_controller_test.exs new file mode 100644 index 0000000..f788cc3 --- /dev/null +++ b/services/bright/test/bright_web/controllers/vod_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.VodControllerTest do + use BrightWeb.ConnCase + + import Bright.StreamsFixtures + + @create_attrs %{s3_cdn_url: "some s3_cdn_url", s3_upload_id: "some s3_upload_id", s3_key: "some s3_key", s3_bucket: "some s3_bucket", mux_asset_id: "some mux_asset_id", mux_playback_id: "some mux_playback_id", ipfs_cid: "some ipfs_cid", torrent: "some torrent"} + @update_attrs %{s3_cdn_url: "some updated s3_cdn_url", s3_upload_id: "some updated s3_upload_id", s3_key: "some updated s3_key", s3_bucket: "some updated s3_bucket", mux_asset_id: "some updated mux_asset_id", mux_playback_id: "some updated mux_playback_id", ipfs_cid: "some updated ipfs_cid", torrent: "some updated torrent"} + @invalid_attrs %{s3_cdn_url: nil, s3_upload_id: nil, s3_key: nil, s3_bucket: nil, mux_asset_id: nil, mux_playback_id: nil, ipfs_cid: nil, torrent: nil} + + describe "index" do + test "lists all vods", %{conn: conn} do + conn = get(conn, ~p"/vods") + assert html_response(conn, 200) =~ "Listing Vods" + end + end + + describe "new vod" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/vods/new") + assert html_response(conn, 200) =~ "New Vod" + end + end + + describe "create vod" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/vods", vod: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/vods/#{id}" + + conn = get(conn, ~p"/vods/#{id}") + assert html_response(conn, 200) =~ "Vod #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/vods", vod: @invalid_attrs) + assert html_response(conn, 200) =~ "New Vod" + end + end + + describe "edit vod" do + setup [:create_vod] + + test "renders form for editing chosen vod", %{conn: conn, vod: vod} do + conn = get(conn, ~p"/vods/#{vod}/edit") + assert html_response(conn, 200) =~ "Edit Vod" + end + end + + describe "update vod" do + setup [:create_vod] + + test "redirects when data is valid", %{conn: conn, vod: vod} do + conn = put(conn, ~p"/vods/#{vod}", vod: @update_attrs) + assert redirected_to(conn) == ~p"/vods/#{vod}" + + conn = get(conn, ~p"/vods/#{vod}") + assert html_response(conn, 200) =~ "some updated s3_cdn_url" + end + + test "renders errors when data is invalid", %{conn: conn, vod: vod} do + conn = put(conn, ~p"/vods/#{vod}", vod: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Vod" + end + end + + describe "delete vod" do + setup [:create_vod] + + test "deletes chosen vod", %{conn: conn, vod: vod} do + conn = delete(conn, ~p"/vods/#{vod}") + assert redirected_to(conn) == ~p"/vods" + + assert_error_sent 404, fn -> + get(conn, ~p"/vods/#{vod}") + end + end + end + + defp create_vod(_) do + vod = vod_fixture() + %{vod: vod} + end +end diff --git a/services/bright/test/bright_web/controllers/vtuber_controller_test.exs b/services/bright/test/bright_web/controllers/vtuber_controller_test.exs new file mode 100644 index 0000000..0a895a3 --- /dev/null +++ b/services/bright/test/bright_web/controllers/vtuber_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BrightWeb.VtuberControllerTest do + use BrightWeb.ConnCase + + import Bright.VtubersFixtures + + @create_attrs %{} + @update_attrs %{} + @invalid_attrs %{} + + describe "index" do + test "lists all vtubers", %{conn: conn} do + conn = get(conn, ~p"/vtubers") + assert html_response(conn, 200) =~ "Listing Vtubers" + end + end + + describe "new vtuber" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/vtubers/new") + assert html_response(conn, 200) =~ "New Vtuber" + end + end + + describe "create vtuber" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/vtubers", vtuber: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/vtubers/#{id}" + + conn = get(conn, ~p"/vtubers/#{id}") + assert html_response(conn, 200) =~ "Vtuber #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/vtubers", vtuber: @invalid_attrs) + assert html_response(conn, 200) =~ "New Vtuber" + end + end + + describe "edit vtuber" do + setup [:create_vtuber] + + test "renders form for editing chosen vtuber", %{conn: conn, vtuber: vtuber} do + conn = get(conn, ~p"/vtubers/#{vtuber}/edit") + assert html_response(conn, 200) =~ "Edit Vtuber" + end + end + + describe "update vtuber" do + setup [:create_vtuber] + + test "redirects when data is valid", %{conn: conn, vtuber: vtuber} do + conn = put(conn, ~p"/vtubers/#{vtuber}", vtuber: @update_attrs) + assert redirected_to(conn) == ~p"/vtubers/#{vtuber}" + + conn = get(conn, ~p"/vtubers/#{vtuber}") + assert html_response(conn, 200) + end + + test "renders errors when data is invalid", %{conn: conn, vtuber: vtuber} do + conn = put(conn, ~p"/vtubers/#{vtuber}", vtuber: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Vtuber" + end + end + + describe "delete vtuber" do + setup [:create_vtuber] + + test "deletes chosen vtuber", %{conn: conn, vtuber: vtuber} do + conn = delete(conn, ~p"/vtubers/#{vtuber}") + assert redirected_to(conn) == ~p"/vtubers" + + assert_error_sent 404, fn -> + get(conn, ~p"/vtubers/#{vtuber}") + end + end + end + + defp create_vtuber(_) do + vtuber = vtuber_fixture() + %{vtuber: vtuber} + end +end diff --git a/services/bright/test/bright_web/live/post_live_test.exs b/services/bright/test/bright_web/live/post_live_test.exs new file mode 100644 index 0000000..af2b7d9 --- /dev/null +++ b/services/bright/test/bright_web/live/post_live_test.exs @@ -0,0 +1,113 @@ +defmodule BrightWeb.PostLiveTest do + use BrightWeb.ConnCase + + import Phoenix.LiveViewTest + import Bright.BlogFixtures + + @create_attrs %{title: "some title", body: "some body"} + @update_attrs %{title: "some updated title", body: "some updated body"} + @invalid_attrs %{title: nil, body: nil} + + defp create_post(_) do + post = post_fixture() + %{post: post} + end + + describe "Index" do + setup [:create_post] + + test "lists all posts", %{conn: conn, post: post} do + {:ok, _index_live, html} = live(conn, ~p"/posts") + + assert html =~ "Listing Posts" + assert html =~ post.title + end + + test "saves new post", %{conn: conn} do + {:ok, index_live, _html} = live(conn, ~p"/posts") + + assert index_live |> element("a", "New Post") |> render_click() =~ + "New Post" + + assert_patch(index_live, ~p"/posts/new") + + assert index_live + |> form("#post-form", post: @invalid_attrs) + |> render_change() =~ "can't be blank" + + assert index_live + |> form("#post-form", post: @create_attrs) + |> render_submit() + + assert_patch(index_live, ~p"/posts") + + html = render(index_live) + assert html =~ "Post created successfully" + assert html =~ "some title" + end + + test "updates post in listing", %{conn: conn, post: post} do + {:ok, index_live, _html} = live(conn, ~p"/posts") + + assert index_live |> element("#posts-#{post.id} a", "Edit") |> render_click() =~ + "Edit Post" + + assert_patch(index_live, ~p"/posts/#{post}/edit") + + assert index_live + |> form("#post-form", post: @invalid_attrs) + |> render_change() =~ "can't be blank" + + assert index_live + |> form("#post-form", post: @update_attrs) + |> render_submit() + + assert_patch(index_live, ~p"/posts") + + html = render(index_live) + assert html =~ "Post updated successfully" + assert html =~ "some updated title" + end + + test "deletes post in listing", %{conn: conn, post: post} do + {:ok, index_live, _html} = live(conn, ~p"/posts") + + assert index_live |> element("#posts-#{post.id} a", "Delete") |> render_click() + refute has_element?(index_live, "#posts-#{post.id}") + end + end + + describe "Show" do + setup [:create_post] + + test "displays post", %{conn: conn, post: post} do + {:ok, _show_live, html} = live(conn, ~p"/posts/#{post}") + + assert html =~ "Show Post" + assert html =~ post.title + end + + test "updates post within modal", %{conn: conn, post: post} do + {:ok, show_live, _html} = live(conn, ~p"/posts/#{post}") + + assert show_live |> element("a", "Edit") |> render_click() =~ + "Edit Post" + + assert_patch(show_live, ~p"/posts/#{post}/show/edit") + + assert show_live + |> form("#post-form", post: @invalid_attrs) + |> render_change() =~ "can't be blank" + + assert show_live + |> form("#post-form", post: @update_attrs) + |> render_submit() + + assert_patch(show_live, ~p"/posts/#{post}") + + html = render(show_live) + assert html =~ "Post updated successfully" + assert html =~ "some updated title" + end + end +end diff --git a/services/bright/test/support/conn_case.ex b/services/bright/test/support/conn_case.ex new file mode 100644 index 0000000..ea00949 --- /dev/null +++ b/services/bright/test/support/conn_case.ex @@ -0,0 +1,38 @@ +defmodule BrightWeb.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 BrightWeb.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 BrightWeb.Endpoint + + use BrightWeb, :verified_routes + + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import BrightWeb.ConnCase + end + end + + setup tags do + Bright.DataCase.setup_sandbox(tags) + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/services/bright/test/support/data_case.ex b/services/bright/test/support/data_case.ex new file mode 100644 index 0000000..d28ddbf --- /dev/null +++ b/services/bright/test/support/data_case.ex @@ -0,0 +1,58 @@ +defmodule Bright.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 Bright.DataCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + alias Bright.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import Bright.DataCase + end + end + + setup tags do + Bright.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!(Bright.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/services/bright/test/support/fixtures/blog_fixtures.ex b/services/bright/test/support/fixtures/blog_fixtures.ex new file mode 100644 index 0000000..9e3b2f0 --- /dev/null +++ b/services/bright/test/support/fixtures/blog_fixtures.ex @@ -0,0 +1,21 @@ +defmodule Bright.BlogFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Blog` context. + """ + + @doc """ + Generate a post. + """ + def post_fixture(attrs \\ %{}) do + {:ok, post} = + attrs + |> Enum.into(%{ + body: "some body", + title: "some title" + }) + |> Bright.Blog.create_post() + + post + end +end diff --git a/services/bright/test/support/fixtures/catalog_fixtures.ex b/services/bright/test/support/fixtures/catalog_fixtures.ex new file mode 100644 index 0000000..853f34d --- /dev/null +++ b/services/bright/test/support/fixtures/catalog_fixtures.ex @@ -0,0 +1,42 @@ +defmodule Bright.CatalogFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Catalog` context. + """ + + @doc """ + Generate a product. + """ + def product_fixture(attrs \\ %{}) do + {:ok, product} = + attrs + |> Enum.into(%{ + description: "some description", + price: "120.5", + title: "some title", + views: 42 + }) + |> Bright.Catalog.create_product() + + product + end + + @doc """ + Generate a unique category title. + """ + def unique_category_title, do: "some title#{System.unique_integer([:positive])}" + + @doc """ + Generate a category. + """ + def category_fixture(attrs \\ %{}) do + {:ok, category} = + attrs + |> Enum.into(%{ + title: unique_category_title() + }) + |> Bright.Catalog.create_category() + + category + end +end diff --git a/services/bright/test/support/fixtures/orders_fixtures.ex b/services/bright/test/support/fixtures/orders_fixtures.ex new file mode 100644 index 0000000..77c6a0c --- /dev/null +++ b/services/bright/test/support/fixtures/orders_fixtures.ex @@ -0,0 +1,36 @@ +defmodule Bright.OrdersFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Orders` context. + """ + + @doc """ + Generate a order. + """ + def order_fixture(attrs \\ %{}) do + {:ok, order} = + attrs + |> Enum.into(%{ + total_price: "120.5", + user_uuid: "7488a646-e31f-11e4-aace-600308960662" + }) + |> Bright.Orders.create_order() + + order + end + + @doc """ + Generate a line_item. + """ + def line_item_fixture(attrs \\ %{}) do + {:ok, line_item} = + attrs + |> Enum.into(%{ + price: "120.5", + quantity: 42 + }) + |> Bright.Orders.create_line_item() + + line_item + end +end diff --git a/services/bright/test/support/fixtures/patrons_fixtures.ex b/services/bright/test/support/fixtures/patrons_fixtures.ex new file mode 100644 index 0000000..f9dbfb7 --- /dev/null +++ b/services/bright/test/support/fixtures/patrons_fixtures.ex @@ -0,0 +1,22 @@ +defmodule Bright.PatronsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Patrons` context. + """ + + @doc """ + Generate a patron. + """ + def patron_fixture(attrs \\ %{}) do + {:ok, patron} = + attrs + |> Enum.into(%{ + lifetime_support_cents: 42, + name: "some name", + public: true + }) + |> Bright.Patrons.create_patron() + + patron + end +end diff --git a/services/bright/test/support/fixtures/platforms_fixtures.ex b/services/bright/test/support/fixtures/platforms_fixtures.ex new file mode 100644 index 0000000..461fb78 --- /dev/null +++ b/services/bright/test/support/fixtures/platforms_fixtures.ex @@ -0,0 +1,36 @@ +defmodule Bright.PlatformsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Platforms` context. + """ + + @doc """ + Generate a platform. + """ + def platform_fixture(attrs \\ %{}) do + {:ok, platform} = + attrs + |> Enum.into(%{ + + }) + |> Bright.Platforms.create_platform() + + platform + end + + @doc """ + Generate a platform. + """ + def platform_fixture(attrs \\ %{}) do + {:ok, platform} = + attrs + |> Enum.into(%{ + icon: "some icon", + name: "some name", + url: "some url" + }) + |> Bright.Platforms.create_platform() + + platform + end +end diff --git a/services/bright/test/support/fixtures/shopping_cart_fixtures.ex b/services/bright/test/support/fixtures/shopping_cart_fixtures.ex new file mode 100644 index 0000000..a5e57ff --- /dev/null +++ b/services/bright/test/support/fixtures/shopping_cart_fixtures.ex @@ -0,0 +1,42 @@ +defmodule Bright.ShoppingCartFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.ShoppingCart` context. + """ + + @doc """ + Generate a unique cart user_uuid. + """ + def unique_cart_user_uuid do + raise "implement the logic to generate a unique cart user_uuid" + end + + @doc """ + Generate a cart. + """ + def cart_fixture(attrs \\ %{}) do + {:ok, cart} = + attrs + |> Enum.into(%{ + user_uuid: unique_cart_user_uuid() + }) + |> Bright.ShoppingCart.create_cart() + + cart + end + + @doc """ + Generate a cart_item. + """ + def cart_item_fixture(attrs \\ %{}) do + {:ok, cart_item} = + attrs + |> Enum.into(%{ + price_when_carted: "120.5", + quantity: 42 + }) + |> Bright.ShoppingCart.create_cart_item() + + cart_item + end +end diff --git a/services/bright/test/support/fixtures/streams_fixtures.ex b/services/bright/test/support/fixtures/streams_fixtures.ex new file mode 100644 index 0000000..d3775a3 --- /dev/null +++ b/services/bright/test/support/fixtures/streams_fixtures.ex @@ -0,0 +1,43 @@ +defmodule Bright.StreamsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Streams` context. + """ + + @doc """ + Generate a stream. + """ + def stream_fixture(attrs \\ %{}) do + {:ok, stream} = + attrs + |> Enum.into(%{ + date: ~U[2024-12-28 03:31:00Z], + notes: "some notes", + title: "some title" + }) + |> Bright.Streams.create_stream() + + stream + end + + @doc """ + Generate a vod. + """ + def vod_fixture(attrs \\ %{}) do + {:ok, vod} = + attrs + |> Enum.into(%{ + ipfs_cid: "some ipfs_cid", + mux_asset_id: "some mux_asset_id", + mux_playback_id: "some mux_playback_id", + s3_bucket: "some s3_bucket", + s3_cdn_url: "some s3_cdn_url", + s3_key: "some s3_key", + s3_upload_id: "some s3_upload_id", + torrent: "some torrent" + }) + |> Bright.Streams.create_vod() + + vod + end +end diff --git a/services/bright/test/support/fixtures/tags_fixtures.ex b/services/bright/test/support/fixtures/tags_fixtures.ex new file mode 100644 index 0000000..24cbf24 --- /dev/null +++ b/services/bright/test/support/fixtures/tags_fixtures.ex @@ -0,0 +1,39 @@ +defmodule Bright.TagsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Tags` context. + """ + + @doc """ + Generate a tag. + """ + def tag_fixture(attrs \\ %{}) do + {:ok, tag} = + attrs + |> Enum.into(%{ + name: "some name" + }) + |> Bright.Tags.create_tag() + + tag + end + + @doc """ + Generate a unique tag name. + """ + def unique_tag_name, do: "some name#{System.unique_integer([:positive])}" + + @doc """ + Generate a tag. + """ + def tag_fixture(attrs \\ %{}) do + {:ok, tag} = + attrs + |> Enum.into(%{ + name: unique_tag_name() + }) + |> Bright.Tags.create_tag() + + tag + end +end diff --git a/services/bright/test/support/fixtures/urls_fixtures.ex b/services/bright/test/support/fixtures/urls_fixtures.ex new file mode 100644 index 0000000..2892c98 --- /dev/null +++ b/services/bright/test/support/fixtures/urls_fixtures.ex @@ -0,0 +1,21 @@ +defmodule Bright.UrlsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Urls` context. + """ + + @doc """ + Generate a url. + """ + def url_fixture(attrs \\ %{}) do + {:ok, url} = + attrs + |> Enum.into(%{ + link: "some link", + title: "some title" + }) + |> Bright.Urls.create_url() + + url + end +end diff --git a/services/bright/test/support/fixtures/vtubers_fixtures.ex b/services/bright/test/support/fixtures/vtubers_fixtures.ex new file mode 100644 index 0000000..38f5173 --- /dev/null +++ b/services/bright/test/support/fixtures/vtubers_fixtures.ex @@ -0,0 +1,59 @@ +defmodule Bright.VtubersFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Bright.Vtubers` context. + """ + + @doc """ + Generate a vtuber. + """ + def vtuber_fixture(attrs \\ %{}) do + {:ok, vtuber} = + attrs + |> Enum.into(%{ + carrd: "some carrd", + chaturbate: "some chaturbate", + chaturbate_id: "some chaturbate_id", + description_1: "some description_1", + description_2: "some description_2", + discord: "some discord", + display_name: "some display_name", + facebook: "some facebook", + fansly: "some fansly", + fansly_id: "some fansly_id", + image: "some image", + instagram: "some instagram", + linktree: "some linktree", + merch: "some merch", + onlyfans: "some onlyfans", + patreon: "some patreon", + pornhub: "some pornhub", + reddit: "some reddit", + slug: "some slug", + theme_color: "some theme_color", + throne: "some throne", + tiktok: "some tiktok", + twitch: "some twitch", + twitter: "some twitter", + twitter_id: "some twitter_id", + youtube: "some youtube" + }) + |> Bright.Vtubers.create_vtuber() + + vtuber + end + + @doc """ + Generate a vtuber. + """ + def vtuber_fixture(attrs \\ %{}) do + {:ok, vtuber} = + attrs + |> Enum.into(%{ + + }) + |> Bright.Vtubers.create_vtuber() + + vtuber + end +end diff --git a/services/bright/test/test_helper.exs b/services/bright/test/test_helper.exs new file mode 100644 index 0000000..ab163d0 --- /dev/null +++ b/services/bright/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +Ecto.Adapters.SQL.Sandbox.mode(Bright.Repo, :manual)