hMarket Trae ilk versiyon

This commit is contained in:
hOLOlu
2026-02-03 01:22:08 +03:00
commit 2b861156fe
74 changed files with 42127 additions and 0 deletions

View File

@@ -0,0 +1,523 @@
const admin = require('firebase-admin');
class NotificationService {
constructor(prisma, io) {
this.prisma = prisma;
this.io = io;
this.initializeFirebase();
}
initializeFirebase() {
try {
if (!admin.apps.length) {
const serviceAccount = {
type: process.env.FIREBASE_TYPE,
project_id: process.env.FIREBASE_PROJECT_ID,
private_key_id: process.env.FIREBASE_PRIVATE_KEY_ID,
private_key: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
client_email: process.env.FIREBASE_CLIENT_EMAIL,
client_id: process.env.FIREBASE_CLIENT_ID,
auth_uri: process.env.FIREBASE_AUTH_URI,
token_uri: process.env.FIREBASE_TOKEN_URI,
auth_provider_x509_cert_url: process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL,
client_x509_cert_url: process.env.FIREBASE_CLIENT_X509_CERT_URL
};
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
console.log('Firebase Admin SDK initialized successfully');
}
} catch (error) {
console.error('Firebase initialization error:', error);
}
}
/**
* Veritabanına bildirim kaydet
*/
async createNotification(data) {
try {
const notification = await this.prisma.notification.create({
data: {
userId: data.userId,
type: data.type,
title: data.title,
message: data.message,
data: data.data ? JSON.stringify(data.data) : null,
relatedListId: data.relatedListId || null,
relatedUserId: data.relatedUserId || null
},
include: {
relatedList: {
select: {
id: true,
name: true
}
},
relatedUser: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true
}
}
}
});
return notification;
} catch (error) {
console.error('Error creating notification:', error);
throw error;
}
}
/**
* Socket.IO ile gerçek zamanlı bildirim gönder
*/
async sendRealtimeNotification(userId, notification) {
try {
if (this.io) {
this.io.to(`user_${userId}`).emit('notification', notification);
}
} catch (error) {
console.error('Error sending realtime notification:', error);
}
}
/**
* Push notification gönder
*/
async sendPushNotification(userId, notification) {
try {
// Kullanıcının push notification ayarını kontrol et
const setting = await this.prisma.setting.findUnique({
where: {
userId_key: {
userId,
key: 'notification_push_enabled'
}
}
});
if (setting && setting.value === 'false') {
return; // Push notification kapalı
}
// Kullanıcının device token'larını al
const deviceTokens = await this.prisma.deviceToken.findMany({
where: {
userId,
isActive: true
}
});
if (deviceTokens.length === 0) {
return; // Device token yok
}
const tokens = deviceTokens.map(dt => dt.token);
const message = {
notification: {
title: notification.title,
body: notification.message
},
data: {
type: notification.type,
notificationId: notification.id,
...(notification.data ? JSON.parse(notification.data) : {})
},
tokens
};
const response = await admin.messaging().sendMulticast(message);
// Başarısız token'ları temizle
if (response.failureCount > 0) {
const failedTokens = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
failedTokens.push(tokens[idx]);
}
});
if (failedTokens.length > 0) {
await this.prisma.deviceToken.updateMany({
where: {
token: { in: failedTokens }
},
data: {
isActive: false
}
});
}
}
console.log(`Push notification sent to ${response.successCount} devices`);
} catch (error) {
console.error('Error sending push notification:', error);
}
}
/**
* Tam bildirim gönderme (DB + Realtime + Push)
*/
async sendNotification(data) {
try {
// Kullanıcının bildirim ayarını kontrol et
const notificationKey = `notification_${data.type}`;
const setting = await this.prisma.setting.findUnique({
where: {
userId_key: {
userId: data.userId,
key: notificationKey
}
}
});
if (setting && setting.value === 'false') {
return; // Bu tür bildirim kapalı
}
// Veritabanına kaydet
const notification = await this.createNotification(data);
// Gerçek zamanlı gönder
await this.sendRealtimeNotification(data.userId, notification);
// Push notification gönder
await this.sendPushNotification(data.userId, notification);
return notification;
} catch (error) {
console.error('Error in sendNotification:', error);
throw error;
}
}
/**
* Birden fazla kullanıcıya bildirim gönder
*/
async sendNotificationToMultipleUsers(userIds, data) {
try {
const promises = userIds.map(userId =>
this.sendNotification({ ...data, userId })
);
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`Notifications sent: ${successful} successful, ${failed} failed`);
return { successful, failed };
} catch (error) {
console.error('Error sending notifications to multiple users:', error);
throw error;
}
}
/**
* Liste üyelerine bildirim gönder
*/
async sendNotificationToListMembers(listId, data, excludeUserId = null) {
try {
const members = await this.prisma.listMember.findMany({
where: {
listId,
userId: excludeUserId ? { not: excludeUserId } : undefined
},
select: {
userId: true
}
});
const userIds = members.map(m => m.userId);
if (userIds.length === 0) {
return { successful: 0, failed: 0 };
}
return await this.sendNotificationToMultipleUsers(userIds, {
...data,
relatedListId: listId
});
} catch (error) {
console.error('Error sending notifications to list members:', error);
throw error;
}
}
/**
* Liste daveti bildirimi
*/
async sendListInviteNotification(invitedUserId, inviterUser, list) {
return await this.sendNotification({
userId: invitedUserId,
type: 'list_invite',
title: 'Liste Daveti',
message: `${inviterUser.firstName} ${inviterUser.lastName} sizi "${list.name}" listesine davet etti`,
data: {
inviterId: inviterUser.id,
listId: list.id
},
relatedListId: list.id,
relatedUserId: inviterUser.id
});
}
/**
* Ürün eklendi bildirimi
*/
async sendItemAddedNotification(listId, adderUser, itemName, excludeUserId = null) {
return await this.sendNotificationToListMembers(listId, {
type: 'item_added',
title: 'Ürün Eklendi',
message: `${adderUser.firstName} "${itemName}" ürününü listeye ekledi`,
data: {
adderId: adderUser.id,
itemName
},
relatedUserId: adderUser.id
}, excludeUserId);
}
/**
* Ürün silindi bildirimi
*/
async sendItemRemovedNotification(listId, removerUser, itemName, excludeUserId = null) {
return await this.sendNotificationToListMembers(listId, {
type: 'item_removed',
title: 'Ürün Silindi',
message: `${removerUser.firstName} "${itemName}" ürününü listeden sildi`,
data: {
removerId: removerUser.id,
itemName
},
relatedUserId: removerUser.id
}, excludeUserId);
}
/**
* Ürün satın alındı bildirimi
*/
async sendItemPurchasedNotification(listId, purchaserUser, itemName, excludeUserId = null) {
return await this.sendNotificationToListMembers(listId, {
type: 'item_purchased',
title: 'Ürün Satın Alındı',
message: `${purchaserUser.firstName} "${itemName}" ürününü satın aldı`,
data: {
purchaserId: purchaserUser.id,
itemName
},
relatedUserId: purchaserUser.id
}, excludeUserId);
}
/**
* Liste paylaşıldı bildirimi
*/
async sendListSharedNotification(listId, sharerUser, sharedWithUser, listName) {
return await this.sendNotification({
userId: sharedWithUser.id,
type: 'list_shared',
title: 'Liste Paylaşıldı',
message: `${sharerUser.firstName} "${listName}" listesini sizinle paylaştı`,
data: {
sharerId: sharerUser.id,
listName
},
relatedListId: listId,
relatedUserId: sharerUser.id
});
}
/**
* Device token kaydet
*/
async registerDeviceToken(userId, token, deviceInfo = {}) {
try {
// Mevcut token'ı kontrol et
const existingToken = await this.prisma.deviceToken.findFirst({
where: {
token,
userId
}
});
if (existingToken) {
// Token'ı güncelle
return await this.prisma.deviceToken.update({
where: { id: existingToken.id },
data: {
isActive: true,
deviceInfo: deviceInfo ? JSON.stringify(deviceInfo) : null,
updatedAt: new Date()
}
});
} else {
// Yeni token oluştur
return await this.prisma.deviceToken.create({
data: {
userId,
token,
deviceInfo: deviceInfo ? JSON.stringify(deviceInfo) : null
}
});
}
} catch (error) {
console.error('Error registering device token:', error);
throw error;
}
}
/**
* Device token'ı deaktive et
*/
async unregisterDeviceToken(userId, token) {
try {
return await this.prisma.deviceToken.updateMany({
where: {
userId,
token
},
data: {
isActive: false
}
});
} catch (error) {
console.error('Error unregistering device token:', error);
throw error;
}
}
/**
* Liste üyelerine bildirim gönder
*/
async notifyListMembers(listId, excludeUserId, notificationData) {
try {
// Liste üyelerini getir
const listMembers = await this.prisma.listMember.findMany({
where: {
listId,
userId: {
not: excludeUserId // Aksiyonu yapan kullanıcıyı hariç tut
}
},
include: {
user: {
select: {
id: true,
firstName: true,
lastName: true
}
}
}
});
// Liste sahibini de dahil et (eğer üye değilse)
const list = await this.prisma.shoppingList.findUnique({
where: { id: listId },
include: {
owner: {
select: {
id: true,
firstName: true,
lastName: true
}
}
}
});
const memberUserIds = listMembers.map(member => member.userId);
if (list.owner.id !== excludeUserId && !memberUserIds.includes(list.owner.id)) {
listMembers.push({
user: list.owner,
userId: list.owner.id
});
}
// Her üyeye bildirim gönder
const notifications = [];
for (const member of listMembers) {
// Socket.IO ile gerçek zamanlı bildirim
if (this.io) {
this.io.to(`user_${member.userId}`).emit('notification', {
type: notificationData.type,
title: notificationData.title,
message: notificationData.message,
data: notificationData.data,
timestamp: new Date()
});
}
// Veritabanına bildirim kaydet
const notification = await this.createNotification({
userId: member.userId,
type: notificationData.type,
title: notificationData.title,
message: notificationData.message,
data: notificationData.data,
relatedListId: listId,
relatedUserId: excludeUserId
});
notifications.push(notification);
// Push notification gönder
await this.sendPushNotification(member.userId, {
title: notificationData.title,
body: notificationData.message,
data: notificationData.data
});
}
return notifications;
} catch (error) {
console.error('Error notifying list members:', error);
throw error;
}
}
}
// Singleton instance
let notificationServiceInstance = null;
// Factory function
const createNotificationService = (prisma, io) => {
if (!notificationServiceInstance) {
notificationServiceInstance = new NotificationService(prisma, io);
}
return notificationServiceInstance;
};
// Export both class and factory
module.exports = {
NotificationService,
createNotificationService,
// Default export for backward compatibility
notifyListMembers: async (...args) => {
if (!notificationServiceInstance) {
throw new Error('NotificationService not initialized. Call createNotificationService first.');
}
return notificationServiceInstance.notifyListMembers(...args);
},
createNotification: async (...args) => {
if (!notificationServiceInstance) {
throw new Error('NotificationService not initialized. Call createNotificationService first.');
}
return notificationServiceInstance.createNotification(...args);
},
sendPushNotification: async (...args) => {
if (!notificationServiceInstance) {
throw new Error('NotificationService not initialized. Call createNotificationService first.');
}
return notificationServiceInstance.sendPushNotification(...args);
}
};