Architecture
Platform Integration

Platform Integration Guide

This document provides comprehensive guidance for integrating external systems with the MyNATCA Platform, including authentication patterns, API proxy configuration, and best practices.

Platform Integration Overview

The MyNATCA Platform (mynatca-platform) serves as the central integration hub for the ecosystem, providing:

  • Authentication Services: OAuth 2.0 with Auth0 integration
  • API Proxy Gateway: Bearer token forwarding to external APIs
  • Session Management: Redis-backed server-side sessions
  • Database Operations: Supabase PostgreSQL integration
  • Legacy Data Sync: MySQL to Supabase synchronization

Integration Patterns

1. Frontend Application Integration

Frontend applications (like the Hub) integrate with the Platform using a standard pattern:

Authentication Flow

// Frontend: Request authentication status
const response = await fetch('/api/auth/me', {
  method: 'GET',
  credentials: 'include' // Include session cookies
});
 
if (response.ok) {
  const user = await response.json();
  // User is authenticated, proceed with app logic
} else {
  // Redirect to login
  window.location.href = '/api/auth/login';
}

API Requests Through Proxy

// Frontend: Make API requests via Platform proxy
const memberData = await fetch('/api/mynatca/Member/12985', {
  method: 'GET',
  credentials: 'include' // Platform handles Bearer token forwarding
});
 
const profile = await memberData.json();

2. External API Integration

Proxy Route Configuration

External APIs are integrated through database-configured proxy routes:

-- Supabase: Configure proxy route
INSERT INTO proxy_routes (path, target, path_rewrite)
VALUES (
  '/api/mynatca/*',
  'https://dev.my.natca.org/api',
  '{"^/api/mynatca/": "/"}'::jsonb
);

Proxy Middleware Implementation

The Platform automatically handles Bearer token forwarding for external APIs:

// Platform: middleware/proxy.js
const createProxyMiddleware = require('http-proxy-middleware').createProxyMiddleware;
 
const proxy = createProxyMiddleware({
  target: route.target,
  changeOrigin: true,
  pathRewrite: route.pathRewrite,
 
  // Critical: v3.0 syntax for callbacks
  on: {
    proxyReq: (proxyReq, req, res) => {
      // Forward JWT token (idToken) from session
      if (req.session.user?.idToken) {
        proxyReq.setHeader('Authorization', `Bearer ${req.session.user.idToken}`);
        console.log('🔑 Forwarding Auth0 ID token to API');
      }
    },
 
    proxyRes: (proxyRes, req, res) => {
      // Enable CORS for proxied responses
      proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*';
      proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
    }
  }
});

3. Discord Bot Integration

Discord bots integrate with the Platform for authentication and member data:

Member Verification Flow

// Discord Bot: Verify member against Platform
const verifyMember = async (discordUserId, authToken) => {
  const response = await fetch(`${PLATFORM_URL}/api/members/verify`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${authToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      discordUserId,
      provider: 'discord'
    })
  });
 
  return response.json();
};

Role Assignment Based on Member Data

// Discord Bot: Assign roles based on Platform data
const assignMemberRoles = async (member, memberData) => {
  const roles = [];
 
  // Facility role
  if (memberData.facilityCode) {
    roles.push(`facility-${memberData.facilityCode.toLowerCase()}`);
  }
 
  // Region role
  if (memberData.regionCode) {
    roles.push(`region-${memberData.regionCode.toLowerCase()}`);
  }
 
  // Position-based roles
  if (memberData.positions) {
    memberData.positions.forEach(position => {
      if (position.title.includes('FacRep')) {
        roles.push('facility-representative');
      }
    });
  }
 
  // Apply roles to Discord member
  await member.roles.add(roles);
};

Authentication Integration

Auth0 Configuration

Custom Claims for NATCA Data

Auth0 Rules inject NATCA-specific data into JWT tokens:

