|
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 |
+ |
[](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 | |