ALERTS

Overview

Codaro Alerts API is a centralized backend service designed to receive and process real-time editing events from IDE plugins (VS Code, IntelliJ IDEA, and other supported IDEs). The system tracks active editing sessions, detects potential file conflicts, and facilitates team collaboration by alerting developers when multiple team members are working on the same files or modules.

Purpose

The API serves as the central hub for:

  • Real-time event processing (receives editingStarted and editingStopped events from IDE plugins)

  • Collision detection (identifies when multiple developers edit the same file simultaneously)

  • Module activity tracking (monitors team activity within shared code modules)

  • Privacy-first architecture (file path hashing)

Technology Stack

Core Technologies

  • Runtime: Node.js 18+ with Express.js framework

  • Database: MySQL 8.0+ with connection pooling (mysql2 driver)

  • Deployment: Google Cloud Run (containerized, auto-scaling)

  • Authentication: JWT-based authentication for plugin integration

  • Validation: Joi schema validation for all incoming payloads

Key Dependencies

{
  "express": "HTTP server framework",
  "mysql2": "MySQL database driver with promise support",
  "helmet": "Security headers middleware",
  "cors": "Cross-origin resource sharing",
  "express-rate-limit": "IP-based rate limiting",
  "joi": "Schema validation",
  "winston": "Structured logging",
  "jsonwebtoken": "JWT token generation and validation"
}

Privacy by Design

File Path Hashing (SHA-256)

All file paths are hashed using SHA-256 before being stored in the database to avoid persisting sensitive project structure information in plaintext.

Implementation example:

// src/utils/fileHash.js
const computeFilePathHash = (filePath) => {
  const normalizedPath = filePath
    .trim()
    .replace(/\\/g, '/')      // Normalize path separators
    .replace(/\/+/g, '/')     // Remove duplicate slashes
    .replace(/\/$/, '')       // Remove trailing slash
    .toLowerCase();           // Case insensitive

  return crypto
    .createHash('sha256')
    .update(normalizedPath)
    .digest('hex');
};

Privacy guarantees:

  • No plaintext storage of absolute file paths

  • One-way SHA-256 hashing (irreversible)

  • Collision resistance is statistically negligible

  • Normalized paths ensure consistent hashing across platforms

Logging policy (examples):

// Logs contain only:
{
  "alert_type": "COLLISION",
  "projectRootName": "<project-root-name>",
  "relativePath": "/src/components/payment/Pay.tsx",  // Relative paths only
  "users_count": 2,
  "deduped": false
}

// NEVER logged:
// - Absolute file paths (e.g., /Users/john/projects/secret-app/...)
// - User's local directory structure
// - System-specific paths

How the System Receives Data from IDE Plugins

Event flow architecture (high-level):

IDE Plugin -> POST /events/editing -> Validate (Joi) -> Rate limiter -> Hash file path -> Upsert ActiveEditingSessions -> Collision detection -> Generate alert (if needed) -> 200 OK

Primary Endpoint: POST /events/editing

Purpose: Receives real-time editing events from IDE plugins.

Request payload:

{
  "user": 123,                                    // User ID (numeric or Firebase UID string)
  "event": "editingStarted",                      // or "editingStopped"
  "file": "/absolute/or/relative/path/to/file.js",// File path (will be hashed)
  "projectRootName": "<project-root-name>",       // Optional: Project identifier
  "relativePath": "/src/components/payment/Pay.tsx" // Optional: For module detection
}

Field aliases (backward compatibility):

  • user or userId — user identifier

  • file or filePathHash — file path or pre-computed hash

Validation rules (Joi schema):

const editingEventSchema = Joi.object({
  userId: Joi.alternatives().try(Joi.string(), Joi.number()).optional(),
  user: Joi.alternatives().try(Joi.string(), Joi.number()).optional(),
  event: Joi.string().valid('editingStarted', 'editingStopped').required(),
  filePathHash: Joi.string().min(6).max(128).optional(),
  file: Joi.string().min(1).max(1000).optional(),
  projectRootName: Joi.string().optional(),
  relativePath: Joi.string().pattern(/^\/.*/).optional()
}).custom((value, helpers) => {
  // At least one user identifier required
  if (value.userId == null && value.user == null) {
    return helpers.error('any.custom', { message: 'userId or user is required' });
  }
  // At least one file identifier required
  if (!value.filePathHash && !value.file) {
    return helpers.error('any.custom', { message: 'filePathHash or file is required' });
  }
  return value;
});

Response codes:

Code
Meaning
Description

200

Success

Event processed successfully

400

Bad Request

Validation failed (missing required fields)

429

Too Many Requests

Rate limit exceeded

503

Service Unavailable

Database table missing or unavailable

Example success response:

{
  "success": true,
  "event": "editingStarted",
  "filePath": "/path/to/file.js",
  "filePathHash": "a3f5b9c2e1d4f6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"
}

Event Processing: editingStarted

Use a stepper to represent the sequential processing steps.

1

Upsert to ActiveEditingSessions

SQL example:

