chore: updated docs 9e63c584
Steve · 2026-04-18 07:51 3 file(s) · +121 −97
README.md +1 −1
9 9
| App | Description | Deploy |
10 10
|---|---|---|
11 11
| [**Sipp**](apps/sipp) | Minimal code sharing with web UI and TUI | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Axcf_D?referralCode=JGcIp6) |
12 -
| [**Feeds**](apps/feeds) | Minimal RSS reader with FreshRSS and OPML support | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Ezvmhx?referralCode=JGcIp6) |
12 +
| [**Feeds**](apps/feeds) | Minimal RSS reader with OPML import/export and a JSON API | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Ezvmhx?referralCode=JGcIp6) |
13 13
| [**Parcels**](apps/parcels) | Minimal package tracking (USPS) | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/HNQUs4?referralCode=JGcIp6) |
14 14
| [**Jotts**](apps/jotts) | Minimal markdown notes app | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/DLhUhH?referralCode=JGcIp6) |
15 15
| [**OG**](apps/og) | Open Graph tag inspector | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/OdXBt_?referralCode=JGcIp6) |
apps/feeds/README.md +67 −56
15 15
2. Clone and build
16 16
17 17
```bash
18 -
git clone https://github.com/stevedylandev/feeds
19 -
cd feeds
20 -
cargo build
18 +
git clone https://github.com/stevedylandev/andromeda
19 +
cd andromeda
20 +
cargo build -p feeds
21 21
```
22 22
23 23
3. Run the dev server
24 24
25 25
```bash
26 -
cargo run
26 +
cargo run -p feeds
27 27
# Server running on http://localhost:3000
28 28
```
29 29
31 31
32 32
| Variable | Description | Default |
33 33
|---|---|---|
34 -
| `FRESHRSS_URL` | URL of your FreshRSS instance | — |
35 -
| `FRESHRSS_USERNAME` | FreshRSS username | — |
36 -
| `FRESHRSS_PASSWORD` | FreshRSS password | — |
37 34
| `ADMIN_PASSWORD` | Password for the admin panel | — |
35 +
| `API_KEY` | Bearer token for the JSON API at `/api/*` | — |
36 +
| `BASE_URL` | Public base URL of the app | `http://localhost:3000` |
37 +
| `HOST` | Bind address | `0.0.0.0` |
38 +
| `PORT` | Bind port | `3000` |
39 +
| `DB_PATH` | SQLite database path | `feeds.sqlite` |
40 +
| `DEFAULT_POLL_MINUTES` | Background poll interval in minutes (overridable from the admin panel) | `30` |
41 +
| `ITEM_CAP_PER_FEED` | Maximum stored items per subscription; older items pruned | `200` |
38 42
| `COOKIE_SECURE` | Enable HTTPS-only cookies | `false` |
39 43
40 44
## Overview
42 46
Feeds is a minimal RSS reader that mimics the original experience of RSS. It's just a list of posts. No categories, no marking a post read or unread, and there is no in-app reading. With this approach you have to read the post on the author's personal website and experience it in its original context. A few highlights:
43 47
44 48
- Single Rust binary with embedded assets
45 -
- Multiple feed sources: URL params, OPML file, or FreshRSS API
46 -
- Password-protected admin panel for managing subscriptions
47 -
- Feeds API with JSON and OPML export
49 +
- Local SQLite storage with a background poller (ETag / `If-Modified-Since` aware)
50 +
- Password-protected admin panel for managing subscriptions and categories
51 +
- OPML import and JSON/OPML export
52 +
- Feed discovery from any site URL
53 +
- JSON REST API guarded by a Bearer token
54 +
- Ad-hoc preview by passing feed URLs as query params
48 55
- Dark themed UI with Commit Mono font
49 56
50 57
## Usage
51 58
52 -
There are several built-in ways to source RSS feeds.
59 +
### Admin Panel
53 60
54 -
### URL Query Param
61 +
Set `ADMIN_PASSWORD` and visit `/admin/login`. From the admin panel you can:
55 62
56 -
Once you have the app running you can add the following to the URL to source an RSS feed:
63 +
- Add feeds by URL (title and site URL are auto-detected on first fetch)
64 +
- Discover feeds from any site URL
65 +
- Import an OPML file
66 +
- Organize subscriptions into categories
67 +
- Adjust the poll interval
57 68
58 -
```
59 -
?url=https://bearblog.dev/discover/feed/
60 -
```
69 +
The background poller starts automatically on launch and re-polls every `DEFAULT_POLL_MINUTES` (or the value saved in the admin panel). Items are deduplicated by GUID and each subscription is capped at `ITEM_CAP_PER_FEED`.
61 70
62 -
You can also add multiple URLs by using commas to separate them:
71 +
### URL Query Param (preview mode)
72 +
73 +
You can preview any feed without subscribing by passing it via query string:
63 74
64 75
```
76 +
?url=https://bearblog.dev/discover/feed/
65 77
?urls=https://bearblog.dev/discover/feed/,https://bearblog.stevedylan.dev/feed/
66 78
```
67 79
68 -
### OPML File
80 +
Preview mode bypasses the database and renders whatever the feed returns live.
69 81
70 -
If you save a `feeds.opml` file in the root of the project the app will automatically source it and fetch the posts for the feeds inside.
71 -
72 -
### FreshRSS API
82 +
### Feeds Export
73 83
74 -
If neither of the above are provided the app will default to using a FreshRSS API instance. Simply run the following command:
84 +
The `/feeds` endpoint exports your subscriptions:
75 85
76 -
```bash
77 -
cp .env.sample .env
78 86
```
79 -
80 -
Then fill in the environment variables:
81 -
82 -
```
83 -
FRESHRSS_URL=
84 -
FRESHRSS_USERNAME=
85 -
FRESHRSS_PASSWORD=
87 +
/feeds?format=json
88 +
/feeds?format=opml
86 89
```
87 90
88 -
### Admin Panel
91 +
### JSON API
89 92
90 -
Feeds includes a password-protected admin panel at `/admin` for managing your FreshRSS subscriptions. Set the `ADMIN_PASSWORD` environment variable to enable it:
93 +
Set `API_KEY` to enable programmatic access. All `/api/*` routes accept `Authorization: Bearer <API_KEY>` or a valid admin session cookie.
91 94
92 -
```
93 -
ADMIN_PASSWORD=your_secret_password
94 -
```
95 -
96 -
From the admin panel you can view your current subscriptions and add new feeds directly to your FreshRSS instance.
97 -
98 -
### Feeds API
99 -
100 -
The `/feeds` endpoint exports your FreshRSS subscriptions in JSON or OPML format:
101 -
102 -
```
103 -
/feeds?format=json
104 -
/feeds?format=opml
105 -
```
95 +
| Method | Path | Purpose |
96 +
|---|---|---|
97 +
| `GET` | `/api/items` | List items. Query: `limit`, `unread`, `category_id`, `subscription_id` |
98 +
| `POST` | `/api/items/{id}/read` | Mark item read |
99 +
| `POST` | `/api/items/{id}/unread` | Mark item unread |
100 +
| `GET` | `/api/subscriptions` | List subscriptions |
101 +
| `POST` | `/api/subscriptions` | Add subscription. Body: `{feed_url, title?, category_id?, category_name?}` |
102 +
| `PATCH` | `/api/subscriptions/{id}` | Update subscription. Body: `{category_id?, category_name?, clear_category?}` |
103 +
| `DELETE` | `/api/subscriptions/{id}` | Remove subscription |
104 +
| `GET` | `/api/categories` | List categories |
105 +
| `POST` | `/api/categories` | Create category. Body: `{name}` |
106 +
| `DELETE` | `/api/categories/{id}` | Remove category |
107 +
| `POST` | `/api/import/opml` | Import OPML (multipart `file` field) |
108 +
| `GET` | `/api/settings` | Get poll interval and item cap |
109 +
| `PUT` | `/api/settings` | Update `poll_interval_minutes` (1-1440) |
110 +
| `POST` | `/api/discover` | Discover feeds for a site. Body: `{base_url}` |
106 111
107 112
## Structure
108 113
109 114
```
110 115
feeds/
111 116
├── src/
112 -
│   ├── main.rs        # Axum server with routing, templates, and static asset serving
113 -
│   ├── feeds.rs       # Feed fetching, OPML parsing, and FreshRSS API integration
114 -
│   ├── auth.rs        # Session-based authentication with constant-time password verification
115 -
│   └── models.rs      # Data structures for feeds and FreshRSS responses
117 +
│   ├── main.rs        # Axum server, admin routes, templates, static serving
118 +
│   ├── api.rs         # JSON REST API handlers
119 +
│   ├── poller.rs      # Background feed poller
120 +
│   ├── feeds.rs       # Feed fetching, OPML parsing, feed discovery
121 +
│   ├── auth.rs        # Session + API-key guards
122 +
│   └── models.rs      # Data structures
116 123
├── templates/         # Askama HTML templates
117 -
├── assets/            # Static assets embedded at compile time via rust-embed
124 +
├── static/            # Static assets embedded at compile time via rust-embed
118 125
├── Dockerfile
119 126
└── docker-compose.yml
120 127
```
128 +
129 +
Subscription and item storage lives in `crates/db/src/feeds.rs` (shared `andromeda-db` crate).
121 130
122 131
## Deployment
123 132
130 139
### Docker (recommended)
131 140
132 141
```bash
133 -
git clone https://github.com/stevedylandev/feeds
134 -
cd feeds
135 -
cp .env.sample .env
142 +
git clone https://github.com/stevedylandev/andromeda
143 +
cd andromeda/apps/feeds
144 +
cp .env.example .env
136 145
# Edit .env with your credentials
137 146
docker compose up -d
138 147
```
139 148
149 +
Mount a volume at `DB_PATH` to persist the SQLite database.
150 +
140 151
### Binary
141 152
142 153
```bash
143 -
cargo build --release
154 +
cargo build --release -p feeds
144 155
```
145 156
146 157
The resulting binary at `./target/release/feeds` is self-contained with all assets embedded. Copy it to your server with a configured `.env` file and run it directly.
docs/docs/pages/apps/feeds.mdx +53 −40
5 5
Feeds is a minimal RSS reader that mimics the original experience of RSS. It's just a list of posts. No categories, no marking a post read or unread, and there is no in-app reading. With this approach you have to read the post on the author's personal website and experience it in its original context.
6 6
7 7
- Single Rust binary with embedded assets
8 -
- Multiple feed sources: URL params, OPML file, or FreshRSS API
9 -
- Password-protected admin panel for managing subscriptions
10 -
- Feeds API with JSON and OPML export
8 +
- Local SQLite storage with a background poller (ETag / `If-Modified-Since` aware)
9 +
- Password-protected admin panel for managing subscriptions and categories
10 +
- OPML import and JSON/OPML export
11 +
- Feed discovery from any site URL
12 +
- JSON REST API guarded by a Bearer token
13 +
- Ad-hoc preview by passing feed URLs as query params
11 14
- Dark themed UI with Commit Mono font
12 15
13 16
## Configure
16 19
17 20
| Variable | Description | Default |
18 21
|---|---|---|
19 -
| `DEFAULT_FEED` | Comma-separated list of RSS feed URLs to load when no `?url=` or `?urls=` query param is provided | -- |
20 -
| `FRESHRSS_URL` | URL of your FreshRSS instance | -- |
21 -
| `FRESHRSS_USERNAME` | FreshRSS username | -- |
22 -
| `FRESHRSS_PASSWORD` | FreshRSS password | -- |
23 22
| `ADMIN_PASSWORD` | Password for the admin panel | -- |
23 +
| `API_KEY` | Bearer token for the JSON API at `/api/*` | -- |
24 +
| `BASE_URL` | Public base URL of the app | `http://localhost:3000` |
25 +
| `HOST` | Bind address | `0.0.0.0` |
26 +
| `PORT` | Bind port | `3000` |
27 +
| `DB_PATH` | SQLite database path | `feeds.sqlite` |
28 +
| `DEFAULT_POLL_MINUTES` | Background poll interval in minutes (overridable from the admin panel) | `30` |
29 +
| `ITEM_CAP_PER_FEED` | Maximum stored items per subscription; older items pruned | `200` |
24 30
| `COOKIE_SECURE` | Enable HTTPS-only cookies | `false` |
25 31
26 -
The FreshRSS credentials are only needed if you want to source feeds from a FreshRSS instance.
32 +
`ADMIN_PASSWORD` is required to access the admin panel. `API_KEY` is only needed if you want to call the JSON API from outside the browser.
27 33
28 34
## Deploy
29 35
30 36
### Railway
31 37
32 -
The easiest way to deploy Feeds is with the one-click Railway template. See the [Deploying with Railway](/deploy-railway) guide for a walkthrough of the process. Feeds requires `ADMIN_PASSWORD` to enable the admin panel, and optionally the `FRESHRSS_*` variables if you want to source feeds from a FreshRSS instance.
38 +
The easiest way to deploy Feeds is with the one-click Railway template. See the [Deploying with Railway](/deploy-railway) guide for a walkthrough of the process. Feeds requires `ADMIN_PASSWORD` to enable the admin panel. Attach a volume at `DB_PATH` to persist the SQLite database.
33 39
34 40
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Ezvmhx?referralCode=JGcIp6)
35 41
37 43
38 44
```bash
39 45
cd apps/feeds
40 -
cp .env.sample .env
46 +
cp .env.example .env
41 47
# Edit .env with your credentials
42 48
docker compose up -d
43 49
```
62 68
63 69
## Use
64 70
65 -
There are several built-in ways to source RSS feeds.
71 +
### Admin Panel
72 +
73 +
Set `ADMIN_PASSWORD` and visit `/admin/login`. From the admin panel you can:
74 +
75 +
- Add feeds by URL (title and site URL are auto-detected on first fetch)
76 +
- Discover feeds from any site URL
77 +
- Import an OPML file
78 +
- Organize subscriptions into categories
79 +
- Adjust the background poll interval
80 +
81 +
The poller runs automatically on launch and re-polls every `DEFAULT_POLL_MINUTES` (or the value saved in the admin panel). Items are deduplicated by GUID and each subscription is capped at `ITEM_CAP_PER_FEED`.
66 82
67 -
### URL Query Param
83 +
### URL Query Param (preview mode)
68 84
69 -
Once you have the app running you can add the following to the URL to source an RSS feed:
85 +
You can preview any feed without subscribing by passing it via query string:
70 86
71 87
```
72 88
?url=https://bearblog.dev/discover/feed/
73 89
```
74 90
75 -
You can also add multiple URLs by using commas to separate them:
91 +
You can also preview multiple URLs at once by using commas to separate them:
76 92
77 93
```
78 94
?urls=https://bearblog.dev/discover/feed/,https://bearblog.stevedylan.dev/feed/
79 95
```
80 96
81 -
### Default Feed
97 +
Preview mode bypasses the database and renders whatever the feed returns live.
82 98
83 -
Set the `DEFAULT_FEED` environment variable to a comma-separated list of feed URLs and the app will load them whenever no `?url=` or `?urls=` query param is provided:
99 +
### Feeds Export
84 100
85 -
```
86 -
DEFAULT_FEED=https://bearblog.dev/discover/feed/,https://bearblog.stevedylan.dev/feed/
87 -
```
88 -
89 -
### OPML File
90 -
91 -
If you save a `feeds.opml` file in the root of the project the app will automatically source it and fetch the posts for the feeds inside.
92 -
93 -
### FreshRSS API
94 -
95 -
If neither of the above are provided the app will default to using a FreshRSS API instance. Set the following environment variables:
101 +
The `/feeds` endpoint exports your subscriptions in JSON or OPML format:
96 102
97 103
```
98 -
FRESHRSS_URL=
99 -
FRESHRSS_USERNAME=
100 -
FRESHRSS_PASSWORD=
104 +
/feeds?format=json
105 +
/feeds?format=opml
101 106
```
102 107
103 -
### Admin Panel
104 -
105 -
Feeds includes a password-protected admin panel at `/admin` for managing your FreshRSS subscriptions. Set the `ADMIN_PASSWORD` environment variable to enable it. From the admin panel you can view your current subscriptions and add new feeds directly to your FreshRSS instance.
106 -
107 -
### Feeds API
108 +
### JSON API
108 109
109 -
The `/feeds` endpoint exports your FreshRSS subscriptions in JSON or OPML format:
110 +
Set `API_KEY` to enable programmatic access. All `/api/*` routes accept `Authorization: Bearer <API_KEY>` or a valid admin session cookie.
110 111
111 -
```
112 -
/feeds?format=json
113 -
/feeds?format=opml
114 -
```
112 +
| Method | Path | Purpose |
113 +
|---|---|---|
114 +
| `GET` | `/api/items` | List items. Query: `limit`, `unread`, `category_id`, `subscription_id` |
115 +
| `POST` | `/api/items/{id}/read` | Mark item read |
116 +
| `POST` | `/api/items/{id}/unread` | Mark item unread |
117 +
| `GET` | `/api/subscriptions` | List subscriptions |
118 +
| `POST` | `/api/subscriptions` | Add subscription. Body: `{feed_url, title?, category_id?, category_name?}` |
119 +
| `PATCH` | `/api/subscriptions/{id}` | Update subscription. Body: `{category_id?, category_name?, clear_category?}` |
120 +
| `DELETE` | `/api/subscriptions/{id}` | Remove subscription |
121 +
| `GET` | `/api/categories` | List categories |
122 +
| `POST` | `/api/categories` | Create category. Body: `{name}` |
123 +
| `DELETE` | `/api/categories/{id}` | Remove category |
124 +
| `POST` | `/api/import/opml` | Import OPML (multipart `file` field) |
125 +
| `GET` | `/api/settings` | Get poll interval and item cap |
126 +
| `PUT` | `/api/settings` | Update `poll_interval_minutes` (1-1440) |
127 +
| `POST` | `/api/discover` | Discover feeds for a site. Body: `{base_url}` |