feat: markdown rendering /m/<id>
Some checks failed
Build CI / pre-build-checks (push) Failing after 16s
Build CI / build (amd64) (push) Has been skipped
Build CI / build (arm64) (push) Has been skipped

This commit is contained in:
2026-04-21 16:24:08 +02:00
parent a03bb47b80
commit c9132e836a
11 changed files with 772 additions and 26 deletions

268
.llm/analysis.md Normal file
View File

@@ -0,0 +1,268 @@
# Analiza repozytorium: bin (Pastebin)
> Data analizy: 2026-04-21
---
## 1. Cel projektu
**Bin** to minimalistyczny, samodzielny serwis pastebin akceptujący zarówno tekst, jak i pliki binarne (obrazy, PDF-y itp.). Filozofia projektu opiera się na prostocie, łatwości wdrożenia i minimalizmie. W odróżnieniu od tradycyjnych pastebinów **nie wymaga bazy danych** — wszystkie pasty przechowywane są w płaskim systemie plików. Projekt zawiera klienty: interfejs webowy, CLI i integrację z Vimem.
**Live demo:** https://basedbin.fly.dev
**Docker:** `wantguns/bin` (multi-arch: amd64 + arm64)
---
## 2. Stack technologiczny
| Warstwa | Technologia |
|---------|-------------|
| Język | Rust (Edition 2021) |
| Framework web | Rocket 0.5.0-rc.1 (async) |
| Szablony HTML | Tera (via rocket_dyn_templates) |
| Kolorowanie składni | Syntect 4.6.0 + motyw Ayu Dark |
| Frontend | Vanilla JavaScript, HTML, CSS (brak frameworków) |
| Detekcja MIME | tree_magic 0.2.3 |
| CLI args | clap 3.0.9 |
| Kryptografia | sha256 (ETag) |
| Obsadzanie zasobów | rust-embed 6.3.0 |
| Konteneryzacja | Docker (obraz scratch) |
| Deployment | Fly.io |
---
## 3. Struktura katalogów
```
bin/
├── src/ # Kod źródłowy Rust
│ ├── main.rs # Punkt wejścia, setup serwera, parsowanie CLI
│ ├── routes/ # Handlery endpointów HTTP
│ │ ├── mod.rs
│ │ ├── index.rs # GET / strona główna
│ │ ├── upload.rs # POST / upload pliku binarnego
│ │ ├── submit.rs # POST /submit formularz tekstowy
│ │ ├── retrieve.rs # GET /<id> surowe pobranie pasty
│ │ ├── pretty_retrieve.rs # GET /p/<id> wyświetlanie z podświetlaniem
│ │ └── static_files.rs # GET /static/<file> zasoby statyczne
│ └── models/
│ ├── mod.rs
│ ├── paste_id.rs # Generowanie i walidacja ID pasty
│ ├── pretty_syntax.rs # Parsowanie rozszerzenia z URL
│ ├── pretty.rs # Logika podświetlania składni
│ └── response_wrapper.rs # Abstrakcja odpowiedzi HTTP
├── templates/ # Szablony Tera
│ ├── base.html.tera
│ ├── index.html.tera
│ └── pretty.html.tera
├── static/ # Zasoby osadzone w binarce
│ ├── css/
│ ├── js/
│ ├── fonts/ # Iosevka (ttf + woff2)
│ └── media/ # Favicony
├── resources/ # Zasoby binarne (syntaksy, motyw)
├── contrib/cli/client # Skrypt bash CLI
├── Cargo.toml # Manifest projektu
├── build.rs # Wstrzykiwanie git hash do binarki
├── Dockerfile # Wielostopniowy build
├── docker-compose.yml
└── fly.toml # Konfiguracja Fly.io
```
---
## 4. Endpointy API
| Metoda | Ścieżka | Opis |
|--------|---------|------|
| GET | `/` | Strona główna z formularzem |
| GET | `/static/<file>` | Zasoby statyczne (CSS, JS, czcionki) |
| POST | `/` | Upload pliku binarnego → zwraca ID |
| POST | `/submit` | Formularz tekstowy → redirect do `/p/<id>.<ext>` |
| GET | `/<id>` | Surowa treść pasty |
| GET | `/<id>.<ext>` | Surowa treść z rozszerzeniem (rank=1) |
| GET | `/p/<id>` | Pasta z podświetlaniem składni (HTML) |
| GET | `/p/<id>.<ext>` | Pasta z wymuszonym językiem (rank=1) |
**Priorytet routingu:** trasy z rozszerzeniem (`rank=1`) są dopasowywane przed trasami generycznymi (`rank=2`).
---
## 5. Modele danych
### PasteId
```rust
pub struct PasteId<'a>(Cow<'a, str>)
```
- Generuje losowe 6-znakowe ID alfanumeryczne (36^6 ≈ 2,1 mld kombinacji)
- Implementuje `FromParam` dla automatycznej walidacji URL w Rocket
### PasteIdSyntax
```rust
pub struct PasteIdSyntax<'a> { syn_id: Cow<'a, str> }
```
- Parsuje URL typu `/p/abc123.cpp` na nazwę pliku i rozszerzenie
- `get_fname()``"abc123"`, `get_ext()``"cpp"`
### ResponseWrapper<R>
```rust
enum ResponseWrapper<R> {
MetaInterfaceResponse(R),
PrettyPasteContentResponse(R, SystemTime),
RawPasteContentResponse(R, SystemTime),
Redirect(Box<Redirect>),
NotFound(String),
ServerError(String),
}
```
Centralnie zarządza nagłówkami HTTP:
- `Server: bin v.<VERSION> (<GIT_HASH>)`
- `ETag`, `Last-Modified`, `Cache-Control`
---
## 6. Zależności (Cargo.toml)
| Crate | Wersja | Zastosowanie |
|-------|--------|--------------|
| `rand` | 0.8.4 | Generowanie ID |
| `rocket` | 0.5.0-rc.1 | Framework web |
| `tree_magic` | 0.2.3 | Detekcja MIME |
| `syntect` | 4.6.0 | Podświetlanie składni |
| `rust-embed` | 6.3.0 | Osadzanie zasobów |
| `clap` | 3.0.9 | Parsowanie CLI |
| `once_cell` | 1 | Lazy static |
| `sha256` | 1 | ETag |
| `time` | 0.3 | Formatowanie czasu |
| `rocket_dyn_templates` | 0.1.0-rc.1 | Szablony Tera |
---
## 7. System budowania
### build.rs
Przechwytuje hash commita git i wstrzykuje go jako stałą czasu kompilacji (`GIT_HASH`).
### .cargo/config.toml
- Kompilacja statyczna: `-C target-feature=+crt-static`
- Domyślny target: `x86_64-unknown-linux-gnu`
- Cross-kompilacja ARM64: `aarch64-linux-gnu-gcc`
### Docker (wielostopniowy)
1. **Builder:** obraz Rust → `cargo build --release`
2. **Runner:** obraz `scratch` (pusty) + tylko binarka
3. Rezultat: minimalistyczny obraz (~20-30 MB)
---
## 8. Konfiguracja serwera
### Argumenty CLI
| Flag | Domyślnie | Opis |
|------|-----------|------|
| `-a` | `127.0.0.1` | Adres nasłuchu |
| `-p` | `6162` | Port |
| `-u` | `./upload` | Katalog przechowywania plików |
| `-b` | `100 MiB` | Limit rozmiaru uploadu binarnego |
| `-c` | off | Pokaż opis klienta CLI na stronie głównej |
### Zmienne środowiskowe (prefiks `BIN_`)
```bash
BIN_PORT=6163
BIN_ADDRESS=0.0.0.0
BIN_LIMITS={form="16 MiB"}
BIN_WORKERS=8
BIN_IDENT=false
```
---
## 9. Architektura i wzorce projektowe
1. **Embedded Resources** — wszystkie zasoby (CSS, JS, czcionki, definicje syntaksy) skompilowane w binarce (`rust-embed`). Zero zależności zewnętrznych.
2. **Response Wrapper** — generyczny wrapper abstrakcji odpowiedzi z centralizowaną logiką nagłówków HTTP.
3. **Type-Safe URL Params**`PasteId` i `PasteIdSyntax` implementują trait `FromParam` — Rocket automatycznie waliduje parametry URL.
4. **Lazy Static**`BINARY_ETAG` obliczany raz przy starcie (SHA256 wersji) via `once_cell::sync::Lazy`.
5. **Build-Time Version Injection** — hash git osadzony w czasie kompilacji, widoczny w nagłówku `Server`.
6. **Flat Filesystem Storage** — brak bazy danych; pasty jako pliki w katalogu `./upload`.
---
## 10. Frontend (JavaScript)
### index.js (~150 linii)
- Drag-and-drop upload plików
- Wklejanie obrazów ze schowka (`paste` event)
- Tab jako 4 spacje (nie nawigacja formularza)
- Ctrl+Enter → submit
- Dynamiczne pokazywanie/ukrywanie UI
- Fork przez localStorage
### pretty.js
- Przełącznik zawijania: 3 stany (brak → auto → 80 znaków)
- Fork: kopiuje treść do localStorage, przekierowuje na główną
- Raw: przełącza `/p/<id>``/<id>`
- New: przejście do strony głównej
---
## 11. Strategia cachowania
| Typ odpowiedzi | Cache-Control |
|----------------|---------------|
| Pasta pretty | `max-age=604800, stale-while-revalidate=86400` |
| Pasta raw | `max-age=604800, immutable` |
| Meta/Statyczne | ETag (SHA256 wersji) → `304 Not Modified` |
---
## 12. Statystyki kodu
| Metryka | Wartość |
|---------|---------|
| Pliki źródłowe Rust | 13 |
| Linie kodu Rust | ~600 |
| Szablony HTML | 3 |
| Pliki JavaScript | 2 |
| Arkusze CSS | 2 |
| Obsługiwane języki (syntaksy) | 100+ |
| Endpointy API | 7 |
| Typy odpowiedzi HTTP | 6 |
---
## 13. Algorytmy kluczowe
### Generowanie ID
```
1. rand::thread_rng() → thread-safe RNG
2. Alphanumeric distribution (a-z, A-Z, 0-9)
3. Pobierz 6 próbek → String
4. Opakuj w Cow::Owned
```
### Detekcja MIME (upload binarny)
```
1. Wczytaj bajty pliku
2. tree_magic::from_filepath() → MIME
3. MIME zawiera "text" → /p/<id> (kolorowanie)
4. Inaczej → /<id> (surowe pobranie)
```
### Renderowanie składni
```
1. Parsuj URL /p/abc123.cpp
2. Wyodrębnij rozszerzenie ("cpp")
3. Znajdź definicję syntaxu w syntect
4. Fallback → plain_text
5. Renderuj HTML z motywem Ayu Dark
```