devel #97

Merged
inhji merged 4 commits from devel into main 2023-06-06 22:01:31 +02:00
14 changed files with 205 additions and 15 deletions

View file

@ -47,20 +47,39 @@ window.liveSocket = liveSocket
const reactRoot = document.querySelector('#react-root')
if (reactRoot) {
const root = createRoot(reactRoot);
root.render(<KBar/>);
const root = createRoot(reactRoot);
root.render(<KBar/>);
}
document
.querySelector("#dark-mode-toggle")
.addEventListener("click", (e) => {
e.preventDefault()
const data = document.documentElement.dataset
if (data["mode"] && data["mode"] == "dark") {
delete data["mode"]
window.localStorage.removeItem("theme")
} else {
data["mode"] = "dark"
window.localStorage.setItem("theme", "dark")
}
})
.querySelector("#dark-mode-toggle")
.addEventListener("click", (e) => {
e.preventDefault()
const data = document.documentElement.dataset
if (data["mode"] && data["mode"] == "dark") {
delete data["mode"]
window.localStorage.removeItem("theme")
} else {
data["mode"] = "dark"
window.localStorage.setItem("theme", "dark")
}
})
document
.querySelectorAll('textarea')
.forEach(e => e.addEventListener('keydown', function(e) {
if (e.key == 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = this.value.substring(0, start) +
"\t" + this.value.substring(end);
// put caret at right position again
this.selectionStart =
this.selectionEnd = start + 1;
}
}))

View file

@ -8,6 +8,10 @@ defmodule Chiya.Accounts do
alias Chiya.Accounts.{User, UserToken, UserNotifier}
defp preload_user(user) do
Repo.preload(user, [:tokens])
end
def has_user?() do
Repo.exists?(User)
end
@ -240,6 +244,21 @@ defmodule Chiya.Accounts do
Repo.update(changeset)
end
## App Tokens
@doc """
Generates a application token.
"""
def generate_app_token(user, app_name, context) do
attrs = UserToken.build_app_token(user, app_name, context)
changeset = UserToken.app_token_changeset(%UserToken{}, attrs)
Repo.insert(changeset)
end
def delete_app_token(id) do
Repo.delete(Repo.get(UserToken, id))
end
## Session
@doc """
@ -256,7 +275,7 @@ defmodule Chiya.Accounts do
"""
def get_user_by_session_token(token) do
{:ok, query} = UserToken.verify_session_token_query(token)
Repo.one(query)
Repo.one(query) |> preload_user()
end
@doc """

View file

@ -11,6 +11,8 @@ defmodule Chiya.Accounts.User do
field :user_image, ChiyaWeb.Uploaders.UserImage.Type
has_many :tokens, Chiya.Accounts.UserToken
timestamps()
end

View file

@ -1,6 +1,7 @@
defmodule Chiya.Accounts.UserToken do
use Ecto.Schema
import Ecto.Query
import Ecto.Changeset
alias Chiya.Accounts.UserToken
@hash_algorithm :sha256
@ -22,6 +23,23 @@ defmodule Chiya.Accounts.UserToken do
timestamps(updated_at: false)
end
def build_app_token(user, app_name, context) do
token = :crypto.strong_rand_bytes(@rand_size)
%{
token: token,
context: context,
user_id: user.id,
sent_to: app_name
}
end
def app_token_changeset(token, attrs) do
token
|> cast(attrs, [:context, :token, :sent_to, :user_id])
|> validate_required([:context, :token, :sent_to, :user_id])
end
@doc """
Generates a token that will be stored in a signed place,
such as session or cookie. As they are signed, those

View file

