🏗️ 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
761 lines
18 KiB
Markdown
761 lines
18 KiB
Markdown
# 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```html
|
|
<!-- 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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.
|