INSERT INTO ActiveEditingSessions (user_id, file_path_hash, file_path, session_start_time, last_update_time)
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
  last_update_time = CURRENT_TIMESTAMP,
  file_path = VALUES(file_path)

This ensures idempotent creation/updating of a user's active editing session for a file.

2

Collision Detection

  • Query for other users editing the same file_path_hash within the collision window (default: 15 minutes).

  • If ≥ 2 users found, generate a COLLISION alert.

3

Module Activity Detection

  • If projectRootName and relativePath provided, extract module name and upsert to ActiveModuleActivity.

  • Check for other users in the same module within the module window (default: 3 hours).

  • If ≥ 2 users found, generate a SAME_MODULE alert.

Event Processing: editingStopped

1

Delete from ActiveEditingSessions

SQL example:

DELETE FROM ActiveEditingSessions 
WHERE user_id = ? AND file_path_hash = ?
2

Idempotent Operation

  • Returns 200 OK even if no rows were deleted.

  • Prevents errors from duplicate stop events and supports safe retries.

Rate Limiting

Protection against request flooding via IP-based rate limiting.

Configuration example:

// src/server.js
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,    // 15 minutes
  max: 1000,                    // 1000 requests per window per IP
  message: {
    error: 'Too many requests from this IP, please try again later.'
  },
  trustProxy: true,             // Trust X-Forwarded-For headers (Cloud Run)
  standardHeaders: true,        // Include RateLimit-* headers in response
  legacyHeaders: false,         // Disable deprecated X-RateLimit-* headers
  keyGenerator: (req) => req.ip // Use IP after trust proxy processing
});

Rate limit headers example:

RateLimit-Limit: 1000
RateLimit-Remaining: 999
RateLimit-Reset: 1638360000

Behavior:

  • Per-IP tracking with a 15-minute sliding window

  • Returns 429 Too Many Requests when exceeded

  • Configured to trust Cloud Run proxy headers for real IP extraction

Security Features

  1. Payload validation (Joi) — all incoming requests validated before DB operations.

  2. Idempotent operations — editingStarted and editingStopped are retry-safe.

  3. SQL injection protection — all queries use parameterized statements.

  4. Authentication (JWT) — plugin tokens and protected endpoints.

  5. Security headers — Helmet.js enabled.

  6. CORS configuration — environment-aware origin handling.

Examples and relevant snippets:

Validation error example:

{
  "error": "Validation Error",
  "message": "userId or user is required",
  "field": "userId",
  "required": ["user", "event", "file"]
}

Safe parameterized query example:

await query(
  'SELECT * FROM ActiveEditingSessions WHERE user_id = ? AND file_path_hash = ?',
  [userId, filePathHash]
);

JWT token generation endpoint: POST /api/auth/plugin-token (generates 30-day tokens). Protected endpoints require valid JWT.

Helmet usage:

app.use(helmet());

CORS configuration example:

app.use(cors({
  origin: process.env.NODE_ENV === 'production' 
    ? ['https://yourdomain.com'] 
    : true,
  credentials: true
}));

Deployment on Google Cloud Run

Cloud Run architecture (high-level):

Cloud Load Balancer -> Cloud Run Service Instance (Node.js Express container) -> VPC Connector -> MySQL Database (VM/Cloud SQL)

Dockerfile:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8080
CMD ["node", "src/server.js"]

Environment variables (examples):

# Database
DB_HOST=<db-host>
DB_PORT=3306
DB_USER=<db-user>
DB_PASS=<secure-password>
DB_NAME=<db-name>

# Authentication
AUTH_MODE=jwt
JWT_SECRET=<min-32-character-secret>

# Server
PORT=8080
NODE_ENV=production

# Cloud Run
GOOGLE_CLOUD_PROJECT=your-project-id
SERVICE_NAME=<service-name>
REGION=us-central1

Cloud Run deploy example:

gcloud run deploy <service-name> \
  --image gcr.io/PROJECT_ID/<image-name> \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --port 8080 \
  --memory 512Mi \
  --cpu 1 \
  --min-instances 0 \
  --max-instances 100 \
  --vpc-connector <vpc-connector-name> \
  --vpc-egress private-ranges-only \
  --set-env-vars "NODE_ENV=production,AUTH_MODE=jwt"

Health Checks

Liveness: GET /healthz — ultra-light, no DB dependency.

app.get('/healthz', (req, res) => res.status(200).send('ok'));

Readiness: GET /readyz — includes DB checks.