// Auth0 Rule: Add NATCA custom claims
function addNATCACustomClaims(user, context, callback) {
  const namespace = 'https://my.natca.org/';
  const customClaims = {
    [`${namespace}natca_id`]: user.user_metadata.natca_id,
    [`${namespace}member_number`]: user.user_metadata.member_number,
    [`${namespace}facility_code`]: user.user_metadata.facility_code,
    [`${namespace}region_code`]: user.user_metadata.region_code
  };
 
  context.idToken = Object.assign(context.idToken, customClaims);
  context.accessToken = Object.assign(context.accessToken, customClaims);
 
  callback(null, user, context);
}

JWT Token Handling

Critical Implementation Note: The Platform uses JWT idToken for API forwarding, not the encrypted accessToken:

// Platform: session management
app.get('/api/auth/callback', async (req, res) => {
  try {
    const { user, accessToken, idToken } = await auth0.handleCallback(req, res);
 
    // Store both tokens in session
    req.session.user = {
      ...user,
      accessToken, // JWE encrypted - for Auth0 Management API
      idToken      // JWT signed - for external API forwarding
    };
 
    res.redirect('/');
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Session Management

Redis Configuration

// Platform: Redis session store
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
 
const redisClient = redis.createClient({
  url: process.env.REDIS_URL,
  socket: {
    connectTimeout: 10000,
    commandTimeout: 5000,
  }
});
 
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  name: 'platform.session',
  resave: false,
  saveUninitialized: false,
  rolling: true,
  cookie: {
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax'
  }
}));

Database Integration

Supabase Integration

Connection Setup

// Platform: lib/supabase.js
import { createClient } from '@supabase/supabase-js';
 
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
 
export const supabase = createClient(supabaseUrl, supabaseKey);

Row Level Security (RLS)

-- Supabase: Member data access policy
CREATE POLICY "Users can view their own data" ON members
  FOR SELECT
  USING (auth.jwt() ->> 'sub' = auth_user_id);
 
CREATE POLICY "Service role has full access" ON members
  FOR ALL
  USING (auth.role() = 'service_role');

Legacy MySQL Synchronization

Sync Configuration

// Platform: sync/config.js
export const syncConfig = {
  mysql: {
    host: process.env.MYSQL_HOST,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DATABASE,
    port: process.env.MYSQL_PORT || 3306
  },
 
  supabase: {
    url: process.env.SUPABASE_URL,
    serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY
  },
 
  batchSize: 1000,
  retryAttempts: 3,
  retryDelay: 5000
};

API Response Patterns

Standardized Response Format

All Platform APIs return a consistent response format:

// TypeScript: API Response interface
interface APIResponse<T> {
  success: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: any;
  };
  meta?: {
    timestamp: string;
    requestId: string;
    version: string;
  };
}
 
// Example successful response
{
  "success": true,
  "data": {
    "id": 12985,
    "firstname": "John",
    "lastname": "Doe",
    "memberNumber": "446754"
  },
  "meta": {
    "timestamp": "2025-01-01T00:00:00Z",
    "requestId": "req-123456",
    "version": "1.0.0"
  }
}
 
// Example error response
{
  "success": false,
  "error": {
    "code": "MEMBER_NOT_FOUND",
    "message": "Member with ID 12985 not found",
    "details": {
      "searchId": "12985",
      "searchType": "natca_id"
    }
  },
  "meta": {
    "timestamp": "2025-01-01T00:00:00Z",
    "requestId": "req-123456",
    "version": "1.0.0"
  }
}

MyNATCA API Integration

The Platform proxies MyNATCA API requests with automatic response transformation:

// Platform: services/mynatca.js
export interface MyNATCAMemberProfile {
  id: number;
  firstname: string;
  lastname: string;
  membernumber: string;
  emailAddresses: MemberEmail[];
  phones: MemberPhone[];
  addresses: MemberAddress[];
  status: string;
  facility?: MyNATCAFacility;
  region?: MyNATCARegion;
}
 
