create gists

This commit is contained in:
zack 2024-10-26 15:01:33 -04:00
parent 79a17290d5
commit 43a8412f06
No known key found for this signature in database
GPG key ID: 5F873416BCF59F35
90 changed files with 1777 additions and 2107 deletions

104
lib/zoeyscomputer/gists.ex Normal file
View file

@ -0,0 +1,104 @@
defmodule Zoeyscomputer.Gists do
@moduledoc """
The Gists context.
"""
import Ecto.Query, warn: false
alias Zoeyscomputer.Repo
alias Zoeyscomputer.Gists.Gist
@doc """
Returns the list of gists.
## Examples
iex> list_gists()
[%Gist{}, ...]
"""
def list_gists do
Repo.all(Gist)
end
@doc """
Gets a single gist.
Raises `Ecto.NoResultsError` if the Gist does not exist.
## Examples
iex> get_gist!(123)
%Gist{}
iex> get_gist!(456)
** (Ecto.NoResultsError)
"""
def get_gist!(id), do: Repo.get!(Gist, id)
@doc """
Creates a gist.
## Examples
iex> create_gist(%{field: value})
{:ok, %Gist{}}
iex> create_gist(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_gist(attrs \\ %{}) do
%Gist{}
|> Gist.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a gist.
## Examples
iex> update_gist(gist, %{field: new_value})
{:ok, %Gist{}}
iex> update_gist(gist, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_gist(%Gist{} = gist, attrs) do
gist
|> Gist.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a gist.
## Examples
iex> delete_gist(gist)
{:ok, %Gist{}}
iex> delete_gist(gist)
{:error, %Ecto.Changeset{}}
"""
def delete_gist(%Gist{} = gist) do
Repo.delete(gist)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking gist changes.
## Examples
iex> change_gist(gist)
%Ecto.Changeset{data: %Gist{}}
"""
def change_gist(%Gist{} = gist, attrs \\ %{}) do
Gist.changeset(gist, attrs)
end
end

View file

@ -0,0 +1,18 @@
defmodule Zoeyscomputer.Gists.Gist do
use Ecto.Schema
import Ecto.Changeset
schema "gists" do
field :code, :string
field :lang, :string
timestamps(type: :utc_datetime)
end
@doc false
def changeset(gist, attrs) do
gist
|> cast(attrs, [:code, :lang])
|> validate_required([:code, :lang])
end
end

View file

@ -0,0 +1,93 @@
defmodule ZoeyscomputerWeb.CodeBlock do
use Phoenix.Component
alias Phoenix.LiveView.JS
@moduledoc """
A code block component with syntax highlighting using Shiki.
## Features:
- Syntax highlighting with Shiki
- Optional line numbers
- Optional highlighted lines
- Optional title
- Copy button
- Catppuccin theme styling
"""
@doc """
Renders a code block with syntax highlighting.
## Examples
<.code_block
code="def hello, do: :world"
language="elixir"
title="Example Code"
line_numbers={true}
highlighted_lines={[1, 3]}
/>
## Options
* `:code` - Required. The code string to highlight
* `:language` - Required. Programming language for syntax highlighting
* `:title` - Optional. Title displayed above the code block
* `:line_numbers` - Optional. Show line numbers (default: false)
* `:highlighted_lines` - Optional. List of line numbers to highlight
"""
attr :code, :string, required: true
attr :language, :string, required: true
attr :title, :string, default: nil
attr :line_numbers, :boolean, default: false
attr :highlighted_lines, :list, default: []
def code_block(assigns) do
# Calculate the number of lines for line numbers
assigns = assign(assigns, :num_lines, String.split(assigns.code, "\n") |> length())
~H"""
<div class="relative ctp-bg-base rounded-lg overflow-hidden">
<%= if @title do %>
<div class="ctp-bg-mantle px-4 py-2 border-b ctp-border-surface0">
<h3 class="ctp-text-text text-sm font-medium"><%= @title %></h3>
</div>
<% end %>
<div class="relative">
<%= if @line_numbers do %>
<div class="absolute left-0 top-0 bottom-0 ctp-bg-crust w-12 flex flex-col items-end pr-2 py-4 ctp-text-surface2 select-none">
<%= for line_num <- 1..@num_lines do %>
<span class={[
"text-sm leading-6",
line_num in @highlighted_lines && "ctp-text-mauve font-medium"
]}>
<%= line_num %>
</span>
<% end %>
</div>
<% end %>
<div class={["overflow-x-auto", @line_numbers && "pl-12"]}>
<pre
class="p-4"
id={"code-block-#{System.unique_integer()}"}
phx-hook="CodeBlockHook"
data-code={@code}
data-language={@language}
data-highlighted-lines={Jason.encode!(@highlighted_lines)}
><code class="text-sm"><%= @code %></code></pre>
</div>
</div>
<div class="ctp-bg-mantle px-4 py-2 border-t ctp-border-surface0 flex justify-end">
<button
type="button"
class="ctp-text-subtext0 hover:ctp-text-text text-sm"
phx-click={JS.dispatch("clipcopy", detail: %{text: @code})}
>
Copy code
</button>
</div>
</div>
"""
end
end

View file

@ -1,29 +1,3 @@
<%!-- <header class="px-4 sm:px-6 lg:px-8"> --%>
<%!-- <div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm"> --%>
<%!-- <div class="flex items-center gap-4"> --%>
<%!-- <a href="/"> --%>
<%!-- <img src={~p"/images/logo.svg"} width="36" /> --%>
<%!-- </a> --%>
<%!-- <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6"> --%>
<%!-- v<%= Application.spec(:phoenix, :vsn) %> --%>
<%!-- </p> --%>
<%!-- </div> --%>
<%!-- <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900"> --%>
<%!-- <a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700"> --%>
<%!-- @elixirphoenix --%>
<%!-- </a> --%>
<%!-- <a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700"> --%>
<%!-- GitHub --%>
<%!-- </a> --%>
<%!-- <a --%>
<%!-- href="https://hexdocs.pm/phoenix/overview.html" --%>
<%!-- class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80" --%>
<%!-- > --%>
<%!-- Get Started <span aria-hidden="true">&rarr;</span> --%>
<%!-- </a> --%>
<%!-- </div> --%>
<%!-- </div> --%>
<%!-- </header> --%>
<main class="bg-ctp-base px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />

View file

@ -8,20 +8,37 @@
<%= assigns[:page_title] || "" %>
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<link phx-track-statuc rel="stylesheet" href={~p"/fonts/Iosevka.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class="ctp-mocha bg-ctp-base text-ctp-overlay0">
<body class="ctp-mocha bg-ctp-base text-base text-ctp-overlay0">
<%= if @current_user do %>
<div class="flex w-full">
<div class="p-4 grow">
<div class="p-4">
<.link
class="text-ctp-mauve font-bold hover:border-b-2 border-ctp-mauve"
class="text-ctp-mauve text-sm font-bold hover:border-b-2 border-ctp-mauve"
navigate={~p"/"}
>
zoey
</.link>
</div>
<div class="p-4">
<.link
class="text-ctp-pink text-sm hover:border-b-2 border-ctp-pink"
navigate={~p"/images"}
>
image
</.link>
</div>
<div class="p-4 grow">
<.link
class="text-ctp-pink text-sm hover:border-b-2 border-ctp-pink"
navigate={~p"/gists"}
>
gists
</.link>
</div>
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
<li class="text-[0.8125rem] leading-6">
<%= @current_user.email %>

View file

@ -0,0 +1,81 @@
defmodule ZoeyscomputerWeb.SearchableDropdown do
use Phoenix.Component
alias Phoenix.LiveView.JS
attr :id, :string, required: true
attr :options, :list, required: true
attr :selected, :string, default: nil
attr :class, :string, default: nil
attr :name, :string, required: true
attr :form, :any, required: true
def searchable_dropdown(assigns) do
~H"""
<div class={["relative w-full", @class]} id={"#{@id}-container"}>
<div class="relative" phx-click-away={JS.hide(to: "##{@id}-dropdown", transition: "fade-out")}>
<input type="hidden" name={@name} value={@selected} id={"#{@id}-input"} />
<button
type="button"
class="flex w-full items-center justify-between rounded-md border border-ctp-surface0 bg-ctp-base px-3 py-2 text-sm text-ctp-text shadow-sm hover:bg-ctp-surface0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-click={
JS.toggle(to: "##{@id}-dropdown", in: "fade-in", out: "fade-out")
|> JS.focus(to: "##{@id}-search")
}
aria-haspopup="listbox"
aria-expanded="false"
>
<span class="block truncate">
<%= @selected || "Select an option..." %>
</span>
<svg
class="h-5 w-5 text-ctp-overlay0 transform transition-transform duration-200"
class={"#{if @selected, do: "rotate-180", else: ""}"}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
id={"#{@id}-dropdown"}
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border border-ctp-surface0 bg-ctp-base shadow-lg hidden transition-all duration-200 ease-in-out"
phx-hook="DropdownAnimation"
>
<div class="p-2">
<input
type="text"
id={"#{@id}-search"}
placeholder="Search..."
class="w-full rounded-md border border-ctp-surface0 bg-ctp-mantle px-3 py-2 text-sm text-ctp-text placeholder-ctp-overlay0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-hook="SearchableDropdown"
data-dropdown-id={@id}
data-form-id={@form.id}
/>
</div>
<ul class="max-h-48 overflow-y-auto py-1" role="listbox" id={"#{@id}-options"}>
<li :for={option <- @options} class="transition-colors duration-150 ease-in-out">
<button
type="button"
class="w-full px-3 py-2 text-left text-sm hover:bg-ctp-surface0 focus:bg-ctp-surface0 focus:outline-none cursor-pointer transition-colors duration-150 ease-in-out"
phx-click={
JS.push("select_language", value: %{language: option}, target: "##{@form.id}")
|> JS.hide(to: "##{@id}-dropdown", transition: "fade-out")
}
phx-target={"##{@form.id}"}
role="option"
data-option={option}
>
<%= option %>
</button>
</li>
</ul>
</div>
</div>
</div>
"""
end
end

View file

@ -0,0 +1,43 @@
defmodule ZoeyscomputerWeb.GistController do
use ZoeyscomputerWeb, :controller
alias Zoeyscomputer.Gists
alias Zoeyscomputer.Gists.Gist
action_fallback ZoeyscomputerWeb.FallbackController
def index(conn, _params) do
gists = Gists.list_gists()
render(conn, :index, gists: gists)
end
def create(conn, %{"gist" => gist_params}) do
with {:ok, %Gist{} = gist} <- Gists.create_gist(gist_params) do
conn
|> put_status(:created)
|> put_resp_header("location", ~p"/api/gists/#{gist}")
|> render(:show, gist: gist)
end
end
def show(conn, %{"id" => id}) do
gist = Gists.get_gist!(id)
render(conn, :show, gist: gist)
end
def update(conn, %{"id" => id, "gist" => gist_params}) do
gist = Gists.get_gist!(id)
with {:ok, %Gist{} = gist} <- Gists.update_gist(gist, gist_params) do
render(conn, :show, gist: gist)
end
end
def delete(conn, %{"id" => id}) do
gist = Gists.get_gist!(id)
with {:ok, %Gist{}} <- Gists.delete_gist(gist) do
send_resp(conn, :no_content, "")
end
end
end

View file

@ -0,0 +1,25 @@
defmodule ZoeyscomputerWeb.GistJSON do
alias Zoeyscomputer.Gists.Gist
@doc """
Renders a list of gists.
"""
def index(%{gists: gists}) do
%{data: for(gist <- gists, do: data(gist))}
end
@doc """
Renders a single gist.
"""
def show(%{gist: gist}) do
%{data: data(gist)}
end
defp data(%Gist{} = gist) do
%{
id: gist.id,
code: gist.code,
lang: gist.lang
}
end
end

View file

@ -1,9 +0,0 @@
<div class="container p-4 flex justify-center align-middle">
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
<h1 class="font-bold text-ctp-mauve">zoey</h1>
<p class="text-ctp-text"><i>Software Engineer 🏳️‍⚧️</i></p>
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
Currently cooking this up, stay tuned... in the meantime, you can monitor my server's resources.
</p>
</div>
</div>

View file

@ -0,0 +1,83 @@
defmodule ZoeyscomputerWeb.GistLive.FormComponent do
use ZoeyscomputerWeb, :live_component
alias Zoeyscomputer.Gists
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage gist records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="gist-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:code]} type="text" label="Code" />
<.input field={@form[:lang]} type="text" label="Lang" />
<:actions>
<.button phx-disable-with="Saving...">Save Gist</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{gist: gist} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Gists.change_gist(gist))
end)}
end
@impl true
def handle_event("validate", %{"gist" => gist_params}, socket) do
changeset = Gists.change_gist(socket.assigns.gist, gist_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def handle_event("save", %{"gist" => gist_params}, socket) do
save_gist(socket, socket.assigns.action, gist_params)
end
defp save_gist(socket, :edit, gist_params) do
case Gists.update_gist(socket.assigns.gist, gist_params) do
{:ok, gist} ->
notify_parent({:saved, gist})
{:noreply,
socket
|> put_flash(:info, "Gist updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_gist(socket, :new, gist_params) do
case Gists.create_gist(gist_params) do
{:ok, gist} ->
notify_parent({:saved, gist})
{:noreply,
socket
|> put_flash(:info, "Gist 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

View file

@ -0,0 +1,47 @@
defmodule ZoeyscomputerWeb.GistLive.Index do
use ZoeyscomputerWeb, :live_view
alias Zoeyscomputer.Gists
alias Zoeyscomputer.Gists.Gist
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :gists, Gists.list_gists())}
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 Gist")
|> assign(:gist, Gists.get_gist!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Gist")
|> assign(:gist, %Gist{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Gists")
|> assign(:gist, nil)
end
@impl true
def handle_info({ZoeyscomputerWeb.GistLive.FormComponent, {:saved, gist}}, socket) do
{:noreply, stream_insert(socket, :gists, gist)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
gist = Gists.get_gist!(id)
{:ok, _} = Gists.delete_gist(gist)
{:noreply, stream_delete(socket, :gists, gist)}
end
end

View file

@ -0,0 +1,42 @@
<.header>
Listing Gists
<:actions>
<.link patch={~p"/gists/new"}>
<.button>New Gist</.button>
</.link>
</:actions>
</.header>
<.table
id="gists"
rows={@streams.gists}
row_click={fn {_id, gist} -> JS.navigate(~p"/gists/#{gist}") end}
>
<:col :let={{_id, gist}} label="Code"><%= gist.code %></:col>
<:col :let={{_id, gist}} label="Lang"><%= gist.lang %></:col>
<:action :let={{_id, gist}}>
<div class="sr-only">
<.link navigate={~p"/gists/#{gist}"}>Show</.link>
</div>
<.link patch={~p"/gists/#{gist}/edit"}>Edit</.link>
</:action>
<:action :let={{id, gist}}>
<.link
phx-click={JS.push("delete", value: %{id: gist.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
<.modal :if={@live_action in [:new, :edit]} id="gist-modal" show on_cancel={JS.patch(~p"/gists")}>
<.live_component
module={ZoeyscomputerWeb.GistLive.FormComponent}
id={@gist.id || :new}
title={@page_title}
action={@live_action}
gist={@gist}
patch={~p"/gists"}
/>
</.modal>

View file

@ -0,0 +1,21 @@
defmodule ZoeyscomputerWeb.GistLive.Show do
use ZoeyscomputerWeb, :live_view
alias Zoeyscomputer.Gists
@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(:gist, Gists.get_gist!(id))}
end
defp page_title(:show), do: "Show Gist"
defp page_title(:edit), do: "Edit Gist"
end

View file

@ -0,0 +1,27 @@
<.header>
Gist <%= @gist.id %>
<:subtitle>This is a gist record from your database.</:subtitle>
<:actions>
<.link patch={~p"/gists/#{@gist}/show/edit"} phx-click={JS.push_focus()}>
<.button>Edit gist</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Code"><%= @gist.code %></:item>
<:item title="Lang"><%= @gist.lang %></:item>
</.list>
<.back navigate={~p"/gists"}>Back to gists</.back>
<.modal :if={@live_action == :edit} id="gist-modal" show on_cancel={JS.patch(~p"/gists/#{@gist}")}>
<.live_component
module={ZoeyscomputerWeb.GistLive.FormComponent}
id={@gist.id}
title={@page_title}
action={@live_action}
gist={@gist}
patch={~p"/gists/#{@gist}"}
/>
</.modal>

View file

@ -1,5 +1,4 @@
defmodule ZoeyscomputerWeb.HomeLive do
alias Hex.API.Key
use ZoeyscomputerWeb, :live_view
def mount(_params, _session, socket) do
@ -40,8 +39,8 @@ defmodule ZoeyscomputerWeb.HomeLive do
def render(assigns) do
~H"""
<div class="container p-4 flex justify-center align-middle">
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
<div class="container p-8 flex justify-center align-middle">
<div class="border border-ctp-overlay0 rounded-md p-8 flex-col flex items-center">
<h1 class="font-bold text-ctp-mauve">zoey</h1>
<p class="text-ctp-text"><i>Software Engineer 🏳</i></p>
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">

