🚫 Warning: this is a heavily vibe-coded experiment I used to learn about RSS feeds. Use at your own peril.
A local, database-backed RSS reader that fetches posts from your favorite feeds, stores them in PostgreSQL, and generates a clean static HTML page you can open in your browser.
Make sure PostgreSQL is running locally and create a database named rssdb. If you've created the db but not logged in, you can skip all this with something like:
psql -U username -d rssdb -h localhost
createdb rssdbCreate your tables (if you haven’t already):
CREATE TABLE feeds (
id SERIAL PRIMARY KEY,
url TEXT NOT NULL UNIQUE,
title TEXT,
poll_interval INTEGER DEFAULT 3600,
last_fetched_at TIMESTAMPTZ,
next_poll_at TIMESTAMPTZ,
last_error TEXT,
last_status INTEGER
);
CREATE TABLE entries (
id SERIAL PRIMARY KEY,
feed_id INTEGER REFERENCES feeds(id) ON DELETE CASCADE,
guid TEXT,
link TEXT,
title TEXT,
summary TEXT,
content TEXT,
published_at TIMESTAMPTZ,
CONSTRAINT entries_unique_guid UNIQUE (feed_id, guid)
);To insert a feed URL into the database:
INSERT INTO feeds (url, next_poll_at) VALUES ('https://example.com/feed.xml', now());You can use psql to connect:
psql rssdbThis script polls feeds that are due for fetching, parses entries, and writes them into the database.
python3 rss_reader.pyYou should see logs like:
14:09:46 [INFO] Found 2 feeds ready to fetch
14:09:46 [INFO] Fetching feed https://rmoff.net/index.xml
14:09:46 [INFO] Parsed 10 items from https://rmoff.net/index.xmlAfter feeds are fetched, generate a local HTML view:
python3 generate_html.pyYou’ll see output like:
✅ HTML generated at /Users/luciacerchie/reader/output/index.htmlThen open it in your browser:
open output/index.htmlIf something isn’t working, here are common checks:
✅ Check connection
Make sure your DSN matches your local username:
DB_DSN = "postgresql://<username>@localhost:5432/rssdb"Example for user luciacerchie:
DB_DSN = "postgresql://luciacerchie@localhost:5432/rssdb"🔍 See all feeds
SELECT id, url, title, next_poll_at, last_error FROM feeds;🔍 See latest entries
SELECT id, feed_id, link, title, published_at
FROM entries
ORDER BY published_at DESC
LIMIT 10;🔧 Force a re-fetch
If you updated a feed but it’s not being fetched:
UPDATE feeds SET next_poll_at = now();
🧹 Clear broken feeds or entries
```sql
DELETE FROM feeds WHERE last_error IS NOT NULL;
DELETE FROM entries WHERE published_at IS NULL;Error Likely Cause Fix DB write error: constraint "entries_unique_guid" does not exist The unique constraint wasn’t created Recreate it using ALTER TABLE entries ADD CONSTRAINT entries_unique_guid UNIQUE (feed_id, guid); asyncpg.exceptions.InvalidCatalogNameError Database rssdb doesn’t exist Run createdb rssdb No output from rss_reader.py No feeds are due to poll Run UPDATE feeds SET next_poll_at = now();
🧩 Folder Overview graphql
reader/ ├── rss_reader.py # main fetcher and database writer ├── generate_html.py # generates index.html from DB ├── templates/ │ └── index.html.j2 # Jinja2 template for HTML page ├── output/ │ └── index.html # rendered output └── README.md # this file ✨ Example Output After running generate_html.py, your index.html will show a simple feed reader like this:
🗞️ My RSS Feed Reader
──────────────────────────────
Title: Kafka Blog
Feed: blog.net
Published: 2025-10-08
[Open Link]🧭 ### Troubleshooting Tips
If feeds aren’t being fetched:
Ensure their next_poll_at ≤ now().
Check last_error in the feeds table.
If nothing appears in HTML:
Run the SQL query manually to confirm entries exist.
To clean up and start fresh:
TRUNCATE entries, feeds RESTART IDENTITY;