chore: updated docs
9e63c584
3 file(s) · +121 −97
| 9 | 9 | | App | Description | Deploy | |
|
| 10 | 10 | |---|---|---| |
|
| 11 | 11 | | [**Sipp**](apps/sipp) | Minimal code sharing with web UI and TUI | [](https://railway.com/deploy/Axcf_D?referralCode=JGcIp6) | |
|
| 12 | - | | [**Feeds**](apps/feeds) | Minimal RSS reader with FreshRSS and OPML support | [](https://railway.com/deploy/Ezvmhx?referralCode=JGcIp6) | |
|
| 12 | + | | [**Feeds**](apps/feeds) | Minimal RSS reader with OPML import/export and a JSON API | [](https://railway.com/deploy/Ezvmhx?referralCode=JGcIp6) | |
|
| 13 | 13 | | [**Parcels**](apps/parcels) | Minimal package tracking (USPS) | [](https://railway.com/deploy/HNQUs4?referralCode=JGcIp6) | |
|
| 14 | 14 | | [**Jotts**](apps/jotts) | Minimal markdown notes app | [](https://railway.com/deploy/DLhUhH?referralCode=JGcIp6) | |
|
| 15 | 15 | | [**OG**](apps/og) | Open Graph tag inspector | [](https://railway.com/deploy/OdXBt_?referralCode=JGcIp6) | |
| 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. |
|
| 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 | [](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}` | |
|