@ -21,6 +21,11 @@
icon: "hero-user-solid",
name: "Identities"
},
%{
path: ~p"/admin/tokens",
icon: "hero-key-solid",
name: "Tokens"
},
%{
path: ~p"/admin/settings",
icon: "hero-wrench-screwdriver-solid",

View file

@ -76,6 +76,7 @@
<ul class="list-disc list-inside">
<li><a href="#"><del>Wiki</del></a></li>
<li><a href={~p"/admin"}>Admin</a></li>
</ul>
</div>

View file

@ -0,0 +1,47 @@
defmodule ChiyaWeb.TokenController do
use ChiyaWeb, :controller
alias Chiya.Accounts.UserToken
def index(conn, _params) do
tokens = conn.assigns.current_user.tokens
render(conn, :index, tokens: tokens)
end
def show(conn, %{"id" => id}) do
token_id = String.to_integer(id)
tokens = conn.assigns.current_user.tokens
token = Enum.find(tokens, fn t -> t.id == token_id end)
render(conn, :show, token: token)
end
def new(conn, _params) do
changeset =
UserToken.app_token_changeset(%UserToken{}, %{
user_id: conn.assigns.current_user.id,
context: "app"
})
render(conn, :new, changeset: changeset)
end
def create(conn, %{"user_token" => %{"sent_to" => app_name, "context" => context}}) do
case Chiya.Accounts.generate_app_token(conn.assigns.current_user, app_name, context) do
{:ok, token} ->
conn
|> put_flash(:info, "token created successfully.")
|> redirect(to: ~p"/admin/tokens/#{token}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
{:ok, _token} = Chiya.Accounts.delete_app_token(id)
conn
|> put_flash(:info, "Token deleted successfully.")
|> redirect(to: ~p"/admin/tokens")
end
end

View file

@ -0,0 +1,13 @@
defmodule ChiyaWeb.TokenHTML do
use ChiyaWeb, :html
embed_templates "token_html/*"
@doc """
Renders a token form.
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
def token_form(assigns)
end

View file

@ -0,0 +1,8 @@
<.header>
Edit Channel <%= @channel.id %>
<:subtitle>Use this form to manage channel records in your database.</:subtitle>
</.header>
<.token_form changeset={@changeset} action={~p"/admin/channels/#{@channel}"} />
<.back navigate={~p"/admin/channels"}>Back to channels</.back>

View file

@ -0,0 +1,21 @@
<.header>
<.icon name="hero-key" /> Tokens
<:subtitle>Tokens are like keys.</:subtitle>
<:actions>
<.link href={~p"/admin/tokens/new"}>
<.button>New Token</.button>
</.link>
</:actions>
</.header>
<.table id="tokens" rows={@tokens} row_click={&JS.navigate(~p"/admin/tokens/#{&1}")}>
<:col :let={token} label="Id"><%= token.id %></:col>
<:col :let={token} label="Context"><%= token.context %></:col>
<:col :let={token} label="App Name"><%= token.sent_to %></:col>
<:col :let={token} label="Created"><%= pretty_date(token.inserted_at) %></:col>
<:action :let={token}>
<.link href={~p"/admin/tokens/#{token}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>

View file

@ -0,0 +1,8 @@
<.header>
New Token
<:subtitle>Use this form to create new token records in your database.</:subtitle>
</.header>
<.token_form changeset={@changeset} action={~p"/admin/tokens"} />
<.back navigate={~p"/admin/tokens"}>Back to channels</.back>

View file

@ -0,0 +1,17 @@
<.header>
Token <%= @token.id %>
<:subtitle>This is a token.</:subtitle>
<:actions></:actions>
</.header>
<.list>
<:item title="Id"><%= @token.id %></:item>
<:item title="App Name"><%= @token.sent_to %></:item>
<:item title="Context"><%= @token.context %></:item>
<:item title="Created"><%= @token.inserted_at %></:item>
<:item title="Value">
<p class="break-all"><%= :crypto.bytes_to_integer(@token.token) %></p>
</:item>
</.list>
<.back navigate={~p"/admin/tokens"}>Back to channels</.back>

View file

@ -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.
</.error>
<.input field={f[:context]} type="text" label="Context" />
<.input field={f[:sent_to]} type="text" label="Application" />
<.input field={f[:user_id]} type="hidden" />
<:actions>
<.button>Save Token</.button>
</:actions>
</.simple_form>

View file

@ -73,6 +73,7 @@ defmodule ChiyaWeb.Router do
resources "/settings", SettingController, singleton: true
resources "/identities", IdentityController
resources "/comments", CommentController, only: [:index, :show]
resources "/tokens", TokenController, only: [:index, :show, :new, :create, :delete]
get "/notes/import", NoteController, :import_prepare
post "/notes/import", NoteController, :import_run