Your projects, one dashboard.
A real-time DevOps dashboard for small development teams. Monitor GitHub activity, deployments, and infrastructure metrics in one unified interface with live WebSocket updates.
Built for teams of 3-10 developers who are tired of switching between GitHub, CI/CD dashboards, and monitoring tools.
- Overview
- Tech Stack
- Architecture
- Features
- Project Structure
- Database Schema
- API Endpoints
- WebSocket Events
- Setup & Installation
- Development Workflow
- Deployment
- Roadmap
Development teams waste time context-switching between multiple tools:
- GitHub for PRs and code reviews
- Vercel/Railway for deployment status
- DataDog/monitoring tools for infrastructure
- Slack for notifications
Pulsar Dev aggregates all critical project information into a single, real-time dashboard. Know what's happening across all your projects without switching tabs.
- Real-time updates - See changes as they happen via WebSocket
- Single pane of glass - All project data in one place
- Extensible - Plugin architecture for adding new integrations
- Free & open source - Self-host or use our hosted version
- Language: Go 1.21+
- HTTP Framework: Chi (lightweight, idiomatic router)
- WebSocket: gorilla/websocket
- Database: Neon PostgreSQL (serverless)
- Cache/Pub-Sub: Redis (Upstash for production)
- Database Driver: pgx/v5
- Migrations: golang-migrate
- Framework: Next.js 14+ (App Router)
- Language: TypeScript
- State Management:
- React Query/TanStack Query (server state)
- Zustand (UI state)
- UI Library: shadcn/ui + Tailwind CSS
- WebSocket Client: Native WebSocket API
- Frontend Hosting: Vercel
- Backend Hosting: Railway
- Database: Neon (managed PostgreSQL)
- Redis: Upstash (serverless Redis)
- CI/CD: GitHub Actions
- Containerization: Docker + Docker Compose (local dev)
- GitHub API (primary integration)
- Vercel API (future)
- Railway API (future)
- Custom webhook support (future)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Next.js Frontend (React) β β
β β - Dashboard UI β β
β β - WebSocket Client β β
β β - React Query (caching) β β
β β - Zustand (UI state) β β
β ββββββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββββββββ β
βββββββββββββββββΌββββββββββββββββββββββββββΌβββββββββββββββββββββ
β HTTP/REST β WebSocket
β β
βββββββββββββββββΌββββββββββββββββββββββββββΌβββββββββββββββββββββ
β Go Backend (Railway) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β REST API β β WebSocket β β Auth β β
β β (Chi) β β Hub β β Middleware β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββββββββββ β
β β β β
β ββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββββ β
β β Application Services β β
β β - ProjectService β β
β β - IntegrationService β β
β β - MetricsService β β
β β - WebSocketService β β
β ββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ β
β β Background Workers (Go routines) β β
β β - GitHub Poller (polls every 30-60s) β β
β β - Deployment Poller (future) β β
β β - Custom Integration Pollers β β
β ββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Layer β
β ββββββββββββββββ ββββββββββββββββ β
β β Neon β β Redis β β
β β PostgreSQL β β (Upstash) β β
β β β β β β
β β - Projects β β - Cache β β
β β - Users β β - Pub/Sub β β
β β - Metrics β β - Sessions β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
βββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββββββββββ
β β
β ββββββββββΌβββββββββ
β β Redis Pub/Sub β
β β Channel β
β ββββββββββ¬βββββββββ
β β
ββββββββββββββββββββ
β
βββββββββββΌββββββββββ
β External APIs β
β - GitHub β
β - Vercel β
β - Railway β
βββββββββββββββββββββ
This is how a GitHub PR update flows through the system:
1. [GitHub Poller Worker]
ββ> Polls GitHub API every 30 seconds
ββ> GET /repos/:owner/:repo/pulls?state=open
2. [GitHub Poller Worker]
ββ> Detects new PR #42 opened
ββ> Compares with last known state in PostgreSQL
3. [GitHub Poller Worker]
ββ> Saves to Neon PostgreSQL
ββ> INSERT INTO pull_requests (...)
4. [GitHub Poller Worker]
ββ> Publishes event to Redis
ββ> PUBLISH "project:123:events" '{"type":"pr_opened","pr_id":42,...}'
5. [WebSocket Hub]
ββ> Subscribed to Redis channel
ββ> Receives event via Redis Pub/Sub
6. [WebSocket Hub]
ββ> Identifies relevant connected clients
ββ> Filters by project_id and user permissions
7. [WebSocket Hub]
ββ> Broadcasts to connected clients
ββ> ws.Send({"type":"pr_opened","data":{...}})
8. [Frontend - React]
ββ> WebSocket client receives message
ββ> React Query cache invalidated
ββ> UI re-renders with new PR
ββ> User sees update instantly (< 1 second from GitHub change)
1. HTTP Server (Chi Router)
- Handle REST API requests
- Serve API endpoints
- Authentication middleware
- CORS handling
- Request validation
2. WebSocket Hub
- Manage WebSocket connections
- Route messages to correct clients
- Handle connection lifecycle
- Implement heartbeat (ping/pong)
- User/project-based message filtering
3. Application Services
- Business logic layer
- Database operations (CRUD)
- Integration orchestration
- Data transformation
- Permission checks
4. Background Workers
- Poll external APIs (GitHub, etc.)
- Process and transform data
- Save to database
- Publish events to Redis
- Handle rate limits and retries
5. Redis Subscriber
- Listen to Redis pub/sub channels
- Forward events to WebSocket Hub
- Decouple workers from WebSocket server
1. Next.js App Router
- Server-side rendering
- API routes (optional proxy)
- Authentication handling
- Route protection
2. React Query
- Server state management
- Automatic caching
- Background refetching
- Optimistic updates
- Cache invalidation on WebSocket events
3. Zustand Store
- UI state (modals, filters, etc.)
- WebSocket connection status
- User preferences
- Temporary UI state
4. WebSocket Client
- Maintain connection to backend
- Reconnect on disconnect
- Trigger React Query invalidation
- Handle incoming real-time events
- β User authentication (Neon Auth)
- β Project management (create, list, view)
- β
GitHub integration
- Repository connection
- Pull request listing
- PR status (open, merged, closed)
- Recent commits
- Workflow run status
- β Real-time updates via WebSocket
- β
Dashboard UI
- Project overview
- Activity feed
- PR queue
- Build status widgets
- Decision: Use Neon Auth instead of GitHub OAuth for user authentication.
- Reason: Simpler integration with the existing Neon database setup and streamlined user management within the Neon ecosystem.
- π Deployment tracking (Vercel, Railway)
- π Infrastructure metrics (error rates, response times)
- π Team activity analytics
- π Custom webhook support
- π Slack/Discord notifications
- π Plugin system for custom integrations
- π Mobile app (React Native)
pulsar-dev/
βββ backend/
β βββ cmd/
β β βββ server/
β β βββ main.go # Entry point
β βββ internal/
β β βββ api/
β β β βββ handlers/ # HTTP handlers
β β β β βββ auth.go
β β β β βββ projects.go
β β β β βββ integrations.go
β β β β βββ metrics.go
β β β βββ middleware/ # Auth, CORS, logging
β β β βββ router.go # Chi router setup
β β βββ websocket/
β β β βββ hub.go # Connection manager
β β β βββ client.go # Individual connection
β β β βββ message.go # Message types
β β βββ services/
β β β βββ project.go # Project business logic
β β β βββ integration.go # Integration logic
β β β βββ metrics.go # Metrics processing
β β β βββ auth.go # Auth logic
β β βββ workers/
β β β βββ github_poller.go # GitHub API polling
β β β βββ coordinator.go # Worker management
β β β βββ publisher.go # Redis publishing
β β βββ integrations/
β β β βββ github/ # GitHub API client
β β β β βββ client.go
β β β β βββ pulls.go
β β β β βββ workflows.go
β β β βββ registry.go # Integration registry
β β βββ models/
β β β βββ project.go
β β β βββ user.go
β β β βββ integration.go
β β β βββ metric.go
β β βββ database/
β β β βββ db.go # Database connection
β β β βββ queries.go # SQL queries
β β βββ config/
β β βββ config.go # Configuration
β βββ migrations/
β β βββ 000001_init_schema.up.sql
β β βββ 000001_init_schema.down.sql
β β βββ 000002_add_integrations.up.sql
β β βββ 000002_add_integrations.down.sql
β βββ pkg/ # Public packages (if any)
β βββ go.mod
β βββ go.sum
β βββ Dockerfile
β βββ .env.example
βββ frontend/
β βββ src/
β β βββ app/
β β β βββ layout.tsx
β β β βββ page.tsx # Landing page
β β β βββ dashboard/
β β β β βββ page.tsx # Dashboard home
β β β β βββ [projectId]/
β β β β βββ page.tsx # Project detail
β β β βββ api/ # API routes (if needed)
β β βββ components/
β β β βββ ui/ # shadcn/ui components
β β β βββ dashboard/
β β β β βββ ProjectCard.tsx
β β β β βββ PRList.tsx
β β β β βββ ActivityFeed.tsx
β β β β βββ MetricWidget.tsx
β β β βββ layout/
β β β βββ Sidebar.tsx
β β β βββ Header.tsx
β β βββ hooks/
β β β βββ useWebSocket.ts # WebSocket hook
β β β βββ useProjects.ts # React Query hooks
β β β βββ useAuth.ts
β β βββ lib/
β β β βββ api.ts # API client
β β β βββ websocket.ts # WebSocket client
β β β βββ utils.ts
β β βββ store/
β β βββ uiStore.ts # Zustand store
β βββ public/
β βββ package.json
β βββ tsconfig.json
β βββ tailwind.config.ts
β βββ next.config.js
β βββ .env.local.example
βββ docker-compose.yml # Local development
βββ .github/
β βββ workflows/
β βββ backend-ci.yml
β βββ frontend-ci.yml
βββ README.md
βββ .gitignore
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
github_id TEXT UNIQUE,
github_username TEXT,
avatar_url TEXT,
access_token TEXT, -- Encrypted GitHub OAuth token
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_users_github_id ON users(github_id);
CREATE INDEX idx_users_email ON users(email);CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
owner_id UUID REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_projects_owner ON projects(owner_id);CREATE TABLE integrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- 'github', 'vercel', 'railway', etc.
config JSONB NOT NULL, -- { "repo": "owner/repo", "token": "..." }
enabled BOOLEAN DEFAULT true,
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(project_id, type) -- One integration of each type per project
);
CREATE INDEX idx_integrations_project ON integrations(project_id);
CREATE INDEX idx_integrations_type ON integrations(type);CREATE TABLE pull_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
integration_id UUID REFERENCES integrations(id) ON DELETE CASCADE,
pr_number INTEGER NOT NULL,
title TEXT NOT NULL,
state TEXT NOT NULL, -- 'open', 'closed', 'merged'
author TEXT NOT NULL,
author_avatar TEXT,
url TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
merged_at TIMESTAMPTZ,
UNIQUE(integration_id, pr_number)
);
CREATE INDEX idx_prs_project ON pull_requests(project_id);
CREATE INDEX idx_prs_state ON pull_requests(state);
CREATE INDEX idx_prs_updated ON pull_requests(updated_at DESC);CREATE TABLE workflow_runs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
integration_id UUID REFERENCES integrations(id) ON DELETE CASCADE,
run_id BIGINT NOT NULL, -- GitHub's workflow run ID
name TEXT NOT NULL,
status TEXT NOT NULL, -- 'queued', 'in_progress', 'completed'
conclusion TEXT, -- 'success', 'failure', 'cancelled', etc.
workflow_name TEXT NOT NULL,
branch TEXT,
commit_sha TEXT,
url TEXT NOT NULL,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(integration_id, run_id)
);
CREATE INDEX idx_runs_project ON workflow_runs(project_id);
CREATE INDEX idx_runs_status ON workflow_runs(status);
CREATE INDEX idx_runs_started ON workflow_runs(started_at DESC);-- Time-series metrics data
CREATE TABLE metrics (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
integration_id UUID REFERENCES integrations(id) ON DELETE CASCADE,
metric_type TEXT NOT NULL, -- 'pr_count', 'build_success_rate', etc.
value JSONB NOT NULL, -- Flexible storage for different metric shapes
timestamp TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_metrics_project_type_time
ON metrics(project_id, metric_type, timestamp DESC);
-- Optional: Partition by month for better performance
-- CREATE TABLE metrics_2025_02 PARTITION OF metrics
-- FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');CREATE TABLE project_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member', -- 'owner', 'admin', 'member'
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(project_id, user_id)
);
CREATE INDEX idx_members_project ON project_members(project_id);
CREATE INDEX idx_members_user ON project_members(user_id);Use golang-migrate for schema management:
# Create new migration
migrate create -ext sql -dir migrations -seq add_deployments_table
# Apply migrations
migrate -path ./migrations -database "$DATABASE_URL" up
# Rollback
migrate -path ./migrations -database "$DATABASE_URL" down 1POST /api/auth/github # Initiate GitHub OAuth
GET /api/auth/github/callback # GitHub OAuth callback
POST /api/auth/logout # Logout
GET /api/auth/me # Get current user
GET /api/projects # List user's projects
POST /api/projects # Create new project
GET /api/projects/:id # Get project details
PATCH /api/projects/:id # Update project
DELETE /api/projects/:id # Delete project
GET /api/projects/:id/integrations # List project integrations
POST /api/projects/:id/integrations # Add integration
GET /api/projects/:id/integrations/:type # Get specific integration
PATCH /api/projects/:id/integrations/:type # Update integration
DELETE /api/projects/:id/integrations/:type # Remove integration
POST /api/projects/:id/integrations/:type/sync # Trigger manual sync
GET /api/projects/:id/pull-requests # List PRs
GET /api/projects/:id/workflows # List workflow runs
GET /api/projects/:id/metrics # Get project metrics
GET /api/projects/:id/activity # Get activity feed
GET /ws # WebSocket upgrade endpoint
# Query params: ?token=<jwt_token>
Create Project:
POST /api/projects
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"name": "My Awesome Project",
"description": "Backend services for our app"
}Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "My Awesome Project",
"description": "Backend services for our app",
"owner_id": "987fbc97-4bed-5078-9f07-9141ba07c9f3",
"created_at": "2025-02-18T10:30:00Z",
"updated_at": "2025-02-18T10:30:00Z"
}Add GitHub Integration:
POST /api/projects/:id/integrations
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"type": "github",
"config": {
"repo": "owner/repository-name"
}
}Response:
{
"id": "456e7890-e89b-12d3-a456-426614174111",
"project_id": "123e4567-e89b-12d3-a456-426614174000",
"type": "github",
"config": {
"repo": "owner/repository-name"
},
"enabled": true,
"created_at": "2025-02-18T10:35:00Z"
}Client connects to /ws with JWT token:
const ws = new WebSocket('wss://api.pulsardev.io/ws?token=<jwt_token>');All events follow this structure:
interface WebSocketMessage {
type: string; // Event type
project_id: string; // Associated project
data: any; // Event-specific payload
timestamp: number; // Unix timestamp
}1. Pull Request Events
{
"type": "pr_opened",
"project_id": "123e4567-e89b-12d3-a456-426614174000",
"data": {
"pr_number": 42,
"title": "Add user authentication",
"author": "johndoe",
"url": "https://github.com/owner/repo/pull/42"
},
"timestamp": 1708257600
}Event types: pr_opened, pr_closed, pr_merged, pr_updated
2. Workflow Events
{
"type": "workflow_completed",
"project_id": "123e4567-e89b-12d3-a456-426614174000",
"data": {
"workflow_name": "CI",
"status": "completed",
"conclusion": "success",
"url": "https://github.com/owner/repo/actions/runs/12345"
},
"timestamp": 1708257600
}Event types: workflow_started, workflow_completed, workflow_failed
3. Metric Updates
{
"type": "metric_update",
"project_id": "123e4567-e89b-12d3-a456-426614174000",
"data": {
"metric_type": "pr_count",
"value": {
"open": 5,
"merged_today": 3
}
},
"timestamp": 1708257600
}4. Integration Sync
{
"type": "integration_synced",
"project_id": "123e4567-e89b-12d3-a456-426614174000",
"data": {
"integration_type": "github",
"items_updated": 15,
"last_synced_at": "2025-02-18T10:45:00Z"
},
"timestamp": 1708257600
}For future features like marking notifications as read:
{
"type": "mark_read",
"data": {
"notification_id": "abc123"
}
}Server sends ping every 30 seconds. Client must respond with pong to maintain connection.
- Go 1.21+
- Node.js 18+ and npm/yarn/pnpm
- Docker & Docker Compose (for local development)
- Neon account (free tier)
- Upstash account (free tier) or local Redis
- GitHub OAuth App credentials
git clone https://github.com/MRQ67/pulsar-dev.git
cd pulsar-devcd backend
# Install dependencies
go mod download
# Copy environment file
cp .env.example .env
# Edit .env with your credentials:
# - DATABASE_URL (Neon connection string)
# - REDIS_URL (Upstash or local Redis)
# - GITHUB_CLIENT_ID
# - GITHUB_CLIENT_SECRET
# - JWT_SECRET
# Run migrations
migrate -path ./migrations -database "$DATABASE_URL" up
# Start backend
go run cmd/server/main.goBackend will run on http://localhost:8080
cd frontend
# Install dependencies
npm install # or: yarn / pnpm install
# Copy environment file
cp .env.local.example .env.local
# Edit .env.local:
# NEXT_PUBLIC_API_URL=http://localhost:8080
# NEXT_PUBLIC_WS_URL=ws://localhost:8080
# Start development server
npm run devFrontend will run on http://localhost:3000
# From project root
docker-compose up -d
# This starts:
# - PostgreSQL (local, for dev)
# - Redis (local, for dev)
# - Backend (rebuilds on code changes)
# - Frontend (rebuilds on code changes)- Go to GitHub Settings β Developer settings β OAuth Apps
- Create new OAuth App:
- Application name:
Pulsar Dev (Local) - Homepage URL:
http://localhost:3000 - Callback URL:
http://localhost:8080/api/auth/github/callback
- Application name:
- Copy Client ID and Client Secret to backend
.env
Backend (.env):
# Server
PORT=8080
ENVIRONMENT=development
# Database
DATABASE_URL=postgresql://user:pass@host/db?sslmode=require
# Redis
REDIS_URL=redis://localhost:6379
# GitHub OAuth
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
GITHUB_CALLBACK_URL=http://localhost:8080/api/auth/github/callback
# JWT
JWT_SECRET=your-super-secret-key-change-this
# CORS
CORS_ORIGINS=http://localhost:3000
# Workers
GITHUB_POLL_INTERVAL=30 # secondsFrontend (.env.local):
NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_WS_URL=ws://localhost:8080Option 1: Manual (Recommended for development)
# Terminal 1: Backend
cd backend
go run cmd/server/main.go
# Terminal 2: Frontend
cd frontend
npm run dev
# Terminal 3: Redis (if not using Upstash)
redis-serverOption 2: Docker Compose
docker-compose up- Create migration:
migrate create -ext sql -dir migrations -seq add_new_table- Write up migration (
migrations/000X_add_new_table.up.sql) - Write down migration (
migrations/000X_add_new_table.down.sql) - Apply migration:
migrate -path ./migrations -database "$DATABASE_URL" upBackend:
cd backend
go test ./...
# With coverage
go test -cover ./...
# Specific package
go test ./internal/servicesFrontend:
cd frontend
npm test
# Watch mode
npm test -- --watchBackend:
# Format
go fmt ./...
# Lint
golangci-lint run
# Vet
go vet ./...Frontend:
# Format
npm run format
# Lint
npm run lint
# Type check
npm run type-check- Create Railway project:
railway init-
Add Neon database:
- Link existing Neon database
- Or create new PostgreSQL instance
-
Add Redis:
- Use Upstash (recommended - serverless)
- Or Railway Redis plugin
-
Deploy backend:
railway up- Set environment variables in Railway dashboard:
- All variables from
.env - Update
GITHUB_CALLBACK_URLto production URL
- All variables from
-
Connect GitHub repository:
- Import project on Vercel dashboard
- Select
frontenddirectory as root
-
Configure build:
- Framework Preset: Next.js
- Build Command:
npm run build - Output Directory:
.next
-
Set environment variables:
NEXT_PUBLIC_API_URL: Railway backend URLNEXT_PUBLIC_WS_URL: Railway WebSocket URL (wss://)
-
Deploy:
- Automatic on push to main branch
Using Railway CLI:
railway run migrate -path ./migrations -database "$DATABASE_URL" upOr use Neon branching:
- Create branch:
migration-test - Test migration on branch
- Merge to main if successful
Backend CI (.github/workflows/backend-ci.yml):
name: Backend CI
on:
push:
branches: [main, develop]
paths:
- 'backend/**'
pull_request:
paths:
- 'backend/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run tests
working-directory: ./backend
run: go test -v ./...Frontend CI (.github/workflows/frontend-ci.yml):
name: Frontend CI
on:
push:
branches: [main, develop]
paths:
- 'frontend/**'
pull_request:
paths:
- 'frontend/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
working-directory: ./frontend
run: npm ci
- name: Run tests
working-directory: ./frontend
run: npm test
- name: Build
working-directory: ./frontend
run: npm run build- Project architecture & planning
- Backend foundation
- Chi HTTP server
- WebSocket Hub
- Neon PostgreSQL connection
- Redis pub/sub
- GitHub integration
- OAuth authentication
- Repository connection
- PR fetching
- Workflow status
- Background poller
- Frontend dashboard
- Authentication flow
- Project list/create
- Dashboard UI
- Real-time updates
- Deployment to Railway + Vercel
- Vercel integration (deployments)
- Railway integration (deployments)
- Team collaboration
- Invite team members
- Role-based permissions
- Notifications
- In-app notifications
- Email notifications (optional)
- Analytics
- PR metrics (cycle time, review time)
- Deployment frequency
- Build success rates
- Custom webhooks
- Plugin system
- Slack/Discord integration
- API for third-party integrations
- Mobile app (React Native)
- Self-hosted version with Docker
- Identify target organizations
- Contribute to related open source projects
- Submit GSoC proposal based on Pulsar Dev
- Integrate Pulsar Dev with GSoC org's ecosystem
- User clicks "Sign in with GitHub" on frontend
- Frontend redirects to
/api/auth/github - Backend redirects to GitHub OAuth
- User authorizes app
- GitHub redirects to
/api/auth/github/callback - Backend:
- Exchanges code for access token
- Fetches user info from GitHub
- Creates/updates user in database
- Generates JWT token
- Redirects to frontend with JWT
- Frontend stores JWT in localStorage
- All subsequent requests include JWT in Authorization header
// Simplified coordinator example
type WorkerCoordinator struct {
db *sql.DB
redis *redis.Client
workers map[string]*Worker
workerPool chan struct{} // Limit concurrent workers
}
func (c *WorkerCoordinator) Start() {
// Fetch all active integrations
integrations := c.fetchActiveIntegrations()
// Start worker for each integration
for _, integration := range integrations {
go c.startWorker(integration)
}
// Monitor for new integrations
ticker := time.NewTicker(1 * time.Minute)
for range ticker.C {
c.checkForNewIntegrations()
}
}
func (c *WorkerCoordinator) startWorker(integration Integration) {
worker := NewGitHubPoller(integration, c.redis)
c.workers[integration.ID] = worker
worker.Start()
}// Publisher (in worker)
func (w *GitHubPoller) publishEvent(eventType string, data interface{}) {
event := Event{
Type: eventType,
ProjectID: w.projectID,
Data: data,
Timestamp: time.Now().Unix(),
}
payload, _ := json.Marshal(event)
channel := fmt.Sprintf("project:%s:events", w.projectID)
w.redis.Publish(ctx, channel, payload)
}
// Subscriber (in WebSocket server)
func subscribeToEvents(hub *Hub, redis *redis.Client) {
pubsub := redis.Subscribe(ctx, "project:*:events")
ch := pubsub.Channel()
for msg := range ch {
var event Event
json.Unmarshal([]byte(msg.Payload), &event)
// Broadcast to WebSocket clients
hub.BroadcastToProject(event.ProjectID, event.Type, event.Data)
}
}// hooks/useProjects.ts
export function useProjects() {
return useQuery({
queryKey: ['projects'],
queryFn: async () => {
const res = await fetch('/api/projects', {
headers: { Authorization: `Bearer ${getToken()}` }
});
return res.json();
},
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// hooks/useWebSocket.ts with React Query integration
export function useWebSocket() {
const queryClient = useQueryClient();
useEffect(() => {
const ws = new WebSocket(WS_URL);
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
// Invalidate relevant queries on real-time updates
if (message.type === 'pr_opened') {
queryClient.invalidateQueries(['projects', message.project_id, 'prs']);
}
if (message.type === 'workflow_completed') {
queryClient.invalidateQueries(['projects', message.project_id, 'workflows']);
}
};
return () => ws.close();
}, [queryClient]);
}This is a personal learning project, but suggestions and feedback are welcome!
- Keep it simple - Don't over-engineer
- Ship fast - MVP over perfect
- Document as you go - Future you will thank you
- Test what matters - Core logic, not every line
- Learn by doing - Experiment and iterate
MIT License - feel free to use this for learning or building your own projects.
Abdellah Qadi
- GitHub: @MRQ67
- Website: aa3.site
- Twitter: @HimoNotting
Built as a learning project for GSoC 2025 preparation and contract work opportunities.
Special thanks to the Go, React, and open-source communities for excellent tools and documentation.
Ready to build? Start with the backend setup and let's ship this! π