From 238dc9645406827b2e8a87f6503b0159cf4d06f3 Mon Sep 17 00:00:00 2001 From: poslop Date: Tue, 8 Sep 2026 23:54:54 -0500 Subject: [PATCH] README rework: user-first structure, features, screenshots, config table; API detail moved to docs/API.md (#47) --- README.md | 115 +++++++++++++++++----------------------------------- docs/API.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 78 deletions(-) create mode 100644 docs/API.md diff --git a/README.md b/README.md index be2435f..25eb22c 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,51 @@ # Palette -Fast, self-hosted pastebin with paste cans, password lock, expiry, custom URLs, and an API-first design. +Palette is a fast, self-hosted pastebin. One Go binary, a SQLite database, and +a web UI for sharing code and text with links that expire on your terms. -## Quick start +## Features + +- Paste cans — bundle notes, text, and files into one shareable page +- Password lock — protect individual pastes with a password +- Custom expiry — from 1 minute up to 1 year, or never +- Burn after N reads — a paste that vanishes after a chosen number of reads +- Custom URLs — reserve `/my-snippet` instead of a random slug +- Syntax highlighting with language auto-detection (go-enry) +- Rate limiting on create and unlock +- Saved page — see and manage everything created from your browser +- API-first — every UI action is also a plain HTTP call +- Single binary — templates and assets are embedded; no external deps + +## Get Started + +### Build from source + +Requires Go 1.21+. ```bash go build -o palette . ./palette -# UI at http://localhost:8080 +# open http://localhost:8080 ``` -## Docker +### Docker ```bash -docker build -t palette . -docker run -p 8080:8080 -v palette-data:/data palette +docker run -p 8080:8080 -v palette-data:/data git.archfox.org/poslop/palette ``` +The SQLite database lives in the `/data` volume inside the container. + +## Screenshots + +| | | +|---|---| +| ![Editor](docs/palette-previews/pastel-lavender-new.png) | ![Paste view](docs/palette-previews/pastel-lavender-paste.png) | +| ![History](docs/palette-previews/midnight-history.png) | | + ## Configuration -| Env var | Default | Description | +| Setting | Default | Description | |---|---|---| | `PALETTE_ADDR` | `:8080` | Listen address | | `PALETTE_DB` | `palette.db` | SQLite database path | @@ -28,82 +54,15 @@ docker run -p 8080:8080 -v palette-data:/data palette ## API -### Create paste +Create a paste with one call: + ```bash curl -X POST http://localhost:8080/api/pastes \ -H "Content-Type: application/json" \ - -d '{ - "content": "print(hello)", - "title": "my snippet", - "language": "python", - "expires_in": "168h", - "password": "optional", - "custom_slug": "optional", - "burn_after_read": false, - "visibility": "public" - }' + -d '{"content": "print(hello)", "language": "python", "expires_in": "168h"}' ``` -Response includes `id`, `url`, `raw_url`, `api_url`, and a one-time `deletion_token`. - -### Get paste -```bash -curl http://localhost:8080/api/pastes/{id} -# password-protected pastes: -curl "http://localhost:8080/api/pastes/{id}?password=secret" -# or via header: X-Paste-Password: secret -``` - -### Raw content -```bash -curl http://localhost:8080/raw/{id} -``` - -### Soft delete -```bash -curl -X DELETE http://localhost:8080/api/pastes/{id} -``` - -### Hard delete (requires deletion token) -```bash -curl -X DELETE "http://localhost:8080/api/pastes/{id}/redeem?token=TOKEN" -``` - -### Public history -```bash -curl "http://localhost:8080/api/public?limit=25&offset=0" -``` - -### Create can (bundle of items) -```bash -curl -X POST http://localhost:8080/api/pastes/can \ - -F "title=My bundle" \ - -F "expires_in=48h" \ - -F 'json_items=[{"title":"notes.txt","content":"some notes"}]' \ - -F "files=@screenshot.png" \ - -F "files=@log.txt" -``` - -### Get can + items -```bash -curl http://localhost:8080/api/cans/{id} -curl http://localhost:8080/api/cans/{id}/items/{item_id} -``` - -## Expiry and deletion - -- Expired pastes are soft-deleted by a background sweeper (runs every minute). -- Soft-deleted pastes are hard-deleted after a 7-day grace period. -- Deletion tokens allow immediate hard delete. -- Burn-after-read pastes are soft-deleted on first read. - -## Web pages - -- `/new` — create a paste -- `/history` — public paste history -- `/{id}` — view a paste -- `/unlock/{id}` — password gate for protected pastes -- `/raw/{id}` — raw content with original content type +Full API docs: [docs/API.md](docs/API.md). ## CI diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..cd9562e --- /dev/null +++ b/docs/API.md @@ -0,0 +1,107 @@ +# Palette API + +All endpoints are JSON unless noted. The web UI is served from the same port. + +## Create paste + +```bash +curl -X POST http://localhost:8080/api/pastes \ + -H "Content-Type: application/json" \ + -d '{ + "content": "print(hello)", + "title": "my snippet", + "language": "python", + "expires_in": "168h", + "password": "optional", + "custom_slug": "optional", + "burn_after_read": false, + "burn_after_reads": 1, + "visibility": "public" + }' +``` + +- `expires_in` is a Go duration string (`90m`, `6h`, `336h`). Omit for no expiry. +- `visibility` is `public` or `unlisted`. +- `burn_after_reads` sets how many reads the paste survives (default 1 when + `burn_after_read` is true). A read is counted per unique viewer session; + the same viewer returning within 15 minutes does not count again. +- Response includes `id`, `url`, `raw_url`, `api_url`, `expires_at`, + `created_at`, and a one-time `deletion_token`. + +Errors: `400` invalid body/content too large/duplicate slug, `401` password +required, `404` paste expired/burned/gone, `413` content exceeds max bytes, +`429` rate limited. + +## Get paste + +```bash +curl http://localhost:8080/api/pastes/{id} +# password-protected pastes: +curl "http://localhost:8080/api/pastes/{id}?password=secret" +# or via header: X-Paste-Password: secret +``` + +The response includes `reads_remaining` (`null` when no read budget is set). + +## Raw content + +```bash +curl http://localhost:8080/raw/{id} +``` + +Raw reads count against a burn-after-read budget, same as page views. + +## Delete + +```bash +# soft delete (creator browser only; plain API clients unaffected) +curl -X DELETE http://localhost:8080/api/pastes/{id} + +# hard delete immediately (requires the one-time deletion token) +curl -X DELETE "http://localhost:8080/api/pastes/{id}/redeem?token=TOKEN" +``` + +## Lists + +```bash +curl "http://localhost:8080/api/public?limit=25&offset=0" # public history +curl http://localhost:8080/api/mine # this browser's pastes (viewer cookie) +``` + +## Language detection + +```bash +curl -X POST http://localhost:8080/api/guess-language \ + -H "Content-Type: application/json" \ + -d '{"content": "package main"}' +``` + +## Cans (bundles of items) + +```bash +curl -X POST http://localhost:8080/api/pastes/can \ + -F "title=My bundle" \ + -F "expires_in=48h" \ + -F 'json_items=[{"title":"notes.txt","content":"some notes"}]' \ + -F "files=@screenshot.png" \ + -F "files=@log.txt" + +curl http://localhost:8080/api/cans/{id} +curl http://localhost:8080/api/cans/{id}/items/{item_id} +``` + +## Web pages + +- `/new` — create a paste +- `/history` — public paste history +- `/mine` — pastes created from this browser +- `/{id}` — view a paste +- `/unlock/{id}` — password gate for protected pastes +- `/raw/{id}` — raw content with original content type + +## Expiry and deletion + +- Expired pastes are soft-deleted by a background sweeper (runs every minute). +- Soft-deleted pastes are hard-deleted after a 7-day grace period. +- Deletion tokens allow immediate hard delete. +- Burn-after-read pastes are soft-deleted once the read budget is exhausted.