File size: 5,182 Bytes
14a0342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4c70c3d
14a0342
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
 * Ably Real-time Client
 * 
 * Provides Ably client and helper functions for real-time broadcasting.
 * Uses Ably REST API for server-side publishing.
 */

import Ably from 'ably';
import {
    ABLY_CHANNELS,
    IssueUpdatePayload,
    SyncCompletePayload,
    ChatMessagePayload,
    IssueEventType,
} from '@/lib/types/ably';

// =============================================================================
// Client Singleton
// =============================================================================

let ablyClient: Ably.Rest | null = null;

function getAblyClient(): Ably.Rest {
    if (!ablyClient) {
        const apiKey = process.env.ABLY_API_KEY;
        if (!apiKey) {
            throw new Error('ABLY_API_KEY environment variable is not set');
        }
        ablyClient = new Ably.Rest({ key: apiKey });
    }
    return ablyClient;
}

// =============================================================================
// Channel Helpers
// =============================================================================

function getEventsChannel() {
    return getAblyClient().channels.get(ABLY_CHANNELS.EVENTS);
}

function getGlobalChatChannel() {
    return getAblyClient().channels.get(ABLY_CHANNELS.GLOBAL_CHAT);
}

// =============================================================================
// Issue/PR Event Publishers
// =============================================================================

export async function publishIssueEvent(
    eventType: IssueEventType,
    issue: {
        id: string;
        githubIssueId: number;
        number: number;
        title: string;
        repoName: string;
        owner: string;
        repo: string;
        isPR: boolean;
        state: string;
    }
): Promise<void> {
    const channel = getEventsChannel();
    const payload: IssueUpdatePayload = {
        eventType,
        issueId: issue.id,
        githubIssueId: issue.githubIssueId,
        number: issue.number,
        title: issue.title,
        repoName: issue.repoName,
        owner: issue.owner,
        repo: issue.repo,
        isPR: issue.isPR,
        state: issue.state,
        timestamp: new Date().toISOString(),
    };

    await channel.publish(eventType, payload);
}

export async function publishIssueCreated(issue: Parameters<typeof publishIssueEvent>[1]): Promise<void> {
    await publishIssueEvent(issue.isPR ? 'pr_created' : 'issue_created', issue);
}

export async function publishIssueUpdated(issue: Parameters<typeof publishIssueEvent>[1]): Promise<void> {
    await publishIssueEvent(issue.isPR ? 'pr_updated' : 'issue_updated', issue);
}

export async function publishIssueDeleted(issue: Parameters<typeof publishIssueEvent>[1]): Promise<void> {
    await publishIssueEvent(issue.isPR ? 'pr_deleted' : 'issue_deleted', issue);
}

// =============================================================================
// Sync Event Publisher
// =============================================================================

export async function publishSyncComplete(stats: {
    reposProcessed: number;
    issuesUpdated: number;
    issuesDeleted: number;
}): Promise<void> {
    const channel = getEventsChannel();
    const payload: SyncCompletePayload = {
        eventType: 'sync_complete',
        reposProcessed: stats.reposProcessed,
        issuesUpdated: stats.issuesUpdated,
        issuesDeleted: stats.issuesDeleted,
        timestamp: new Date().toISOString(),
    };

    await channel.publish('sync_complete', payload);
}

// =============================================================================
// Global Chat Publisher
// =============================================================================

export async function publishChatMessage(message: {
    messageId: string;
    senderId: string;
    senderUsername: string;
    senderAvatarUrl?: string;
    content: string;
}): Promise<void> {
    const channel = getGlobalChatChannel();
    const payload: ChatMessagePayload = {
        messageId: message.messageId,
        senderId: message.senderId,
        senderUsername: message.senderUsername,
        senderAvatarUrl: message.senderAvatarUrl,
        content: message.content,
        timestamp: new Date().toISOString(),
    };

    await channel.publish('message', payload);
}

// =============================================================================
// Ably Token Request (for client-side auth)
// =============================================================================

export async function createTokenRequest(clientId: string): Promise<Ably.TokenRequest> {
    const client = getAblyClient();
    return await client.auth.createTokenRequest({
        clientId,
        capability: {
            [ABLY_CHANNELS.EVENTS]: ['subscribe'],
            [ABLY_CHANNELS.GLOBAL_CHAT]: ['subscribe', 'publish'],
            'OpenTriage-Chat-*': ['subscribe', 'publish'],
        },
    });
}

// =============================================================================
// Health Check
// =============================================================================

export function isAblyConfigured(): boolean {
    return !!process.env.ABLY_API_KEY;
}