NeahStable/lib/services/notifications/rocketchat-adapter.ts
2026-01-11 22:33:08 +01:00

296 lines
8.4 KiB
TypeScript

import { NotificationAdapter } from './notification-adapter.interface';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/app/api/auth/options";
import { logger } from '@/lib/logger';
import { Notification, NotificationCount } from '@/lib/types/notification';
export class RocketChatAdapter implements NotificationAdapter {
readonly sourceName = 'rocketchat';
private baseUrl: string;
constructor() {
this.baseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0] || '';
logger.debug('[ROCKETCHAT_ADAPTER] Initialized', {
hasBaseUrl: !!this.baseUrl,
});
}
async isConfigured(): Promise<boolean> {
return !!this.baseUrl &&
!!process.env.ROCKET_CHAT_TOKEN &&
!!process.env.ROCKET_CHAT_USER_ID;
}
/**
* Get user's email from session
*/
private async getUserEmail(): Promise<string | null> {
try {
const session = await getServerSession(authOptions);
const email = session?.user?.email || null;
logger.debug('[ROCKETCHAT_ADAPTER] getUserEmail', {
hasSession: !!session,
hasEmail: !!email,
emailHash: email ? Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12) : null,
});
return email;
} catch (error) {
logger.error('[ROCKETCHAT_ADAPTER] Error getting user email', {
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}
/**
* Get RocketChat user ID from email
*/
private async getRocketChatUserId(email: string): Promise<string | null> {
try {
const username = email.split('@')[0];
if (!username) return null;
const adminHeaders = {
'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!,
'X-User-Id': process.env.ROCKET_CHAT_USER_ID!,
'Content-Type': 'application/json'
};
const usersResponse = await fetch(`${this.baseUrl}/api/v1/users.list`, {
method: 'GET',
headers: adminHeaders
});
if (!usersResponse.ok) {
logger.error('[ROCKETCHAT_ADAPTER] Failed to get users list', {
status: usersResponse.status,
});
return null;
}
const usersData = await usersResponse.json();
if (!usersData.success || !Array.isArray(usersData.users)) {
logger.error('[ROCKETCHAT_ADAPTER] Invalid users list response', {
success: usersData.success,
hasUsers: Array.isArray(usersData.users),
});
return null;
}
logger.debug('[ROCKETCHAT_ADAPTER] Searching for user', {
username,
totalUsers: usersData.users.length,
});
const currentUser = usersData.users.find((u: any) => u.username === username);
if (!currentUser) {
logger.warn('[ROCKETCHAT_ADAPTER] User not found in RocketChat', {
username,
searchedIn: usersData.users.length,
availableUsernames: usersData.users.slice(0, 5).map((u: any) => u.username),
});
return null;
}
logger.debug('[ROCKETCHAT_ADAPTER] Found user', {
username,
userId: currentUser._id,
});
return currentUser._id;
} catch (error) {
logger.error('[ROCKETCHAT_ADAPTER] Error getting RocketChat user ID', {
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}
/**
* Get user token for RocketChat API
*/
private async getUserToken(): Promise<{ authToken: string; userId: string } | null> {
try {
const adminHeaders = {
'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!,
'X-User-Id': process.env.ROCKET_CHAT_USER_ID!,
'Content-Type': 'application/json'
};
const createTokenResponse = await fetch(`${this.baseUrl}/api/v1/users.createToken`, {
method: 'POST',
headers: adminHeaders
});
if (!createTokenResponse.ok) {
logger.error('[ROCKETCHAT_ADAPTER] Failed to create user token', {
status: createTokenResponse.status,
});
return null;
}
const tokenData = await createTokenResponse.json();
return {
authToken: tokenData.data.authToken,
userId: tokenData.data.userId
};
} catch (error) {
logger.error('[ROCKETCHAT_ADAPTER] Error getting user token', {
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}
async getNotificationCount(userId: string): Promise<NotificationCount> {
logger.debug('[ROCKETCHAT_ADAPTER] getNotificationCount called', { userId });
try {
const email = await this.getUserEmail();
if (!email) {
logger.error('[ROCKETCHAT_ADAPTER] Could not get user email');
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
const rocketChatUserId = await this.getRocketChatUserId(email);
if (!rocketChatUserId) {
logger.debug('[ROCKETCHAT_ADAPTER] User not found in RocketChat');
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
const userToken = await this.getUserToken();
if (!userToken) {
logger.error('[ROCKETCHAT_ADAPTER] Could not get user token');
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
const userHeaders = {
'X-Auth-Token': userToken.authToken,
'X-User-Id': userToken.userId,
'Content-Type': 'application/json'
};
// Get user's subscriptions
const subscriptionsResponse = await fetch(`${this.baseUrl}/api/v1/subscriptions.get`, {
method: 'GET',
headers: userHeaders
});
if (!subscriptionsResponse.ok) {
logger.error('[ROCKETCHAT_ADAPTER] Failed to get subscriptions', {
status: subscriptionsResponse.status,
});
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
const subscriptionsData = await subscriptionsResponse.json();
if (!subscriptionsData.success || !Array.isArray(subscriptionsData.update)) {
logger.error('[ROCKETCHAT_ADAPTER] Invalid subscriptions response');
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
// Filter subscriptions with unread messages
const userSubscriptions = subscriptionsData.update.filter((sub: any) => {
return (sub.unread > 0 || sub.alert) && ['d', 'c', 'p'].includes(sub.t);
});
// Calculate total unread count
const totalUnreadCount = userSubscriptions.reduce((sum: number, sub: any) =>
sum + (sub.unread || 0), 0);
logger.debug('[ROCKETCHAT_ADAPTER] Notification counts', {
total: userSubscriptions.length,
unread: totalUnreadCount,
});
return {
total: userSubscriptions.length,
unread: totalUnreadCount,
sources: {
rocketchat: {
total: userSubscriptions.length,
unread: totalUnreadCount
}
}
};
} catch (error) {
logger.error('[ROCKETCHAT_ADAPTER] Error getting notification count', {
error: error instanceof Error ? error.message : String(error),
});
return {
total: 0,
unread: 0,
sources: {
rocketchat: {
total: 0,
unread: 0
}
}
};
}
}
async getNotifications(userId: string, page = 1, limit = 20): Promise<Notification[]> {
// For now, return empty array - we can implement this later if needed
// The notification count is what matters for the badge
return [];
}
async markAsRead(userId: string, notificationId: string): Promise<boolean> {
// Not implemented yet - RocketChat handles read status automatically
return false;
}
async markAllAsRead(userId: string): Promise<boolean> {
// Not implemented yet - RocketChat handles read status automatically
return false;
}
}