chore: fix ai service
This commit is contained in:
@@ -11,24 +11,3 @@ export class WriteEmailAssistanceDto {
|
||||
})
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export class EmailSuggestionDto {
|
||||
@ApiProperty({
|
||||
description: "Suggested email subject",
|
||||
example: "Re: Project Timeline Discussion - Meeting Request",
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Generated email content",
|
||||
example: "Dear John,\n\nThank you for your email regarding the project timeline...",
|
||||
})
|
||||
content: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Alternative variations of the email",
|
||||
type: [String],
|
||||
example: ["More formal version", "Shorter version", "More detailed version"],
|
||||
})
|
||||
alternatives: string[];
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class AiAssistantService {
|
||||
async assistEmailWriting(writeAssistanceDto: WriteEmailAssistanceDto, userId: string) {
|
||||
const assistance = await this.generateEmailWritingAssistance(writeAssistanceDto, userId);
|
||||
|
||||
return assistance;
|
||||
return { assistance };
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
@@ -116,10 +116,6 @@ export class EmailService {
|
||||
wildDuckQuery.query = query.search;
|
||||
wildDuckQuery.from = query.search;
|
||||
}
|
||||
if (query.q) {
|
||||
wildDuckQuery.query = query.q;
|
||||
wildDuckQuery.from = query.q;
|
||||
}
|
||||
|
||||
if (query.from) wildDuckQuery.from = query.from;
|
||||
if (query.to) wildDuckQuery.to = query.to;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { EntityManager } from "@mikro-orm/postgresql";
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
|
||||
import { BaseNotificationHandler } from "./base-notification.handler";
|
||||
import { PushNotificationHandler } from "./push-notification.handler";
|
||||
import { INewEmailNotificationData } from "../interfaces/ISendNotificationData";
|
||||
import { NotificationsService } from "../services/notifications.service";
|
||||
|
||||
@@ -10,21 +9,13 @@ import { NotificationsService } from "../services/notifications.service";
|
||||
export class NewEmailHandler extends BaseNotificationHandler<INewEmailNotificationData> {
|
||||
protected readonly logger = new Logger(NewEmailHandler.name);
|
||||
|
||||
constructor(
|
||||
private readonly notificationsService: NotificationsService,
|
||||
private readonly pushNotificationHandler: PushNotificationHandler,
|
||||
) {
|
||||
constructor(private readonly notificationsService: NotificationsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async handle(recipientId: string, data: INewEmailNotificationData, em: EntityManager): Promise<void> {
|
||||
try {
|
||||
// Create in-app notification
|
||||
await this.notificationsService.createNewEmailNotification(recipientId, data, em);
|
||||
|
||||
// Also send push notification
|
||||
await this.pushNotificationHandler.processWithTransaction(recipientId, data, em);
|
||||
|
||||
this.logger.log(`New email notifications (in-app + push) processed for user: ${recipientId}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error processing new email notifications for user ${recipientId}:`, error);
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { EntityManager } from "@mikro-orm/postgresql";
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
|
||||
import { BaseNotificationHandler } from "./base-notification.handler";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { INewEmailNotificationData } from "../interfaces/ISendNotificationData";
|
||||
import { PushNotificationService, WebPushSubscription } from "../services/push-notification.service";
|
||||
|
||||
@Injectable()
|
||||
export class PushNotificationHandler extends BaseNotificationHandler<INewEmailNotificationData> {
|
||||
protected readonly logger = new Logger(PushNotificationHandler.name);
|
||||
|
||||
constructor(private readonly pushNotificationService: PushNotificationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async handle(recipientId: string, data: INewEmailNotificationData, em: EntityManager): Promise<void> {
|
||||
try {
|
||||
// Get user with push notification settings
|
||||
const user = await em.findOne(User, {
|
||||
id: recipientId,
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
this.logger.warn(`User not found: ${recipientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if push notifications are enabled for this user
|
||||
if (!user.pushNotificationsEnabled || !user.pushTokens || user.pushTokens.length === 0) {
|
||||
this.logger.debug(`Push notifications not enabled or no subscriptions for user: ${recipientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if push notification service is enabled
|
||||
if (!this.pushNotificationService.isEnabled()) {
|
||||
this.logger.debug("Push notification service is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert stored subscriptions to WebPushSubscription format
|
||||
const subscriptions: WebPushSubscription[] = user.pushTokens.map((sub) => ({
|
||||
endpoint: sub.endpoint,
|
||||
keys: sub.keys,
|
||||
}));
|
||||
|
||||
// Send push notification using the data from INewEmailNotificationData
|
||||
const success = await this.pushNotificationService.sendNewEmailNotification(
|
||||
subscriptions,
|
||||
"New Email", // Default subject - can be enhanced with more email data
|
||||
data.fromName || "Unknown Sender",
|
||||
data.fromEmail,
|
||||
user.id,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
// Update last push notification timestamp
|
||||
user.lastPushNotificationAt = new Date();
|
||||
await em.flush();
|
||||
|
||||
this.logger.log(`Push notification sent successfully for user: ${recipientId}`);
|
||||
} else {
|
||||
this.logger.warn(`Failed to send push notification for user: ${recipientId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Error in push notification handler for user ${recipientId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
protected getHandlerName(): string {
|
||||
return "PushNotification";
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,10 @@ import { Notification } from "./entities/notification.entity";
|
||||
import { ChangePasswordHandler } from "./handlers/change-password.handler";
|
||||
import { NewEmailHandler } from "./handlers/new-email.handler";
|
||||
import { NotificationHandlerFactory } from "./handlers/notification-handler.factory";
|
||||
import { PushNotificationHandler } from "./handlers/push-notification.handler";
|
||||
import { NotificationController } from "./notifications.controller";
|
||||
import { NotificationProcessor } from "./queue/notification.processor";
|
||||
import { NotificationQueue } from "./queue/notification.queue";
|
||||
import { NotificationsService } from "./services/notifications.service";
|
||||
import { PushNotificationService } from "./services/push-notification.service";
|
||||
import { pushNotificationsConfig } from "../../configs/push-notifications.config";
|
||||
import { NotificationSetting } from "../settings/entities/notification-setting.entity";
|
||||
import { SettingModule } from "../settings/settings.module";
|
||||
@@ -44,15 +42,13 @@ import { UserLoginHandler } from "./handlers/user-login.handler";
|
||||
NotificationsService,
|
||||
NotificationQueue,
|
||||
NotificationProcessor,
|
||||
PushNotificationService,
|
||||
// notification handlers
|
||||
NotificationHandlerFactory,
|
||||
UserLoginHandler,
|
||||
NewEmailHandler,
|
||||
ChangePasswordHandler,
|
||||
PushNotificationHandler,
|
||||
],
|
||||
controllers: [NotificationController],
|
||||
exports: [NotificationsService, NotificationQueue, PushNotificationService],
|
||||
exports: [NotificationsService, NotificationQueue],
|
||||
})
|
||||
export class NotificationModule {}
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import * as webpush from "web-push";
|
||||
|
||||
import { PushNotificationsConfigType } from "../../../configs/push-notifications.config";
|
||||
|
||||
export interface WebPushSubscription {
|
||||
endpoint: string;
|
||||
keys: {
|
||||
p256dh: string;
|
||||
auth: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WebPushNotificationPayload {
|
||||
title: string;
|
||||
body: string;
|
||||
icon?: string;
|
||||
badge?: string;
|
||||
image?: string;
|
||||
data?: any;
|
||||
actions?: Array<{
|
||||
action: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
}>;
|
||||
tag?: string;
|
||||
requireInteraction?: boolean;
|
||||
silent?: boolean;
|
||||
vibrate?: number[];
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export interface BulkWebPushPayload extends WebPushNotificationPayload {
|
||||
subscriptions: WebPushSubscription[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PushNotificationService {
|
||||
private readonly logger = new Logger(PushNotificationService.name);
|
||||
private readonly config: PushNotificationsConfigType;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.config = this.configService.get<PushNotificationsConfigType>("pushNotifications")!;
|
||||
|
||||
// Configure web-push with VAPID keys
|
||||
if (this.config.webPush.vapidPublicKey && this.config.webPush.vapidPrivateKey) {
|
||||
webpush.setVapidDetails(this.config.webPush.vapidSubject, this.config.webPush.vapidPublicKey, this.config.webPush.vapidPrivateKey);
|
||||
this.logger.log("Web Push service initialized with VAPID keys");
|
||||
} else {
|
||||
this.logger.warn("Web Push service initialized without VAPID keys - push notifications will not work");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send push notification to a single subscription
|
||||
*/
|
||||
async sendPushNotification(subscription: WebPushSubscription, payload: WebPushNotificationPayload, userId?: string): Promise<boolean> {
|
||||
if (!this.config.enabled) {
|
||||
this.logger.debug("Push notifications are disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.isConfigured()) {
|
||||
this.logger.warn("Web Push is not properly configured - missing VAPID keys");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
TTL: this.config.ttl,
|
||||
urgency: this.config.urgency as webpush.Urgency,
|
||||
headers: {},
|
||||
};
|
||||
|
||||
const payloadString = JSON.stringify({
|
||||
...payload,
|
||||
timestamp: payload.timestamp || Date.now(),
|
||||
});
|
||||
|
||||
await webpush.sendNotification(subscription, payloadString, options);
|
||||
|
||||
this.logger.log(`Push notification sent successfully${userId ? ` to user: ${userId}` : ""}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to send push notification${userId ? ` to user ${userId}` : ""}:`, error);
|
||||
|
||||
// Handle specific web push errors
|
||||
if (error instanceof Error && "statusCode" in error) {
|
||||
const webPushError = error as any;
|
||||
if (webPushError.statusCode === 410 || webPushError.statusCode === 404) {
|
||||
this.logger.warn("Subscription is no longer valid - should be removed from database");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send push notifications to multiple subscriptions
|
||||
*/
|
||||
async sendBulkPushNotifications(bulkPayload: BulkWebPushPayload): Promise<{
|
||||
successful: number;
|
||||
failed: number;
|
||||
invalidSubscriptions: WebPushSubscription[];
|
||||
}> {
|
||||
const results = {
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
invalidSubscriptions: [] as WebPushSubscription[],
|
||||
};
|
||||
|
||||
const promises = bulkPayload.subscriptions.map(async (subscription) => {
|
||||
try {
|
||||
const success = await this.sendPushNotification(subscription, bulkPayload);
|
||||
if (success) {
|
||||
results.successful++;
|
||||
} else {
|
||||
results.failed++;
|
||||
}
|
||||
} catch (error) {
|
||||
results.failed++;
|
||||
if (error instanceof Error && "statusCode" in error) {
|
||||
const webPushError = error as any;
|
||||
if (webPushError.statusCode === 410 || webPushError.statusCode === 404) {
|
||||
results.invalidSubscriptions.push(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
this.logger.log(
|
||||
`Bulk push notifications completed. Successful: ${results.successful}, Failed: ${results.failed}, Invalid: ${results.invalidSubscriptions.length}`,
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send new email notification
|
||||
*/
|
||||
async sendNewEmailNotification(
|
||||
subscriptions: WebPushSubscription[],
|
||||
emailSubject: string,
|
||||
senderName: string,
|
||||
senderEmail: string,
|
||||
userId?: string,
|
||||
): Promise<boolean> {
|
||||
const truncatedSubject = emailSubject.length > 50 ? emailSubject.substring(0, 47) + "..." : emailSubject;
|
||||
|
||||
const payload: WebPushNotificationPayload = {
|
||||
title: "📧 New Email",
|
||||
body: `From: ${senderName || senderEmail}\nSubject: ${truncatedSubject}`,
|
||||
icon: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}/assets/email-icon.png`,
|
||||
badge: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}/assets/email-badge.png`,
|
||||
tag: "new-email",
|
||||
requireInteraction: true,
|
||||
data: {
|
||||
type: "new-email",
|
||||
userId,
|
||||
sender: senderEmail,
|
||||
subject: emailSubject,
|
||||
url: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}/inbox`,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: "view",
|
||||
title: "View Email",
|
||||
icon: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}/assets/view-icon.png`,
|
||||
},
|
||||
{
|
||||
action: "dismiss",
|
||||
title: "Dismiss",
|
||||
},
|
||||
],
|
||||
vibrate: [200, 100, 200],
|
||||
};
|
||||
|
||||
if (subscriptions.length === 1) {
|
||||
return this.sendPushNotification(subscriptions[0], payload, userId);
|
||||
} else {
|
||||
const results = await this.sendBulkPushNotifications({
|
||||
...payload,
|
||||
subscriptions,
|
||||
});
|
||||
return results.successful > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test notification
|
||||
*/
|
||||
async sendTestNotification(subscriptions: WebPushSubscription[], userId?: string): Promise<boolean> {
|
||||
const payload: WebPushNotificationPayload = {
|
||||
title: "🧪 Test Notification",
|
||||
body: "Your web push notifications are working correctly!",
|
||||
icon: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}/assets/test-icon.png`,
|
||||
tag: "test",
|
||||
data: {
|
||||
type: "test",
|
||||
userId,
|
||||
url: `${process.env.FRONTEND_URL || "https://mail.danakcorp.com"}`,
|
||||
},
|
||||
};
|
||||
|
||||
if (subscriptions.length === 1) {
|
||||
return this.sendPushNotification(subscriptions[0], payload, userId);
|
||||
} else {
|
||||
const results = await this.sendBulkPushNotifications({
|
||||
...payload,
|
||||
subscriptions,
|
||||
});
|
||||
return results.successful > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate VAPID keys (for initial setup)
|
||||
*/
|
||||
generateVAPIDKeys(): { publicKey: string; privateKey: string } {
|
||||
const vapidKeys = webpush.generateVAPIDKeys();
|
||||
return {
|
||||
publicKey: vapidKeys.publicKey,
|
||||
privateKey: vapidKeys.privateKey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VAPID public key for client-side subscription
|
||||
*/
|
||||
getVAPIDPublicKey(): string {
|
||||
return this.config.webPush.vapidPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if push notifications are enabled and configured
|
||||
*/
|
||||
isEnabled(): boolean {
|
||||
return this.config.enabled && this.isConfigured();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if VAPID keys are properly configured
|
||||
*/
|
||||
isConfigured(): boolean {
|
||||
return !!(this.config.webPush.vapidPublicKey && this.config.webPush.vapidPrivateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a web push subscription object
|
||||
*/
|
||||
validateSubscription(subscription: any): subscription is WebPushSubscription {
|
||||
return (
|
||||
subscription &&
|
||||
typeof subscription.endpoint === "string" &&
|
||||
subscription.keys &&
|
||||
typeof subscription.keys.p256dh === "string" &&
|
||||
typeof subscription.keys.auth === "string"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service configuration info
|
||||
*/
|
||||
getServiceInfo() {
|
||||
return {
|
||||
enabled: this.isEnabled(),
|
||||
configured: this.isConfigured(),
|
||||
vapidPublicKey: this.config.webPush.vapidPublicKey,
|
||||
ttl: this.config.ttl,
|
||||
urgency: this.config.urgency,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsObject, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
export class WebPushKeysDto {
|
||||
@ApiProperty({
|
||||
description: "p256dh key for web push subscription",
|
||||
example: "BNbxxx...",
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
p256dh: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "auth key for web push subscription",
|
||||
example: "abc123...",
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
auth: string;
|
||||
}
|
||||
|
||||
export class WebPushSubscriptionDto {
|
||||
@ApiProperty({
|
||||
description: "Push subscription endpoint URL",
|
||||
example: "https://fcm.googleapis.com/fcm/send/...",
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
endpoint: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Subscription keys",
|
||||
type: WebPushKeysDto,
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => WebPushKeysDto)
|
||||
@IsObject()
|
||||
keys: WebPushKeysDto;
|
||||
}
|
||||
|
||||
export class RegisterPushSubscriptionDto {
|
||||
@ApiProperty({
|
||||
description: "Web push subscription object",
|
||||
type: WebPushSubscriptionDto,
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => WebPushSubscriptionDto)
|
||||
@IsObject()
|
||||
subscription: WebPushSubscriptionDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Device type or platform",
|
||||
example: "desktop",
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deviceType?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Device name or identifier",
|
||||
example: "Chrome on MacBook Pro",
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deviceName?: string;
|
||||
}
|
||||
|
||||
export class UpdatePushSubscriptionsDto {
|
||||
@ApiProperty({
|
||||
description: "Array of web push subscriptions",
|
||||
type: [WebPushSubscriptionDto],
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMinSize(0)
|
||||
@ArrayMaxSize(10) // Limit to 10 subscriptions per user
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => WebPushSubscriptionDto)
|
||||
subscriptions: WebPushSubscriptionDto[];
|
||||
}
|
||||
|
||||
export class UpdatePushNotificationSettingsDto {
|
||||
@ApiProperty({
|
||||
description: "Enable or disable push notifications",
|
||||
example: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class TestPushNotificationDto {
|
||||
@ApiProperty({
|
||||
description: "Test message to send",
|
||||
example: "Test notification from Dmail",
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class RemovePushSubscriptionDto {
|
||||
@ApiProperty({
|
||||
description: "Endpoint URL of the subscription to remove",
|
||||
example: "https://fcm.googleapis.com/fcm/send/...",
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
endpoint: string;
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
import { EntityManager } from "@mikro-orm/postgresql";
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
BusinessMessage,
|
||||
CommonMessage,
|
||||
DomainMessage,
|
||||
MailServerMessage,
|
||||
RoleMessage,
|
||||
TemplateMessage,
|
||||
UserMessage,
|
||||
} from "../../../common/enums/message.enum";
|
||||
import { BusinessMessage, 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";
|
||||
@@ -18,18 +10,11 @@ import { DomainAutomationService } from "../../domains/services/domain-automatio
|
||||
import { DomainsService } from "../../domains/services/domains.service";
|
||||
import { UpdateUserDto } from "../../mail-server/DTO/update-user.dto";
|
||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||
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";
|
||||
@@ -51,7 +36,6 @@ export class UsersService {
|
||||
private readonly domainAutomationService: DomainAutomationService,
|
||||
private readonly quotaSyncService: QuotaSyncService,
|
||||
private readonly userSettingsService: UserSettingsService,
|
||||
private readonly pushNotificationService: PushNotificationService,
|
||||
) {}
|
||||
|
||||
async getUserEmailIdFromId(userId: string) {
|
||||
@@ -491,240 +475,4 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,6 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseIntercepto
|
||||
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
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";
|
||||
@@ -45,56 +39,6 @@ export class UsersController {
|
||||
return this.usersService.updateProfile(userId, updateMeDto);
|
||||
}
|
||||
|
||||
// Push Notification Endpoints
|
||||
@Post("push-subscriptions")
|
||||
@ApiOperation({ summary: "Register web push subscription" })
|
||||
@ApiResponse({ status: 201, description: "Push subscription registered successfully" })
|
||||
registerPushSubscription(@UserDec("id") userId: string, @Body() registerPushSubscriptionDto: RegisterPushSubscriptionDto) {
|
||||
return this.usersService.registerPushSubscription(userId, registerPushSubscriptionDto);
|
||||
}
|
||||
|
||||
@Patch("push-subscriptions")
|
||||
@ApiOperation({ summary: "Update web push subscriptions" })
|
||||
@ApiResponse({ status: 200, description: "Push subscriptions updated successfully" })
|
||||
updatePushSubscriptions(@UserDec("id") userId: string, @Body() updatePushSubscriptionsDto: UpdatePushSubscriptionsDto) {
|
||||
return this.usersService.updatePushSubscriptions(userId, updatePushSubscriptionsDto);
|
||||
}
|
||||
|
||||
@Delete("push-subscriptions")
|
||||
@ApiOperation({ summary: "Remove web push subscription by endpoint" })
|
||||
@ApiResponse({ status: 200, description: "Push subscription removed successfully" })
|
||||
removePushSubscription(@UserDec("id") userId: string, @Body() body: { endpoint: string }) {
|
||||
return this.usersService.removePushSubscription(userId, body.endpoint);
|
||||
}
|
||||
|
||||
@Patch("push-notifications/settings")
|
||||
@ApiOperation({ summary: "Update push notification settings" })
|
||||
@ApiResponse({ status: 200, description: "Push notification settings updated successfully" })
|
||||
updatePushNotificationSettings(@UserDec("id") userId: string, @Body() settingsDto: UpdatePushNotificationSettingsDto) {
|
||||
return this.usersService.updatePushNotificationSettings(userId, settingsDto);
|
||||
}
|
||||
|
||||
@Post("push-notifications/test")
|
||||
@ApiOperation({ summary: "Test web push notification" })
|
||||
@ApiResponse({ status: 200, description: "Test notification sent successfully" })
|
||||
testPushNotification(@UserDec("id") userId: string, @Body() testDto: TestPushNotificationDto) {
|
||||
return this.usersService.testPushNotification(userId, testDto);
|
||||
}
|
||||
|
||||
@Get("push-notifications/info")
|
||||
@ApiOperation({ summary: "Get web push notification info and settings" })
|
||||
@ApiResponse({ status: 200, description: "Push notification info retrieved successfully" })
|
||||
getPushNotificationInfo(@UserDec("id") userId: string) {
|
||||
return this.usersService.getPushNotificationInfo(userId);
|
||||
}
|
||||
|
||||
@Get("push-notifications/vapid-key")
|
||||
@ApiOperation({ summary: "Get VAPID public key for client-side subscription" })
|
||||
@ApiResponse({ status: 200, description: "VAPID public key retrieved successfully" })
|
||||
getVAPIDPublicKey(@UserDec("id") _userId: string) {
|
||||
return this.usersService.getVAPIDPublicKey();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Create a new email user" })
|
||||
|
||||
Reference in New Issue
Block a user