tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

commit b7055a61a1b2014113baf22a4a263d3d72331183
parent 81051135e22e2ae489da68862e5ac81276f4f77b
Author: mtmn <miro@haravara.org>
Date:   Fri,  8 May 2026 11:26:23 +0200

shirts: add validation for url schema

Diffstat:
Abandeno/README.md | 34++++++++++++++++++++++++++++++++++
Mbandeno/flake.nix | 2+-
Ashirts/README.md | 46++++++++++++++++++++++++++++++++++++++++++++++
Mshirts/config/runtime.exs | 2+-
Dshirts/deploy | 2--
Mshirts/lib/shortener/application.ex | 6------
Mshirts/lib/shortener/db.ex | 18++++++++++--------
Mshirts/lib/shortener/router.ex | 23+++++++++++++++++++----
8 files changed, 111 insertions(+), 22 deletions(-)

diff --git a/bandeno/README.md b/bandeno/README.md @@ -0,0 +1,34 @@ +# bandeno + +A Bandcamp JSON Feed proxy. Returns artist discographies and fan collections as [JSON Feed](https://jsonfeed.org/) for use in feed readers. + +## Running + +```sh +deno task dev +``` + +## Usage + +### Artist feed + +Returns a JSON Feed of all releases on an artist's Bandcamp page. + +``` +GET /?artist=<artist> +``` + +### Fan collection feed + +Returns a JSON Feed of all albums in a fan's Bandcamp collection. + +``` +GET /?fan=<fan> +``` + +## Configuration + +| Variable | Default | Description | +|----------|-------------|--------------------| +| `PORT` | `8000` | Port to listen on | +| `HOST` | `127.0.0.1` | Host to bind to | diff --git a/bandeno/flake.nix b/bandeno/flake.nix @@ -11,7 +11,7 @@ src = ./.; installPhase = '' mkdir -p $out - cp -r . $out/ + cp deno.json deno.lock main.ts $out/ ''; }; }); diff --git a/shirts/README.md b/shirts/README.md @@ -0,0 +1,46 @@ +# shirts + +A URL shortener. Links are stored in a SQLite database and served under a configurable base URL. + +## Building + +```sh +mix deps.get +mix release +``` + +## Usage + +### Create a short link + +```sh +curl -X POST http://localhost:8721/curtail \ + -H "Authorization: Bearer <token>" \ + -H "Content-Type: application/json" \ + -d '{"url": "https://example.com/very/long/path"}' +``` + +Optionally provide a custom code: + +```sh +curl -X POST http://localhost:8721/curtail \ + -H "Authorization: Bearer <token>" \ + -H "Content-Type: application/json" \ + -d '{"url": "https://example.com/very/long/path", "code": "my-code"}' +``` + +### Follow a short link + +``` +GET /s/<code> +``` + +## Configuration + +| Variable | Description | +|------------|--------------------------------------| +| `PORT` | Port to listen on (default: `8721`) | +| `HOST` | Host to bind to (default: `127.0.0.1`) | +| `BASE_URL` | Base URL used when generating links | +| `AUTH_TOKEN` | Bearer token for the create endpoint | +| `DB_PATH` | Path to the SQLite database file | diff --git a/shirts/config/runtime.exs b/shirts/config/runtime.exs @@ -4,7 +4,7 @@ if config_env() == :prod do config :shortener, port: System.get_env("PORT") |> then(&(if &1, do: String.to_integer(&1), else: 4000)), host: System.get_env("HOST") || "127.0.0.1", - auth_token: System.get_env("AUTH_TOKEN") || "changeme", + auth_token: System.fetch_env!("AUTH_TOKEN"), db_path: System.get_env("DB_PATH") || "data.db", base_url: System.get_env("BASE_URL") || "http://localhost" end diff --git a/shirts/deploy b/shirts/deploy @@ -1,2 +0,0 @@ -#!/bin/bash -podman build -t shirt_linkener . && podman save shirt_linkener | ssh clam "sudo podman load && sudo systemctl restart shirt-linkener" diff --git a/shirts/lib/shortener/application.ex b/shirts/lib/shortener/application.ex @@ -23,12 +23,6 @@ defmodule Shortener.Application do end @impl true - def prep_stop(state) do - Logger.info("Shortener application is preparing to stop") - state - end - - @impl true def stop(_state) do Logger.info("Shortener application stopped") :ok diff --git a/shirts/lib/shortener/db.ex b/shirts/lib/shortener/db.ex @@ -47,8 +47,8 @@ defmodule Shortener.DB do with {:ok, stmt} <- Exqlite.Sqlite3.prepare(conn, "INSERT INTO shirts (code, url) VALUES (?1, ?2)"), :ok <- Exqlite.Sqlite3.bind(stmt, [code, url]), - :done <- Exqlite.Sqlite3.step(conn, stmt), - :ok <- Exqlite.Sqlite3.release(conn, stmt) do + :done <- Exqlite.Sqlite3.step(conn, stmt) do + _ = Exqlite.Sqlite3.release(conn, stmt) :ok else {:error, reason} -> {:error, reason} @@ -62,12 +62,14 @@ defmodule Shortener.DB do with {:ok, stmt} <- Exqlite.Sqlite3.prepare(conn, "SELECT url FROM shirts WHERE code = ?1"), :ok <- Exqlite.Sqlite3.bind(stmt, [code]), - {:row, [url]} <- Exqlite.Sqlite3.step(conn, stmt), - :ok <- Exqlite.Sqlite3.release(conn, stmt) do - {:ok, url} - else - :done -> :not_found - {:error, reason} -> {:error, reason} + step_result <- Exqlite.Sqlite3.step(conn, stmt) do + _ = Exqlite.Sqlite3.release(conn, stmt) + + case step_result do + {:row, [url]} -> {:ok, url} + :done -> :not_found + {:error, reason} -> {:error, reason} + end end {:reply, result, conn} diff --git a/shirts/lib/shortener/router.ex b/shirts/lib/shortener/router.ex @@ -36,8 +36,11 @@ defmodule Shortener.Router do {:error, :missing_url} -> json(conn, 422, %{error: "missing required field: url"}) - {:error, reason} -> - json(conn, 409, %{error: "could not save link: #{inspect(reason)}"}) + {:error, :invalid_url} -> + json(conn, 422, %{error: "url must be a valid http or https URL"}) + + {:error, _reason} -> + json(conn, 409, %{error: "could not save link"}) end end @@ -53,6 +56,9 @@ defmodule Shortener.Router do :not_found -> json(conn, 404, %{error: "not found"}) + + {:error, _} -> + json(conn, 500, %{error: "internal error"}) end end @@ -78,8 +84,17 @@ defmodule Shortener.Router do defp fetch_url(conn) do case conn.body_params do - %{"url" => url} when is_binary(url) and url != "" -> {:ok, url} - _ -> {:error, :missing_url} + %{"url" => url} when is_binary(url) and url != "" -> + uri = URI.parse(url) + + if uri.scheme in ["http", "https"] and is_binary(uri.host) and uri.host != "" do + {:ok, url} + else + {:error, :invalid_url} + end + + _ -> + {:error, :missing_url} end end