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 "=================================================="