Vite/React + shadcn/ui frontend with Superhuman-style keyboard nav (j/k/o/v/e/s/u, feed switching, localStorage caching for instant loads) backed by an Express API that polls the HN Firebase API into Postgres and serves cached feeds/comments. Instrumented with OpenTelemetry (traces/metrics/logs via OTLP/HTTP).
This commit is contained in:
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nightly Postgres backup for the HN Reader app. Invoked by app-54d6877c-backup.timer.
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR="/home/exedev/backups"
|
||||
RETENTION_DAYS=7
|
||||
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
FILE="$BACKUP_DIR/hn-reader-$TIMESTAMP.sql.gz"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
docker exec hn-reader-postgres pg_dump -U hnreader -d hnreader | gzip > "$FILE"
|
||||
|
||||
find "$BACKUP_DIR" -name 'hn-reader-*.sql.gz' -mtime +"$RETENTION_DAYS" -delete
|
||||
|
||||
echo "Backed up hn-reader database to $FILE"
|
||||
@@ -0,0 +1,38 @@
|
||||
-- Schema for hn-reader: cached Hacker News stories + local read state.
|
||||
-- Single-user app: no user table, state is keyed directly by story id.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stories (
|
||||
id BIGINT PRIMARY KEY, -- Hacker News item id
|
||||
type TEXT NOT NULL DEFAULT 'story',
|
||||
title TEXT NOT NULL,
|
||||
url TEXT,
|
||||
domain TEXT,
|
||||
by_user TEXT,
|
||||
score INTEGER NOT NULL DEFAULT 0,
|
||||
descendants INTEGER NOT NULL DEFAULT 0, -- comment count
|
||||
story_text TEXT, -- self-text for Ask HN / Show HN
|
||||
hn_created_at TIMESTAMPTZ NOT NULL,
|
||||
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Which feed(s) a story currently ranks in, and at what rank, so the
|
||||
-- frontend can render top/new/best/ask/show/job lists from one table.
|
||||
CREATE TABLE IF NOT EXISTS feed_items (
|
||||
feed TEXT NOT NULL, -- top | new | best | ask | show | job
|
||||
rank INTEGER NOT NULL,
|
||||
story_id BIGINT NOT NULL REFERENCES stories(id) ON DELETE CASCADE,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (feed, rank)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS feed_items_story_idx ON feed_items(story_id);
|
||||
|
||||
-- Per-story local state: read/starred/archived, superhuman-style.
|
||||
CREATE TABLE IF NOT EXISTS story_state (
|
||||
story_id BIGINT PRIMARY KEY REFERENCES stories(id) ON DELETE CASCADE,
|
||||
read BOOLEAN NOT NULL DEFAULT false,
|
||||
starred BOOLEAN NOT NULL DEFAULT false,
|
||||
archived BOOLEAN NOT NULL DEFAULT false,
|
||||
read_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
Reference in New Issue
Block a user