{"id":51527847,"url":"https://github.com/avijeetpandey/localpaste","last_synced_at":"2026-07-08T23:30:59.214Z","repository":{"id":362153910,"uuid":"1257631311","full_name":"avijeetpandey/localpaste","owner":"avijeetpandey","description":"Production-grade self-hosted Pastebin clone built with Angular 21, FastAPI, PostgreSQL, MinIO, Redis \u0026 ClickHouse","archived":false,"fork":false,"pushed_at":"2026-06-02T21:49:55.000Z","size":248,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T23:21:30.666Z","etag":null,"topics":["angular","clickhouse","code-sharing","docker","fastapi","kubernetes","minio","pastebin","postgresql","python","redis","self-hosted","tailwindcss","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avijeetpandey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T21:32:41.000Z","updated_at":"2026-06-02T21:53:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/avijeetpandey/localpaste","commit_stats":null,"previous_names":["avijeetpandey/localpaste"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/avijeetpandey/localpaste","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avijeetpandey%2Flocalpaste","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avijeetpandey%2Flocalpaste/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avijeetpandey%2Flocalpaste/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avijeetpandey%2Flocalpaste/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avijeetpandey","download_url":"https://codeload.github.com/avijeetpandey/localpaste/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avijeetpandey%2Flocalpaste/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35281838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","clickhouse","code-sharing","docker","fastapi","kubernetes","minio","pastebin","postgresql","python","redis","self-hosted","tailwindcss","typescript"],"created_at":"2026-07-08T23:30:58.507Z","updated_at":"2026-07-08T23:30:59.205Z","avatar_url":"https://github.com/avijeetpandey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# localpaste\n\nA modern, self-hosted Pastebin alternative — built end-to-end with **FastAPI**, **PostgreSQL**, **Redis**, **MinIO**, and **Angular** (standalone components, signals, Tailwind / Zard-style UI).\n\n\u003e **Tech**: FastAPI · SQLAlchemy 2 (async) · PostgreSQL · Redis · MinIO · Angular 19 (latest stable, Standalone + Signals) · Tailwind CSS · Docker · Kubernetes.\n\n---\n\n## Features\n\n- **Paste creation** with title, language, visibility, expiration, and burn-after-reading.\n- **Optional AES-GCM encryption** of the paste body before persisting to object storage.\n- **Pre-computed Base62 key pool** (KGS) — no collisions or retries on creation.\n- **Cache-aside on Redis** for hot pastes; sliding-window rate limiting middleware.\n- **JWT-authenticated API** (no expiry tokens, per requirement) — every paste endpoint requires login.\n- **Seed data** — default `demo` and `admin` users plus three sample pastes are loaded on first boot.\n- **Modern Angular UI** — signals-based state, dark/light theme switcher, toast micro-delights, mobile drawer for settings, live expiry countdown, and one-click copy.\n- **Production manifests** for Docker Compose and Kubernetes.\n\n---\n\n## Project layout\n\n```\nlocalpaste/\n├── backend/                FastAPI service\n│   ├── app/\n│   │   ├── api/v1/         REST endpoints (auth, pastes, health)\n│   │   ├── core/           config, logging, security, encryption, base62\n│   │   ├── db/             async engine + Base + session\n│   │   ├── middleware/     rate limiter\n│   │   ├── models/         SQLAlchemy ORM\n│   │   ├── schemas/        Pydantic v2 request/response contracts\n│   │   ├── services/       auth · paste · KGS · MinIO · Redis\n│   │   └── utils/          startup seed\n│   └── tests/              pytest unit + integration suites\n├── frontend/               Angular standalone SPA\n│   ├── src/app/\n│   │   ├── core/           services, guards, interceptors, models\n│   │   ├── features/       auth · editor · paste-view · dashboard\n│   │   └── shared/         app shell, toast, utilities\n│   └── tests/              Playwright e2e example + Karma specs\n├── k8s/                    Kubernetes manifests (namespace, stateful, app, ingress)\n├── scripts/                init-db.sql\n├── docker-compose.yml      one-command local stack\n└── .env.example            all env variables\n```\n\n---\n\n## Quick start (Docker Compose)\n\n```bash\ncp .env.example .env\ndocker compose up --build\n```\n\nThis brings up:\n\n| Service           | URL                               | Notes                           |\n|-------------------|-----------------------------------|---------------------------------|\n| Frontend (Angular) | http://localhost:4200            | served by nginx, proxies `/api` |\n| Backend (FastAPI)  | http://localhost:8000/docs       | auto-generated OpenAPI          |\n| MinIO console      | http://localhost:9001            | `minioadmin / minioadmin123`    |\n| PostgreSQL         | `localhost:5432`                  | `localpaste / localpaste_secret`|\n| Redis              | `localhost:6379`                  |                                 |\n\n### Seeded accounts\n\n| Email                     | Password    | Role  |\n|---------------------------|-------------|-------|\n| `demo@localpaste.dev`     | `demo12345` | user  |\n| `admin@localpaste.dev`    | `admin12345`| admin |\n\nThe startup hook also creates 3 sample pastes for the demo user.\n\n---\n\n## API quick tour (curl)\n\n```bash\n# Login\nTOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"email\":\"demo@localpaste.dev\",\"password\":\"demo12345\"}' | jq -r .access_token)\n\n# Create a paste\ncurl -s -X POST http://localhost:8000/api/v1/pastes \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' \\\n  -d '{\"title\":\"Hi\",\"content\":\"hello\",\"language\":\"plaintext\",\"visibility\":\"public\",\"burn_after_read\":false,\"expiration\":\"never\",\"encrypt\":false}'\n\n# List your pastes\ncurl -s http://localhost:8000/api/v1/pastes -H \"Authorization: Bearer $TOKEN\"\n\n# Fetch a paste with body\ncurl -s http://localhost:8000/api/v1/pastes/\u003cid\u003e -H \"Authorization: Bearer $TOKEN\"\n\n# Raw text (browser-friendly)\ncurl -s http://localhost:8000/api/v1/pastes/\u003cid\u003e/raw -H \"Authorization: Bearer $TOKEN\"\n\n# Delete\ncurl -s -X DELETE http://localhost:8000/api/v1/pastes/\u003cid\u003e -H \"Authorization: Bearer $TOKEN\"\n```\n\n\u003e **JWT note**: per the project requirement, `JWT_EXPIRE_MINUTES=0` means tokens are issued **without an `exp` claim** and never expire.\n\n---\n\n## Local development\n\n### Backend\n\n```bash\ncd backend\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -r requirements.txt\n\n# Unit tests (no infra required)\npytest tests/test_unit_core.py tests/test_schemas.py\n\n# Integration tests (require docker compose up)\nLOCALPASTE_INTEGRATION=1 pytest tests/test_integration_api.py\n\n# Run dev server (assumes Postgres/Redis/MinIO running locally)\nuvicorn app.main:app --reload\n```\n\n### Frontend\n\n```bash\ncd frontend\nnpm install\nnpm start          # dev server on http://localhost:4200\nnpm test           # Karma + Jasmine unit tests (headless Chrome)\nnpm run build      # production bundle\n```\n\nThe dev server proxies `/api/*` to the backend through Nginx in Docker, or you can adjust `environment.ts` to point directly to `http://localhost:8000/api/v1`.\n\n---\n\n## Configuration reference\n\nAll values live in `.env` (see `.env.example`). Highlights:\n\n| Variable                    | Default                            | Description                                |\n|-----------------------------|------------------------------------|--------------------------------------------|\n| `JWT_SECRET_KEY`            | `please-change-me…`                | HMAC key for tokens                        |\n| `JWT_EXPIRE_MINUTES`        | `0`                                | `0` = tokens never expire                  |\n| `AES_MASTER_KEY`            | 32-char string                     | Master key for AES-GCM body encryption     |\n| `KGS_KEY_LENGTH`            | `6`                                | Length of generated Base62 paste IDs       |\n| `KGS_POOL_TARGET`           | `5000`                             | Target unused-key pool size                |\n| `KGS_REFILL_THRESHOLD`      | `1000`                             | Trigger refill when pool drops below       |\n| `RATE_LIMIT_ANON_PER_MINUTE`| `30`                               | Per-IP requests/min for anonymous traffic  |\n| `RATE_LIMIT_AUTH_PER_MINUTE`| `120`                              | Per-token requests/min for auth'd traffic  |\n| `MINIO_BUCKET`              | `pastes`                           | Created on startup if missing              |\n| `CORS_ORIGINS`              | localhost dev origins              | Comma-separated allow list                 |\n\n---\n\n## Kubernetes deployment\n\n```bash\nkubectl apply -f k8s/00-namespace-config.yaml\nkubectl apply -f k8s/10-stateful.yaml      # Postgres + Redis + MinIO\nkubectl apply -f k8s/20-app.yaml           # backend + frontend\nkubectl apply -f k8s/30-ingress.yaml       # public ingress\n```\n\nEdit `30-ingress.yaml` to use your real hostname and TLS secret.\n\n---\n\n## Architecture in one diagram\n\n```\n        ┌──────────┐   /api    ┌────────────┐\n        │ Angular  │──proxy──▶ │  FastAPI   │\n        │ (nginx)  │           └─────┬──────┘\n        └──────────┘                 │\n                                     │ async\n                  ┌──────────────────┼──────────────────┐\n                  ▼                  ▼                  ▼\n            ┌───────────┐      ┌──────────┐      ┌───────────┐\n            │ Postgres  │      │  Redis   │      │   MinIO   │\n            │ (metadata)│      │ (cache + │      │ (paste    │\n            │           │      │   RL)    │      │  bodies)  │\n            └───────────┘      └──────────┘      └───────────┘\n```\n\n- **Create flow**: `KGS.reserve_key → MinIO.put_object → Postgres.insert`. Failures roll back the reservation.\n- **Read flow**: `Redis.get(body) → MinIO.get_object` (cached for 5 min). Burn-after-read deletes immediately after first successful fetch.\n- **Rate limit**: per-IP / per-token sliding minute window in Redis; 429s returned without ever touching Postgres or MinIO.\n\n---\n\n## Enterprise Features\n\n### Zero-Knowledge Encryption\nEnable the **Zero-Knowledge Encryption** toggle in the editor. The payload is encrypted in-browser using AES-GCM 256-bit (Web Crypto API) before upload. The decryption key never touches the server — it is appended as a URL hash fragment:\n```\nhttp://localhost:4200/p/abc123#key=\u003cbase64key\u003e.\u003cbase64iv\u003e\n```\nShare the full URL (including the `#key=...` fragment) with recipients. The app decrypts locally when loading the page.\n\n### CLI Tool\n```bash\n# Install dependencies (only stdlib used)\npython3 cli/localpaste_cli.py --help\n\n# Login\npython3 cli/localpaste_cli.py login --email demo@localpaste.dev --password demo12345\n\n# Create paste from stdin / file / pipe\necho \"print('hello')\" | python3 cli/localpaste_cli.py create --language python --title \"Hello\"\ncat main.py | python3 cli/localpaste_cli.py create --private --language python\n\n# List, get, delete\npython3 cli/localpaste_cli.py list\npython3 cli/localpaste_cli.py get \u003cid\u003e --raw\npython3 cli/localpaste_cli.py delete \u003cid\u003e --yes\n\n# Version management\npython3 cli/localpaste_cli.py fork \u003cid\u003e\npython3 cli/localpaste_cli.py versions \u003cid\u003e\npython3 cli/localpaste_cli.py diff \u003cid\u003e\n\n# Analytics\npython3 cli/localpaste_cli.py analytics \u003cid\u003e\n```\nSet `LOCALPASTE_API_URL` and `LOCALPASTE_BASE_URL` environment variables to point to custom deployments.\n\n### Webhooks\nConfigure HTTP webhook endpoints at `/webhooks` in the UI, or via the API:\n```bash\nTOKEN=\"...\"\n# Create webhook\ncurl -X POST http://localhost:8000/api/v1/webhooks \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"target_url\":\"https://example.com/hook\",\"secret_token\":\"mysecret\",\"events\":[\"paste.created\",\"paste.viewed\"]}'\n\n# Test delivery\ncurl -X POST http://localhost:8000/api/v1/webhooks/\u003cid\u003e/test \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\nDeliveries include `X-Localpaste-Signature: sha256=\u003chmac-sha256\u003e` for verification using `secret_token`.\n\n### Versioning \u0026 Diff\nEvery paste can be forked to create a new version:\n```bash\n# Fork via API\ncurl -X POST http://localhost:8000/api/v1/pastes/\u003cid\u003e/fork \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Get diff vs parent\ncurl http://localhost:8000/api/v1/pastes/\u003cfork_id\u003e/diff \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Get full version chain\ncurl http://localhost:8000/api/v1/pastes/\u003cid\u003e/versions \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\n\n### ClickHouse Analytics\nView analytics at `/p/\u003cid\u003e/analytics` in the UI. Raw API:\n```bash\ncurl http://localhost:8000/api/v1/pastes/\u003cid\u003e/analytics \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\nClickHouse is available at `http://localhost:8123` (HTTP interface).\n\n### Team Workspaces\n```bash\n# Create workspace\ncurl -X POST http://localhost:8000/api/v1/workspaces \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"My Team\",\"slug\":\"my-team\"}'\n\n# Invite member\ncurl -X POST http://localhost:8000/api/v1/workspaces/my-team/members \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"user_email\":\"colleague@example.com\",\"role\":\"editor\"}'\n```\n\n### Real-Time Collaboration (SSE)\nSubscribe to live edits on any paste:\n```bash\ncurl -N \"http://localhost:8000/api/v1/collab/\u003cpaste_id\u003e/stream?token=$TOKEN\"\n```\nBroadcast an update:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/collab/\u003cpaste_id\u003e/broadcast\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"edit\",\"data\":{\"content\":\"new content\"}}'\n```\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favijeetpandey%2Flocalpaste","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favijeetpandey%2Flocalpaste","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favijeetpandey%2Flocalpaste/lists"}