A no-nonsense read later service
- β Add an article by making a
POST
request (withurl
in aJSON
body) to/articles/add
. - π Get a
JSON
array of all articles by making aGET
request to/articles
. - ποΈ Remove all articles by making a
DELETE
request to/articles/clear
. - ποΈ All articles are stored in a
/data/local.sqlite
SQLite database. - π₯ Get an RSS, Atom, and JSON feed of articles at
/rss
,/atom
, and/json
respectively.
Although you can clone/build readl8r locally, it's recommended for users to run the docker image on Docker Hub. Copy the contents of this docker-compose.yml file to your computer and run:
docker compose up
Name | Required | Description | Default |
---|---|---|---|
AUTH_SECRET |
If PASSWORD is set |
Used to sign auth JWTs | undefined |
HOST |
No | Hostname or IP address where the service is hosted | 0.0.0.0 |
PORT |
No | The port number used for the service | 80 |
SECURE |
No | Indicates whether to use HTTPS (true) or HTTP (false) | false |
PASSWORD |
No | Password required for authentication | undefined |
FEED_TITLE |
No | Title of the feed (displayed on the web app) | undefined |
FEED_DESCRIPTION |
No | Brief description of the feed's content and purpose (displayed on the web app) | undefined |
FEED_IMAGE |
No | URL to an image that represents the feed (e.g., logo or banner) | undefined |
FEED_FAVICON |
No | URL to the favicon to be displayed in browsers for the feed | undefined |
FEED_COPYRIGHT |
No | Copyright information regarding the content of the feed | undefined |
AUTHOR_NAME |
No | Name of the feed's author | undefined |
AUTHOR_EMAIL |
No | Email address of the author | undefined |
AUTHOR_LINK |
No | URL to the author's website or social media profile | undefined |
You can optionally protect your reading list with a password by setting the PASSWORD
and AUTH_SECRET
environment variables in your docker compose config.
This will protect all routes excluding feed routes (/rss
, /atom
, etc).
I'm still looking into how rss aggregators generally handle auth for feeds and only want to add auth when it doesn't prevent aggregators from accessing reading lists.
{
id: number;
url: string;
publish_date: string; // date article was published (added_date if this can't be found)
added_date: string; // date the article was added to readl8r
title: string | null;
description: string | null;
content: string | null;
author: string | null;
favicon: string | null;
ttr: number | null; // estimated time to read article in seconds
}
For the most up to date definition, see the actual typescript type.
π Requires Authentication
You can add an article by providing the article's url in the body of a POST
request:
POST (http|https)://HOST:PORT/articles/add
{
// required
"url": "https://dev.to/jacobshuman/wtf-is-a-github-profile-readmemd-1p8c"
}
Status | Body | Content-Type |
---|---|---|
200 | article added successfully |
text/plain |
400 | url is required |
text/plain |
400 | unable to extract metadata at {url} |
text/plain |
401 | not authorized |
text/plain |
π Requires Authentication
You can get a single JSON
object representing an article by making a GET
request to the /articles/:id
route:
GET (http|https)://HOST:PORT/articles/:id
Status | Body | Content-Type |
---|---|---|
200 | Article | application/json |
401 | not authorized |
text/plain |
404 | there is no article with an id of ":id" |
text/plain |
π Requires Authentication
You can get a JSON
array of articles by making a GET
request to the /articles
route:
GET (http|https)://HOST:PORT/articles
Status | Body | Content-Type |
---|---|---|
200 | Article[] | application/json |
401 | not authorized |
text/plain |
π Requires Authentication
You can update an article based on it's id by making a PATCH
request to the /articles/:id/update
route:
PATCH (http|https)://HOST:PORT/articles/:id/update
{
"article": {
"url": "", // optional
"publish_date": "", // optional
"added_date": "", // optional
"title": "", // optional
"description": "", // optional
"content": "", // optional
"author": "", // optional
"favicon": "", // optional
"ttr": "" // optional
}
}
Status | Body | Content-Type |
---|---|---|
200 | article :id deleted successfully |
text/plain |
401 | not authorized |
text/plain |
404 | there is no article with id of :id |
text/plain |
π Requires Authentication
You can delete an article based on it's id by making a DELETE
request to the /articles/:id/delete
route:
DELETE (http|https)://HOST:PORT/articles/:id/delete
Status | Body | Content-Type |
---|---|---|
200 | article :id deleted successfully |
text/plain |
401 | not authorized |
text/plain |
404 | there is no article with id of :id |
text/plain |
π Requires Authentication
DELETE (http|https)://HOST:PORT/articles/clear
Status | Body | Content-Type |
---|---|---|
200 | x articles cleared successfully |
text/plain |
401 | not authorized |
text/plain |
π Requires Authentication
You can manually purge articles older than a certain threshhold using the /articles/purge
route. Simply pass an older_than
query parameter in the url with the following format:
h = hours
d = days
m = months
y = years
<integer>h|d|m|y
Examples
30d = 30 days
4m = 4 months
2y = 2 years
Please note the
older_than
parameter does not accept numbers with decimals.
DELETE (http|https)://HOST:PORT/articles/purge?older_than=<number>(h|d|m|y)
Status | Body | Content-Type |
---|---|---|
200 | x articles purged successfully |
text/plain |
400 | invalid format, use the formula "<number><h | d | m | y>" |
text/plain |
401 | not authorized |
text/plain |
GET (http|https)://HOST:PORT/rss
GET (http|https)://HOST:PORT/rss.xml
GET (http|https)://HOST:PORT/feed
GET (http|https)://HOST:PORT/feed.xml
Status | Body | Content-Type |
---|---|---|
200 | RSS2 Feed | application/rss+xml |
GET (http|https)://HOST:PORT/atom
Status | Body | Content-Type |
---|---|---|
200 | Atom Feed | application/atom+xml |
GET (http|https)://HOST:PORT/json
Status | Body | Content-Type |
---|---|---|
200 | JSON Feed | application/json |
A simple GET
route to see if the server is up and ready to handle incoming requests.
GET (http|https)://HOST:PORT/health
Status | Body | Content-Type |
---|---|---|
200 | OK |
text/plain |