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
editingStartedandeditingStoppedevents 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 pathsHow 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 OKPrimary 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):
useroruserId— user identifierfileorfilePathHash— 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:
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.
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.
Event Processing: editingStopped
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: 1638360000Behavior:
Per-IP tracking with a 15-minute sliding window
Returns
429 Too Many Requestswhen exceededConfigured to trust Cloud Run proxy headers for real IP extraction
Security Features
Payload validation (Joi) — all incoming requests validated before DB operations.
Idempotent operations —
editingStartedandeditingStoppedare retry-safe.SQL injection protection — all queries use parameterized statements.
Authentication (JWT) — plugin tokens and protected endpoints.
Security headers — Helmet.js enabled.
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-central1Cloud 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):
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):
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):
POST
/internal/cleanup
Manual cleanup of stale sessions
GET
/internal/cleanup/stats
Get cleanup statistics
Cron endpoints (Cloud Scheduler):
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_timeComposite keys:
(user_id, file_path_hash)for fast lookupsUse
LIMIT 1for 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