chore: push notification test
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user