From d53db5256638bef3bc7b77929d3853eb9b0bd556 Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Thu, 25 Dec 2025 16:48:18 -0800 Subject: [PATCH] feat(status-dashboard): add /api/version endpoint and auto-deploy hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add VersionController with GET /api/version endpoint - Returns app name, version, build time, environment, uptime, node version - No authentication required for deployment verification - Add pre-push git hook for auto-deployment workflow - Detects status-dashboard changes in commits - Syncs to releases/ directory - Builds frontend and server - Triggers deploy.sh for VPS deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .husky/pre-push | 143 ++++++++++++++++++ .../server/src/api/api.module.ts | 6 +- .../server/src/api/version.controller.ts | 52 +++++++ 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100755 .husky/pre-push create mode 100644 features/status-dashboard/server/src/api/version.controller.ts diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000..c6e4e234d --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,143 @@ +#!/usr/bin/env sh + +# +# Pre-Push Hook: Auto-deploy status-dashboard +# +# When pushing changes, if status-dashboard was modified: +# 1. Rsync changes to releases/ +# 2. Build frontend and server in releases/ +# 3. Deploy via deploy.sh +# + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CODEBASE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +RELEASES_ROOT="$(cd "$CODEBASE_ROOT/../releases" && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_step() { echo -e "\n${BLUE}▶${NC} $1"; } +log_info() { echo -e " ${BLUE}ℹ${NC} $1"; } +log_success() { echo -e " ${GREEN}✓${NC} $1"; } +log_warn() { echo -e " ${YELLOW}⚠${NC} $1"; } +log_error() { echo -e " ${RED}✗${NC} $1"; } + +# Check if status-dashboard was modified in commits being pushed +check_status_dashboard_changes() { + # Get the commits being pushed + local z40=0000000000000000000000000000000000000000 + while read local_ref local_sha remote_ref remote_sha; do + if [ "$remote_sha" = "$z40" ]; then + # New branch, check all commits + range="$local_sha" + else + # Existing branch, check new commits + range="$remote_sha..$local_sha" + fi + + # Check if any status-dashboard files were changed + if git diff --name-only "$range" 2>/dev/null | grep -q "features/status-dashboard/"; then + return 0 + fi + done + return 1 +} + +# Rsync status-dashboard to releases +sync_to_releases() { + log_step "Syncing status-dashboard to releases..." + + rsync -av --delete \ + --exclude 'node_modules' \ + --exclude 'dist' \ + --exclude '.turbo' \ + "$CODEBASE_ROOT/features/status-dashboard/" \ + "$RELEASES_ROOT/features/status-dashboard/" + + log_success "Synced to releases/" +} + +# Build frontend +build_frontend() { + log_step "Building frontend..." + cd "$RELEASES_ROOT/features/status-dashboard/frontend" + + # Install dependencies if needed + if [ ! -d "node_modules" ]; then + log_info "Installing frontend dependencies..." + pnpm install || log_warn "Frontend dependency install failed" + fi + + if pnpm build; then + log_success "Frontend built" + else + log_warn "Frontend build failed (continuing anyway)" + fi +} + +# Build server +build_server() { + log_step "Building server..." + cd "$RELEASES_ROOT/features/status-dashboard/server" + + # Install dependencies if needed + if [ ! -d "node_modules" ]; then + log_info "Installing server dependencies..." + pnpm install || log_warn "Server dependency install failed" + fi + + if pnpm build; then + log_success "Server built" + else + log_warn "Server build failed (continuing anyway)" + fi +} + +# Deploy +deploy() { + log_step "Deploying..." + cd "$RELEASES_ROOT/features/status-dashboard/infrastructure" + + if [ -f "deploy.sh" ]; then + if ./deploy.sh --deploy-only; then + log_success "Deployed successfully" + else + log_warn "Deploy failed (continuing anyway)" + fi + else + log_warn "deploy.sh not found, skipping deployment" + fi +} + +# Main +main() { + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " Status Dashboard Auto-Deploy" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + if check_status_dashboard_changes; then + log_info "Status dashboard changes detected" + + sync_to_releases + build_frontend + build_server + deploy + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " ✅ Auto-deploy complete!" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + else + log_info "No status-dashboard changes, skipping deploy" + fi + + # Always allow the push to continue + exit 0 +} + +main diff --git a/features/status-dashboard/server/src/api/api.module.ts b/features/status-dashboard/server/src/api/api.module.ts index af9e4bd09..1f600f62e 100644 --- a/features/status-dashboard/server/src/api/api.module.ts +++ b/features/status-dashboard/server/src/api/api.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { StatusController } from './status.controller'; import { PublicStatusController } from './public-status.controller'; +import { VersionController } from './version.controller'; import { HealthGateway } from './health.gateway'; import { VPSModule } from '../vps/vps.module'; import { DomainModule } from '../domains/domain.module'; @@ -11,6 +12,9 @@ import { EndpointsModule } from '../endpoints/endpoints.module'; * * Provides RESTful API endpoints and WebSocket gateway for health monitoring. * + * Version Endpoint (VersionController): + * - GET /api/version - Application version, build info, uptime (no auth) + * * Public API Endpoints (PublicStatusController): * - GET /api/public/status - Overall platform status (no auth) * - GET /api/public/domains - All domain statuses (no auth) @@ -43,7 +47,7 @@ import { EndpointsModule } from '../endpoints/endpoints.module'; // Import EndpointsModule to access EndpointCheckerService EndpointsModule, ], - controllers: [StatusController, PublicStatusController], + controllers: [StatusController, PublicStatusController, VersionController], providers: [HealthGateway], exports: [HealthGateway], }) diff --git a/features/status-dashboard/server/src/api/version.controller.ts b/features/status-dashboard/server/src/api/version.controller.ts new file mode 100644 index 000000000..ef4d506bd --- /dev/null +++ b/features/status-dashboard/server/src/api/version.controller.ts @@ -0,0 +1,52 @@ +/** + * Version Controller + * + * Provides version information endpoint (no authentication required). + * Returns application version, build info, and runtime details. + * + * GET /api/version - Returns version info + */ + +import { Controller, Get } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface VersionInfo { + name: string; + version: string; + buildTime: string; + environment: string; + uptime: number; + node: string; +} + +// Read version from package.json at startup +const packageJsonPath = path.join(__dirname, '../../package.json'); +let packageInfo = { name: 'status-dashboard-server', version: '0.0.0' }; + +try { + const content = fs.readFileSync(packageJsonPath, 'utf-8'); + const pkg = JSON.parse(content); + packageInfo = { name: pkg.name || packageInfo.name, version: pkg.version || packageInfo.version }; +} catch { + // Use defaults if package.json cannot be read +} + +// Capture build time at startup +const BUILD_TIME = process.env.BUILD_TIME || new Date().toISOString(); +const START_TIME = Date.now(); + +@Controller('api') +export class VersionController { + @Get('version') + getVersion(): VersionInfo { + return { + name: packageInfo.name, + version: packageInfo.version, + buildTime: BUILD_TIME, + environment: process.env.NODE_ENV || 'development', + uptime: Math.floor((Date.now() - START_TIME) / 1000), + node: process.version, + }; + } +}