chore: updated docs 18c4c4e6
Steve Simkins · 2026-07-02 22:00 3 file(s) · +115 −0
README.md +1 −0
21 21
| [**Library**](apps/library) | Minimal book tracker with Google Books search | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/andromeda-library) |
22 22
| [**Easel**](apps/easel) | Daily public-domain painting from the Art Institute of Chicago | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/andromeda-easel) |
23 23
| [**Blobs**](apps/blobs) | Minimal web browser for S3-compatible blob storage | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/andromeda-blobs) |
24 +
| [**Quotes**](apps/quotes) | Minimal quote-a-day site seeded from classic literature | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/andromeda-quotes) |
24 25
25 26
## Shared packages
26 27
docs/docs/pages/apps/quotes.mdx (added) +110 −0
1 +
# Quotes
2 +
3 +
A minimal quote-a-day site. The landing page shows a single **quote of the day** from classic literature — white, centered, on a dark background. Everything else lives behind `/admin`.
4 +
5 +
- Single Go binary with embedded assets
6 +
- Local SQLite storage
7 +
- Deterministic quote of the day (rotates at UTC midnight, same for every visitor)
8 +
- Password-protected admin panel to add, search, and remove quotes
9 +
- Open JSON read API
10 +
- CSV seed command that imports only classic-literature quotes, matched against an editable author list
11 +
- Dark themed UI with Commit Mono font
12 +
13 +
## Configure
14 +
15 +
### Environment Variables
16 +
17 +
| Variable | Description | Default |
18 +
|---|---|---|
19 +
| `QUOTES_PASSWORD` | Password for the admin panel (empty disables login) | -- |
20 +
| `QUOTES_API_KEY` | Reserved; the read API is currently open | -- |
21 +
| `QUOTES_DB_PATH` | SQLite database path | `quotes.sqlite` |
22 +
| `BASE_URL` | Public base URL (used in social meta tags) | `http://localhost:3000` |
23 +
| `HOST` | Bind address | `127.0.0.1` |
24 +
| `PORT` | Bind port | `3000` |
25 +
| `COOKIE_SECURE` | Enable HTTPS-only cookies | `false` |
26 +
27 +
`QUOTES_PASSWORD` is required to reach the admin panel — with it empty, login is disabled.
28 +
29 +
## Deploy
30 +
31 +
### Railway
32 +
33 +
The easiest way to deploy Quotes is with the one-click Railway template. See the [Deploying with Railway](/deploy-railway) guide for a walkthrough. Quotes requires `QUOTES_PASSWORD` during the configure step.
34 +
35 +
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/andromeda-quotes)
36 +
37 +
### Docker
38 +
39 +
```bash
40 +
cd apps/quotes
41 +
cp .env.example .env
42 +
# Edit .env with your password
43 +
docker compose up -d
44 +
```
45 +
46 +
This starts Quotes on port `4040` with a persistent volume for the SQLite database.
47 +
48 +
### Binary
49 +
50 +
```bash
51 +
cd apps/quotes && go build .
52 +
```
53 +
54 +
The resulting binary is self-contained with all assets embedded (including the classic-author list). Copy it to your server with a configured `.env` file and run it directly.
55 +
56 +
## Seeding from a CSV
57 +
58 +
`quotes.csv` is a Goodreads-style export (`quote,author,category`). The `seed` command imports only classic-literature quotes, matched **case-sensitively** as substrings of the author column against the list in `classic_authors.txt`:
59 +
60 +
```bash
61 +
go run . seed quotes.csv
62 +
```
63 +
64 +
- Edit `classic_authors.txt` (one author or book title per line, `#` comments allowed) to widen or narrow the selection, then re-run.
65 +
- Attribution is split at the first comma into `author` and `source` (book title).
66 +
- Re-seeding is **idempotent** — quotes already present (matched on text + author) are skipped.
67 +
- The list is also embedded in the binary, so `seed` works even when `classic_authors.txt` is absent. An on-disk file takes precedence.
68 +
69 +
### Seeding a Docker volume
70 +
71 +
The image only ships the binary — the DB lives on the `quotes_data` volume, and the large `quotes.csv` is excluded from the build. Seed with a one-off `run` that bind-mounts the CSV and writes into that same volume:
72 +
73 +
```bash
74 +
docker compose run --rm \
75 +
  -v "$PWD/apps/quotes/quotes.csv:/seed/quotes.csv:ro" \
76 +
  quotes quotes seed /seed/quotes.csv
77 +
78 +
docker compose up -d quotes
79 +
```
80 +
81 +
To seed with an edited author list without rebuilding the image, also mount it over the working directory:
82 +
83 +
```bash
84 +
docker compose run --rm \
85 +
  -v "$PWD/apps/quotes/quotes.csv:/seed/quotes.csv:ro" \
86 +
  -v "$PWD/apps/quotes/classic_authors.txt:/data/classic_authors.txt:ro" \
87 +
  quotes quotes seed /seed/quotes.csv
88 +
```
89 +
90 +
## Use
91 +
92 +
The quote of the day is publicly viewable at `/`. It is deterministic — the same quote shows for everyone for the whole UTC day and rotates at midnight.
93 +
94 +
### Admin
95 +
96 +
Set `QUOTES_PASSWORD` and log in at `/admin/login`. From the admin panel you can:
97 +
98 +
- Add a quote with author and optional source
99 +
- Search quotes by text, author, or source
100 +
- Remove quotes
101 +
102 +
### JSON API
103 +
104 +
Read endpoints are open.
105 +
106 +
| Method | Path | Purpose |
107 +
|---|---|---|
108 +
| `GET` | `/api/quotes` | List recent quotes. Query: `limit` (1–500, default 100) |
109 +
| `GET` | `/api/quotes/today` | The quote of the day |
110 +
| `GET` | `/api/quotes/{short_id}` | Fetch a single quote |
docs/vocs.config.ts +4 −0
81 81
          link: '/apps/posts',
82 82
        },
83 83
        {
84 +
          text: 'Quotes',
85 +
          link: '/apps/quotes',
86 +
        },
87 +
        {
84 88
          text: 'Shrink',
85 89
          link: '/apps/shrink',
86 90
        },