View file

@ -1,82 +0,0 @@
defmodule ZoeyscomputerWeb.LinkLive.FormComponent do
use ZoeyscomputerWeb, :live_component
alias Zoeyscomputer.Links
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage link records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="link-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:url]} type="text" label="Url" />
<:actions>
<.button phx-disable-with="Saving...">Save Link</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{link: link} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Links.change_link(link))
end)}
end
@impl true
def handle_event("validate", %{"link" => link_params}, socket) do
changeset = Links.change_link(socket.assigns.link, link_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def handle_event("save", %{"link" => link_params}, socket) do
save_link(socket, socket.assigns.action, link_params)
end
defp save_link(socket, :edit, link_params) do
case Links.update_link(socket.assigns.link, link_params) do
{:ok, link} ->
notify_parent({:saved, link})
{:noreply,
socket
|> put_flash(:info, "Link updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_link(socket, :new, link_params) do
case Links.create_link(link_params) do
{:ok, link} ->
notify_parent({:saved, link})
{:noreply,
socket
|> put_flash(:info, "Link 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

View file

@ -1,39 +0,0 @@
defmodule ZoeyscomputerWeb.LinkLive.Index do
use ZoeyscomputerWeb, :live_view
alias Zoeyscomputer.Links
def mount(_params, _session, socket) do
user_id = socket.assigns.current_user.id
changeset = Links.Link.changeset(%Links.Link{})
socket =
socket
|> assign(:links, Links.list_links(user_id))
|> assign(:form, to_form(changeset))
{:ok, socket}
end
def handle_event("submit", %{"link" => link_params}, socket) do
params =
link_params
|> Map.put("user_id", socket.assigns.current_user.id)
case Links.create_link(params) do
{:ok, link} ->
socket =
socket
|> assign(:links, [link | socket.assigns.links])
{:noreply, socket}
{:error, changeset} ->
socket
|> assign(:form, to_form(changeset))
{:noreply, socket}
end
end
end

View file

@ -1,30 +0,0 @@
<div class="flex gap-2">
<h1 class="text-2xl grow font-bold">Links</h1>
<.link
navigate={~p"/links/new"}
class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md"
>
Add Link
</.link>
</div>
<div class="divide-y">
<div :for={link <- @links}>
<div>
<div class="font-bold"><%= link.url %></div>
<div class="text-sm"><%= link.inserted_at %></div>
</div>
</div>
</div>
<.form for={@form} phx-submit="submit">
<div class="flex gap-2 items-end">
<div class="grow">
<.input field={@form[:url]} type="text" label="url" />
</div>
<button class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md">
Create
</button>
</div>
</.form>

View file

@ -1,37 +0,0 @@
defmodule ZoeyscomputerWeb.LinkLive.New do
use ZoeyscomputerWeb, :live_view
alias Zoeyscomputer.Links
def mount(_params, _session, socket) do
changeset = Links.Link.changeset(%Links.Link{})
socket =
socket
|> assign(:form, to_form(changeset))
{:ok, socket}
end
def handle_event("submit", %{"link" => link_params}, socket) do
params =
link_params
|> Map.put("user_id", socket.assigns.current_user.id)
case Links.create_link(params) do
{:ok, _link} ->
socket =
socket
|> put_flash(:info, "Link created successfully")
|> push_navigate(to: ~p"/links")
{:noreply, socket}
{:error, changeset} ->
socket
|> assign(:form, to_form(changeset))
{:noreply, socket}
end
end
end

View file

@ -1,12 +0,0 @@
<h1 class="text-2xl grow font-bold mb-6">Create a new link</h1>
<.form for={@form} phx-submit="submit">
<div class="flex gap-2 items-end">
<div class="grow">
<.input field={@form[:url]} type="text" label="url" />
</div>
<button class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md">
Create
</button>
</div>
</.form>

View file

@ -1,6 +1,5 @@
defmodule ZoeyscomputerWeb.DiscordHandler do
require Logger
alias ElixirSense.Log
alias ExAws.S3
import Plug.Conn
import Mogrify
@ -15,7 +14,7 @@ defmodule ZoeyscomputerWeb.DiscordHandler do
def call(%{path_info: ["images", _id | _]} = conn, _opts) do
user_agent = List.first(get_req_header(conn, "user-agent"))
request_id = Logger.metadata()[:request_id]
Logger.metadata()[:request_id]
Logger.info("Processing image request:\n user_agent: #{user_agent}")

View file

@ -1,6 +1,5 @@
defmodule ZoeyscomputerWeb.Router do
require Logger
alias ExAws.S3
use ZoeyscomputerWeb, :router
import ZoeyscomputerWeb.UserAuth
@ -29,9 +28,11 @@ defmodule ZoeyscomputerWeb.Router do
pipe_through :api
resources "/images", ImageController, except: [:create, :edit]
resources "/gists", GistController, except: [:new, :edit]
pipe_through [:api_authentication]
post "/images/create", ImageController, :create
post "/gists/create", GistController, :create
end
# Enable LiveDashboard and Swoosh mailbox preview in development
@ -77,15 +78,17 @@ defmodule ZoeyscomputerWeb.Router do
on_mount: [{ZoeyscomputerWeb.UserAuth, :ensure_authenticated}] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
live "/links", LinkLive.Index
live "/links/new", LinkLive.New
live "/images/new", ImageLive.Index, :new
live "/gists/new", GistLive.Index, :new
live "/images/:id/edit", ImageLive.Index, :edit
live "/gists/:id/edit", GistLive.Index, :edit
live "/images", ImageLive.Index, :index
live "/gists", GistLive.Index
live "/images/:id/show/edit", ImageLive.Show, :edit
live "/gists/:id/show/edit", GistLive.Show, :edit
live "/api-keys", ApiKeyLive.Index, :index
live "/api-keys/new", ApiKeyLive.Index, :new
@ -108,27 +111,7 @@ defmodule ZoeyscomputerWeb.Router do
live "/", HomeLive, :index
live "/images/:id", ImageLive.Show, :show
end
end
scope "/", ZoeyscomputerWeb do
pipe_through [:browser, :require_authenticated_user]
end
defp download_from_s3(bucket, key) do
case S3.get_object(bucket, key) |> ExAws.request() do
{:ok, %{body: image_binary, headers: headers}} ->
content_type =
Enum.find_value(headers, fn
{"Content-Type", value} -> value
{"content-type", value} -> value
_ -> nil
end)
{:ok, image_binary, content_type || "application/octet-stream"}
error ->
error
live "/gists/:id", GistLive.Show, :show
end
end
end