🏗️ Database Schema - Create gameforge_integrations table - Add gameforge_project_id to conversations - Create audit_logs table for operation tracking - Add metadata and is_archived columns - Implement indexes and triggers 🔧 Backend Services - GameForgeIntegrationService (450+ lines) - Auto-provision projects and channels - Team member synchronization - Role-based permission enforcement - System notification delivery - Project archival management 🔐 Authentication & Security - GameForge API key authentication - HMAC-SHA256 signature validation - Timestamp verification (5-minute window) - Replay attack prevention - Audit logging 🌐 API Endpoints (8 endpoints) - POST /api/gameforge/projects - Provision project - PATCH /api/gameforge/projects/:id/team - Update team - DELETE /api/gameforge/projects/:id - Archive project - GET /api/gameforge/projects/:id/channels - List channels - POST /api/gameforge/projects/:id/channels - Create channel - PATCH /api/gameforge/channels/:id - Update channel - DELETE /api/gameforge/channels/:id - Delete channel - POST /api/gameforge/projects/:id/notify - Send notification 🎨 Frontend Components - GameForgeChat - Embedded chat container - ChannelList - Channel navigation with icons - ChannelView - Message display and input - Responsive CSS with mobile support 📚 Documentation - PHASE3-GAMEFORGE.md - Complete technical docs - GAMEFORGE-EXAMPLES.md - Integration code samples - API reference with request/response examples - Testing guidelines and checklist ✨ Features - Automatic channel provisioning on project creation - Role-based channel permissions (dev, art, testing, etc.) - System notifications (builds, commits, deployments) - Team member sync with role updates - Custom channel creation - Project archival 📊 Stats - 16 new files created - 2 files updated - ~2,750 lines of code - 8 API endpoints - 7 React components
18 KiB
18 KiB
GameForge Integration - Implementation Examples
This document provides practical examples for integrating AeThex Connect with GameForge.
Example 1: Complete Project Lifecycle
Step 1: Create Project in GameForge
// GameForge: src/services/projectService.js
const crypto = require('crypto');
function generateSignature(payload, secret) {
const timestamp = Date.now().toString();
const data = JSON.stringify(payload);
return {
signature: crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${data}`)
.digest('hex'),
timestamp
};
}
async function createProject(projectData, userId) {
// 1. Create project in GameForge database
const project = await db.projects.create({
name: projectData.name,
description: projectData.description,
owner_id: userId,
created_at: new Date()
});
// 2. Get owner's AeThex identifier
const owner = await db.users.findById(userId);
const ownerIdentifier = `${owner.username}@dev.aethex.dev`;
// 3. Provision AeThex Connect channels
const connectPayload = {
projectId: project.id,
name: project.name,
ownerId: userId,
ownerIdentifier: ownerIdentifier,
teamMembers: [],
settings: {
autoProvisionChannels: true,
defaultChannels: ['general', 'dev', 'art', 'design'],
customChannels: []
}
};
const { signature, timestamp } = generateSignature(
connectPayload,
process.env.AETHEX_CONNECT_API_SECRET
);
try {
const response = await fetch('https://connect.aethex.app/api/gameforge/projects', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(connectPayload)
});
const connectData = await response.json();
if (connectData.success) {
// Store integration data
await db.projects.update(project.id, {
connect_domain: connectData.integration.domain,
connect_channels: connectData.integration.channels
});
console.log(`✅ Provisioned ${connectData.integration.channels.length} channels`);
} else {
console.error('Failed to provision AeThex Connect:', connectData.error);
}
} catch (error) {
console.error('AeThex Connect provisioning error:', error);
// Non-critical - project still created
}
return project;
}
Step 2: Add Team Members
// GameForge: src/services/teamService.js
async function addTeamMember(projectId, userId, role) {
// 1. Add to GameForge team
const teamMember = await db.projectTeams.create({
project_id: projectId,
user_id: userId,
role: role,
joined_at: new Date()
});
// 2. Get user's AeThex identifier
const user = await db.users.findById(userId);
const identifier = `${user.username}@dev.aethex.dev`;
// 3. Update AeThex Connect
const payload = {
action: 'add',
members: [{
userId: userId,
identifier: identifier,
role: role
}]
};
const { signature, timestamp } = generateSignature(
payload,
process.env.AETHEX_CONNECT_API_SECRET
);
try {
const response = await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/team`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(payload)
}
);
const data = await response.json();
console.log(`✅ Added to ${data.updated[0].addedToChannels.length} channels`);
} catch (error) {
console.error('Failed to update AeThex Connect team:', error);
}
return teamMember;
}
async function removeTeamMember(projectId, userId) {
// 1. Remove from GameForge team
await db.projectTeams.delete({
project_id: projectId,
user_id: userId
});
// 2. Remove from AeThex Connect
const payload = {
action: 'remove',
members: [{ userId: userId }]
};
const { signature, timestamp } = generateSignature(
payload,
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/team`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(payload)
}
);
}
async function updateMemberRole(projectId, userId, newRole) {
// 1. Update in GameForge
await db.projectTeams.update({
project_id: projectId,
user_id: userId
}, {
role: newRole
});
// 2. Update in AeThex Connect
const user = await db.users.findById(userId);
const payload = {
action: 'update_role',
members: [{
userId: userId,
identifier: `${user.username}@dev.aethex.dev`,
role: newRole
}]
};
const { signature, timestamp } = generateSignature(
payload,
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/team`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(payload)
}
);
}
Step 3: Send Notifications
// GameForge: src/services/buildService.js
async function onBuildComplete(buildId, projectId, status) {
const build = await db.builds.findById(buildId);
const notification = {
channelName: 'dev',
type: 'build',
title: `Build #${build.number} ${status === 'success' ? 'Completed' : 'Failed'}`,
message: status === 'success'
? `Build completed successfully in ${formatDuration(build.duration)}`
: `Build failed: ${build.error}`,
metadata: {
buildNumber: build.number,
status: status,
duration: build.duration,
commitHash: build.commit_hash.substring(0, 7),
branch: build.branch
},
actions: [
{
label: 'View Build',
url: `https://gameforge.aethex.dev/projects/${projectId}/builds/${buildId}`
},
{
label: 'View Logs',
url: `https://gameforge.aethex.dev/projects/${projectId}/builds/${buildId}/logs`
}
]
};
const { signature, timestamp } = generateSignature(
notification,
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/notify`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(notification)
}
);
}
// GameForge: src/services/gitService.js
async function onCommitPushed(commitData, projectId) {
const notification = {
channelName: 'dev',
type: 'commit',
title: 'New Commit Pushed',
message: commitData.message,
metadata: {
author: commitData.author,
hash: commitData.hash.substring(0, 7),
branch: commitData.branch,
filesChanged: commitData.stats.filesChanged
},
actions: [
{
label: 'View Commit',
url: `https://gameforge.aethex.dev/projects/${projectId}/commits/${commitData.hash}`
}
]
};
const { signature, timestamp } = generateSignature(
notification,
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/notify`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(notification)
}
);
}
// GameForge: src/services/deploymentService.js
async function onDeploymentComplete(deploymentId, projectId, status) {
const deployment = await db.deployments.findById(deploymentId);
const notification = {
channelName: 'general',
type: 'deployment',
title: `Deployment ${status === 'success' ? 'Successful' : 'Failed'}`,
message: status === 'success'
? `Successfully deployed to ${deployment.environment}`
: `Deployment to ${deployment.environment} failed`,
metadata: {
environment: deployment.environment,
version: deployment.version,
status: status
},
actions: [
{
label: 'View Deployment',
url: `https://gameforge.aethex.dev/projects/${projectId}/deployments/${deploymentId}`
}
]
};
const { signature, timestamp } = generateSignature(
notification,
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}/notify`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(notification)
}
);
}
Step 4: Archive Project
// GameForge: src/services/projectService.js
async function archiveProject(projectId) {
// 1. Archive in GameForge
await db.projects.update(projectId, {
is_archived: true,
archived_at: new Date()
});
// 2. Archive AeThex Connect channels
const { signature, timestamp } = generateSignature(
{},
process.env.AETHEX_CONNECT_API_SECRET
);
await fetch(
`https://connect.aethex.app/api/gameforge/projects/${projectId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
}
}
);
}
Example 2: Embedded Chat Component
React Integration
// GameForge: src/pages/ProjectPage.jsx
import React from 'react';
import { useParams } from 'react-router-dom';
import GameForgeChat from '@aethex/connect/components/GameForgeChat';
export default function ProjectPage() {
const { projectId } = useParams();
return (
<div className="project-page">
<div className="project-header">
<h1>My Game Project</h1>
<ProjectMenu />
</div>
<div className="project-content">
<div className="project-main">
<ProjectDashboard />
</div>
<div className="project-chat">
<GameForgeChat
projectId={projectId}
embedded={true}
/>
</div>
</div>
</div>
);
}
Iframe Integration
<!-- GameForge: templates/project.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ project.name }} - GameForge</title>
<style>
.chat-container {
height: 600px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
</style>
</head>
<body>
<div class="project-layout">
<div class="project-main">
<!-- Project content -->
</div>
<div class="project-sidebar">
<h3>Team Chat</h3>
<div class="chat-container">
<iframe
src="https://connect.aethex.app/gameforge/project/{{ project.id }}/chat"
width="100%"
height="100%"
frameborder="0"
allow="microphone; camera"
></iframe>
</div>
</div>
</div>
</body>
</html>
Example 3: Testing Integration
Integration Test Suite
// GameForge: tests/aethex-connect.test.js
const { generateSignature } = require('../utils/signature');
describe('AeThex Connect Integration', () => {
let testProjectId;
beforeAll(async () => {
// Setup test environment
});
test('should provision project with channels', async () => {
const payload = {
projectId: 'test-project-1',
name: 'Test Game',
ownerId: 'test-user-1',
ownerIdentifier: 'testuser@dev.aethex.dev',
teamMembers: [],
settings: {
autoProvisionChannels: true,
defaultChannels: ['general', 'dev']
}
};
const { signature, timestamp } = generateSignature(
payload,
process.env.AETHEX_CONNECT_API_SECRET
);
const response = await fetch('https://connect.aethex.app/api/gameforge/projects', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(payload)
});
const data = await response.json();
expect(data.success).toBe(true);
expect(data.integration.channels).toHaveLength(2);
expect(data.integration.domain).toBe('test-game@forge.aethex.dev');
testProjectId = 'test-project-1';
});
test('should add team member to appropriate channels', async () => {
const payload = {
action: 'add',
members: [{
userId: 'test-user-2',
identifier: 'developer@dev.aethex.dev',
role: 'developer'
}]
};
const { signature, timestamp } = generateSignature(
payload,
process.env.AETHEX_CONNECT_API_SECRET
);
const response = await fetch(
`https://connect.aethex.app/api/gameforge/projects/${testProjectId}/team`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(payload)
}
);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.updated[0].addedToChannels.length).toBeGreaterThan(0);
});
test('should send system notification', async () => {
const notification = {
channelName: 'dev',
type: 'build',
title: 'Build #1 Completed',
message: 'Test build completed successfully',
metadata: {
buildNumber: 1,
status: 'success'
}
};
const { signature, timestamp } = generateSignature(
notification,
process.env.AETHEX_CONNECT_API_SECRET
);
const response = await fetch(
`https://connect.aethex.app/api/gameforge/projects/${testProjectId}/notify`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': process.env.AETHEX_CONNECT_API_KEY,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: JSON.stringify(notification)
}
);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.messageId).toBeDefined();
expect(data.channelId).toBeDefined();
});
afterAll(async () => {
// Cleanup test data
});
});
Example 4: Error Handling
// GameForge: src/utils/aethexConnect.js
class AeThexConnectClient {
constructor(apiKey, apiSecret, baseUrl = 'https://connect.aethex.app') {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.baseUrl = baseUrl;
}
generateSignature(payload) {
const crypto = require('crypto');
const timestamp = Date.now().toString();
const data = JSON.stringify(payload);
const signature = crypto
.createHmac('sha256', this.apiSecret)
.update(`${timestamp}.${data}`)
.digest('hex');
return { signature, timestamp };
}
async request(endpoint, method, payload) {
const { signature, timestamp } = this.generateSignature(payload);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-GameForge-API-Key': this.apiKey,
'X-GameForge-Signature': signature,
'X-GameForge-Timestamp': timestamp
},
body: method !== 'GET' ? JSON.stringify(payload) : undefined
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Request failed');
}
return data;
} catch (error) {
console.error(`AeThex Connect ${method} ${endpoint} failed:`, error);
throw error;
}
}
async provisionProject(projectData) {
return this.request('/api/gameforge/projects', 'POST', projectData);
}
async updateTeam(projectId, action, members) {
return this.request(
`/api/gameforge/projects/${projectId}/team`,
'PATCH',
{ action, members }
);
}
async sendNotification(projectId, notification) {
return this.request(
`/api/gameforge/projects/${projectId}/notify`,
'POST',
notification
);
}
async archiveProject(projectId) {
return this.request(
`/api/gameforge/projects/${projectId}`,
'DELETE',
{}
);
}
}
// Usage
const connectClient = new AeThexConnectClient(
process.env.AETHEX_CONNECT_API_KEY,
process.env.AETHEX_CONNECT_API_SECRET
);
// Provision with error handling
try {
const result = await connectClient.provisionProject({
projectId: project.id,
name: project.name,
ownerId: userId,
ownerIdentifier: `${user.username}@dev.aethex.dev`,
teamMembers: [],
settings: {
autoProvisionChannels: true,
defaultChannels: ['general', 'dev']
}
});
console.log('✅ Provisioned:', result.integration.domain);
} catch (error) {
console.error('❌ Provisioning failed:', error.message);
// Handle gracefully - project still usable without chat
}
Utility Functions
// GameForge: src/utils/helpers.js
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return minutes > 0 ? `${minutes}m ${secs}s` : `${secs}s`;
}
function getChannelForNotificationType(type) {
const channelMap = {
'build': 'dev',
'commit': 'dev',
'deployment': 'general',
'issue': 'dev',
'playtesting': 'playtesting',
'announcement': 'announcements'
};
return channelMap[type] || 'general';
}
module.exports = {
formatDuration,
getChannelForNotificationType
};
These examples demonstrate complete integration patterns for GameForge projects with AeThex Connect.