Platform Settings Architecture
Technical design and implementation details for the platform settings system.
Table of Contents
- System Overview
- Database Schema
- Backend Architecture
- Frontend Implementation
- MCP Integration
- Security Measures
- Performance Considerations
- Testing Strategy
- Deployment Process
- Monitoring & Observability
- Troubleshooting
- Future Enhancements
- Related Documentation
- References
System Overview
The platform settings system provides a centralized, persistent configuration management solution for the Momentum learning platform. It uses a key-value store approach with JSONB values in PostgreSQL, Redis caching for performance, and a comprehensive audit trail.
Architecture Diagram
graph TD
A[Admin UI] -->|HTTPS| B[API Gateway]
B -->|Invoke| C[Lambda Handlers]
C -->|Query/Update| D[Settings Service]
D -->|Cache Check| E[Redis ElastiCache]
D -->|Persist| F[Aurora PostgreSQL]
D -->|Audit| G[Audit Table]
F -->|Trigger| H[Auto Timestamp]
subgraph Frontend
A
end
subgraph API Layer
B
C
end
subgraph Business Logic
D
end
subgraph Data Layer
E
F
G
end
style A fill:#3b82f6
style D fill:#8b5cf6
style E fill:#f59e0b
style F fill:#10b981
style G fill:#ef4444
Request Flow
sequenceDiagram
participant U as Admin User
participant F as Frontend
participant A as API Gateway
participant L as Lambda
participant S as Settings Service
participant R as Redis Cache
participant D as Aurora DB
U->>F: Update Settings
F->>A: PATCH /admin/settings/{section}
A->>A: Verify JWT (Admin)
A->>L: Invoke Handler
L->>S: updateSettingsSection()
S->>S: Validate Input
S->>S: Sanitize Data
S->>D: Upsert Settings
D->>D: Update Timestamp (Trigger)
D->>S: Success
S->>R: Invalidate Cache
R->>S: Success
S->>D: Insert Audit Record
D->>S: Success
S->>L: Return
L->>A: 200 OK
A->>F: Success Response
F->>U: Show Success Message
Note over U,D: Next GET request uses fresh data
U->>F: Refresh Page
F->>A: GET /admin/settings
A->>L: Invoke Handler
L->>S: getSettings()
S->>R: Check Cache
R->>S: Cache Miss
S->>D: Query Settings
D->>S: Return Records
S->>S: Transform to State
S->>R: Cache Result (5min TTL)
S->>L: Return Settings
L->>F: Settings Data
F->>U: Display Settings
Database Schema
Platform Settings Table
Stores all configuration settings as key-value pairs with JSONB values.
CREATE TABLE platform_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
section VARCHAR(50) NOT NULL, -- Settings section/namespace
key VARCHAR(100) NOT NULL, -- Setting key
value JSONB NOT NULL, -- Setting value (typed)
data_type VARCHAR(20) NOT NULL, -- 'string', 'number', 'boolean', 'json'
description TEXT, -- Human-readable description
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_by UUID REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT unique_section_key UNIQUE(section, key)
);
Indexes:
CREATE INDEX idx_platform_settings_section ON platform_settings(section);
CREATE INDEX idx_platform_settings_updated_at ON platform_settings(updated_at DESC);
CREATE INDEX idx_platform_settings_section_key ON platform_settings(section, key);
Why JSONB?
- Flexible schema for different data types
- Efficient storage and querying
- Native PostgreSQL operators for manipulation
- Type safety maintained in application layer
Audit Trail Table
Tracks all changes to settings with old/new values.
CREATE TABLE platform_settings_audit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
setting_id UUID REFERENCES platform_settings(id) ON DELETE CASCADE,
section VARCHAR(50) NOT NULL,
key VARCHAR(100) NOT NULL,
old_value JSONB, -- Previous value (NULL on create)
new_value JSONB NOT NULL, -- New value
changed_by UUID REFERENCES users(id) ON DELETE SET NULL,
changed_at TIMESTAMPTZ DEFAULT NOW()
);
Indexes:
CREATE INDEX idx_platform_settings_audit_changed_at ON platform_settings_audit(changed_at DESC);
CREATE INDEX idx_platform_settings_audit_setting_id ON platform_settings_audit(setting_id);
Audit Queries:
-- Get recent changes
SELECT * FROM platform_settings_audit
WHERE changed_at > NOW() - INTERVAL '30 days'
ORDER BY changed_at DESC;
-- Get changes for specific setting
SELECT * FROM platform_settings_audit
WHERE section = 'general' AND key = 'platform_name'
ORDER BY changed_at DESC;
-- Get changes by user
SELECT a.*, u.email
FROM platform_settings_audit a
JOIN users u ON a.changed_by = u.id
WHERE a.changed_at > NOW() - INTERVAL '7 days'
ORDER BY a.changed_at DESC;
Database Triggers
Auto-update Timestamp:
CREATE OR REPLACE FUNCTION update_platform_settings_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_platform_settings_updated_at
BEFORE UPDATE ON platform_settings
FOR EACH ROW
EXECUTE FUNCTION update_platform_settings_updated_at();
Backend Architecture
Lambda Handlers
Location: backend/functions/settings/src/handlers/
1. Get All Settings
File: get-settings.ts
export const handler: APIGatewayProxyHandler = async (event) => {
// Verify admin role
const groups = event.requestContext.authorizer?.claims['cognito:groups'];
if (!groups || !groups.includes('admin')) {
throw new AuthorizationError('Admin access required');
}
// Get all settings from service
const settings = await settingsService.getSettings();
return createSuccessResponse(settings);
};
Flow:
- Verify admin authentication
- Call
SettingsService.getSettings() - Return complete settings state
Caching: Uses Redis cache with 5-minute TTL
2. Get Settings Section
File: get-settings-section.ts
export const handler: APIGatewayProxyHandler = async (event) => {
const section = event.pathParameters?.section;
// Validate section name
if (!VALID_SECTIONS.includes(section)) {
throw new ValidationError(`Invalid section: ${section}`);
}
// Get section data
const sectionData = await settingsService.getSettingsSection(section);
return createSuccessResponse(sectionData);
};
Valid Sections: general, branding, courses, ai, user, payment, analytics
3. Update All Settings
File: update-settings.ts
export const handler: APIGatewayProxyHandler = async (event) => {
const userId = event.requestContext.authorizer?.claims?.sub;
const settings = JSON.parse(event.body);
// Update all settings
await settingsService.updateSettings(settings, userId);
return createSuccessResponse({
message: 'Settings updated successfully'
});
};
Flow:
- Parse request body
- Extract user ID from JWT
- Validate and sanitize input
- Upsert all settings
- Invalidate cache
- Log to audit trail
4. Update Settings Section
File: update-settings-section.ts
export const handler: APIGatewayProxyHandler = async (event) => {
const section = event.pathParameters?.section;
const userId = event.requestContext.authorizer?.claims?.sub;
const sectionData = JSON.parse(event.body);
// Update specific section
await settingsService.updateSettingsSection(section, sectionData, userId);
return createSuccessResponse({
message: `Settings section '${section}' updated successfully`
});
};
Optimized: Only updates changed keys within the section
5. Export Settings
File: export-settings.ts
export const handler: APIGatewayProxyHandler = async (event) => {
const userEmail = event.requestContext.authorizer?.claims?.email;
// Export with metadata
const exportData = await settingsService.exportSettings(userEmail);
return createResponse(200, exportData);
};
Export Format:
{
"version": "1.0.0",
"exported_at": "2025-12-23T10:30:00.000Z",
"exported_by": "admin@momentum.com",
"settings": { /* complete settings */ }
}
6. Import Settings
File: import-settings.ts
export const handler: APIGatewayProxyHandler = async (event) => {
const userId = event.requestContext.authorizer?.claims?.sub;
const importData = JSON.parse(event.body);
const mergeMode = event.queryStringParameters?.merge_mode === 'true';
// Validate and import
await settingsService.importSettings(importData, userId, mergeMode);
return createSuccessResponse({
message: 'Settings imported successfully',
mode: mergeMode ? 'merge' : 'replace'
});
};
Import Modes:
- Replace (default): Overwrites all settings
- Merge: Updates only provided settings
Settings Service
Location: backend/functions/settings/src/services/settings-service.ts
Core business logic for settings management.
export class SettingsService {
private rdsClient: RDSDataClient;
private cache: ReturnType<typeof createCacheClient>;
private readonly CACHE_KEY = 'platform:settings';
private readonly CACHE_TTL = 300; // 5 minutes
/**
* Get all settings (cached)
*/
async getSettings(): Promise<SettingsState> {
// Try cache first
const cached = await this.cache.get(this.CACHE_KEY);
if (cached) return JSON.parse(cached);
// Fetch from database
const result = await this.rdsClient.send(new ExecuteStatementCommand({
resourceArn: process.env.DB_CLUSTER_ARN,
secretArn: process.env.DB_SECRET_ARN,
database: process.env.DB_NAME,
sql: 'SELECT section, key, value FROM platform_settings ORDER BY section, key'
}));
// Transform to SettingsState
const settings = this.transformToSettingsState(result.records);
// Cache result
await this.cache.set(this.CACHE_KEY, JSON.stringify(settings), this.CACHE_TTL);
return settings;
}
/**
* Update settings section
*/
async updateSettingsSection(
section: string,
sectionData: any,
userId: string
): Promise<void> {
// Validate the section
const validated = validateSection(section, sectionData);
const sanitized = sanitizeSettings({ [section]: validated });
// Upsert each key in the section
for (const [key, value] of Object.entries(sanitized[section])) {
await this.upsertSetting({ section, key, value, userId });
}
// Invalidate cache
await this.cache.del(this.CACHE_KEY);
// Audit trail
await this.logChange(userId, `update_section:${section}`, sanitized);
}
/**
* Upsert a single setting
*/
private async upsertSetting(params: {
section: string;
key: string;
value: any;
userId: string;
}): Promise<void> {
const { section, key, value, userId } = params;
// Get old value for audit
const oldValue = await this.getSettingValue(section, key);
// Upsert
await this.rdsClient.send(new ExecuteStatementCommand({
resourceArn: process.env.DB_CLUSTER_ARN,
secretArn: process.env.DB_SECRET_ARN,
database: process.env.DB_NAME,
sql: `
INSERT INTO platform_settings (section, key, value, data_type, updated_by)
VALUES (:section, :key, :value, :dataType, :userId)
ON CONFLICT (section, key)
DO UPDATE SET
value = EXCLUDED.value,
data_type = EXCLUDED.data_type,
updated_by = EXCLUDED.updated_by
`,
parameters: [
{ name: 'section', value: { stringValue: section } },
{ name: 'key', value: { stringValue: key } },
{ name: 'value', value: { stringValue: JSON.stringify(value) } },
{ name: 'dataType', value: { stringValue: this.getDataType(value) } },
{ name: 'userId', value: { stringValue: userId } }
]
}));
// Audit trail
await this.insertAudit(section, key, oldValue, value, userId);
}
/**
* Transform database records to SettingsState
*/
private transformToSettingsState(records: Field[][]): SettingsState {
const settings: any = {
general: {},
branding: {},
courses: {},
ai: {},
user: {},
payment: {},
analytics: {}
};
for (const record of records) {
const section = record[0]?.stringValue;
const key = record[1]?.stringValue;
const value = JSON.parse(record[2]?.stringValue || 'null');
if (section && key) {
settings[section][key] = value;
}
}
return settings;
}
}
Key Methods:
getSettings()- Retrieve all settings with cachinggetSettingsSection(section)- Get one sectionupdateSettings(settings, userId)- Full replacementupdateSettingsSection(section, data, userId)- Partial updateexportSettings(userId)- Create export JSONimportSettings(data, userId, merge)- Import with validationupsertSetting()- Insert or update single settingtransformToSettingsState()- Convert DB format to API format
Validation
Location: backend/functions/settings/src/utils/validation.ts
Uses Zod schemas for type-safe validation.
import { z } from 'zod';
// General settings schema
const GeneralSettingsSchema = z.object({
platform_name: z.string().min(1).max(100),
platform_tagline: z.string().max(200).optional(),
support_email: z.string().email(),
default_timezone: z.string().min(1).max(100),
default_language: z.string().length(2),
maintenance_mode: z.boolean(),
maintenance_message: z.string().max(500).optional()
});
// Branding settings schema
const BrandingSettingsSchema = z.object({
primary_color: z.string().regex(/^#[0-9A-Fa-f]{6}$/),
secondary_color: z.string().regex(/^#[0-9A-Fa-f]{6}$/),
accent_color: z.string().regex(/^#[0-9A-Fa-f]{6}$/),
logo_url: z.string().url().optional().or(z.literal('')),
logo_dark_url: z.string().url().optional().or(z.literal('')),
favicon_url: z.string().url().optional().or(z.literal('')),
custom_css: z.string().max(10000).optional(),
custom_footer_html: z.string().max(5000).optional()
});
// Complete settings schema
const SettingsStateSchema = z.object({
general: GeneralSettingsSchema,
branding: BrandingSettingsSchema,
courses: CourseSettingsSchema,
ai: AISettingsSchema,
user: UserSettingsSchema,
payment: PaymentSettingsSchema,
analytics: AnalyticsSettingsSchema
});
/**
* Validate complete settings state
*/
export function validateSettings(settings: any): SettingsState {
return SettingsStateSchema.parse(settings);
}
/**
* Validate a specific section
*/
export function validateSection(section: string, data: any): any {
const schemas = {
general: GeneralSettingsSchema,
branding: BrandingSettingsSchema,
courses: CourseSettingsSchema,
ai: AISettingsSchema,
user: UserSettingsSchema,
payment: PaymentSettingsSchema,
analytics: AnalyticsSettingsSchema
};
const schema = schemas[section as keyof typeof schemas];
if (!schema) {
throw new Error(`Invalid section: ${section}`);
}
return schema.partial().parse(data);
}
Validation Features:
- Type checking (string, number, boolean)
- Range validation (min/max values)
- Format validation (email, URL, hex colors)
- Length limits
- Required vs optional fields
- Partial validation for PATCH operations
Sanitization
Location: backend/functions/settings/src/utils/sanitization.ts
Prevents XSS attacks and cleans input.
/**
* Sanitize settings to prevent XSS
*/
export function sanitizeSettings(settings: Partial<SettingsState>): Partial<SettingsState> {
const sanitized: any = {};
for (const [section, sectionData] of Object.entries(settings)) {
sanitized[section] = {};
for (const [key, value] of Object.entries(sectionData as Record<string, any>)) {
// String sanitization
if (typeof value === 'string') {
sanitized[section][key] = sanitizeString(value, key);
} else {
sanitized[section][key] = value;
}
}
}
return sanitized;
}
/**
* Sanitize string based on field type
*/
function sanitizeString(value: string, key: string): string {
// Allow HTML in specific fields
const htmlAllowedFields = ['custom_css', 'custom_footer_html', 'maintenance_message'];
if (htmlAllowedFields.includes(key)) {
// Basic sanitization but allow HTML
return value.trim();
}
// Strip all HTML tags from other fields
return value
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/[<>]/g, '') // Remove < and >
.trim();
}
Sanitization Rules:
- Remove HTML tags from most fields
- Allow HTML only in
custom_css,custom_footer_html,maintenance_message - Strip
<script>tags everywhere - Trim whitespace
- Escape special characters
Caching Strategy
Location: backend/functions/settings/src/utils/cache.ts
Uses Redis ElastiCache Serverless for performance.
import { createClient } from 'redis';
export function createCacheClient() {
const client = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
// Connect on first use
let connected = false;
async function ensureConnected() {
if (!connected) {
await client.connect();
connected = true;
}
}
return {
async get(key: string): Promise<string | null> {
await ensureConnected();
return await client.get(key);
},
async set(key: string, value: string, ttl: number): Promise<void> {
await ensureConnected();
await client.setEx(key, ttl, value);
},
async del(key: string): Promise<void> {
await ensureConnected();
await client.del(key);
}
};
}
Cache Configuration:
- Key:
platform:settings - TTL: 300 seconds (5 minutes)
- Invalidation: On any mutation (PUT/PATCH/POST import)
- Size: ~10-50 KB (compressed JSON)
Why Redis?
- Sub-millisecond read latency
- Automatic eviction with TTL
- Serverless scaling with ElastiCache
- Reduces database load by 95%+
Cache Coherency:
- Write-through: Update DB first, then invalidate cache
- Read-through: Cache miss fetches from DB and populates cache
- Single cache key ensures atomic updates
Frontend Implementation
Settings Page
Location: frontend/app/admin/settings/page.tsx
React component with state management and API integration.
'use client';
import { useState, useEffect } from 'react';
import { apiClient } from '@/lib/api/api-client';
interface SettingsState {
general: GeneralSettingsData;
branding: BrandingSettingsData;
courses: CourseSettingsData;
ai: AISettingsData;
users: UserSettingsData;
email: EmailSettingsData;
payments: PaymentSettingsData;
analytics: AnalyticsSettingsData;
}
export function SettingsPage() {
const [settings, setSettings] = useState<SettingsState>(DEFAULT_SETTINGS);
const [activeSection, setActiveSection] = useState('general');
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
// Load settings on mount
useEffect(() => {
loadSettings();
}, []);
async function loadSettings() {
setLoading(true);
try {
const response = await apiClient.get<SettingsState>('/admin/settings');
setSettings(response);
} catch (error) {
console.error('Failed to load settings:', error);
} finally {
setLoading(false);
}
}
async function handleSave() {
setSaving(true);
try {
await apiClient.put('/admin/settings', settings);
// Show success message
} catch (error) {
// Show error message
} finally {
setSaving(false);
}
}
async function handleExport() {
const response = await apiClient.post<SettingsExport>('/admin/settings/export');
const blob = new Blob([JSON.stringify(response, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `momentum-settings-${new Date().toISOString()}.json`;
a.click();
}
async function handleImport(file: File) {
const text = await file.text();
const importData = JSON.parse(text);
await apiClient.post('/admin/settings/import', importData);
await loadSettings();
}
return (
<div className="flex">
<SettingsSidebar
activeSection={activeSection}
onSectionChange={setActiveSection}
/>
<div className="flex-1">
{activeSection === 'general' && (
<GeneralSettings
data={settings.general}
onChange={(data) => setSettings({ ...settings, general: data })}
/>
)}
{/* Other sections... */}
<div className="flex gap-2">
<button onClick={handleSave} disabled={saving}>
Save Changes
</button>
<button onClick={handleExport}>
Export
</button>
<input
type="file"
accept=".json"
onChange={(e) => e.target.files?.[0] && handleImport(e.target.files[0])}
/>
</div>
</div>
</div>
);
}
Component Structure:
page.tsx (main component)
├── SettingsSidebar (navigation)
└── Section Components
├── GeneralSettings
├── BrandingSettings
├── CourseSettings
├── AISettings
├── UserSettings
├── PaymentSettings
└── AnalyticsSettings
API Client
Location: frontend/lib/api/api-client.ts
export const apiClient = {
async get<T>(path: string): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
headers: {
'Authorization': `Bearer ${await getJwtToken()}`
}
});
if (!response.ok) {
throw new ApiError(response);
}
return response.json();
},
async put<T>(path: string, data: any): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${await getJwtToken()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new ApiError(response);
}
return response.json();
},
async patch<T>(path: string, data: any): Promise<T> {
// Similar to PUT
}
};
MCP Integration
The platform includes MCP (Model Context Protocol) tools for programmatic settings management from development environments.
MCP Tools
Location: mcp-servers/database/src/index.ts (if implemented)
- get_platform_settings - Retrieve current settings
- update_platform_settings - Update settings programmatically
- reset_platform_settings - Reset to defaults
Use Case: AI-assisted development can query and update settings directly from the IDE.
Security Measures
Authentication & Authorization
// All handlers verify admin role
const groups = event.requestContext.authorizer?.claims['cognito:groups'];
if (!groups || !groups.includes('admin')) {
throw new AuthorizationError('Admin access required');
}
// Extract user ID for audit trail
const userId = event.requestContext.authorizer?.claims?.sub;
Input Validation
- Zod schemas validate all inputs
- Type checking before database operations
- Range and format validation
- Required field enforcement
SQL Injection Prevention
- All queries use parameterized statements
- AWS RDS Data API handles escaping
- No string concatenation in SQL
XSS Prevention
- HTML tags stripped from most fields
- Whitelisted fields for HTML (custom_css, custom_footer_html)
- Content Security Policy headers (recommended)
Audit Trail
- All changes logged with old/new values
- User ID and timestamp recorded
- Queryable for compliance/security review
Performance Considerations
Optimization Strategies
1. Redis Caching
- 5-minute TTL reduces DB load by 95%+
- Single cache key for atomic updates
- Automatic invalidation on mutations
2. Database Indexing
- Indexes on
section,(section, key),updated_at - Query planner uses indexes effectively
- EXPLAIN ANALYZE shows index usage
3. Lambda Optimization
- Keep Lambda warm with scheduled pings (optional)
- Minimize cold start time (<500ms)
- Reuse database connections
4. API Gateway
- Regional endpoint for lowest latency
- CloudWatch metrics for monitoring
- X-Ray tracing for debugging
Performance Metrics
| Operation | P50 | P95 | P99 |
|---|---|---|---|
| GET all (cached) | 15ms | 25ms | 50ms |
| GET all (uncached) | 120ms | 180ms | 250ms |
| PATCH section | 200ms | 300ms | 450ms |
| PUT all | 600ms | 900ms | 1200ms |
| Export | 150ms | 250ms | 400ms |
| Import (10 settings) | 500ms | 800ms | 1100ms |
Monitoring:
- CloudWatch metrics track latency and errors
- X-Ray traces show bottlenecks
- Alarms on P95 > 500ms
Testing Strategy
Unit Tests
Location: backend/functions/settings/__tests__/
# Run all settings tests
cd backend/functions/settings
npm test
# Run with coverage
npm run test:coverage
Test Coverage:
- Settings Service: 100% statement coverage
- Validation: 100% branch coverage
- Sanitization: 100% statement coverage
- Handlers: 90%+ coverage
Example Test:
describe('SettingsService', () => {
it('should get settings from cache if available', async () => {
const service = new SettingsService();
// Mock cache hit
jest.spyOn(service['cache'], 'get').mockResolvedValue(
JSON.stringify(mockSettings)
);
const result = await service.getSettings();
expect(result).toEqual(mockSettings);
expect(service['cache'].get).toHaveBeenCalledWith('platform:settings');
});
it('should invalidate cache on update', async () => {
const service = new SettingsService();
const delSpy = jest.spyOn(service['cache'], 'del').mockResolvedValue();
await service.updateSettingsSection('general', { platform_name: 'New' }, 'user-123');
expect(delSpy).toHaveBeenCalledWith('platform:settings');
});
});
Integration Tests
describe('Settings API Integration', () => {
it('should update and retrieve settings', async () => {
// Update settings
await apiClient.patch('/admin/settings/general', {
platform_name: 'Test Platform'
});
// Wait for cache invalidation
await new Promise(resolve => setTimeout(resolve, 100));
// Retrieve settings
const settings = await apiClient.get('/admin/settings');
expect(settings.general.platform_name).toBe('Test Platform');
});
});
End-to-End Tests
Location: frontend/__tests__/e2e/settings.spec.ts
test('admin can update settings', async ({ page }) => {
await page.goto('/admin/settings');
// Update platform name
await page.fill('input[name="platform_name"]', 'E2E Test Platform');
await page.click('button:has-text("Save Changes")');
// Wait for success message
await page.waitForSelector('text=Settings updated successfully');
// Reload and verify
await page.reload();
const value = await page.inputValue('input[name="platform_name"]');
expect(value).toBe('E2E Test Platform');
});
Deployment Process
Prerequisites
- AWS credentials configured
- Terraform installed
- Node.js 20.x
- Database migration applied
Deployment Steps
1. Run Database Migration
cd backend
./scripts/database/db-migrate.sh 025_create_platform_settings.sql
2. Build Lambda Functions
cd backend/functions/settings
npm install
npm run build
3. Deploy Infrastructure
cd infrastructure/terraform
terraform init
terraform plan -var-file="environments/dev.tfvars"
terraform apply -var-file="environments/dev.tfvars"
4. Deploy Lambda Code
cd scripts/deployment
./deploy-backend.sh settings
5. Verify Deployment
# Test GET endpoint
curl -X GET "https://iu7ewpcwvc.execute-api.us-east-1.amazonaws.com/dev/admin/settings" \
-H "Authorization: Bearer $JWT_TOKEN"
# Test PATCH endpoint
curl -X PATCH "https://iu7ewpcwvc.execute-api.us-east-1.amazonaws.com/dev/admin/settings/general" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"platform_name":"Deployed Successfully"}'
Rollback Procedure
# Revert Terraform changes
cd infrastructure/terraform
terraform plan -var-file="environments/dev.tfvars" -target=module.settings
terraform apply -var-file="environments/dev.tfvars" -target=module.settings
# Revert database migration (if needed)
psql -h <host> -U <user> -d momentum < rollback_025.sql
Monitoring & Observability
CloudWatch Logs
Log Groups:
/aws/lambda/settings-get-settings/aws/lambda/settings-update-settings/aws/lambda/settings-export/aws/lambda/settings-import
Key Metrics:
- Invocation count
- Error rate
- Duration (P50, P95, P99)
- Throttles
- Concurrent executions
CloudWatch Alarms
# Terraform configuration
resource "aws_cloudwatch_metric_alarm" "settings_errors" {
alarm_name = "settings-high-error-rate"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "Errors"
namespace = "AWS/Lambda"
period = 300
statistic = "Sum"
threshold = 10
alarm_description = "Settings API error rate too high"
dimensions = {
FunctionName = "settings-update-settings"
}
}
X-Ray Tracing
Enable X-Ray for distributed tracing:
// In Lambda handler
import AWSXRay from 'aws-xray-sdk-core';
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
Traces Show:
- API Gateway → Lambda latency
- Lambda → Redis latency
- Lambda → RDS latency
- Total request duration
- Error traces
Custom Metrics
// In SettingsService
import { CloudWatch } from 'aws-sdk';
const cloudwatch = new CloudWatch();
async function recordCacheHit() {
await cloudwatch.putMetricData({
Namespace: 'Momentum/Settings',
MetricData: [{
MetricName: 'CacheHit',
Value: 1,
Unit: 'Count'
}]
}).promise();
}
Troubleshooting
Common Issues
1. Settings Not Saving
Symptoms: PATCH returns 200 but settings don’t change
Diagnosis:
# Check Lambda logs
aws logs tail /aws/lambda/settings-update-settings --follow
# Check database
psql -h <host> -U <user> -d momentum -c "
SELECT * FROM platform_settings
WHERE updated_at > NOW() - INTERVAL '5 minutes'
ORDER BY updated_at DESC;
"
Solutions:
- Verify RDS Data API permissions
- Check database connectivity
- Verify user has admin role
2. Cache Not Invalidating
Symptoms: Changes saved but old values returned on GET
Diagnosis:
# Check Redis connection
redis-cli -h <redis-host> PING
# Check cache key
redis-cli -h <redis-host> GET platform:settings
Solutions:
- Verify Redis ElastiCache cluster is healthy
- Check Lambda security group allows Redis access
- Manually flush cache:
redis-cli FLUSHDB
3. Import Validation Failing
Symptoms: Import returns 400 with validation errors
Diagnosis:
// Enable debug logging
console.log('Import data:', JSON.stringify(importData, null, 2));
Solutions:
- Validate JSON format (use jsonlint.com)
- Ensure version matches (currently 1.0.0)
- Check all required fields present
- Verify data types match schema
Future Enhancements
Planned Features
- Settings Versioning
- Track settings history over time
- Ability to rollback to previous versions
- Diff view between versions
- Environment-specific Settings
- Different settings per environment (dev/staging/prod)
- Promotion workflow between environments
- Environment variable overrides
- Settings Templates
- Pre-configured templates for common scenarios
- Industry-specific presets
- Quick setup for new deployments
- Advanced Validation
- Custom validation rules per setting
- Dependencies between settings
- Warning for risky changes
- Real-time Updates
- WebSocket notifications for setting changes
- Live preview of changes
- Collaborative editing protection
- Bulk Operations
- Import/export per section
- Batch updates via CSV
- Search and replace across settings
Performance Improvements
- GraphQL API for selective field retrieval
- Server-side pagination for audit logs
- Compression for large export files
- CDN caching for public settings
Related Documentation
- Admin Settings User Guide - User-facing documentation
- Settings API Reference - Complete API documentation
- Technical Architecture - Platform architecture overview
- Database Schema - Database documentation