router.get('/readyz', async (req, res) => {
  try {
    // Test database connection
    const [result] = await pool.execute('SELECT 1 as ok, CURRENT_USER() as who');
    
    // Verify ActiveEditingSessions table exists
    await pool.execute('SELECT 1 FROM ActiveEditingSessions LIMIT 1');
    
    res.status(200).json({ 
      ok: true, 
      db: result[0],
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    res.status(503).json({ 
      ok: false, 
      error: error.code || error.message,
      timestamp: new Date().toISOString()
    });
  }
});

Auto-scaling behavior:

  • Scale to zero to save costs

  • Cold start ~2–3 seconds after scale-to-zero

  • Horizontal scaling based on request rate, CPU, memory

  • Each instance handles up to 80 concurrent requests by default

Graceful shutdown:

const gracefulShutdown = async (signal) => {
  logger.info(`Received ${signal}. Starting graceful shutdown...`);
  
  try {
    await closePool();  // Close database connections
    logger.info('Graceful shutdown completed');
    process.exit(0);
  } catch (error) {
    logger.error('Error during graceful shutdown:', error);
    process.exit(1);
  }
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

API Endpoints Summary

Public endpoints (no authentication):

Method
Endpoint
Purpose

GET

/healthz

Liveness check (ultra-light)

GET

/readyz

Readiness check (includes DB)

POST

/events/editing

Receive editing events from IDE plugins

POST

/api/auth/plugin-token

Generate JWT token for plugin

Protected endpoints (JWT required):

Method
Endpoint
Purpose

GET

/api/current-user

Get user info and team list

GET

/api/users/:uid/teams

Get teams for a specific user

GET

/api/collisions

Check for file editing collisions

Internal endpoints (authenticated):

Method
Endpoint
Purpose

POST

/internal/cleanup

Manual cleanup of stale sessions

GET

/internal/cleanup/stats

Get cleanup statistics

Cron endpoints (Cloud Scheduler):

Method
Endpoint
Purpose

GET

/cron/cleanup

Automated cleanup (runs every 30 min)

Error Handling

All errors return a consistent JSON structure:

{
  "error": "Error Type",
  "message": "Human-readable error message",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "field": "fieldName"  // For validation errors
}

Error handling middleware example:

// src/middleware/errorHandler.js
app.use((error, req, res, next) => {
  logger.error('Request error:', {
    error: error.message,
    stack: error.stack,
    path: req.path,
    method: req.method
  });

  res.status(error.statusCode || 500).json({
    error: error.name || 'Internal Server Error',
    message: error.message,
    timestamp: new Date().toISOString()
  });
});

Database error handling example (missing tables):

if (error.code === 'ER_NO_SUCH_TABLE') {
  if (error.message.includes('ActiveEditingSessions')) {
    return res.status(503).json({
      error: 'Service Unavailable',
      message: 'ActiveEditingSessions table not configured'
    });
  }
}

Monitoring and Logging

Structured logging (Winston) example:

logger.info('Processing editing event', {
  userId,
  event,
  filePathHash,  // Hashed, not plaintext
  projectRootName
});

Log levels:

  • error: critical errors

  • warn: non-critical issues

  • info: normal operations

  • debug: detailed debugging (development only)

Cloud Run metrics to monitor:

  • Request Count

  • Request Latency (P50, P95, P99)

  • Error Rate (4xx/5xx)

  • Instance Count

  • Memory Usage

  • CPU Utilization

Request ID tracking example:

app.use((req, res, next) => {
  req.id = crypto.randomUUID();
  logger.info('Incoming request', {
    requestId: req.id,
    method: req.method,
    path: req.path,
    ip: req.ip
  });
  next();
});

Performance Considerations

Database connection pooling example:

// src/config/database.js
const pool = mysql.createPool({
  host: env.DB_HOST,
  port: env.DB_PORT,
  user: env.DB_USER,
  password: env.DB_PASS,
  database: env.DB_NAME,
  waitForConnections: true,
  connectionLimit: 10,      // Max 10 concurrent connections
  queueLimit: 0,            // Unlimited queue
  enableKeepAlive: true,
  keepAliveInitialDelay: 0
});

Query optimization:

  • Index columns: user_id, file_path_hash, last_update_time

  • Composite keys: (user_id, file_path_hash) for fast lookups

  • Use LIMIT 1 for existence checks

Caching strategy:

  • No application-level cache: database is source of truth

  • Leverage MySQL built-in caching

  • Connection pooling for reuse

Security Best Practices

What we do:

  • Hash file paths before storage (SHA-256)

  • Validate all inputs with Joi schemas

  • Use parameterized queries

  • Rate limit requests (1000 / 15min per IP)

  • Implement JWT authentication for protected endpoints

  • Add security headers via Helmet.js

  • Log without sensitive data (no absolute paths)

  • Graceful error handling (no stack traces in production)

What we don't expose:

  • Absolute file paths

  • Database credentials (kept in env vars)

  • Internal implementation details in responses

  • Stack traces in production responses

  • User session details (only aggregated collision data)

Conclusion

The Codaro Alerts API is a production-ready, privacy-focused backend service designed to handle real-time editing events from IDE plugins at scale. It provides privacy-by-design hashing, idempotent operations, auto-scaling on Cloud Run, strong validation and security, and production-grade monitoring.

Key strengths:

  • Privacy by Design (file path hashing)

  • Idempotent operations (retry-safe)

  • Auto-scaling deployment (Cloud Run)

  • Comprehensive security (rate limiting, validation, JWT)

  • Production-grade monitoring (health checks, structured logging)

For integration guides, see:

  • PLUGIN_INTEGRATION.md - IDE plugin integration

  • QUICK_START.md - Quick start guide

  • API_EXAMPLES.md - API usage examples