Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Latest commit

 

History

History
204 lines (157 loc) · 6.26 KB

File metadata and controls

204 lines (157 loc) · 6.26 KB

Loopless — System Architecture

Overview

Loopless is a full-stack SaaS platform (Next.js 15 frontend + .NET 10 backend) deployed via Docker Compose (dev) and Kubernetes + Helm (prod). All traffic enters through Nginx; the frontend and backend are independently scalable.


Full System Diagram

flowchart TB
  subgraph Client["Client Layer"]
    Browser[User Browser]
  end

  subgraph Gateway["Gateway Layer"]
    Nginx[Nginx Reverse Proxy\nport 80 / 443]
  end

  subgraph Frontend["Frontend (Next.js 15)"]
    NextApp[Next.js App Router\nport 3000]
    TanStack[TanStack Query]
    Zustand[Zustand Store]
    SignalRClient[SignalR Client\nexponential backoff]
    PostHog[PostHog Analytics]
  end

  subgraph Backend[".NET 10 API (Clean Architecture)"]
    API[Minimal API\nEndpoint Extensions]
    MediatR[MediatR CQRS\nValidation + Logging Behaviors]
    Hangfire[Hangfire\n4 recurring jobs]
    SignalRHub[SignalR Hubs\nMessagingHub + NotificationsHub]
    RateLimiter[Rate Limiter\nFixed + Auth policies]
    CorrelationID[Correlation ID\nMiddleware]
  end

  subgraph Data["Data Layer"]
    Postgres[(PostgreSQL 16\n+ pgvector)]
    Redis[(Redis 7\nCache + SignalR backplane)]
    RabbitMQ[(RabbitMQ 3\nTopic exchange)]
    S3[(MinIO / S3\nFile storage)]
  end

  subgraph Identity["Identity"]
    Keycloak[Keycloak 25\nOAuth2 / OIDC\nGitHub SSO]
  end

  subgraph AI["AI Services"]
    OpenAI[OpenAI API\nEmbeddings + Chat]
  end

  subgraph Observability["Observability"]
    Prometheus[Prometheus 2.54\nMetrics scrape]
    Grafana[Grafana 11\nDashboards]
    Loki[Loki 3.1\nLog aggregation]
    Elasticsearch[Elasticsearch 8.14]
    Kibana[Kibana 8.14]
    UptimeKuma[Uptime Kuma\nStatus page]
    Alertmanager[Alertmanager\nWebhook → AIOps]
    AIOps[aiops-triage\nOpenAI → Slack]
  end

  subgraph Automation["Automation"]
    N8N[n8n\nWorkflow automation\nport 5678]
  end

  Browser --> Nginx
  Nginx --> NextApp
  Nginx --> API

  NextApp --> TanStack
  NextApp --> Zustand
  NextApp --> SignalRClient
  NextApp --> PostHog
  SignalRClient --> SignalRHub

  API --> MediatR
  API --> Hangfire
  API --> SignalRHub
  API --> RateLimiter
  API --> CorrelationID

  MediatR --> Postgres
  MediatR --> Redis
  MediatR --> RabbitMQ
  MediatR --> OpenAI
  MediatR --> S3
  API --> Keycloak

  SignalRHub --> Redis

  Prometheus --> Grafana
  Loki --> Grafana
  Elasticsearch --> Kibana
  API --> Prometheus
  API --> Loki
  API --> Elasticsearch

  Prometheus --> Alertmanager
  Alertmanager --> AIOps
  AIOps --> OpenAI

  N8N --> API
Loading

Bounded Contexts

Context Responsibility Key Entities
Identity Auth, RBAC, session User, KeycloakId
Matching Semantic freelancer discovery FreelancerProfile, Embedding, MatchScore
Projects Project lifecycle + GitHub sync Project, GitHubCommit, ProjectSummary
Messaging Real-time DMs Conversation, Message
Standups Async daily check-ins + blocker detection Standup, BlockerFlag
Notifications In-app + email delivery Notification, EmailNotification
Analytics Platform-level metrics AuditTrail, PostHog events

Request Flow — Matching Query

Browser
  → Nginx (port 80)
    → Next.js GET /discover
      → TanStack Query → axios → GET /api/v1/matching
        → .NET API (rate limiter → correlation ID → JWT validate → MediatR)
          → GetMatchesQuery handler
            → Redis cache check (miss)
              → PostgreSQL pgvector cosine similarity
                → OpenAI embedding (if new query)
              → result cached in Redis (TTL 5 min)
          → response: MatchResultDto[]
      → MatchCard components rendered

Deployment Topology

Environment Stack Notes
Dev Docker Compose (16 services) Hot reload, mock auth
Staging K8s + Kustomize overlay Image from GHCR, staging secrets
Production K8s + Helm chart + HPA TLS via cert-manager, Azure Key Vault secrets

Non-Functional Requirements

Requirement Target Mechanism
API latency (p95) < 200ms Redis cache-aside, pgvector IVFFlat index
Throughput 60 req/min per IP Fixed window rate limiter
Availability 99.5% Uptime Kuma monitors, K8s HPA (min 2 replicas)
Observability Full trace Correlation ID on every request, Serilog → Loki
Auth brute force Max 10 req/min on /auth Stricter rate limit policy on auth endpoints

Integrations

GitHub Commit Sync

Loopless keeps project commit history in sync with GitHub via two complementary mechanisms.

Webhook (primary)

When a project has a github_webhook_secret configured, GitHub delivers push events to:

POST /api/v1/projects/{id}/github/webhook

The endpoint:

  1. Reads the raw request body and validates the X-Hub-Signature-256 HMAC-SHA256 header against the per-project github_webhook_secret.
  2. Returns 401 Unauthorized on signature mismatch — no detail leaked.
  3. Acknowledges non-push event types with 200 OK (no-op).
  4. On a valid push event, immediately enqueues a SyncGithubCommitsJob via Hangfire — commits appear within seconds of the push.

The endpoint is unauthenticated (no JWT required); the HMAC signature is the sole authentication mechanism. The per-project secret is stored in the github_webhook_secret column (VARCHAR 256, nullable) added by the AddGithubWebhookSecret migration.

Fallback polling

A Hangfire recurring job (github-commit-sync, GitHubSyncJob.SyncAllAsync) polls all projects with a configured GitHubRepoUrl every 6 hours as a catch-all for missed webhooks (webhook not yet configured, transient GitHub delivery failure). On GitHub rate-limit responses (429 or 403 with X-RateLimit-Remaining: 0), the job reschedules itself 30 minutes into the future.

Flow summary

GitHub push
  → POST /api/v1/projects/{id}/github/webhook
      → HMAC-SHA256 validate (X-Hub-Signature-256 vs project.GitHubWebhookSecret)
          → Hangfire enqueue: SyncGithubCommitsJob (immediate)
              → GitHubService.FetchCommitsAsync → store new commits
                  → enqueue SummaryGenerationJob

Fallback (every 6 h):
  GitHubSyncJob.SyncAllAsync
    → SyncProjectAsync per project
        → same fetch + store + summary pipeline