chore: push notification test

This commit is contained in:
mahyargdz
2025-07-24 13:03:29 +03:30
parent 5f4b679467
commit 7b7167ab7d
17 changed files with 1558 additions and 12 deletions
+258 -2
View File
@@ -1,20 +1,37 @@
import { EntityManager } from "@mikro-orm/postgresql";
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { BusinessMessage, DomainMessage, MailServerMessage, RoleMessage, TemplateMessage, UserMessage } from "../../../common/enums/message.enum";
import {
BusinessMessage,
CommonMessage,
DomainMessage,
MailServerMessage,
RoleMessage,
TemplateMessage,
UserMessage,
} from "../../../common/enums/message.enum";
import { QUOTA_CONSTANTS } from "../../businesses/constant";
import { Business } from "../../businesses/entities/business.entity";
import { DomainStatus } from "../../domains/enums/domain-status.enum";
import { DomainAutomationService } from "../../domains/services/domain-automation.service";
import { DomainsService } from "../../domains/services/domains.service";
import { MailboxResolverService } from "../../email/services/mailbox-resolver.service";
import { UpdateUserDto } from "../../mail-server/DTO/update-user.dto";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { NotificationQueue } from "../../notifications/queue/notification.queue";
import { PushNotificationService } from "../../notifications/services/push-notification.service";
import { QuotaSyncService } from "../../quota-sync/services/quota-sync.service";
import { UserSettingsService } from "../../settings/services/user-settings.service";
import { Template } from "../../templates/entities/template.entity";
import { PasswordService } from "../../utils/services/password.service";
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
import {
RegisterPushSubscriptionDto,
TestPushNotificationDto,
UpdatePushNotificationSettingsDto,
UpdatePushSubscriptionsDto,
} from "../DTO/push-notification.dto";
import { UpdateEmailUserDto } from "../DTO/update-email-user.dto";
import { UpdateUserProfileDto } from "../DTO/update-user-profile.dto";
import { UserListQueryDto } from "../DTO/user-list-query.dto";
@@ -36,6 +53,9 @@ export class UsersService {
private readonly domainAutomationService: DomainAutomationService,
private readonly quotaSyncService: QuotaSyncService,
private readonly userSettingsService: UserSettingsService,
private readonly mailboxResolver: MailboxResolverService,
private readonly notificationQueue: NotificationQueue,
private readonly pushNotificationService: PushNotificationService,
) {}
async getUserEmailIdFromId(userId: string) {
@@ -475,4 +495,240 @@ export class UsersService {
this.logger.log(`Updated business quota for ${business.name}: used=${business.usedQuota}, remaining=${business.remainingQuota}`);
}
// Push Notification Methods
async registerPushSubscription(userId: string, registerPushSubscriptionDto: RegisterPushSubscriptionDto) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
// Validate the subscription
if (!this.pushNotificationService.validateSubscription(registerPushSubscriptionDto.subscription)) {
throw new BadRequestException("Invalid push subscription format");
}
// Initialize pushTokens array if it doesn't exist (now stores subscriptions)
if (!user.pushTokens) {
user.pushTokens = [];
}
// Check if subscription endpoint already exists
const existingIndex = user.pushTokens.findIndex((sub: any) => sub.endpoint === registerPushSubscriptionDto.subscription.endpoint);
if (existingIndex !== -1) {
// Update existing subscription
user.pushTokens[existingIndex] = {
...registerPushSubscriptionDto.subscription,
deviceType: registerPushSubscriptionDto.deviceType,
deviceName: registerPushSubscriptionDto.deviceName,
registeredAt: new Date(),
};
} else {
// Add new subscription (limit to 10 subscriptions per user)
if (user.pushTokens.length >= 10) {
// Remove oldest subscription
user.pushTokens.shift();
}
user.pushTokens.push({
...registerPushSubscriptionDto.subscription,
deviceType: registerPushSubscriptionDto.deviceType,
deviceName: registerPushSubscriptionDto.deviceName,
registeredAt: new Date(),
});
}
await this.em.flush();
this.logger.log(`Push subscription registered for user ${userId}: ${registerPushSubscriptionDto.deviceType || "unknown device"}`);
return {
message: "Subscription registered successfully",
subscriptionCount: user.pushTokens.length,
endpoint: registerPushSubscriptionDto.subscription.endpoint,
};
} catch (error) {
this.logger.error(`Error registering push subscription for user ${userId}:`, error);
throw error;
}
}
async updatePushSubscriptions(userId: string, updatePushSubscriptionsDto: UpdatePushSubscriptionsDto) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
// Validate all subscriptions
for (const subscription of updatePushSubscriptionsDto.subscriptions) {
if (!this.pushNotificationService.validateSubscription(subscription)) {
throw new BadRequestException(`Invalid subscription format for endpoint: ${(subscription as any).endpoint}`);
}
}
user.pushTokens = updatePushSubscriptionsDto.subscriptions.map((sub) => ({
...sub,
updatedAt: new Date(),
}));
await this.em.flush();
this.logger.log(`Push subscriptions updated for user ${userId}: ${updatePushSubscriptionsDto.subscriptions.length} subscriptions`);
return {
message: CommonMessage.UPDATE_SUCCESS,
subscriptionCount: user.pushTokens.length,
};
} catch (error) {
this.logger.error(`Error updating push subscriptions for user ${userId}:`, error);
throw error;
}
}
async removePushSubscription(userId: string, endpoint: string) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
if (!user.pushTokens || user.pushTokens.length === 0) {
throw new BadRequestException("No subscriptions found");
}
const subscriptions = user.pushTokens;
const initialCount = subscriptions.length;
user.pushTokens = subscriptions.filter((sub) => sub.endpoint !== endpoint);
if (user.pushTokens.length === initialCount) {
throw new BadRequestException("Subscription not found");
}
await this.em.flush();
this.logger.log(`Push subscription removed for user ${userId}`);
return {
message: "Subscription removed successfully",
subscriptionCount: user.pushTokens.length,
};
} catch (error) {
this.logger.error(`Error removing push subscription for user ${userId}:`, error);
throw error;
}
}
async updatePushNotificationSettings(userId: string, settingsDto: UpdatePushNotificationSettingsDto) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
user.pushNotificationsEnabled = settingsDto.enabled;
await this.em.flush();
this.logger.log(`Push notification settings updated for user ${userId}: ${settingsDto.enabled ? "enabled" : "disabled"}`);
return {
message: CommonMessage.UPDATE_SUCCESS,
pushNotificationsEnabled: user.pushNotificationsEnabled,
};
} catch (error) {
this.logger.error(`Error updating push notification settings for user ${userId}:`, error);
throw error;
}
}
async testPushNotification(userId: string, _testDto: TestPushNotificationDto) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
if (!user.pushNotificationsEnabled) {
throw new BadRequestException("Push notifications are disabled for this user");
}
if (!user.pushTokens || user.pushTokens.length === 0) {
throw new BadRequestException("No push subscriptions registered for this user");
}
if (!this.pushNotificationService.isEnabled()) {
throw new BadRequestException("Push notification service is disabled");
}
// Convert stored subscriptions to proper format
const subscriptions = user.pushTokens.map((sub) => ({
endpoint: sub.endpoint,
keys: sub.keys,
}));
const success = await this.pushNotificationService.sendTestNotification(subscriptions, user.id);
if (!success) {
throw new BadRequestException("Failed to send test notification");
}
this.logger.log(`Test push notification sent for user ${userId}`);
return {
message: "Test notification sent successfully",
subscriptionCount: subscriptions.length,
serviceInfo: this.pushNotificationService.getServiceInfo(),
};
} catch (error) {
this.logger.error(`Error sending test push notification for user ${userId}:`, error);
throw error;
}
}
async getPushNotificationInfo(userId: string) {
try {
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
if (!user) {
throw new NotFoundException(UserMessage.USER_NOT_FOUND);
}
return {
pushNotificationsEnabled: user.pushNotificationsEnabled,
subscriptionCount: user.pushTokens?.length || 0,
lastPushNotificationAt: user.lastPushNotificationAt,
serviceEnabled: this.pushNotificationService.isEnabled(),
vapidPublicKey: this.pushNotificationService.getVAPIDPublicKey(),
serviceInfo: this.pushNotificationService.getServiceInfo(),
setup: {
instructions: "Register your browser for push notifications using the service worker",
documentation: {
webPushAPI: "https://developer.mozilla.org/en-US/docs/Web/API/Push_API",
serviceWorkers: "https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API",
},
},
};
} catch (error) {
this.logger.error(`Error getting push notification info for user ${userId}:`, error);
throw error;
}
}
async getVAPIDPublicKey() {
try {
const vapidPublicKey = this.pushNotificationService.getVAPIDPublicKey();
if (!vapidPublicKey) {
throw new BadRequestException("VAPID public key is not configured");
}
return {
vapidPublicKey,
serviceInfo: this.pushNotificationService.getServiceInfo(),
};
} catch (error) {
this.logger.error("Error getting VAPID public key:", error);
throw error;
}
}
}