Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:
DATABASE_PORT: 5432
DATABASE_TEST_NAME: textbin_test

OTP_VERSION: "28.1"
OTP_VERSION: "28.5"
ELIXIR_VERSION: "1.20"

jobs:
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: Conventional Commits

on:
pull_request:
types: [opened, synchronize, reopened, edited]

permissions:
contents: read
pull-requests: write

jobs:
validate-pr-title:
name: Validate title
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: PR Conventional Commit Validation
uses: ytanikin/pr-conventional-commits@1.5.2
with:
task_types: >-
["feat","fix","doc","docs","test","ci","refactor","perf","chore","revert","style","security"]
94 changes: 94 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration


[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"

[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = true
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# Fail on a commit that is not matched by any commit parser.
fail_on_unmatched_commit = false
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order commits topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "oldest"
# Process submodules commits
recurse_submodules = false
11 changes: 0 additions & 11 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,9 @@
beam.packages.erlang_28.rebar3
erlang_28

# Golang
go
golangci-lint
gotests
gomodifytags
gore
gotools
delve

# LSPs
beamPackages.expert
erlang-language-platform
gopls

# Tools
watchman
Expand All @@ -52,7 +42,6 @@
shellcheck
git-cliff
yaml-language-server
goreleaser
postgresql
];

Expand Down
6 changes: 0 additions & 6 deletions lib/textbin/pastes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ defmodule Textbin.Pastes do
|> Repo.insert()
end

def update_paste(%Paste{} = paste, attrs) do
paste
|> Paste.changeset(attrs)
|> Repo.update()
end

def delete_paste(%Paste{} = paste) do
Repo.delete(paste)
end
Expand Down
23 changes: 0 additions & 23 deletions lib/textbin_web/controllers/apiv1/paste_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,29 +121,6 @@ defmodule TextbinWeb.ApiV1.PasteController do
render(conn, :show, paste: paste)
end

def update(conn, %{"id" => id} = params) do
paste = Pastes.get_paste!(id)
paste_params = paste_params(params)

case validate_paste_size(paste_params) do
:ok ->
case Pastes.update_paste(paste, paste_params) do
{:ok, paste} ->
render(conn, :show, paste: paste)

{:error, changeset} ->
render_changeset_errors(conn, changeset)
end

{:error, :too_large} ->
render_too_large(conn)
end
end

defp paste_params(%{"data" => data}) when is_binary(data), do: %{"data" => data}
defp paste_params(%{"paste" => %{"data" => data}}) when is_binary(data), do: %{"data" => data}
defp paste_params(_params), do: %{"data" => nil}

def delete(conn, %{"id" => id}) do
paste = Pastes.get_paste!(id)
{:ok, _paste} = Pastes.delete_paste(paste)
Expand Down
17 changes: 17 additions & 0 deletions lib/textbin_web/live/ui/paste_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ defmodule TextbinWeb.UI.PasteLive do
{:noreply, assign(socket, :paste, Pastes.get_paste!(id))}
end

def handle_event("delete", %{"id" => id}, %{assigns: %{live_action: :index}} = socket) do
paste = Pastes.get_paste!(id)
{:ok, _paste} = Pastes.delete_paste(paste)

{:noreply, stream_delete(socket, :pastes, paste)}
end

def handle_event("delete", %{"id" => id}, %{assigns: %{live_action: :show}} = socket) do
paste = Pastes.get_paste!(id)
{:ok, _paste} = Pastes.delete_paste(paste)

{:noreply,
socket
|> put_flash(:info, "Paste deleted")
|> push_navigate(to: ~p"/pastes")}
end

def render(%{live_action: :show} = assigns), do: detail(assigns)
def render(assigns), do: index(assigns)
end
18 changes: 15 additions & 3 deletions lib/textbin_web/live/ui/paste_live/detail.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,21 @@
{Calendar.strftime(@paste.inserted_at, "%Y-%m-%d %H:%M:%S UTC")}
</time>
</div>
<.link navigate={~p"/pastes"} class="btn btn-sm btn-ghost">
Back to pastes
</.link>
<div class="flex items-center gap-2">
<.link navigate={~p"/pastes"} class="btn btn-sm btn-ghost">
Back to pastes
</.link>
<button
id={"delete-paste-#{@paste.id}"}
type="button"
phx-click="delete"
phx-value-id={@paste.id}
data-confirm="Delete this paste?"
class="btn btn-sm btn-error"
>
<.icon name="hero-trash" class="size-4" /> Delete
</button>
</div>
</div>

<pre
Expand Down
25 changes: 19 additions & 6 deletions lib/textbin_web/live/ui/paste_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,25 @@
>
{paste.id}
</.link>
<time
class="text-xs text-base-content/50"
datetime={DateTime.to_iso8601(paste.inserted_at)}
>
{Calendar.strftime(paste.inserted_at, "%Y-%m-%d %H:%M:%S UTC")}
</time>
<div class="flex items-center gap-3">
<time
class="text-xs text-base-content/50"
datetime={DateTime.to_iso8601(paste.inserted_at)}
>
{Calendar.strftime(paste.inserted_at, "%Y-%m-%d %H:%M:%S UTC")}
</time>
<button
id={"delete-paste-#{paste.id}"}
type="button"
phx-click="delete"
phx-value-id={paste.id}
data-confirm="Delete this paste?"
class="btn btn-ghost btn-xs text-error hover:bg-error/10"
aria-label="Delete paste"
>
<.icon name="hero-trash" class="size-4" />
</button>
</div>
</div>
</article>
</div>
Expand Down
2 changes: 1 addition & 1 deletion lib/textbin_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule TextbinWeb.Router do
scope "/api/v1", TextbinWeb.ApiV1 do
pipe_through :api

resources "/pastes", PasteController, except: [:new, :edit]
resources "/pastes", PasteController, except: [:new, :edit, :update]
end

# Enable LiveDashboard and Swoosh mailbox preview in development
Expand Down
15 changes: 0 additions & 15 deletions test/textbin/pastes_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule Textbin.PastesTest do

describe "pastes" do
@valid_attrs %{data: "some data"}
@update_attrs %{data: "updated data"}
@invalid_attrs %{data: nil}

test "list_pastes/0 returns all pastes" do
Expand Down Expand Up @@ -36,20 +35,6 @@ defmodule Textbin.PastesTest do
assert {:error, %Ecto.Changeset{}} = Pastes.create_paste(@invalid_attrs)
end

test "update_paste/2 with valid data updates the paste" do
{:ok, paste} = Pastes.create_paste(@valid_attrs)

assert {:ok, %Paste{} = paste} = Pastes.update_paste(paste, @update_attrs)
assert paste.data == "updated data"
end

test "update_paste/2 with invalid data returns error changeset" do
{:ok, paste} = Pastes.create_paste(@valid_attrs)

assert {:error, %Ecto.Changeset{}} = Pastes.update_paste(paste, @invalid_attrs)
assert Pastes.get_paste!(paste.id).data == paste.data
end

test "delete_paste/1 deletes the paste" do
{:ok, paste} = Pastes.create_paste(@valid_attrs)

Expand Down
38 changes: 4 additions & 34 deletions test/textbin_web/controllers/apiv1/paste_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule TextbinWeb.ApiV1.PasteControllerTest do

@max_paste_bytes 1_048_576
@create_attrs %{data: "some data"}
@update_attrs %{data: "updated data"}
@invalid_attrs %{data: nil}

describe "index" do
Expand Down Expand Up @@ -118,42 +117,13 @@ defmodule TextbinWeb.ApiV1.PasteControllerTest do
end

describe "update paste" do
test "renders paste when data is valid", %{conn: conn} do
{:ok, paste} = Pastes.create_paste(%{data: "some data"})

conn = patch(conn, ~p"/api/v1/pastes/#{paste.id}", paste: @update_attrs)

assert %{"id" => id} = json_response(conn, 200)["data"]
assert_stored_data(id, "updated data")

conn = get(conn, ~p"/api/v1/pastes/#{id}")

assert %{
"id" => ^id
} = response_data = json_response(conn, 200)["data"]

refute Map.has_key?(response_data, "data")
end

test "renders errors when data is invalid", %{conn: conn} do
test "is not routable", %{conn: conn} do
{:ok, paste} = Pastes.create_paste(%{data: "some data"})

conn = patch(conn, ~p"/api/v1/pastes/#{paste.id}", paste: @invalid_attrs)

assert %{"data" => [_]} = json_response(conn, 422)["errors"]
end

test "rejects data over the configured size limit", %{conn: conn} do
{:ok, paste} = Pastes.create_paste(%{data: "some data"})
too_large_data = String.duplicate("a", @max_paste_bytes + 1)
conn = patch(conn, ~p"/api/v1/pastes/#{paste.id}", paste: %{data: "updated data"})

conn = patch(conn, ~p"/api/v1/pastes/#{paste.id}", %{data: too_large_data})

assert %{
"errors" => %{
"detail" => "Paste data exceeds the maximum size of 1048576 bytes"
}
} = json_response(conn, 413)
assert response(conn, 404)
assert_stored_data(paste.id, "some data")
end
end

Expand Down
Loading
Loading