Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
90b0ef81ef | |||
34429ce640 | |||
098cca6935 | |||
ba0402c7fe | |||
bd99555cd8 | |||
2dbaf1de10 | |||
1c38624712 | |||
032ea859fb | |||
312be30733 | |||
709fb2ce30 | |||
6ac974f1e3 |
32
.gitea/workflows/elixir.yml
Normal file
32
.gitea/workflows/elixir.yml
Normal file
@ -0,0 +1,32 @@
|
||||
name: Base CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Elixir
|
||||
uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0
|
||||
with:
|
||||
elixir-version: '1.14.0' # [Required] Define the Elixir version
|
||||
otp-version: '25.0' # [Required] Define the Erlang/OTP version
|
||||
- name: Restore dependencies cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: deps
|
||||
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
|
||||
restore-keys: ${{ runner.os }}-mix-
|
||||
- name: Install dependencies
|
||||
run: mix deps.get
|
||||
- name: Run tests
|
||||
run: mix test
|
32
.github/workflows/elixir.yml
vendored
Normal file
32
.github/workflows/elixir.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Base CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Elixir
|
||||
uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0
|
||||
with:
|
||||
elixir-version: '1.14.0' # [Required] Define the Elixir version
|
||||
otp-version: '25.0' # [Required] Define the Erlang/OTP version
|
||||
- name: Restore dependencies cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: deps
|
||||
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
|
||||
restore-keys: ${{ runner.os }}-mix-
|
||||
- name: Install dependencies
|
||||
run: mix deps.get
|
||||
- name: Run tests
|
||||
run: mix test
|
11
Makefile
Normal file
11
Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
up:
|
||||
sudo docker compose up -d
|
||||
make migrate-up
|
||||
mix phx.server
|
||||
|
||||
migrate-up:
|
||||
mix ecto.migrate
|
||||
|
||||
migrate-down:
|
||||
mix ecto.rollback
|
||||
|
@ -14,6 +14,13 @@ 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).
|
||||
|
||||
### Dev DB creds
|
||||
From *./config/dev.exs*
|
||||
Host: localhost:5432
|
||||
Database: draincloud_core_dev
|
||||
Username: postgres
|
||||
Password: postgres
|
||||
|
||||
### Some phoenix related links
|
||||
|
||||
* Official website: https://www.phoenixframework.org/
|
||||
|
@ -9,24 +9,24 @@ services:
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
minio:
|
||||
image: quay.io/minio/minio:RELEASE.2024-08-29T01-40-52Z
|
||||
command: server --console-address ":9001" http://minio/data{1...2}
|
||||
hostname: minio
|
||||
volumes:
|
||||
- data-1:/data1
|
||||
- data-2:/data2
|
||||
expose:
|
||||
- "9000"
|
||||
- "9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# minio:
|
||||
# image: quay.io/minio/minio:RELEASE.2024-08-29T01-40-52Z
|
||||
# command: server --console-address ":9001" http://minio/data{1...2}
|
||||
# hostname: minio
|
||||
# volumes:
|
||||
# - data-1:/data1
|
||||
# - data-2:/data2
|
||||
# expose:
|
||||
# - "9000"
|
||||
# - "9001"
|
||||
# environment:
|
||||
# MINIO_ROOT_USER: minioadmin
|
||||
# MINIO_ROOT_PASSWORD: minioadmin
|
||||
# healthcheck:
|
||||
# test: ["CMD", "mc", "ready", "local"]
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
|
||||
volumes:
|
||||
draincore-data:
|
||||
|
@ -1,6 +1,10 @@
|
||||
defmodule DraincloudCore.Auth do
|
||||
def is_logon(_token) do
|
||||
# TODO here
|
||||
true
|
||||
false
|
||||
end
|
||||
|
||||
def auth_cookies(conn = %Plug.Conn{}, user = %DrainCloudCore.Auth.Users{}) do
|
||||
|
||||
end
|
||||
end
|
||||
|
21
lib/draincloud_core/auth/sessions.ex
Normal file
21
lib/draincloud_core/auth/sessions.ex
Normal file
@ -0,0 +1,21 @@
|
||||
defmodule DrainCloudCore.Auth.Session do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
schema "sessions" do
|
||||
field :token, :string
|
||||
field :user_id, :id
|
||||
field :user_agent, :string
|
||||
field :created_at, :utc_datetime
|
||||
field :expires_at, :utc_datetime
|
||||
end
|
||||
|
||||
def changeset(session, params \\ %{}) do
|
||||
session
|
||||
|> cast(params, [:id, :token, :user_id, :user_agent, :created_at, :expires_at])
|
||||
|> validate_required([:id, :token, :user_id, :user_agent, :created_at, :expires_at])
|
||||
end
|
||||
|
||||
|
||||
end
|
45
lib/draincloud_core/auth/sessions_store.ex
Normal file
45
lib/draincloud_core/auth/sessions_store.ex
Normal file
@ -0,0 +1,45 @@
|
||||
defmodule DrainCloudCore.Auth.SessionsStore do
|
||||
@behaviour Plug.Session.Store
|
||||
|
||||
@token_len 64
|
||||
|
||||
alias DrainCloudCore.Auth.SessionsRepo
|
||||
alias DrainCloudCore.Repo, as: Repo
|
||||
|
||||
def init(_opts), do: :ok
|
||||
|
||||
def put(conn, sid, any, opts) do
|
||||
|
||||
end
|
||||
|
||||
def get(conn, cookie, opts) do
|
||||
|
||||
end
|
||||
|
||||
def delete(conn, sid, opts) do
|
||||
|
||||
end
|
||||
|
||||
def new_session(conn, user) do
|
||||
%DrainCloudCore.Auth.Session {
|
||||
user_id: user.id,
|
||||
token: new_token(@token_len),
|
||||
user_agent: user_agent(conn),
|
||||
created_at: DateTime.utc_now(),
|
||||
expires_at: DateTime.add(DateTime.utc_now(), 7*24, :hour)
|
||||
}
|
||||
end
|
||||
|
||||
defp user_agent(conn) do
|
||||
Enum.find_value(conn.req_headers, "", fn x ->
|
||||
case x do
|
||||
{"user-agent", agent} -> agent
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp new_token(length) do
|
||||
:crypto.strong_rand_bytes(length) |> Base.url_encode64 |> binary_part(0, length)
|
||||
end
|
||||
end
|
29
lib/draincloud_core/auth/users.ex
Normal file
29
lib/draincloud_core/auth/users.ex
Normal file
@ -0,0 +1,29 @@
|
||||
defmodule DrainCloudCore.Auth.Users do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias DrainCloudCore.Repo, as: Repo
|
||||
|
||||
schema "users" do
|
||||
field :login, :string
|
||||
field :password, :string, redact: true
|
||||
field :created_at, :utc_datetime
|
||||
field :updated_at, :utc_datetime
|
||||
field :deleted_at, :utc_datetime
|
||||
end
|
||||
|
||||
def changeset(user, params \\ %{}) do
|
||||
user
|
||||
|> cast(params, [:id, :login, :password, :updated_at, :deleted_at])
|
||||
|> validate_required([:id])
|
||||
end
|
||||
|
||||
def add_user(user) do
|
||||
Repo.insert(user)
|
||||
end
|
||||
|
||||
def password_by_login(login) do
|
||||
Repo.all(from u in "users", where: u.login == ^login, select: u.password, limit: 1)
|
||||
end
|
||||
end
|
@ -10,6 +10,7 @@ defmodule DrainCloudCore.Rtc.Application do
|
||||
type: :supervisor
|
||||
}
|
||||
end
|
||||
|
||||
def start_link(_) do
|
||||
children = [
|
||||
DrainCloudCore.Rtc
|
||||
@ -27,27 +28,32 @@ defmodule DrainCloudCore.Rtc do
|
||||
use GenServer
|
||||
|
||||
# Real-time config table schemas
|
||||
@config_web [attributes: [
|
||||
:host,
|
||||
:enable_https,
|
||||
:port,
|
||||
]]
|
||||
@config_web [
|
||||
attributes: [
|
||||
:host,
|
||||
:enable_https,
|
||||
:port
|
||||
]
|
||||
]
|
||||
|
||||
@config_pg [attributes: [
|
||||
:host,
|
||||
:port,
|
||||
:ssl,
|
||||
:user,
|
||||
:password,
|
||||
:db,
|
||||
]]
|
||||
@config_pg [
|
||||
attributes: [
|
||||
:host,
|
||||
:port,
|
||||
:ssl,
|
||||
:user,
|
||||
:password,
|
||||
:db
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@config_s3 [attributes: [
|
||||
:host,
|
||||
:port,
|
||||
# user / secret / secrets etc...
|
||||
]]
|
||||
@config_s3 [
|
||||
attributes: [
|
||||
:host,
|
||||
:port
|
||||
# user / secret / secrets etc...
|
||||
]
|
||||
]
|
||||
|
||||
# def child_spec(opts) do
|
||||
# %{
|
||||
@ -81,6 +87,7 @@ defmodule DrainCloudCore.Rtc do
|
||||
def fetch_config() do
|
||||
# Mnesia.read()
|
||||
end
|
||||
|
||||
def init() do
|
||||
Log.info("[#{__MODULE__}] start initializin RTC subsystems")
|
||||
|
||||
|
@ -8,8 +8,13 @@
|
||||
<%= assigns[:page_title] || "DrainCloudCore" %>
|
||||
</.live_title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ" crossorigin="anonymous"></script>
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
<script
|
||||
src="https://unpkg.com/htmx.org@2.0.2"
|
||||
integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ"
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
</script>
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-white">
|
||||
|
@ -1,12 +1,45 @@
|
||||
defmodule DrainCloudCoreWeb.AuthController do
|
||||
use DrainCloudCoreWeb, :controller
|
||||
alias DrainCloudCoreWeb.Request, as: Request
|
||||
|
||||
def logon(conn, _params) do
|
||||
if Request.hs_token?(conn) do
|
||||
# TODO validate token here
|
||||
import Plug.Conn
|
||||
|
||||
alias DrainCloudCoreWeb.AuthController.RegisterRequest, as: RegisterRequest
|
||||
alias DrainCloudCoreWeb.AuthController.LoginRequest, as: LoginRequest
|
||||
alias :logger, as: Log
|
||||
alias DrainCloudCore.Auth.Users , as: Repo
|
||||
|
||||
def register(conn, _params) do
|
||||
Log.debug(
|
||||
Enum.find_value(conn.req_headers, "", fn x ->
|
||||
case x do
|
||||
{"user-agent", agent} -> agent
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
|> inspect
|
||||
)
|
||||
|
||||
try do
|
||||
RegisterRequest.from_request(conn)
|
||||
|> RegisterRequest.to_model
|
||||
|> Repo.add_user
|
||||
send_resp(conn, 200, Jason.encode! %{ok: true})
|
||||
rescue
|
||||
e in RuntimeError ->
|
||||
Log.error("failed to create new user: #{e}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def login(conn, _params) do
|
||||
try do
|
||||
LoginRequest.from_request(conn)
|
||||
# TODO send cookies and tokens
|
||||
send_resp(conn, 200, Jason.encode! %{ok: true})
|
||||
rescue
|
||||
e in RuntimeError ->
|
||||
Log.error("failed to insert new user: #{e}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,68 @@
|
||||
defmodule DraincloudCoreWeb.AuthController.LoginRequest do
|
||||
@derive [Poison.Encoder, Jason.Encoder]
|
||||
|
||||
defstruct login: "", password: ""
|
||||
|
||||
alias __MODULE__, as: Request
|
||||
alias DrainCloudCore.Auth.Users, as: User
|
||||
alias DrainCloudCoreWeb.Errors.InvalidArgumentException, as: InvalidArgumentException
|
||||
|
||||
def from_request(conn = %Plug.Conn{}) do
|
||||
validate_and_build(conn.params)
|
||||
end
|
||||
|
||||
def to_model(req = %Request{}) do
|
||||
%User{
|
||||
login: req.login,
|
||||
password: req.password
|
||||
}
|
||||
end
|
||||
|
||||
defp validate_and_build(req) do
|
||||
validate(req)
|
||||
|> build
|
||||
end
|
||||
|
||||
defp validate(req) do
|
||||
validate_login(req)
|
||||
|> validate_password
|
||||
end
|
||||
|
||||
defp validate_login(req) do
|
||||
if String.length(Map.get(req, "login")) >= 3 do
|
||||
req
|
||||
else
|
||||
raise InvalidArgumentException, %{
|
||||
message: "login must be 3 symbols at minimum",
|
||||
args: ["login"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_password(req) do
|
||||
if String.length(Map.get(req, "password")) >= 8 do
|
||||
req
|
||||
else
|
||||
raise InvalidArgumentException, %{
|
||||
message: "password must be 8 symbols at minimum",
|
||||
args: ["password"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp build(req) do
|
||||
%Request{
|
||||
login: Map.get(req, "login"),
|
||||
password: Map.get(req, "password") # TODO hash password
|
||||
}
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: __MODULE__ do
|
||||
def to_string(req) do
|
||||
case Jason.encode(req) do
|
||||
{:ok, str} -> str
|
||||
{:error, msg} -> raise "failed to encode login request to string: #{msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,71 @@
|
||||
defmodule DrainCloudCoreWeb.AuthController.RegisterRequest do
|
||||
@derive [Poison.Encoder, Jason.Encoder]
|
||||
|
||||
defstruct login: "", password: ""
|
||||
|
||||
alias __MODULE__, as: Request
|
||||
alias DrainCloudCore.Auth.Users, as: User
|
||||
alias DrainCloudCoreWeb.Errors.InvalidArgumentException, as: InvalidArgumentException
|
||||
|
||||
def from_request(conn = %Plug.Conn{}) do
|
||||
# TODO remove Kernel.inspect calls
|
||||
:logger.debug("[from_request] incoming request: #{Kernel.inspect(conn.params)}")
|
||||
|
||||
validate_and_build(conn.params)
|
||||
end
|
||||
|
||||
def to_model(req = %Request{}) do
|
||||
%User{
|
||||
login: req.login,
|
||||
password: req.password
|
||||
}
|
||||
end
|
||||
|
||||
defp validate_and_build(req) do
|
||||
validate(req)
|
||||
|> build
|
||||
end
|
||||
|
||||
defp validate(req) do
|
||||
validate_login(req)
|
||||
|> validate_password
|
||||
end
|
||||
|
||||
defp validate_login(req) do
|
||||
if String.length(Map.get(req, "login")) >= 3 do
|
||||
req
|
||||
else
|
||||
raise InvalidArgumentException, %{
|
||||
message: "login must be 3 symbols at minimum",
|
||||
args: ["login"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_password(req) do
|
||||
if String.length(Map.get(req, "password")) >= 4 do
|
||||
req
|
||||
else
|
||||
raise InvalidArgumentException, %{
|
||||
message: "password must be 4 symbols at minimum",
|
||||
args: ["password"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp build(req) do
|
||||
%Request{
|
||||
login: Map.get(req, "login"),
|
||||
password: Map.get(req, "password") # TODO hash password
|
||||
}
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: __MODULE__ do
|
||||
def to_string(req) do
|
||||
case Jason.encode(req) do
|
||||
{:ok, str} -> str
|
||||
{:error, msg} -> raise "failed to encode register request to string: #{msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
28
lib/draincloud_core_web/controllers/errors/errors.ex
Normal file
28
lib/draincloud_core_web/controllers/errors/errors.ex
Normal file
@ -0,0 +1,28 @@
|
||||
defmodule DrainCloudCoreWeb.Errors.InvalidArgumentException do
|
||||
@derive [Jason.Encoder]
|
||||
|
||||
alias __MODULE__, as: InvalidArgumentException
|
||||
defexception [:message, :args]
|
||||
|
||||
@impl true
|
||||
def exception(%{message: message, args: args}) do
|
||||
%InvalidArgumentException{message: message, args: args}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def exception(term) do
|
||||
case term do
|
||||
[] -> %InvalidArgumentException{}
|
||||
_ -> %InvalidArgumentException{message: "Error: " <> term}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: __MODULE__ do
|
||||
def to_string(err) do
|
||||
case Jason.encode(err) do
|
||||
{:ok, str} -> str
|
||||
{:error, msg} -> raise "failed to encode register request to string: #{msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -7,5 +7,4 @@ defmodule DrainCloudCoreWeb.MainController do
|
||||
|> put_root_layout(false)
|
||||
|> json(%{data: %{name: "Some Name"}})
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -15,10 +15,6 @@ defmodule DrainCloudCoreWeb.Endpoint do
|
||||
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: :draincloud_core,
|
||||
|
10
lib/draincloud_core_web/error_handler.ex
Normal file
10
lib/draincloud_core_web/error_handler.ex
Normal file
@ -0,0 +1,10 @@
|
||||
defmodule DrainCloudCoreWeb.ErrorHandler do
|
||||
alias DrainCloudCoreWeb.Errors.InvalidArgumentException, as: InvalidArgumentException
|
||||
def handle_reason(reason = %InvalidArgumentException{}), do: Jason.encode!(reason)
|
||||
|
||||
def handle_reason(reason) do
|
||||
:logger.error(inspect(reason))
|
||||
|
||||
Jason.encode!(%{error: "Oops! Something went wrong!"})
|
||||
end
|
||||
end
|
@ -1,24 +1,3 @@
|
||||
defmodule DrainCloudCoreWeb.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 DrainCloudCoreWeb.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: :draincloud_core
|
||||
end
|
||||
|
@ -1,5 +1,9 @@
|
||||
defmodule DrainCloudCoreWeb.Router do
|
||||
use DrainCloudCoreWeb, :router
|
||||
use Plug.ErrorHandler
|
||||
import Plug.Conn
|
||||
|
||||
alias DrainCloudCoreWeb.ErrorHandler, as: ErrorHandler
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
@ -14,24 +18,19 @@ defmodule DrainCloudCoreWeb.Router do
|
||||
plug :accepts, ["json"]
|
||||
end
|
||||
|
||||
scope "/", DrainCloudCoreWeb do
|
||||
scope "/api", DrainCloudCoreWeb do
|
||||
pipe_through :api
|
||||
|
||||
get "/api", MainController, :test
|
||||
post "/register", AuthController, :register
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", DrainCloudCoreWeb do
|
||||
# pipe_through :api
|
||||
# end
|
||||
@impl Plug.ErrorHandler
|
||||
def handle_errors(conn, %{kind: _kind, reason: reason, stack: _stack}) do
|
||||
|
||||
send_resp(conn, 500, ErrorHandler.handle_reason(reason))
|
||||
end
|
||||
|
||||
# Enable LiveDashboard in development
|
||||
if Application.compile_env(:draincloud_core, :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
|
||||
|
1
mix.exs
1
mix.exs
@ -44,6 +44,7 @@ defmodule DrainCloudCore.MixProject do
|
||||
{:phoenix_live_dashboard, "~> 0.8.3"},
|
||||
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
|
||||
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
{:poison, "~> 6.0"},
|
||||
{:heroicons,
|
||||
github: "tailwindlabs/heroicons",
|
||||
tag: "v2.1.1",
|
||||
|
1
mix.lock
1
mix.lock
@ -27,6 +27,7 @@
|
||||
"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_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"poison": {:hex, :poison, "6.0.0", "9bbe86722355e36ffb62c51a552719534257ba53f3271dacd20fbbd6621a583a", [:mix], [{:decimal, "~> 2.1", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "bb9064632b94775a3964642d6a78281c07b7be1319e0016e1643790704e739a2"},
|
||||
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"},
|
||||
|
@ -1,7 +0,0 @@
|
||||
defmodule DrainCloudCore.Repo.Migrations.Users do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
# TODO users table migration
|
||||
end
|
||||
end
|
15
priv/repo/migrations/20240908091209_add_users.exs
Normal file
15
priv/repo/migrations/20240908091209_add_users.exs
Normal file
@ -0,0 +1,15 @@
|
||||
defmodule DrainCloudCore.Repo.Migrations.AddUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:users) do
|
||||
add :login, :string, size: 120
|
||||
add :password, :string
|
||||
add :created_at, :utc_datetime, null: false, default: fragment("CURRENT_TIMESTAMP")
|
||||
add :updated_at, :utc_datetime, null: false, default: fragment("CURRENT_TIMESTAMP")
|
||||
add :deleted_at, :utc_datetime, null: true, default: nil
|
||||
end
|
||||
|
||||
create unique_index(:users, :login)
|
||||
end
|
||||
end
|
15
priv/repo/migrations/20240917204624_sessions.exs
Normal file
15
priv/repo/migrations/20240917204624_sessions.exs
Normal file
@ -0,0 +1,15 @@
|
||||
defmodule DrainCloudCore.Repo.Migrations.Sessions do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:sessions) do
|
||||
add :token, :string
|
||||
add :user_id, references("users", on_delete: :nothing)
|
||||
add :user_agent, :string
|
||||
add :created_at, :utc_datetime
|
||||
add :expires_at, :utc_datetime
|
||||
end
|
||||
|
||||
create unique_index(:sessions, :token)
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
create table users (
|
||||
id bigserial,
|
||||
login varchar(50),
|
||||
password varchar(256)
|
||||
);
|
@ -9,6 +9,7 @@ defmodule DrainCloudCoreWeb.ErrorHTMLTest do
|
||||
end
|
||||
|
||||
test "renders 500.html" do
|
||||
assert render_to_string(DrainCloudCoreWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
|
||||
assert render_to_string(DrainCloudCoreWeb.ErrorHTML, "500", "html", []) ==
|
||||
"Internal Server Error"
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,9 @@ defmodule DrainCloudCoreWeb.ErrorJSONTest do
|
||||
use DrainCloudCoreWeb.ConnCase, async: true
|
||||
|
||||
test "renders 404" do
|
||||
assert DrainCloudCoreWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
|
||||
assert DrainCloudCoreWeb.ErrorJSON.render("404.json", %{}) == %{
|
||||
errors: %{detail: "Not Found"}
|
||||
}
|
||||
end
|
||||
|
||||
test "renders 500" do
|
||||
|
Loading…
Reference in New Issue
Block a user