feat: project foundation - Go module, PostgreSQL Docker, React/TS frontend
- Initialize Go module with standard layout (cmd/api/, internal/, pkg/, migrations/) - Add Makefile with build, run, test, lint, migrate targets - Add health check endpoint (/healthz) - Add config package with env-based configuration and tests - Set up PostgreSQL 16 with Docker Compose for local dev - Add .env.example with all required variables - Initialize React 19 + TypeScript + Vite frontend in web/ - Configure TailwindCSS v4 with CSS-first config - Set up ESLint + Prettier with typescript-eslint - Configure path aliases (@/* -> src/*) - Add BaseLayout component - Add README with project structure and setup instructions Closes #32, closes #33, closes #34
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
# =============================================================================
|
||||
# PulseScore Environment Configuration
|
||||
# =============================================================================
|
||||
# Copy this file to .env and fill in the values.
|
||||
|
||||
# Server
|
||||
PORT=8080
|
||||
ENVIRONMENT=development
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_USER=pulsescore
|
||||
POSTGRES_PASSWORD=pulsescore
|
||||
POSTGRES_DB=pulsescore_dev
|
||||
DATABASE_URL=postgres://pulsescore:pulsescore@localhost:5432/pulsescore_dev?sslmode=disable
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -171,6 +171,9 @@ go.work.sum
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Go build output
|
||||
bin/
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
|
||||
33
Makefile
Normal file
33
Makefile
Normal file
@@ -0,0 +1,33 @@
|
||||
.PHONY: build run test lint clean migrate-up migrate-down
|
||||
|
||||
BINARY_NAME=pulsescore-api
|
||||
BUILD_DIR=bin
|
||||
|
||||
build:
|
||||
go build -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/api
|
||||
|
||||
run: build
|
||||
./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
||||
test:
|
||||
go test ./... -v -race
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
migrate-up:
|
||||
@echo "Run migrations up (requires golang-migrate)"
|
||||
migrate -path migrations -database "$(DATABASE_URL)" up
|
||||
|
||||
migrate-down:
|
||||
@echo "Run migrations down (requires golang-migrate)"
|
||||
migrate -path migrations -database "$(DATABASE_URL)" down
|
||||
|
||||
dev-db:
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
|
||||
dev-db-down:
|
||||
docker compose -f docker-compose.dev.yml down
|
||||
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# PulseScore
|
||||
|
||||
Customer health scoring platform for B2B SaaS companies. Connect Stripe, HubSpot, and Intercom to monitor customer health with automated scoring.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
pulse-score/
|
||||
├── cmd/api/ # Application entry point
|
||||
│ └── main.go
|
||||
├── internal/ # Private application code
|
||||
│ ├── config/ # Configuration management
|
||||
│ ├── handler/ # HTTP handlers
|
||||
│ ├── middleware/ # HTTP middleware
|
||||
│ ├── model/ # Domain models
|
||||
│ ├── repository/ # Database access layer
|
||||
│ └── service/ # Business logic
|
||||
├── pkg/ # Reusable packages
|
||||
├── migrations/ # Database migrations
|
||||
├── scripts/
|
||||
│ └── init-db/ # PostgreSQL initialization scripts
|
||||
├── web/ # React/TypeScript frontend (Vite + TailwindCSS v4)
|
||||
│ └── src/
|
||||
│ └── components/
|
||||
├── docker-compose.dev.yml
|
||||
├── Makefile
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Backend:** Go (net/http)
|
||||
- **Frontend:** React 19, TypeScript, Vite, TailwindCSS v4
|
||||
- **Database:** PostgreSQL 16
|
||||
- **Deployment:** Docker, Nginx, VPS
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.24+
|
||||
- Node.js 20+
|
||||
- Docker & Docker Compose
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Clone and configure
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 2. Start the database
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
### 3. Run the API
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
The API starts on http://localhost:8080. Health check: `GET /healthz`
|
||||
|
||||
### 4. Run the frontend
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The frontend starts on http://localhost:5173.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Backend (Makefile)
|
||||
|
||||
| Command | Description |
|
||||
| ------------------ | ------------------------------ |
|
||||
| `make build` | Build the Go binary |
|
||||
| `make run` | Build and run the API |
|
||||
| `make test` | Run tests with race detection |
|
||||
| `make lint` | Run `go vet` |
|
||||
| `make dev-db` | Start development PostgreSQL |
|
||||
| `make dev-db-down` | Stop development PostgreSQL |
|
||||
| `make migrate-up` | Run database migrations up |
|
||||
| `make migrate-down`| Roll back database migrations |
|
||||
|
||||
### Frontend (web/)
|
||||
|
||||
| Command | Description |
|
||||
| ------------------- | ----------------------------- |
|
||||
| `npm run dev` | Start Vite dev server |
|
||||
| `npm run build` | Production build |
|
||||
| `npm run lint` | ESLint check |
|
||||
| `npm run format` | Format with Prettier |
|
||||
| `npm run preview` | Preview production build |
|
||||
34
cmd/api/main.go
Normal file
34
cmd/api/main.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/onnwee/pulse-score/internal/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"status":"ok"}`)
|
||||
})
|
||||
|
||||
addr := fmt.Sprintf(":%s", cfg.Port)
|
||||
log.Printf("Starting PulseScore API on %s", addr)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
log.Printf("Server error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
187
dev-plan.md
Normal file
187
dev-plan.md
Normal file
@@ -0,0 +1,187 @@
|
||||
Plan: PulseScore Development Roadmap via GitHub Issues
|
||||
Create ~211 task issues organized under 23 epic issues and 1 master roadmap issue in subculture-collective/pulse-score. Each sub-issue is scoped for a coding agent with detailed descriptions, goals, and acceptance criteria. The master roadmap lists every issue in dependency-respecting chronological order (flat, not grouped by epic). All hosting targets your VPS with Docker + Nginx + SSL (no Vercel).
|
||||
|
||||
Structure
|
||||
1 Roadmap issue — flat chronological list of all ~211 issues
|
||||
23 Epic issues — each lists its child issues in the body
|
||||
~211 Task issues — each has description, goals, technical details, acceptance criteria, dependencies
|
||||
Labels: phase:mvp, phase:expansion, phase:defensibility, type:epic, type:task, area:backend, area:frontend, area:infra, area:docs, area:integration, priority:critical/high/medium/low
|
||||
Milestones: Phase 1: MVP, Phase 2: Expansion, Phase 3: Defensibility
|
||||
Issue Template (each sub-issue follows this)
|
||||
Description — What to build and why, with technical context
|
||||
Goals — Specific deliverables
|
||||
Technical Details — Files to create, packages to use, patterns to follow
|
||||
Acceptance Criteria — Testable checkboxes including "tests written and passing"
|
||||
Dependencies — Blocked by #N links
|
||||
|
||||
PHASE 1: MVP (15 Epics, ~143 issues)
|
||||
E1: Project Foundation & Infrastructure (14 issues)
|
||||
|
||||
Initialize Go module with standard layout (cmd/api/, internal/, pkg/, Makefile)
|
||||
Set up PostgreSQL with Docker Compose for local dev
|
||||
Initialize React/TypeScript project (Vite + TailwindCSS v4)
|
||||
Set up Go HTTP server with Chi router + base middleware (logging, recovery, request ID)
|
||||
Set up database migration framework (golang-migrate)
|
||||
Configure environment variable management (config package, .env.example)
|
||||
Set up GitHub Actions CI (Go lint/test/build, React lint/test/build)
|
||||
Create multi-stage Dockerfile for Go API
|
||||
Create Dockerfile for React frontend with Nginx (SPA-routing)
|
||||
Create production Docker Compose (PostgreSQL + API + frontend)
|
||||
Set up Nginx reverse proxy with SSL (Let's Encrypt/Certbot)
|
||||
Create VPS deployment scripts (initial setup + deploy command)
|
||||
Implement health check endpoints (/healthz, /readyz)
|
||||
Configure CORS, security headers, and rate limiting middleware
|
||||
E2: Database Schema & Migrations (10 issues)
|
||||
|
||||
Organizations table migration
|
||||
Users + user_organizations join table migration
|
||||
Integration_connections table migration (OAuth tokens, encrypted)
|
||||
Customers table migration (tenant-scoped, external_id, MRR, metadata JSONB)
|
||||
Customer_events table migration (event log, typed, timestamped)
|
||||
Health_scores + health_score_history tables migration
|
||||
Stripe-specific data tables (subscriptions, payments)
|
||||
Alert_rules + alert_history tables migration
|
||||
Database seed script (1 org, 3 users, 50 customers, events, scores)
|
||||
Database connection pool + query helpers (pgxpool, repository pattern)
|
||||
E3: Authentication & Multi-tenancy (15 issues)
|
||||
|
||||
User registration endpoint (bcrypt, email validation, creates org)
|
||||
User login endpoint (JWT access + refresh tokens)
|
||||
JWT authentication middleware
|
||||
Refresh token endpoint
|
||||
Multi-tenant isolation middleware (org_id scoping)
|
||||
Organization creation flow (auto on registration)
|
||||
Team member invitation endpoint (generate token, RBAC-gated)
|
||||
Invite email sending via SendGrid
|
||||
Invite acceptance endpoint (create/link user to org)
|
||||
RBAC middleware (owner/admin/member)
|
||||
Password reset flow (request + completion endpoints)
|
||||
User profile API (GET/PATCH)
|
||||
Login page (React)
|
||||
Registration page (React)
|
||||
Auth state management + protected routes (React context, token refresh)
|
||||
E4: Stripe Data Integration (12 issues)
|
||||
|
||||
Stripe OAuth connect flow backend (initiate + callback)
|
||||
Stripe connection UI in settings (React)
|
||||
Stripe customer data sync service
|
||||
Stripe subscription data sync service
|
||||
Stripe payment/invoice data sync service
|
||||
Stripe webhook handler (signature verification, event routing)
|
||||
MRR calculation service (normalize annual → monthly)
|
||||
Failed payment detection and tracking
|
||||
Payment recency calculation per customer
|
||||
Initial data sync orchestrator (full sync on connect)
|
||||
Incremental sync scheduler (periodic delta updates)
|
||||
Connection monitoring + error handling (retry, status updates)
|
||||
E5: Health Scoring Engine (11 issues)
|
||||
|
||||
Scoring configuration model + migration (weights, thresholds, per-org)
|
||||
Payment recency scoring factor (configurable decay curve)
|
||||
MRR trend scoring factor (growth/decline over time windows)
|
||||
Failed payment history scoring factor
|
||||
Support ticket volume scoring factor (normalized)
|
||||
Engagement/activity scoring factor (event frequency, graceful degradation)
|
||||
Weighted score aggregation service (handles missing factors)
|
||||
Score recalculation scheduler (batch + event-triggered)
|
||||
Score change detection + history tracking
|
||||
Customer risk categorization (green/yellow/red thresholds)
|
||||
Score configuration admin API (GET/PUT with validation)
|
||||
E6: REST API Layer (10 issues)
|
||||
|
||||
Customer list endpoint (paginated, sortable, filterable by risk/search)
|
||||
Customer detail endpoint (profile + score factors + subscriptions)
|
||||
Customer timeline/events endpoint (paginated, filterable)
|
||||
Dashboard summary stats endpoint (customer count, at-risk, MRR, trends)
|
||||
Health score distribution endpoint (histogram/risk breakdown)
|
||||
Integration management endpoints (list/disconnect/sync-status)
|
||||
Organization settings endpoints (GET/PATCH)
|
||||
User management endpoints (list/update-role/remove members)
|
||||
Alert rules CRUD endpoints
|
||||
OpenAPI/Swagger documentation generation
|
||||
E7: Dashboard Core UI (17 issues)
|
||||
|
||||
App shell + sidebar navigation
|
||||
Responsive navigation (collapsible sidebar, mobile hamburger)
|
||||
Dashboard overview page with summary stat cards
|
||||
Health score distribution chart (Recharts)
|
||||
MRR trend chart (line chart, time range selector)
|
||||
Customer list page with data table (sort, filter, paginate)
|
||||
Health score badge/indicator component (reusable, color-coded)
|
||||
Customer search + filter controls (URL-synced)
|
||||
Customer detail page layout (header, tabs/sections)
|
||||
Customer event timeline component
|
||||
Customer health score history chart
|
||||
Integration status indicators
|
||||
Settings page with tabs (Org, Profile, Integrations, Scoring, Billing, Team)
|
||||
Toast/notification system
|
||||
Loading skeletons + empty state components
|
||||
Error boundary + 404 page
|
||||
Dark mode / theme toggle
|
||||
E8: HubSpot Integration (8 issues) — OAuth, contact/deal/company sync, data enrichment, webhooks, incremental sync
|
||||
|
||||
E9: Intercom Integration (7 issues) — OAuth, conversation/contact sync, ticket volume metrics, webhooks, incremental sync
|
||||
|
||||
E10: Email Alerts (8 issues) — SendGrid setup, email templates, alert rule evaluation engine, score drop triggers, alert UI, delivery tracking, preferences
|
||||
|
||||
E11: Onboarding Wizard (7 issues) — Multi-step wizard shell, welcome/org setup step, Stripe/HubSpot/Intercom connection steps, "generating scores" preview step, tracking + resume
|
||||
|
||||
E12: Billing & Subscription (9 issues) — Stripe billing products setup, checkout via Stripe Checkout, billing webhook handler, subscription tracking, feature gating middleware (Free: 10 customers/1 integration → Scale: unlimited), pricing page, checkout flow, subscription management, Customer Portal redirect
|
||||
|
||||
E13: Landing Page (6 issues) — Hero with CTA, features section, pricing comparison, social proof, footer, SEO meta/OG tags + sitemap/robots.txt
|
||||
|
||||
E14: Documentation (5 issues) — API reference, quickstart guide, Stripe setup guide, HubSpot/Intercom guides, scoring methodology docs
|
||||
|
||||
E15: Stripe App Marketplace (4 issues) — Requirements research, manifest packaging, app install flow, marketplace listing content
|
||||
|
||||
PHASE 2: EXPANSION (4 Epics, ~36 issues)
|
||||
E16: Automated Playbooks (10 issues) — Data model, CRUD API, visual builder UI, trigger: score threshold, trigger: customer event, action: email customer, action: internal alert, action: tag customer, execution engine, execution history UI
|
||||
|
||||
E17: Team Collaboration (8 issues) — Account assignment API + UI, customer notes API + UI, team activity feed API + page, @mentions + notifications, account handoff workflow
|
||||
|
||||
E18: Additional Integrations (12 issues) — Generic connector interface, Zendesk (OAuth + sync + UI), Salesforce (OAuth + sync + UI), PostHog (API key + sync + UI), generic webhook receiver + config UI, integration health monitoring dashboard
|
||||
|
||||
E19: Advanced Pricing & Feature Gating (6 issues) — Growth/Scale tier definitions, granular feature flags system, tier-based gating, upgrade prompts, usage analytics tracking
|
||||
|
||||
PHASE 3: DEFENSIBILITY (4 Epics, ~28 issues)
|
||||
E20: Anonymized Benchmarking (7 issues) — Data model + anonymization pipeline, aggregation service, benchmark calculation (percentiles), comparison dashboard, industry classification, privacy controls, insight notifications
|
||||
|
||||
E21: AI-Powered Insights (7 issues) — LLM integration service (GPT-4o-mini), prompt template design, per-customer analysis pipeline, insights UI on detail page, action recommendations, cost tracking + rate limiting, dashboard summary insights
|
||||
|
||||
E22: Predictive Churn Models (6 issues) — Feature engineering pipeline, training data preparation, churn model implementation, prediction scoring service, churn indicators in UI, churn forecast dashboard
|
||||
|
||||
E23: Integration Marketplace (8 issues) — Connector SDK design, registration/discovery API, marketplace browse UI, installation flow, developer docs + example, community submission, review pipeline, connector analytics
|
||||
|
||||
Chronological Order (Master Roadmap)
|
||||
The roadmap issue lists all ~211 issues flat, ordered by dependency chains:
|
||||
|
||||
Order Batch Issues
|
||||
1-14 Foundation Go setup → React setup (parallel) → PostgreSQL → Chi server → env config → migrations → health checks → CORS/security → DB pool
|
||||
15-23 Schema All E2 table migrations in order → seed script
|
||||
24-38 Auth Registration → login → JWT middleware → refresh → multi-tenant → org creation → RBAC → profile → password reset → invitations → frontend auth pages + state management
|
||||
39-44 CI/CD & Deploy GitHub Actions → Dockerfiles → production compose → Nginx + SSL → VPS deploy scripts (parallel with auth)
|
||||
45-56 Stripe Integration OAuth → connection UI → customer/subscription/payment sync → webhooks → MRR calc → failed payments → recency → sync orchestrator → scheduler → monitoring
|
||||
57-67 Health Scoring Config model → 5 scoring factors → aggregation → risk categorization → scheduler → change detection → config API
|
||||
68-77 API Layer All REST endpoints → OpenAPI docs
|
||||
78-94 Dashboard UI Shell → nav → shared components → dashboard page → charts → customer list → detail → timeline → settings → dark mode
|
||||
95-109 HubSpot + Intercom Both integration flows (parallel)
|
||||
110-117 Email Alerts SendGrid → templates → evaluation → triggers → UI → tracking → preferences
|
||||
118-124 Onboarding Wizard shell → steps → score generation → tracking
|
||||
125-133 Billing Products → checkout → webhooks → tracking → gating → pricing page → management
|
||||
134-139 Landing Page Hero → features → pricing → social proof → footer → SEO
|
||||
140-144 Docs API ref → quickstart → integration guides → scoring docs
|
||||
145-148 Stripe Marketplace Research → package → install flow → listing
|
||||
149-184 Expansion Playbooks → Team Collaboration → Additional Integrations → Advanced Pricing
|
||||
185-211 Defensibility Benchmarking → AI Insights → Predictive Models → Integration Marketplace
|
||||
Decisions
|
||||
VPS deployment (not Vercel) — Docker Compose + Nginx + Certbot on user's VPS
|
||||
Tests baked in — Each sub-issue acceptance criteria includes "tests written and passing"
|
||||
Tech stack: Go (Chi) + React/TypeScript (Vite/Tailwind) + PostgreSQL — per the plan doc
|
||||
All 3 phases covered — MVP through defensibility, ~211 issues total
|
||||
Issue scoping: Each sub-issue targets 1-4 hours of focused coding agent work with clear inputs/outputs
|
||||
Further Considerations
|
||||
Password reset + invite emails: The plan references SendGrid for alerts, but auth emails (password reset, invites) are needed before the alerts epic. Recommendation: Set up SendGrid basics in E3-8 (invite email) and reuse in E10. Already reflected in the plan.
|
||||
|
||||
Stripe Connect vs Stripe OAuth: The plan mentions "Stripe Connect" but the use case is reading customer data, not processing payments on behalf. Recommendation: Use Stripe OAuth (standard) for reading data, and a separate Stripe account (direct) for PulseScore's own billing. This is how it's structured in E4 (data) vs E12 (billing).
|
||||
|
||||
Frontend component library: No specific UI component library is mentioned. Recommendation: Use shadcn/ui (TailwindCSS-native, widely supported) or keep it custom with Tailwind. This can be decided in E1-3 (React project setup). Which do you prefer?
|
||||
26
docker-compose.dev.yml
Normal file
26
docker-compose.dev.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: pulsescore-postgres
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '5432:5432'
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-pulsescore}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pulsescore}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-pulsescore_dev}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./scripts/init-db:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
'pg_isready -U ${POSTGRES_USER:-pulsescore} -d ${POSTGRES_DB:-pulsescore_dev}',
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
26
internal/config/config.go
Normal file
26
internal/config/config.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
// Config holds application configuration.
|
||||
type Config struct {
|
||||
Port string
|
||||
DatabaseURL string
|
||||
Environment string
|
||||
}
|
||||
|
||||
// Load reads configuration from environment variables with sensible defaults.
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8080"),
|
||||
DatabaseURL: getEnv("DATABASE_URL", "postgres://pulsescore:pulsescore@localhost:5432/pulsescore_dev?sslmode=disable"),
|
||||
Environment: getEnv("ENVIRONMENT", "development"),
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
40
internal/config/config_test.go
Normal file
40
internal/config/config_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadDefaults(t *testing.T) {
|
||||
// Unset any env vars that might be set
|
||||
os.Unsetenv("PORT")
|
||||
os.Unsetenv("DATABASE_URL")
|
||||
os.Unsetenv("ENVIRONMENT")
|
||||
|
||||
cfg := Load()
|
||||
|
||||
if cfg.Port != "8080" {
|
||||
t.Errorf("expected default port 8080, got %s", cfg.Port)
|
||||
}
|
||||
if cfg.Environment != "development" {
|
||||
t.Errorf("expected default environment development, got %s", cfg.Environment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromEnv(t *testing.T) {
|
||||
os.Setenv("PORT", "3000")
|
||||
os.Setenv("ENVIRONMENT", "production")
|
||||
defer func() {
|
||||
os.Unsetenv("PORT")
|
||||
os.Unsetenv("ENVIRONMENT")
|
||||
}()
|
||||
|
||||
cfg := Load()
|
||||
|
||||
if cfg.Port != "3000" {
|
||||
t.Errorf("expected port 3000, got %s", cfg.Port)
|
||||
}
|
||||
if cfg.Environment != "production" {
|
||||
t.Errorf("expected environment production, got %s", cfg.Environment)
|
||||
}
|
||||
}
|
||||
0
internal/handler/.gitkeep
Normal file
0
internal/handler/.gitkeep
Normal file
0
internal/middleware/.gitkeep
Normal file
0
internal/middleware/.gitkeep
Normal file
0
internal/model/.gitkeep
Normal file
0
internal/model/.gitkeep
Normal file
0
internal/repository/.gitkeep
Normal file
0
internal/repository/.gitkeep
Normal file
0
internal/service/.gitkeep
Normal file
0
internal/service/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
0
pkg/.gitkeep
Normal file
0
pkg/.gitkeep
Normal file
6
scripts/init-db/01-extensions.sql
Normal file
6
scripts/init-db/01-extensions.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- PulseScore initial database setup
|
||||
-- This script runs automatically when the PostgreSQL container is first created.
|
||||
|
||||
-- Enable useful extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
6
web/.prettierrc
Normal file
6
web/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
73
web/README.md
Normal file
73
web/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
25
web/eslint.config.js
Normal file
25
web/eslint.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
])
|
||||
13
web/index.html
Normal file
13
web/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
4163
web/package-lock.json
generated
Normal file
4163
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
web/package.json
Normal file
38
web/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.0",
|
||||
"axios": "^1.13.5",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"tailwindcss": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
}
|
||||
16
web/src/App.tsx
Normal file
16
web/src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import BaseLayout from "@/components/BaseLayout";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BaseLayout>
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">Dashboard</h2>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Welcome to PulseScore. Connect your integrations to get started.
|
||||
</p>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
31
web/src/components/BaseLayout.tsx
Normal file
31
web/src/components/BaseLayout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface BaseLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function BaseLayout({ children }: BaseLayoutProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 text-gray-900">
|
||||
<header className="border-b border-gray-200 bg-white">
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-xl font-bold text-indigo-600">PulseScore</h1>
|
||||
<nav className="flex gap-4 text-sm font-medium text-gray-600">
|
||||
<a href="/" className="hover:text-gray-900">
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="/customers" className="hover:text-gray-900">
|
||||
Customers
|
||||
</a>
|
||||
<a href="/settings" className="hover:text-gray-900">
|
||||
Settings
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
web/src/index.css
Normal file
1
web/src/index.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
10
web/src/main.tsx
Normal file
10
web/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
32
web/tsconfig.app.json
Normal file
32
web/tsconfig.app.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
web/tsconfig.json
Normal file
7
web/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
web/tsconfig.node.json
Normal file
26
web/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
14
web/vite.config.ts
Normal file
14
web/vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import path from 'path'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user