From 92749c7d7f37709355dc3055898ed53ad4a3751b Mon Sep 17 00:00:00 2001 From: vjuliaife Date: Fri, 28 Aug 2026 06:47:57 -0700 Subject: [PATCH] feat: add changelog automation, gitpod config, dev portal, and security headers --- .github/workflows/release.yml | 39 ++++++++ .gitpod.yml | 38 +++++++ .releaserc.json | 21 ++++ CHANGELOG.md | 8 ++ backend/.env.example | 4 + backend/src/config.ts | 12 +++ backend/src/index.ts | 7 +- backend/src/middleware/security.ts | 21 ++++ frontend/app/developers/page.tsx | 156 +++++++++++++++++++++++++++++ package.json | 8 +- 10 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .gitpod.yml create mode 100644 .releaserc.json create mode 100644 CHANGELOG.md create mode 100644 frontend/app/developers/page.tsx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..5b03fdb6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + issues: write + pull-requests: write + +env: + NODE_VERSION: '20' + +jobs: + release: + name: Release + runs-on: ubuntu-latest + if: github.repository == 'Smartdevs17/agenticpay' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - run: npm ci + + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..d2187614 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,38 @@ +image: + file: .devcontainer/Dockerfile + +tasks: + - name: Setup + init: | + npm ci --prefer-offline || npm install + if command -v rustup >/dev/null 2>&1; then + rustup target add wasm32-unknown-unknown + fi + (cd backend && npm run db:generate) + command: | + docker compose -f docker-compose.yml up -d postgres redis + npm run dev + +ports: + - name: Frontend + port: 3000 + onOpen: open-preview + - name: Backend API + port: 3001 + onOpen: ignore + - name: PostgreSQL + port: 5432 + onOpen: ignore + visibility: private + - name: Redis + port: 6379 + onOpen: ignore + visibility: private + +vscode: + extensions: + - dbaeumer.vscode-eslint + - esbenp.prettier-vscode + - bradlc.vscode-tailwindcss + - rust-lang.rust-analyzer + - Prisma.prisma diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 00000000..a18d26f2 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,21 @@ +{ + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + "@semantic-release/github", + [ + "@semantic-release/git", + { + "assets": ["CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f18fe299 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project are documented in this file. + +Entries are generated automatically from +[Conventional Commits](https://www.conventionalcommits.org/) by +[semantic-release](https://semantic-release.gitbook.io/) on every push to `main`. +See `.releaserc.json` and `.github/workflows/release.yml`. diff --git a/backend/.env.example b/backend/.env.example index 25346500..319ffb82 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -10,6 +10,10 @@ RATE_LIMIT_ENTERPRISE=1000 RATE_LIMIT_WINDOW_MS=900000 COMPRESSION_THRESHOLD=1024 +# Security headers +HSTS_MAX_AGE_SECONDS=31536000 +PERMISSIONS_POLICY=camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), interest-cohort=() + # IP Allowlist (comma-separated CIDR ranges) # IP_ALLOWLIST=10.0.0.0/8,192.168.1.0/24 # IP_ALLOWLIST_ENABLED=false diff --git a/backend/src/config.ts b/backend/src/config.ts index 3e2b6c5d..28bfd68d 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -23,6 +23,10 @@ const envSchema = z.object({ DB_POOL_ACQUIRE_TIMEOUT_MS: z.string().default('30000'), DB_POOL_MAX_USES: z.string().default('7500'), DB_STATEMENT_TIMEOUT_MS: z.string().default('30000'), + HSTS_MAX_AGE_SECONDS: z.string().default('31536000'), + PERMISSIONS_POLICY: z + .string() + .default('camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), interest-cohort=()'), }); const parsed = envSchema.safeParse(process.env); @@ -77,6 +81,14 @@ export const config = { statementTimeoutMs: Number(env.DB_STATEMENT_TIMEOUT_MS), }, }, + security: { + hsts: { + maxAge: Number(env.HSTS_MAX_AGE_SECONDS), + includeSubDomains: true, + preload: true, + }, + permissionsPolicy: env.PERMISSIONS_POLICY, + }, } as const; export type Config = typeof config; diff --git a/backend/src/index.ts b/backend/src/index.ts index f3138ca6..5efe29c7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -11,6 +11,7 @@ import { stellarRouter } from './routes/stellar.js'; import { catalogRouter } from './routes/catalog.js'; import { jobsRouter } from './routes/jobs.js'; import { healthRouter } from './routes/health.js'; +import { docsRouter } from './routes/docs.js'; import { queueRouter } from './routes/queue.js'; import { slaRouter } from './routes/sla.js'; import { startJobs, getJobScheduler } from './jobs/index.js'; @@ -31,7 +32,7 @@ import { pushRouter } from './routes/push.js'; import { ipAllowlistRouter } from './routes/ip-allowlist.js'; import { stripeRouter } from './routes/stripe.js'; import { ipAllowlistMiddleware, initIpAllowlist } from './middleware/ip-allowlist.js'; -import { SecurityMiddleware, SecurityMonitor } from './middleware/security.js'; +import { SecurityMiddleware, SecurityMonitor, securityHeadersMiddleware } from './middleware/security.js'; import { sanitizeInput, contentSecurityPolicy } from './middleware/sanitize.js'; import { notificationsRouter } from './routes/notifications.js'; import { auditRouter } from './routes/audit.js'; @@ -164,6 +165,7 @@ const invoiceLimiter = rateLimit({ legacyHeaders: false, }); +app.use(securityHeadersMiddleware()); app.use( cors({ origin: config.cors.allowedOrigins, @@ -230,6 +232,9 @@ app.use((req: Request, res: Response, next: NextFunction) => { // Health & Readiness checks app.use(healthRouter); +// Interactive API documentation & playground — Issue #758 +app.use('/docs', docsRouter); + import { versionMiddleware } from './middleware/versioning.js'; import { portfolioRouter } from './routes/portfolio.js'; diff --git a/backend/src/middleware/security.ts b/backend/src/middleware/security.ts index 74c017e6..a9fd29d6 100644 --- a/backend/src/middleware/security.ts +++ b/backend/src/middleware/security.ts @@ -2,12 +2,33 @@ import { Request, Response, NextFunction } from 'express'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import { InputSanitizer, sanitizeInput, contentSecurityPolicy, createSecurityRateLimit } from './sanitize'; +import { config } from '../config'; /** * Comprehensive Security Middleware Stack * Implements defense-in-depth security measures */ +/** + * Security headers middleware — HSTS and Permissions-Policy. + * + * Kept independent of the CSP-bearing helmet() configuration in + * `applySecurity()` so it can be wired into the app without pulling in + * CSP directives, which are managed separately. + */ +export function securityHeadersMiddleware() { + const hsts = helmet.hsts({ + maxAge: config.security.hsts.maxAge, + includeSubDomains: config.security.hsts.includeSubDomains, + preload: config.security.hsts.preload, + }); + + return (req: Request, res: Response, next: NextFunction): void => { + res.setHeader('Permissions-Policy', config.security.permissionsPolicy); + hsts(req, res, next); + }; +} + export class SecurityMiddleware { private static instance: SecurityMiddleware; private sanitizer: InputSanitizer; diff --git a/frontend/app/developers/page.tsx b/frontend/app/developers/page.tsx new file mode 100644 index 00000000..c874eeaa --- /dev/null +++ b/frontend/app/developers/page.tsx @@ -0,0 +1,156 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; +import { BookOpen, Code2, ExternalLink, FileJson, Terminal } from 'lucide-react'; + +export const metadata: Metadata = { + title: 'Developer Portal | AgenticPay', + description: + 'Explore the AgenticPay API playground, OpenAPI specification, and official SDKs for TypeScript, Python, and Go.', +}; + +const GITHUB_REPO = 'https://github.com/Smartdevs17/agenticpay'; + +const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1'; +// The interactive docs/playground are served from the API origin, outside of /api/v1. +const API_ORIGIN = API_BASE.replace(/\/api\/v1\/?$/, ''); +const PLAYGROUND_URL = `${API_ORIGIN}/docs`; +const OPENAPI_SPEC_URL = `${API_ORIGIN}/docs/openapi.json`; + +interface Sdk { + language: string; + install: string; + packagePath: string; +} + +const SDKS: Sdk[] = [ + { language: 'TypeScript', install: 'npm install @agenticpay/sdk', packagePath: 'packages/sdk' }, + { language: 'Python', install: 'pip install agenticpay', packagePath: 'sdks/python' }, + { language: 'Go', install: 'go get github.com/Smartdevs17/agenticpay-sdk-go', packagePath: 'sdks/go' }, +]; + +interface Guide { + title: string; + description: string; + file: string; +} + +const GUIDES: Guide[] = [ + { title: 'SDK overview', description: 'Available SDKs, quick start, and authentication.', file: 'docs/sdk/README.md' }, + { title: 'Migrating from REST', description: 'Move from raw REST calls to the typed SDKs.', file: 'docs/sdk/MIGRATION-FROM-REST.md' }, + { title: 'Error handling', description: 'The SDK error hierarchy and how to handle it.', file: 'docs/sdk/ERROR-HANDLING.md' }, + { title: 'Testing', description: 'Mocking and testing code that uses the SDKs.', file: 'docs/sdk/TESTING.md' }, + { title: 'Versioning', description: 'SDK release and API versioning policy.', file: 'docs/sdk/VERSIONING.md' }, +]; + +export default function DevelopersPage() { + return ( +
+
+
+
+
+
+
+ + Developer Portal +
+

+ Build on AgenticPay +

+

+ Explore the API in an interactive playground, browse the OpenAPI specification, + and get started with an official SDK. +

+ +
+ +
+
+

+ Official SDKs +

+

+ Typed clients for the AgenticPay API, published from this repository. +

+
+ {SDKS.map((sdk) => ( +
+

{sdk.language}

+
+                          {sdk.install}
+                        
+ + View source + + +
+ ))} +
+
+ +
+

+ Guides +

+

+ Reference documentation for integrating with the AgenticPay SDKs and API. +

+
    + {GUIDES.map((guide) => ( +
  • + + + + + + {guide.title} + + {guide.description} + + + + +
  • + ))} +
+
+
+
+
+
+
+
+ ); +} diff --git a/package.json b/package.json index 851adaf0..ea5abe9f 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,14 @@ "generate:api-hooks:watch": "node packages/api-hooks-generator/dist/cli.js --watch", "analyze:bundle": "bash scripts/analyze-bundle.sh", "lighthouse:ci": "lhci autorun --config=./lighthouse/lighthouse.config.json", - "log-viewer": "node packages/log-viewer/dist/cli.js" + "log-viewer": "node packages/log-viewer/dist/cli.js", + "release": "semantic-release" }, "devDependencies": { - "turbo": "^1.10.16" + "turbo": "^1.10.16", + "semantic-release": "^24.2.0", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/git": "^10.0.1" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.62.2",