A no-nonsense read later service
- β Add an article by making a
POSTrequest (withurlin aJSONbody) to/articles/add. - π Get a
JSONarray of all articles by making aGETrequest to/articles. - ποΈ Remove all articles by making a
DELETErequest to/articles/clear. - ποΈ All articles are stored in a
/data/local.sqliteSQLite database. - π₯ Get an RSS, Atom, and JSON feed of articles at
/rss,/atom, and/jsonrespectively.
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
| 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_thanparameter 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 |
{ // required "url": "https://dev.to/jacobshuman/wtf-is-a-github-profile-readmemd-1p8c" }