notifications
This commit is contained in:
parent
735b3ce460
commit
a1e4e14b30
156
NOTIFICATIONS_COMPLETE_SYSTEM.md
Normal file
156
NOTIFICATIONS_COMPLETE_SYSTEM.md
Normal file
@ -0,0 +1,156 @@
|
||||
# 🔔 Système de Notifications Complet
|
||||
|
||||
## ✅ Adapters Implémentés
|
||||
|
||||
Le système de notifications agrège maintenant **3 sources** :
|
||||
|
||||
1. **Leantime** (`leantime-adapter.ts`)
|
||||
- Notifications de tâches, commentaires, mentions
|
||||
- Polling toutes les 30 secondes
|
||||
|
||||
2. **RocketChat** (`rocketchat-adapter.ts`) ⚡ NOUVEAU
|
||||
- Messages non lus dans Parole
|
||||
- Déclenchement en temps réel quand nouveau message détecté
|
||||
|
||||
3. **Email** (`email-adapter.ts`) ⚡ NOUVEAU
|
||||
- Emails non lus dans Courrier (INBOX uniquement)
|
||||
- Déclenchement en temps réel quand nouvel email détecté
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Flow Complet
|
||||
|
||||
### Badge de Notification
|
||||
|
||||
Le badge rouge affiche maintenant le **total agrégé** :
|
||||
```
|
||||
Total = Leantime (unread) + RocketChat (unread) + Email (unread)
|
||||
```
|
||||
|
||||
### Déclenchement Temps Réel
|
||||
|
||||
#### 1. **Nouveau Message Parole**
|
||||
```
|
||||
Widget Parole détecte nouveau message
|
||||
└─> totalUnreadCount augmente
|
||||
└─> triggerNotificationRefresh()
|
||||
└─> Invalide cache notifications
|
||||
└─> NotificationService.getNotificationCount()
|
||||
├─> LeantimeAdapter.getNotificationCount()
|
||||
├─> RocketChatAdapter.getNotificationCount() ⚡
|
||||
└─> EmailAdapter.getNotificationCount() ⚡
|
||||
└─> Badge mis à jour (< 1 seconde)
|
||||
```
|
||||
|
||||
#### 2. **Nouvel Email Courrier**
|
||||
```
|
||||
checkForNewEmails() détecte nouvel email
|
||||
└─> newestEmailId > lastKnownEmailId
|
||||
└─> triggerNotificationRefresh() ⚡
|
||||
└─> Invalide cache notifications
|
||||
└─> NotificationService.getNotificationCount()
|
||||
├─> LeantimeAdapter.getNotificationCount()
|
||||
├─> RocketChatAdapter.getNotificationCount()
|
||||
└─> EmailAdapter.getNotificationCount() ⚡
|
||||
└─> Badge mis à jour (< 1 seconde)
|
||||
```
|
||||
|
||||
#### 3. **Notification Leantime**
|
||||
```
|
||||
Polling toutes les 30s
|
||||
└─> LeantimeAdapter.getNotificationCount()
|
||||
└─> Badge mis à jour
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Structure des Counts
|
||||
|
||||
```typescript
|
||||
NotificationCount {
|
||||
total: number, // Total de toutes les sources
|
||||
unread: number, // Total non lus de toutes les sources
|
||||
sources: {
|
||||
leantime: {
|
||||
total: number,
|
||||
unread: number
|
||||
},
|
||||
rocketchat: {
|
||||
total: number,
|
||||
unread: number
|
||||
},
|
||||
email: {
|
||||
total: number,
|
||||
unread: number
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Fichiers Modifiés/Créés
|
||||
|
||||
### Nouveaux Fichiers
|
||||
- `lib/services/notifications/rocketchat-adapter.ts` - Adapter RocketChat
|
||||
- `lib/services/notifications/email-adapter.ts` - Adapter Email
|
||||
- `hooks/use-trigger-notification.ts` - Hook pour déclencher refresh
|
||||
|
||||
### Fichiers Modifiés
|
||||
- `lib/services/notifications/notification-service.ts` - Enregistrement des nouveaux adapters
|
||||
- `components/parole.tsx` - Détection et déclenchement pour RocketChat
|
||||
- `hooks/use-email-state.ts` - Déclenchement pour Email (déjà présent)
|
||||
- `hooks/use-notifications.ts` - Écoute d'événements custom
|
||||
- `app/api/notifications/count/route.ts` - Support `?force=true`
|
||||
- `app/api/rocket-chat/messages/route.ts` - Retourne `totalUnreadCount`
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Avantages
|
||||
|
||||
1. **⚡ Temps Réel** : Notifications instantanées (< 1 seconde)
|
||||
2. **📊 Multi-Sources** : Leantime + RocketChat + Email
|
||||
3. **💚 Efficace** : Déclenchement uniquement quand nécessaire
|
||||
4. **🔄 Rétrocompatible** : Le polling reste en fallback
|
||||
5. **📈 Scalable** : Facile d'ajouter d'autres adapters
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Résultat Final
|
||||
|
||||
Le badge de notification affiche maintenant :
|
||||
- ✅ Notifications Leantime (polling 30s)
|
||||
- ✅ Messages non lus RocketChat (temps réel)
|
||||
- ✅ Emails non lus Courrier (temps réel)
|
||||
|
||||
**Total = Leantime + RocketChat + Email** 🎉
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes Techniques
|
||||
|
||||
### RocketChat Adapter
|
||||
- Utilise les subscriptions RocketChat
|
||||
- Compte les messages non lus (`unread > 0`)
|
||||
- Supporte channels, groups, et DMs
|
||||
|
||||
### Email Adapter
|
||||
- Utilise le cache Redis de `/api/courrier/unread-counts`
|
||||
- Focus sur INBOX (principal dossier pour notifications)
|
||||
- Peut être étendu pour d'autres dossiers si besoin
|
||||
|
||||
### Cache Strategy
|
||||
- **Leantime** : Cache 30s (aligné avec polling)
|
||||
- **RocketChat** : Pas de cache dédié (utilise cache messages)
|
||||
- **Email** : Cache 2 minutes (via unread-counts API)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
Pour voir les logs :
|
||||
- `[ROCKETCHAT_ADAPTER]` - Adapter RocketChat
|
||||
- `[EMAIL_ADAPTER]` - Adapter Email
|
||||
- `[Parole]` - Détection dans widget Parole
|
||||
- `[useTriggerNotification]` - Déclenchement refresh
|
||||
- `[NOTIFICATION_SERVICE]` - Agrégation des counts
|
||||
@ -194,6 +194,7 @@ export async function GET(request: Request) {
|
||||
userId: currentUser._id,
|
||||
username: currentUser.username,
|
||||
totalSubscriptions: userSubscriptions.length,
|
||||
totalUnreadCount: totalUnreadCount,
|
||||
});
|
||||
|
||||
const messages: any[] = [];
|
||||
|
||||
@ -43,7 +43,8 @@ export function Parole() {
|
||||
const router = useRouter();
|
||||
const { data: session, status } = useSession();
|
||||
const { triggerNotificationRefresh } = useTriggerNotification();
|
||||
const lastUnreadCountRef = useRef<number>(0);
|
||||
const lastUnreadCountRef = useRef<number>(-1); // Initialize to -1 to detect first load
|
||||
const isInitializedRef = useRef<boolean>(false);
|
||||
|
||||
const fetchMessages = async (isRefresh = false) => {
|
||||
try {
|
||||
@ -67,10 +68,22 @@ export function Parole() {
|
||||
// Utiliser le totalUnreadCount de l'API (plus fiable)
|
||||
const currentUnreadCount = data.totalUnreadCount || 0;
|
||||
|
||||
// On initialise le count au premier chargement
|
||||
if (!isInitializedRef.current) {
|
||||
isInitializedRef.current = true;
|
||||
lastUnreadCountRef.current = currentUnreadCount;
|
||||
console.log('[Parole] Initial unread count:', currentUnreadCount);
|
||||
} else {
|
||||
console.log('[Parole] Unread count check', {
|
||||
previous: lastUnreadCountRef.current,
|
||||
current: currentUnreadCount,
|
||||
totalUnreadCount: data.totalUnreadCount,
|
||||
willTrigger: currentUnreadCount > lastUnreadCountRef.current
|
||||
});
|
||||
|
||||
// Si nouveau message non lu détecté, déclencher notification
|
||||
// On vérifie si le count a augmenté (et qu'on avait déjà un count initialisé)
|
||||
if (currentUnreadCount > lastUnreadCountRef.current && lastUnreadCountRef.current >= 0) {
|
||||
console.log('[Parole] Nouveau message non lu détecté', {
|
||||
if (currentUnreadCount > lastUnreadCountRef.current) {
|
||||
console.log('[Parole] ⚡ Nouveau message non lu détecté, déclenchement notification', {
|
||||
previous: lastUnreadCountRef.current,
|
||||
current: currentUnreadCount
|
||||
});
|
||||
@ -78,6 +91,7 @@ export function Parole() {
|
||||
}
|
||||
|
||||
lastUnreadCountRef.current = currentUnreadCount;
|
||||
}
|
||||
setMessages(data.messages);
|
||||
} else {
|
||||
console.warn('Unexpected data format:', data);
|
||||
|
||||
223
lib/services/notifications/email-adapter.ts
Normal file
223
lib/services/notifications/email-adapter.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { NotificationAdapter } from './notification-adapter.interface';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { Notification, NotificationCount } from '@/lib/types/notification';
|
||||
import { getRedisClient } from '@/lib/redis';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getImapConnection } from '@/lib/services/email-service';
|
||||
|
||||
export class EmailAdapter implements NotificationAdapter {
|
||||
readonly sourceName = 'email';
|
||||
private static readonly UNREAD_COUNTS_CACHE_KEY = (userId: string) => `email:unread:${userId}`;
|
||||
private static readonly CACHE_TTL = 120; // 2 minutes (aligned with unread-counts API)
|
||||
|
||||
async isConfigured(): Promise<boolean> {
|
||||
// Email service is always configured if user has email accounts
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch unread counts from IMAP (same logic as unread-counts API)
|
||||
*/
|
||||
private async fetchUnreadCounts(userId: string): Promise<Record<string, Record<string, number>>> {
|
||||
// Get all accounts from the database
|
||||
const accounts = await prisma.mailCredentials.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug('[EMAIL_ADAPTER] Found accounts', {
|
||||
userId,
|
||||
accountCount: accounts.length,
|
||||
});
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return { default: {} };
|
||||
}
|
||||
|
||||
// Mapping to hold the unread counts
|
||||
const unreadCounts: Record<string, Record<string, number>> = {};
|
||||
|
||||
// For each account, get the unread counts for standard folders
|
||||
for (const account of accounts) {
|
||||
const accountId = account.id;
|
||||
try {
|
||||
// Get IMAP connection for this account
|
||||
logger.debug('[EMAIL_ADAPTER] Processing account', {
|
||||
userId,
|
||||
accountId,
|
||||
email: account.email,
|
||||
});
|
||||
const client = await getImapConnection(userId, accountId);
|
||||
unreadCounts[accountId] = {};
|
||||
|
||||
// Standard folders to check (focus on INBOX for notifications)
|
||||
const standardFolders = ['INBOX'];
|
||||
|
||||
// Get mailboxes for this account to check if folders exist
|
||||
const mailboxes = await client.list();
|
||||
const availableFolders = mailboxes.map(mb => mb.path);
|
||||
|
||||
// Check each standard folder if it exists
|
||||
for (const folder of standardFolders) {
|
||||
// Skip if folder doesn't exist in this account
|
||||
if (!availableFolders.includes(folder) &&
|
||||
!availableFolders.some(f => f.toLowerCase() === folder.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check folder status without opening it (more efficient)
|
||||
const status = await client.status(folder, { unseen: true });
|
||||
|
||||
if (status && typeof status.unseen === 'number') {
|
||||
// Store the unread count
|
||||
unreadCounts[accountId][folder] = status.unseen;
|
||||
|
||||
logger.debug('[EMAIL_ADAPTER] Unread count', {
|
||||
userId,
|
||||
accountId,
|
||||
folder,
|
||||
unread: status.unseen,
|
||||
});
|
||||
}
|
||||
} catch (folderError) {
|
||||
logger.error('[EMAIL_ADAPTER] Error getting unread count for folder', {
|
||||
userId,
|
||||
accountId,
|
||||
folder,
|
||||
error: folderError instanceof Error ? folderError.message : String(folderError),
|
||||
});
|
||||
// Continue to next folder even if this one fails
|
||||
}
|
||||
}
|
||||
} catch (accountError) {
|
||||
logger.error('[EMAIL_ADAPTER] Error processing account', {
|
||||
userId,
|
||||
accountId,
|
||||
error: accountError instanceof Error ? accountError.message : String(accountError),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return unreadCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's email accounts and calculate total unread count
|
||||
*/
|
||||
async getNotificationCount(userId: string): Promise<NotificationCount> {
|
||||
logger.debug('[EMAIL_ADAPTER] getNotificationCount called', { userId });
|
||||
|
||||
try {
|
||||
// Try to get from cache first (same cache as unread-counts API)
|
||||
const redis = getRedisClient();
|
||||
const cacheKey = EmailAdapter.UNREAD_COUNTS_CACHE_KEY(userId);
|
||||
|
||||
let unreadCounts: Record<string, Record<string, number>> | null = null;
|
||||
|
||||
try {
|
||||
const cachedData = await redis.get(cacheKey);
|
||||
if (cachedData) {
|
||||
unreadCounts = JSON.parse(cachedData);
|
||||
logger.debug('[EMAIL_ADAPTER] Using cached unread counts', { userId });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug('[EMAIL_ADAPTER] Cache miss or error', {
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
// If no cache, fetch directly (but don't cache here - let the API route handle caching)
|
||||
if (!unreadCounts) {
|
||||
try {
|
||||
// Fetch unread counts directly
|
||||
unreadCounts = await this.fetchUnreadCounts(userId);
|
||||
logger.debug('[EMAIL_ADAPTER] Fetched unread counts directly', { userId });
|
||||
} catch (error) {
|
||||
logger.error('[EMAIL_ADAPTER] Error fetching unread counts', {
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
sources: {
|
||||
email: {
|
||||
total: 0,
|
||||
unread: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total unread count across all accounts and folders
|
||||
// Focus on INBOX for notifications
|
||||
let totalUnread = 0;
|
||||
let foldersWithUnread = 0;
|
||||
|
||||
for (const accountId in unreadCounts) {
|
||||
const accountFolders = unreadCounts[accountId];
|
||||
// Focus on INBOX folder for notifications
|
||||
const inboxCount = accountFolders['INBOX'] || accountFolders[`${accountId}:INBOX`] || 0;
|
||||
if (inboxCount > 0) {
|
||||
totalUnread += inboxCount;
|
||||
foldersWithUnread++;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('[EMAIL_ADAPTER] Notification counts', {
|
||||
userId,
|
||||
total: foldersWithUnread,
|
||||
unread: totalUnread,
|
||||
});
|
||||
|
||||
return {
|
||||
total: foldersWithUnread,
|
||||
unread: totalUnread,
|
||||
sources: {
|
||||
email: {
|
||||
total: foldersWithUnread,
|
||||
unread: totalUnread
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[EMAIL_ADAPTER] Error getting notification count', {
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
sources: {
|
||||
email: {
|
||||
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
|
||||
// In the future, we could return unread emails as notifications
|
||||
return [];
|
||||
}
|
||||
|
||||
async markAsRead(userId: string, notificationId: string): Promise<boolean> {
|
||||
// Not implemented yet - Email read status is handled by the email service
|
||||
return false;
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: string): Promise<boolean> {
|
||||
// Not implemented yet - Email read status is handled by the email service
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { Notification, NotificationCount } from '@/lib/types/notification';
|
||||
import { NotificationAdapter } from './notification-adapter.interface';
|
||||
import { LeantimeAdapter } from './leantime-adapter';
|
||||
import { RocketChatAdapter } from './rocketchat-adapter';
|
||||
import { EmailAdapter } from './email-adapter';
|
||||
import { getRedisClient } from '@/lib/redis';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
@ -22,6 +24,8 @@ export class NotificationService {
|
||||
|
||||
// Register adapters
|
||||
this.registerAdapter(new LeantimeAdapter());
|
||||
this.registerAdapter(new RocketChatAdapter());
|
||||
this.registerAdapter(new EmailAdapter());
|
||||
|
||||
// More adapters will be added as they are implemented
|
||||
// this.registerAdapter(new NextcloudAdapter());
|
||||
|
||||
265
lib/services/notifications/rocketchat-adapter.ts
Normal file
265
lib/services/notifications/rocketchat-adapter.ts
Normal file
@ -0,0 +1,265 @@
|
||||
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);
|
||||
return session?.user?.email || null;
|
||||
} 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)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentUser = usersData.users.find((u: any) => u.username === username);
|
||||
return currentUser?._id || null;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user