feat: complete MVP setup with CI/CD, Makefile, docker, and backlog docs
This commit is contained in:
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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'''
|
||||
]
|
||||
+51
@@ -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"]
|
||||
@@ -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."
|
||||
@@ -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.
|
||||
+5
-5
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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`, `<link rel="icon">`, or `<link rel="apple-touch-icon">` 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.
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/haopengzhan/rsspipe
|
||||
module git.pengzhan.dev/haopengzhan/rsspipe
|
||||
|
||||
go 1.26.5
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/haopengzhan/rsspipe/internal/models"
|
||||
"git.pengzhan.dev/haopengzhan/rsspipe/internal/models"
|
||||
)
|
||||
|
||||
type OPML struct {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
Executable
+42
@@ -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 "=================================================="
|
||||
Reference in New Issue
Block a user