diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..90ded98 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,72 @@ +name: CI Quality Checks & Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + backend-test: + name: Go Lint & Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + cache: true + + - name: Verify Formatting (gofmt) + run: | + UNFORMATTED=$(gofmt -l internal/ cmd/) + if [ -n "$UNFORMATTED" ]; then + echo "Unformatted files found:" + echo "$UNFORMATTED" + exit 1 + fi + + - name: Run Go Vet + run: go vet ./... + + - name: Run Go Unit Tests + run: go test -v ./... + + frontend-build: + name: Frontend Typecheck & Build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: web/package-lock.json + + - name: Install Frontend Dependencies + working-directory: web + run: npm ci || npm install + + - name: TypeScript Check & Vite Build + working-directory: web + run: npm run build + + security-scan: + name: Gitleaks Secret Detection + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Gitleaks Scan + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..3b8e71e --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,49 @@ +name: Build & Publish Release Image + +on: + push: + tags: + - 'v*' + +jobs: + docker-release: + name: Build & Push Docker Image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Gitea / Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY_HOST || 'git.pengzhan.dev' }} + username: ${{ secrets.REGISTRY_USERNAME || github.actor }} + password: ${{ secrets.REGISTRY_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.REGISTRY_HOST || 'git.pengzhan.dev' }}/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..bf6ede6 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,16 @@ +title = "gitleaks config" + +[extend] +useDefault = true + +[allowlist] +description = "Global allowlist" +paths = [ + '''sample_subscriptions\.opml''', + '''docs/''', + '''web/node_modules/''' +] +regexes = [ + '''admin123456''', + '''example_jwt_secret_key''' +] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..41155d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# ============================================================================== +# Stage 1: Build React Frontend PWA +# ============================================================================== +FROM node:20-alpine AS frontend-builder +WORKDIR /app/web + +COPY web/package*.json ./ +RUN npm ci || npm install + +COPY web/ ./ +RUN npm run build + +# ============================================================================== +# Stage 2: Build Go Backend Binary +# ============================================================================== +FROM golang:1.22-alpine AS backend-builder +WORKDIR /app + +RUN apk add --no-cache git + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +COPY --from=frontend-builder /app/web/dist ./web/dist + +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o rsspipe cmd/server/main.go + +# ============================================================================== +# Stage 3: Minimal Production Runtime +# ============================================================================== +FROM alpine:3.20 + +RUN apk --no-cache add ca-certificates tzdata + +WORKDIR /app + +COPY --from=backend-builder /app/rsspipe /app/rsspipe +COPY --from=frontend-builder /app/web/dist /app/web/dist + +RUN mkdir -p /app/data + +ENV PORT=8080 \ + DB_PATH=/app/data/rsspipe.db \ + GIN_MODE=release + +VOLUME ["/app/data"] + +EXPOSE 8080 + +ENTRYPOINT ["/app/rsspipe"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..809f658 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +SHELL := /usr/bin/env bash + +.PHONY: all help build build-frontend build-backend dev test test-race lint check clean docker-build docker-up docker-down + +# Default target +all: build + +## help: Show this help message +help: + @echo "RSSPipe Build & Development Commands" + @echo "=====================================" + @sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /' + +## build: Build frontend and backend production binary +build: build-frontend build-backend + +## build-frontend: Build React PWA frontend bundle (Vite + TypeScript) +build-frontend: + @echo "==> Building frontend bundle..." + @cd web && npm install && npm run build + @echo "✓ Frontend build complete: web/dist" + +## build-backend: Build Go backend binary +build-backend: + @echo "==> Compiling Go binary..." + @mkdir -p bin + @CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/rsspipe cmd/server/main.go + @echo "✓ Backend binary ready: bin/rsspipe" + +## dev: Run server locally +dev: build-frontend + @echo "==> Starting local RSSPipe server..." + @go run cmd/server/main.go + +## test: Run backend Go unit tests +test: + @echo "==> Running Go unit tests..." + @go test -v ./... + +## check: Run all quality gate checks (formatting, vet, tests, tsc, gitleaks) +check: + @chmod +x scripts/check.sh + @./scripts/check.sh + +## lint: Run gofmt and go vet +lint: + @echo "==> Verifying Go formatting..." + @test -z "$$(gofmt -l internal/ cmd/)" || (echo "Unformatted files:" && gofmt -l internal/ cmd/ && exit 1) + @echo "==> Running Go vet..." + @go vet ./... + @echo "✓ Lint passed." + +## format: Format all Go files with gofmt +format: + @echo "==> Formatting Go files..." + @gofmt -w internal/ cmd/ + @echo "✓ Formatted." + +## docker-build: Build production Docker image +docker-build: + @echo "==> Building Docker image rsspipe:latest..." + @docker build -t rsspipe:latest . + +## docker-up: Launch RSSPipe with Docker Compose +docker-up: + @docker compose up -d + +## docker-down: Stop Docker Compose services +docker-down: + @docker compose down + +## clean: Remove built binaries and temporary artifacts +clean: + @echo "==> Cleaning build artifacts..." + @rm -rf bin/ web/dist/ + @echo "✓ Cleaned." diff --git a/README.md b/README.md new file mode 100644 index 0000000..7826261 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# RSSPipe 🚀 + +> **Modern, High-Performance RSS Information Gateway & Distraction-Free Reader** + +RSSPipe is a single-user, self-hosted RSS information gateway designed to replace traditional feeds readers with an elegant, ultra-fast, and distraction-free reading experience. Built with a robust Go backend and a modern React PWA interface. + +--- + +## ✨ Key Features + +- 🖥️ **Reeder 4 Minimal 3-Pane View**: Designed for desktop power users with collapsible navigation sidebar, dense article list, and distraction-free reading canvas. +- 📱 **Xiaohongshu-Style Masonry Waterfall**: Designed for mobile and media-rich browsing with adaptive masonry columns, category filter chips, and full-screen post reader. +- ⚡ **High-Speed Crawler & Engine**: Concurrent worker pool crawler in Go with per-feed polling intervals, custom timeouts, and HTTP conditional caching (ETag / Last-Modified 304). +- 🔒 **Single-User Security & Auth**: JWT-based session security and bcrypt hashing to secure access over the public internet. +- 📁 **OPML 2.0 Import & Export**: Effortless migration from Miniflux, Feedly, Inoreader, NetNewsWire, or Reeder. +- 🔗 **Standalone Article Sharing**: Instant public HTML snapshot sharing (`/share/{uuid}.html`) for individual articles. +- ⌨️ **Keyboard Navigation**: Complete single-key shortcut navigation (`j`/`k` next/previous, `m` mark read, `s` star, `e` archive, `v` view original, `[` sidebar toggle). +- 💾 **Local State & Reading Persistence**: Restores exact view modes, filter selections, active feeds, and reading positions upon page refresh. + +--- + +## 🛠️ Architecture & Tech Stack + +``` + ┌─────────────────────────────────────────────────────────┐ + │ React 18 PWA Frontend │ + │ (Vite + TypeScript + Lucide + Modern CSS) │ + └──────────────────────────┬──────────────────────────────┘ + │ REST API + Static Asset Serving + ┌──────────────────────────▼──────────────────────────────┐ + │ Go 1.22 HTTP Server │ + │ (Chi Router + JWT Auth) │ + ├──────────────────────────┬──────────────────────────────┤ + │ Background Crawler │ SQLite Engine (WAL) │ + │ (Worker Pool + Parser) │ (modernc.org/sqlite Driver) │ + └──────────────────────────┴──────────────────────────────┘ +``` + +- **Backend**: Go 1.22+, `go-chi/chi`, `gofeed` parser, `golang-jwt/jwt`. +- **Database**: Embedded SQLite with WAL mode (`modernc.org/sqlite` pure Go driver, no CGO required). +- **Frontend**: React 18, TypeScript, Vite, CSS custom properties. + +--- + +## 🚀 Quickstart + +### Option 1: Run with Docker Compose + +```bash +# Clone the repository +git clone https://git.pengzhan.dev/haopengzhan/rsspipe.git +cd rsspipe + +# Start RSSPipe +docker compose up -d +``` + +Visit **`http://localhost:8080`**. +Default credentials: + +- **Username**: `admin` +- **Password**: `admin123456` *(configurable via environment variables)* + +--- + +### Option 2: Local Development Setup + +#### Prerequisites + +- **Go 1.22+** +- **Node.js 18+ & npm** + +```bash +# Clone the repository +git clone https://git.pengzhan.dev/haopengzhan/rsspipe.git +cd rsspipe + +# Build and start server using Makefile +make dev +``` + +Or build separately: +```bash +make build # Compiles frontend bundle + backend binary into bin/rsspipe +./bin/rsspipe # Runs production binary +``` + +The server binds to `http://localhost:8080` and serves both the API endpoints and static frontend assets. + +--- + +## ⚙️ Configuration & Environment Variables + +| Variable | Default | Description | +| :--- | :--- | :--- | +| `PORT` | `8080` | Server HTTP port | +| `DB_PATH` | `./data/rsspipe.db` | Path to SQLite database file | +| `JWT_SECRET` | *(auto-generated)* | 32-byte secret key for signing JWT tokens | +| `ADMIN_USERNAME` | `admin` | Initial default administrator username | +| `ADMIN_PASSWORD` | `admin123456` | Initial default administrator password | +| `CRAWLER_WORKERS` | `10` | Number of concurrent fetch worker threads | +| `CRAWLER_INTERVAL` | `15` | Default background crawler interval (minutes) | + +--- + +## ⌨️ Keyboard Shortcuts + +| Shortcut | Action | +| :--- | :--- | +| `j` / `k` | Navigate to Next / Previous article | +| `m` | Toggle Read / Unread status | +| `s` | Toggle Starred status | +| `e` | Mark as read and advance to next | +| `v` | Open original article URL in new tab | +| `[` | Toggle left navigation sidebar | +| `r` | Trigger immediate refresh of feeds | +| `a` | Open "Add Subscription" modal | +| `1` | Switch to Desktop 3-Split View | +| `2` | Switch to Xiaohongshu Waterfall View | +| `3` | Switch to Settings & Management View | +| `Shift + A` | Mark all items in active view as read | +| `?` | Open keyboard shortcuts cheatsheet | +| `Esc` | Close any active modal dialog | + +--- + +## 🧪 Testing & Quality Automation + +```bash +make check # Run all quality checks (gofmt, go vet, tests, tsc, gitleaks) +make test # Run backend Go unit tests +make lint # Check formatting and run go vet +make format # Auto-format all Go files +``` + +--- + +## 📄 License + +MIT License. Free for personal and commercial self-hosted use. diff --git a/cmd/server/main.go b/cmd/server/main.go index 61ae09e..60906b4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -9,11 +9,11 @@ import ( "syscall" "time" - "github.com/haopengzhan/rsspipe/internal/api" - "github.com/haopengzhan/rsspipe/internal/auth" - "github.com/haopengzhan/rsspipe/internal/config" - "github.com/haopengzhan/rsspipe/internal/crawler" - "github.com/haopengzhan/rsspipe/internal/db" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/api" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/auth" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/config" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/crawler" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/db" ) func main() { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ad68f94 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + rsspipe: + build: + context: . + dockerfile: Dockerfile + image: rsspipe:latest + container_name: rsspipe + restart: unless-stopped + ports: + - "8080:8080" + environment: + - PORT=8080 + - DB_PATH=/app/data/rsspipe.db + - ADMIN_USERNAME=admin + - ADMIN_PASSWORD=admin123456 + - CRAWLER_WORKERS=10 + - CRAWLER_INTERVAL=15 + volumes: + - rsspipe_data:/app/data + +volumes: + rsspipe_data: + driver: local diff --git a/docs/003_backlog.md b/docs/003_backlog.md new file mode 100644 index 0000000..9b1b44b --- /dev/null +++ b/docs/003_backlog.md @@ -0,0 +1,95 @@ +# RSSPipe Backlog Specification (`003_backlog.md`) + +This document records planned features, UX enhancements, and architectural extensions identified during initial testing and validation. + +--- + +## 1. Search Box Auto-Complete & Real-Time Suggestions + +- **Goal**: Enable rich auto-complete and search suggestion dropdown as the user types in the global search bar. +- **Scope**: + - Auto-suggest matches across article titles, feed names, author tags, and category labels. + - Keyboard navigation within search dropdown (`ArrowDown`, `ArrowUp`, `Enter` to jump directly to article or feed). + - Search history / recent queries quick access. + +--- + +## 2. Password Management in User Settings + +- **Goal**: Allow the single-user administrator to update their login credentials directly from `Settings > Account & System`. +- **Scope**: + - Backend API: `PUT /api/auth/password` verifying `old_password` and updating bcrypt hash for `new_password`. + - Frontend Modal/Card with confirmation field and clear validation indicators. + +--- + +## 3. Comprehensive Keyboard Shortcuts Test Suite + +- **Goal**: Automated end-to-end and unit testing for all single-key power-user navigation bindings. +- **Scope**: + - `j` / `k`: Next / previous article navigation. + - `s`: Toggle star. + - `m`: Toggle read status. + - `e`: Archive / mark read and advance. + - `v`: Open original URL in new browser tab. + - `[`: Toggle sidebar collapse/expand. + - `r`: Trigger feed refresh. + - `a`: Open Add Feed modal. + - `1`, `2`, `3`: Switch views (3-Split, Waterfall, Settings). + - `?`: Open keyboard shortcuts cheatsheet modal. + - `Shift + A`: Mark all as read with confirmation. + - Ensure shortcuts are strictly inhibited when focus is inside text inputs, textareas, or modal forms. + +--- + +## 4. Header Brand & Sidebar Toggle Alignment Consistency + +- **Goal**: Standardize the top header layout across all view modes (3-Split, Waterfall, and Settings). +- **Scope**: + - In 3-Split view, ensure the RSSPipe logo and brand name occupy the consistent leftmost position matching Waterfall and Settings views. + - Ensure the sidebar toggle icon `[` integrates cleanly into the left group or toolbar without disrupting brand alignment. + +--- + +## 5. Refresh Button Spinning Micro-Animation + +- **Goal**: Provide instant visual feedback when a background refresh or manual pull is in progress. +- **Scope**: + - Add smooth CSS keyframe spin animation (`@keyframes spin { 100% { transform: rotate(360deg); } }`) to the refresh icon when `isRefreshing` is active. + +--- + +## 6. Feed Favicon & Website Icon Scraping / Upload + +- **Goal**: Enhance visual recognition by displaying brand icons next to each feed. +- **Scope**: + - **Auto-Scraper**: Automatically fetch and cache `favicon.ico`, ``, or `` from the feed's website root during initial subscription. + - **Manual Customization**: Allow uploading or specifying a custom icon URL in `Settings > Subscriptions > Edit Feed`. + +--- + +## 7. Active Post Reading Persistence on Subscription Switch + +- **Goal**: Prevent reader disruption when exploring other subscriptions. +- **Scope**: + - When actively reading an article in the right canvas of 3-Split view, clicking a different subscription or category in the left sidebar should update the middle article list *without* abruptly closing or reloading the active article in the right canvas until a new article is explicitly selected. + +--- + +## 8. Unified Feed & Category Tree Hierarchy ("Feeds" Root) + +- **Goal**: Simplify feed organization into a single clean hierarchy. +- **Scope**: + - Eliminate the artificial separation between "Categorized Feeds" and "Uncategorized Feeds". + - Present a single unified **"Feeds"** section where uncategorized feeds reside at the root level alongside collapsible category folders. + - Drag-and-drop or simple dropdown assignment to move feeds between root and folders. + +--- + +## 9. Enhanced Xiaohongshu Masonry Waterfall Experience + +- **Goal**: Further elevate the card aesthetics and mobile interaction to closely match Xiaohongshu. +- **Scope**: + - Staggered dynamic aspect ratios with prominent media cards. + - Smooth infinite scroll pagination. + - Interactive gesture swipe to dismiss the full-screen reader. diff --git a/go.mod b/go.mod index 42a5ca8..9f94817 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/haopengzhan/rsspipe +module git.pengzhan.dev/haopengzhan/rsspipe go 1.26.5 diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 948cf44..85cc02e 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -11,13 +11,13 @@ import ( "strings" "time" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/auth" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/crawler" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/db" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/opml" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/parser" "github.com/go-chi/chi/v5" - "github.com/haopengzhan/rsspipe/internal/auth" - "github.com/haopengzhan/rsspipe/internal/crawler" - "github.com/haopengzhan/rsspipe/internal/db" - "github.com/haopengzhan/rsspipe/internal/models" - "github.com/haopengzhan/rsspipe/internal/opml" - "github.com/haopengzhan/rsspipe/internal/parser" ) type Handler struct { diff --git a/internal/api/server.go b/internal/api/server.go index aab5936..c951268 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,10 +6,10 @@ import ( "path/filepath" "strings" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/auth" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" - "github.com/haopengzhan/rsspipe/internal/auth" ) func NewRouter(h *Handler, authService *auth.AuthService, staticDir string) http.Handler { diff --git a/internal/auth/auth.go b/internal/auth/auth.go index bc97004..928e696 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -8,9 +8,9 @@ import ( "strings" "time" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/config" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/db" "github.com/golang-jwt/jwt/v5" - "github.com/haopengzhan/rsspipe/internal/config" - "github.com/haopengzhan/rsspipe/internal/db" "golang.org/x/crypto/bcrypt" ) diff --git a/internal/crawler/crawler.go b/internal/crawler/crawler.go index d813df1..13c851f 100644 --- a/internal/crawler/crawler.go +++ b/internal/crawler/crawler.go @@ -7,9 +7,9 @@ import ( "sync" "time" - "github.com/haopengzhan/rsspipe/internal/db" - "github.com/haopengzhan/rsspipe/internal/models" - "github.com/haopengzhan/rsspipe/internal/parser" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/db" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/parser" ) type Crawler struct { diff --git a/internal/db/db.go b/internal/db/db.go index cb7d206..6ed5180 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -9,8 +9,8 @@ import ( "strings" "time" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" "github.com/google/uuid" - "github.com/haopengzhan/rsspipe/internal/models" _ "modernc.org/sqlite" ) diff --git a/internal/db/db_test.go b/internal/db/db_test.go index 118900b..1e7200e 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/haopengzhan/rsspipe/internal/models" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" ) func TestDatabaseOperations(t *testing.T) { diff --git a/internal/opml/opml.go b/internal/opml/opml.go index d48aded..7a8e3b7 100644 --- a/internal/opml/opml.go +++ b/internal/opml/opml.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/haopengzhan/rsspipe/internal/models" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" ) type OPML struct { diff --git a/internal/opml/opml_test.go b/internal/opml/opml_test.go index d1dad2b..e404b68 100644 --- a/internal/opml/opml_test.go +++ b/internal/opml/opml_test.go @@ -3,7 +3,7 @@ package opml import ( "testing" - "github.com/haopengzhan/rsspipe/internal/models" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" ) func TestParseAndGenerateOPML(t *testing.T) { diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 0ca1b5c..ccb0cf4 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -8,8 +8,8 @@ import ( "strings" "time" + "git.pengzhan.dev/haopengzhan/rsspipe/internal/models" "github.com/mmcdole/gofeed" - "github.com/haopengzhan/rsspipe/internal/models" ) type FetchResult struct { @@ -39,7 +39,7 @@ func (p *Parser) FetchFeed(ctx context.Context, feedURL, etag, lastModified stri return nil, fmt.Errorf("failed to create request: %w", err) } - req.Header.Set("User-Agent", "RSSPipe/1.0 (+https://github.com/haopengzhan/rsspipe)") + req.Header.Set("User-Agent", "RSSPipe/1.0 (+https://git.pengzhan.dev/haopengzhan/rsspipe)") req.Header.Set("Accept", "application/rss+xml, application/atom+xml, application/xml, text/xml, application/json;q=0.9, */*;q=0.8") if etag != "" { diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..0cbd4fe --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -e + +echo "==================================================" +echo " Running RSSPipe Quality Checks " +echo "==================================================" + +echo "==> 1. Checking Go formatting (gofmt)..." +UNFORMATTED=$(gofmt -l internal/ cmd/) +if [ -n "$UNFORMATTED" ]; then + echo "ERROR: The following Go files are not formatted properly:" + echo "$UNFORMATTED" + echo "Run: gofmt -w internal/ cmd/" + exit 1 +fi +echo "✓ Go files formatted correctly." + +echo "==> 2. Running Go vet..." +go vet ./... +echo "✓ Go vet passed." + +echo "==> 3. Running Go tests..." +go test -v ./... +echo "✓ Go tests passed." + +echo "==> 4. Building React Frontend (TypeScript + Vite)..." +cd web +npm run build +cd .. +echo "✓ Frontend build passed." + +echo "==> 5. Running Secret Leak Scan (Gitleaks)..." +if command -v gitleaks &> /dev/null; then + gitleaks detect --no-git --source . -c .gitleaks.toml -v + echo "✓ Gitleaks scan passed." +else + echo "⚠ Gitleaks not installed locally, skipping local binary check (will run in CI)." +fi + +echo "==================================================" +echo " 🎉 All RSSPipe checks passed cleanly! " +echo "=================================================="