// Response transformation
export const transformMyNATCAResponse = (rawResponse) => {
  if (rawResponse.status === "Success") {
    return {
      success: true,
      data: rawResponse.data,
      meta: {
        timestamp: new Date().toISOString(),
        source: 'mynatca-api',
        version: '1.0.0'
      }
    };
  }
 
  return {
    success: false,
    error: {
      code: 'MYNATCA_API_ERROR',
      message: rawResponse.message || 'API request failed'
    },
    meta: {
      timestamp: new Date().toISOString(),
      source: 'mynatca-api',
      version: '1.0.0'
    }
  };
};

Deployment Integration

DigitalOcean App Platform

Multi-Service Configuration

# .do/app.yaml
name: mynatca-ecosystem
 
services:
  # Platform Service
  - name: platform
    source_dir: /platform
    environment_slug: node-js
    instance_count: 2
    instance_size_slug: professional-xs
    http_port: 1300
 
    # Health check configuration
    health_check:
      http_path: /api/health
      initial_delay_seconds: 60
      period_seconds: 10
      success_threshold: 1
      failure_threshold: 3
      timeout_seconds: 5
 
    # Environment variables
    envs:
      - key: NODE_ENV
        value: production
      - key: SESSION_SECRET
        value: ${session.secret}
        type: SECRET
      - key: REDIS_URL
        value: ${redis.url}
        type: SECRET
 
  # Hub Service
  - name: hub
    source_dir: /hub
    environment_slug: node-js
    instance_count: 2
    instance_size_slug: professional-xs
    http_port: 1301
 
    routes:
      - path: /hub
        preserve_path_prefix: true
 
# Ingress routing
ingress:
  rules:
    - match:
        path:
          prefix: /api
      component:
        name: platform
    - match:
        path:
          prefix: /hub
      component:
        name: hub
    - match:
        path:
          prefix: /
      component:
        name: platform

Environment Variables Management

Development Environment

# .env.development
NODE_ENV=development
SUPABASE_URL=https://your-project.supabase.co
REDIS_URL=redis://localhost:6379/0
VITE_PLATFORM_URL=http://localhost:1300/api

Production Environment

# DigitalOcean App Platform Environment
NODE_ENV=production
SUPABASE_URL=${supabase.url}
REDIS_URL=${redis.connection_string}
VITE_PLATFORM_URL=https://your-app.ondigitalocean.app/api

Security Considerations

API Security

Rate Limiting

// Platform: Rate limiting middleware
const rateLimit = require('express-rate-limit');
 
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many API requests from this IP'
});
 
app.use('/api/', apiLimiter);

Input Validation

// Platform: Request validation
const joi = require('joi');
 
const validateMemberRequest = (req, res, next) => {
  const schema = joi.object({
    natcaId: joi.number().integer().positive().required(),
    includeContacts: joi.boolean().default(false)
  });
 
  const { error, value } = schema.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      error: {
        code: 'VALIDATION_ERROR',
        message: error.details[0].message
      }
    });
  }
 
  req.validatedData = value;
  next();
};

CORS Configuration

// Platform: CORS setup
const cors = require('cors');
 
const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'http://localhost:1301',
      'https://your-hub.ondigitalocean.app'
    ];
 
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true, // Allow cookies
  optionsSuccessStatus: 200
};
 
app.use(cors(corsOptions));

Best Practices

Error Handling

  • Always return consistent error response format
  • Log errors with request context and user information
  • Implement retry logic for external API calls
  • Use proper HTTP status codes

Performance Optimization

  • Implement caching for frequently accessed data
  • Use database connection pooling
  • Optimize SQL queries with proper indexing
  • Implement request/response compression

Monitoring and Logging

  • Log all authentication events
  • Monitor API response times and error rates
  • Track proxy middleware performance
  • Implement health checks for all dependencies

Development Workflow

  • Use environment-specific configurations
  • Implement comprehensive testing for integrations
  • Document all integration points and dependencies
  • Version API endpoints for backward compatibility

This integration guide provides the foundation for building robust integrations with the MyNATCA Platform while maintaining security, performance, and reliability standards.