chore: add ai service
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
"dotenv": "^16.6.1",
|
||||
"fastify": "^5.4.0",
|
||||
"nodemailer": "^7.0.4",
|
||||
"openai": "^5.10.2",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2",
|
||||
|
||||
Generated
+17
@@ -110,6 +110,9 @@ importers:
|
||||
nodemailer:
|
||||
specifier: ^7.0.4
|
||||
version: 7.0.4
|
||||
openai:
|
||||
specifier: ^5.10.2
|
||||
version: 5.10.2
|
||||
passport-jwt:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
@@ -4594,6 +4597,18 @@ packages:
|
||||
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
openai@5.10.2:
|
||||
resolution: {integrity: sha512-n+vi74LzHtvlKcDPn9aApgELGiu5CwhaLG40zxLTlFQdoSJCLACORIPC2uVQ3JEYAbqapM+XyRKFy2Thej7bIw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
zod: ^3.23.8
|
||||
peerDependenciesMeta:
|
||||
ws:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -11232,6 +11247,8 @@ snapshots:
|
||||
is-wsl: 2.2.0
|
||||
optional: true
|
||||
|
||||
openai@5.10.2: {}
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
|
||||
@@ -14,6 +14,7 @@ import { jwtConfig } from "./configs/jwt.config";
|
||||
import { databaseConfig } from "./configs/mikro-orm.config";
|
||||
import { rateLimitConfig } from "./configs/rateLimit.config";
|
||||
import { HTTPLogger } from "./core/middlewares/logger.middleware";
|
||||
import { AiAssistantModule } from "./modules/ai-assistant/ai-assistant.module";
|
||||
import { AuthModule } from "./modules/auth/auth.module";
|
||||
import { DomainsModule } from "./modules/domains/domains.module";
|
||||
import { EmailModule } from "./modules/email/email.module";
|
||||
@@ -37,6 +38,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
||||
ThrottlerModule.forRootAsync(rateLimitConfig()),
|
||||
MikroOrmModule.forRootAsync(databaseConfig),
|
||||
UtilsModule,
|
||||
AiAssistantModule,
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
DomainsModule,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// export const AUTH_THROTTLE = "AUTH_THROTTLE";
|
||||
export const AI_CONFIG = "AI_CONFIG";
|
||||
export const AUTH_THROTTLE_TTL = 1 * 60 * 1000;
|
||||
export const AUTH_THROTTLE_LIMIT = 5;
|
||||
export const AUTH__REFRESH_THROTTLE_TTL = 10 * 60 * 1000;
|
||||
|
||||
@@ -558,3 +558,9 @@ export const enum SettingMessageEnum {
|
||||
EMAIL_MUST_BE_BOOLEAN = "تنظیمات ارسال ایمیل باید یک boolean باشد",
|
||||
NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی آن را ندارید",
|
||||
}
|
||||
|
||||
export const enum AiMessage {
|
||||
NO_RESPONSE_FROM_AI_MODEL = "خطا در دریافت پاسخ از مدل AI",
|
||||
MESSAGE_NOT_FOUND_OR_ACCESS_DENIED = "ایمیل یافت نشد یا دسترسی آن را ندارید",
|
||||
EMAIL_CONTENT_IS_EMPTY_OR_COULD_NOT_BE_EXTRACTED = "محتوای ایمیل خالی است یا قابل استخراج نیست",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import { FactoryProvider } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { AI_CONFIG } from "../common/constants";
|
||||
|
||||
export interface AIConfig {
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
defaultModel: string;
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
timeout: number;
|
||||
farsiPrompts: FarsiPrompts;
|
||||
}
|
||||
|
||||
export interface FarsiPrompts {
|
||||
summarization: {
|
||||
systemPrompt: string;
|
||||
promptTemplate: {
|
||||
intro: string;
|
||||
subjectLabel: string;
|
||||
fromLabel: string;
|
||||
toLabel: string;
|
||||
dateLabel: string;
|
||||
contentLabel: string;
|
||||
attachmentsLabel: string;
|
||||
analysisRequirements: string;
|
||||
summaryLengthLabel: string;
|
||||
toneLabel: string;
|
||||
};
|
||||
};
|
||||
writingAssistance: {
|
||||
systemPrompt: string;
|
||||
promptTemplate: {
|
||||
intro: string;
|
||||
mainMessageLabel: string;
|
||||
emailDetailsLabel: string;
|
||||
purposeLabel: string;
|
||||
toneLabel: string;
|
||||
lengthLabel: string;
|
||||
keyPointsLabel: string;
|
||||
contextLabel: string;
|
||||
originalSubjectLabel: string;
|
||||
originalSenderLabel: string;
|
||||
originalContentLabel: string;
|
||||
additionalInstructionsLabel: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const FARSI_PROMPTS: FarsiPrompts = {
|
||||
summarization: {
|
||||
systemPrompt: `شما یک تحلیلگر و خلاصهکننده ماهر ایمیل هستید. وظیفه شما تحلیل و خلاصهسازی ایمیلها به صورت حرفهای و دقیق است.
|
||||
|
||||
الزامات:
|
||||
- محتوای ایمیل را تحلیل کرده و خلاصهای جامع ارائه دهید
|
||||
- نکات کلیدی که در ایمیل ذکر شدهاند را استخراج کنید
|
||||
- بر اساس محتوای ایمیل، اقدامات قابل انجام پیشنهاد دهید
|
||||
- سطح اولویت (کم، متوسط، زیاد) را بر اساس شاخصهای اضطراری تعیین کنید
|
||||
- احساسات (مثبت، خنثی، منفی) ایمیل را تحلیل کنید
|
||||
- از لحن {tone} در تحلیل خود استفاده کنید
|
||||
- سطح جزئیات {length} ارائه دهید
|
||||
- به زبان {language} پاسخ دهید
|
||||
|
||||
فرمت پاسخ: شیء JSON با ساختار زیر:
|
||||
{
|
||||
"summary": "خلاصه مختصر ایمیل",
|
||||
"keyPoints": ["نکته کلیدی ۱", "نکته کلیدی ۲", ...],
|
||||
"suggestedActions": ["اقدام ۱", "اقدام ۲", ...],
|
||||
"priority": "low|medium|high",
|
||||
"sentiment": "positive|neutral|negative"
|
||||
}`,
|
||||
promptTemplate: {
|
||||
intro: "لطفاً این ایمیل را تحلیل و خلاصه کنید:",
|
||||
subjectLabel: "موضوع:",
|
||||
fromLabel: "از:",
|
||||
toLabel: "به:",
|
||||
dateLabel: "تاریخ:",
|
||||
contentLabel: "محتوا:",
|
||||
attachmentsLabel: "پیوستها:",
|
||||
analysisRequirements: "الزامات تحلیل:",
|
||||
summaryLengthLabel: "- طول خلاصه:",
|
||||
toneLabel: "- لحن:",
|
||||
},
|
||||
},
|
||||
writingAssistance: {
|
||||
systemPrompt: `شما یک دستیار ماهر نگارش ایمیل هستید. وظیفه شما کمک به کاربران برای نوشتن ایمیلهای حرفهای و مؤثر است.
|
||||
|
||||
الزامات:
|
||||
- بر اساس قصد و زمینه کاربر، محتوای ایمیل تولید کنید
|
||||
- در تمام ایمیل از لحن {tone} استفاده کنید
|
||||
- ایمیل با طول {length} بسازید
|
||||
- هدف: {purpose}
|
||||
- نکات و بهبودهای مفید ارائه دهید
|
||||
- در صورت امکان، نسخههای جایگزین تولید کنید
|
||||
- به زبان {language} پاسخ دهید
|
||||
|
||||
فرمت پاسخ: شیء JSON با ساختار زیر:
|
||||
{
|
||||
"subject": "موضوع پیشنهادی ایمیل",
|
||||
"content": "محتوای کامل ایمیل",
|
||||
"alternatives": ["نسخه جایگزین ۱", "نسخه جایگزین ۲", ...],
|
||||
"tips": ["نکته نگارش ۱", "نکته نگارش ۲", ...],
|
||||
"improvements": ["پیشنهاد بهبود ۱", "پیشنهاد بهبود ۲", ...]
|
||||
}`,
|
||||
promptTemplate: {
|
||||
intro: "لطفاً در نوشتن ایمیل با الزامات زیر به من کمک کنید:",
|
||||
mainMessageLabel: "پیام/قصد اصلی:",
|
||||
emailDetailsLabel: "جزئیات ایمیل:",
|
||||
purposeLabel: "- هدف:",
|
||||
toneLabel: "- لحن:",
|
||||
lengthLabel: "- طول:",
|
||||
keyPointsLabel: "نکات کلیدی برای درج:",
|
||||
contextLabel: "زمینه از ایمیل اصلی:",
|
||||
originalSubjectLabel: "- موضوع اصلی:",
|
||||
originalSenderLabel: "- فرستنده اصلی:",
|
||||
originalContentLabel: "- محتوای اصلی:",
|
||||
additionalInstructionsLabel: "دستورالعملهای اضافی:",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const aiConfigProvider: FactoryProvider<AIConfig> = {
|
||||
provide: AI_CONFIG,
|
||||
useFactory: (configService: ConfigService): AIConfig => ({
|
||||
baseUrl: configService.get<string>("AI_BASE_URL", "https://api.openai.com/v1"),
|
||||
apiKey: configService.get<string>("AI_API_KEY", ""),
|
||||
defaultModel: configService.get<string>("AI_DEFAULT_MODEL", "gpt-3.5-turbo"),
|
||||
temperature: configService.get<number>("AI_TEMPERATURE", 0.7),
|
||||
maxTokens: configService.get<number>("AI_MAX_TOKENS", 2000),
|
||||
timeout: configService.get<number>("AI_TIMEOUT", 30000),
|
||||
farsiPrompts: FARSI_PROMPTS,
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
};
|
||||
|
||||
// Available models for different providers
|
||||
export const AI_MODELS = {
|
||||
OPENAI: {
|
||||
GPT_3_5_TURBO: "gpt-3.5-turbo",
|
||||
GPT_4: "gpt-4",
|
||||
GPT_4_TURBO: "gpt-4-turbo-preview",
|
||||
},
|
||||
GEMINI: {
|
||||
GEMINI_2_0_FLASH: "google/gemini-2.0-flash-001",
|
||||
GEMINI_2_5_PRO: "google/gemini-2.5-pro-preview",
|
||||
GEMINI_2_5_FLASH: "google/gemini-2.5-flash-preview",
|
||||
GEMINI_2_0_FLASH_LITE: "google/gemini-2.0-flash-lite-001",
|
||||
GEMINI_FLASH_1_5: "google/gemini-flash-1.5",
|
||||
GEMINI_FLASH_1_5_8B: "google/gemini-flash-1.5-8b",
|
||||
},
|
||||
} as const;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
export function smsConfigs() {
|
||||
return {
|
||||
inject: [ConfigService],
|
||||
useFactory(configService: ConfigService) {
|
||||
return {
|
||||
API_URL: configService.getOrThrow<string>("SMS_API_URL"),
|
||||
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
||||
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP"),
|
||||
SMS_PATTERN_INVOICE: configService.getOrThrow<string>("SMS_PATTERN_INVOICE"),
|
||||
SMS_PATTERN_LOGIN: configService.getOrThrow<string>("SMS_PATTERN_LOGIN"),
|
||||
SMS_PATTERN_INVOICE_CREATED: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_CREATED"),
|
||||
SMS_PATTERN_ANNOUNCEMENT: configService.getOrThrow<string>("SMS_PATTERN_ANNOUNCEMENT"),
|
||||
SMS_PATTERN_TICKET_CREATED: configService.getOrThrow<string>("SMS_PATTERN_TICKET_CREATED"),
|
||||
SMS_PATTERN_TICKET_ASSIGNED_ADMIN: configService.getOrThrow<string>("SMS_PATTERN_TICKET_ASSIGNED_ADMIN"),
|
||||
SMS_PATTERN_INVOICE_APPROVED: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_APPROVED"),
|
||||
SMS_PATTERN_INVOICE_PAID: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_PAID"),
|
||||
SMS_PATTERN_RECURRING_INVOICE_DRAFT: configService.getOrThrow<string>("SMS_PATTERN_RECURRING_INVOICE_DRAFT"),
|
||||
SMS_PATTERN_SUBSCRIPTION_CANCELLED: configService.getOrThrow<string>("SMS_PATTERN_SUBSCRIPTION_CANCELLED"),
|
||||
SMS_PATTERN_INVOICE_REMINDER: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_REMINDER"),
|
||||
SMS_PATTERN_INVOICE_OVERDUE: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_OVERDUE"),
|
||||
SMS_PATTERN_PAYMENT_REMINDER: configService.getOrThrow<string>("SMS_PATTERN_PAYMENT_REMINDER"),
|
||||
SMS_PATTERN_PAYMENT_CANCELLATION: configService.getOrThrow<string>("SMS_PATTERN_PAYMENT_CANCELLATION"),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface ISmsConfigs {
|
||||
API_URL: string;
|
||||
API_KEY: string;
|
||||
SMS_PATTERN_OTP: string;
|
||||
SMS_PATTERN_INVOICE: string;
|
||||
SMS_PATTERN_LOGIN: string;
|
||||
SMS_PATTERN_INVOICE_CREATED: string;
|
||||
SMS_PATTERN_ANNOUNCEMENT: string;
|
||||
SMS_PATTERN_TICKET_CREATED: string;
|
||||
SMS_PATTERN_TICKET_ANSWERED: string;
|
||||
SMS_PATTERN_TICKET_ASSIGNED_ADMIN: string;
|
||||
SMS_PATTERN_INVOICE_APPROVED: string;
|
||||
SMS_PATTERN_INVOICE_PAID: string;
|
||||
SMS_PATTERN_RECURRING_INVOICE_DRAFT: string;
|
||||
SMS_PATTERN_SUBSCRIPTION_CANCELLED: string;
|
||||
SMS_PATTERN_INVOICE_REMINDER: string;
|
||||
SMS_PATTERN_INVOICE_OVERDUE: string;
|
||||
SMS_PATTERN_PAYMENT_REMINDER: string;
|
||||
SMS_PATTERN_PAYMENT_CANCELLATION: string;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
||||
|
||||
export class SummarizeEmailDto {
|
||||
@IsNotEmpty({ message: "Message ID is required" })
|
||||
@IsNumber({}, { message: "Message ID must be a number" })
|
||||
@ApiProperty({ description: "The message ID to fetch and summarize from the mail server", example: 12345 })
|
||||
messageId: number;
|
||||
|
||||
@IsNotEmpty({ message: "Mailbox ID is required" })
|
||||
@IsString({ message: "Mailbox ID must be a string" })
|
||||
@ApiProperty({ description: "The mailbox ID where the message is located", example: "507f1f77bcf86cd799439012" })
|
||||
mailbox: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SummaryLength, { message: "Summary length must be one of: short, medium, detailed" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary length", enum: SummaryLength, default: SummaryLength.MEDIUM })
|
||||
length?: SummaryLength;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SummaryTone, { message: "Summary tone must be one of: formal, casual, professional" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary tone", enum: SummaryTone, default: SummaryTone.PROFESSIONAL })
|
||||
tone?: SummaryTone;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
import { EmailLength, EmailPurpose, WritingTone } from "../enums/ai.enum";
|
||||
|
||||
export class EmailContextDto {
|
||||
@IsOptional()
|
||||
@IsString({ message: "Original email content must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Original email content (for replies/forwards)",
|
||||
example: "Hi, I need to discuss the project timeline...",
|
||||
})
|
||||
originalContent?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: "Original sender must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Original sender (for replies/forwards)",
|
||||
example: "john.doe@company.com",
|
||||
})
|
||||
originalSender?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: "Original subject must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Original subject (for replies/forwards)",
|
||||
example: "Project Timeline Discussion",
|
||||
})
|
||||
originalSubject?: string;
|
||||
}
|
||||
|
||||
export class WriteEmailAssistanceDto {
|
||||
@IsNotEmpty({ message: "Email purpose is required" })
|
||||
@IsEnum(EmailPurpose, { message: "Email purpose must be one of: reply, forward, new, follow_up" })
|
||||
@ApiProperty({
|
||||
description: "Purpose of the email",
|
||||
enum: EmailPurpose,
|
||||
example: EmailPurpose.REPLY,
|
||||
})
|
||||
purpose: EmailPurpose;
|
||||
|
||||
@IsNotEmpty({ message: "Main message or intent is required" })
|
||||
@IsString({ message: "Main message must be a string" })
|
||||
@ApiProperty({
|
||||
description: "Main message or intent of the email",
|
||||
example: "I want to schedule a meeting to discuss the project timeline and address any concerns.",
|
||||
})
|
||||
mainMessage: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: "Recipient information must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Recipient information (optional)",
|
||||
example: "john.doe@company.com",
|
||||
})
|
||||
recipient?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(WritingTone, { message: "Writing tone must be one of: formal, casual, professional, friendly, urgent" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Desired tone of the email",
|
||||
enum: WritingTone,
|
||||
default: WritingTone.PROFESSIONAL,
|
||||
})
|
||||
tone?: WritingTone;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(EmailLength, { message: "Email length must be one of: short, medium, long" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Desired length of the email",
|
||||
enum: EmailLength,
|
||||
default: EmailLength.MEDIUM,
|
||||
})
|
||||
length?: EmailLength;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray({ message: "Key points must be an array" })
|
||||
@IsString({ each: true, message: "Each key point must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Key points to include in the email",
|
||||
type: [String],
|
||||
example: ["Discuss project timeline", "Address resource allocation", "Set next meeting date"],
|
||||
})
|
||||
keyPoints?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => EmailContextDto)
|
||||
@ApiPropertyOptional({
|
||||
description: "Context from original email (for replies/forwards)",
|
||||
type: EmailContextDto,
|
||||
})
|
||||
context?: EmailContextDto;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: "Additional instructions must be a string" })
|
||||
@ApiPropertyOptional({
|
||||
description: "Additional instructions or preferences",
|
||||
example: "Please keep it concise and mention the deadline",
|
||||
})
|
||||
additionalInstructions?: 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[];
|
||||
}
|
||||
|
||||
export class EmailWritingResponseDto {
|
||||
@ApiProperty({
|
||||
description: "Primary email suggestion",
|
||||
type: EmailSuggestionDto,
|
||||
})
|
||||
suggestion: EmailSuggestionDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Writing tips and recommendations",
|
||||
type: [String],
|
||||
example: ["Consider adding a specific deadline", "You might want to include meeting agenda", "Don't forget to CC relevant stakeholders"],
|
||||
})
|
||||
tips: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: "Suggested improvements for the original request",
|
||||
type: [String],
|
||||
example: ["Consider being more specific about the timeline", "Add context about project urgency"],
|
||||
})
|
||||
improvements: string[];
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
# AI Assistant Module
|
||||
|
||||
The AI assistant module provides email summarization and writing assistance functionality using OpenAI/Google Gemini models through the [Liara AI platform](https://docs.liara.ir/ai/google-gemini/).
|
||||
|
||||
## Features
|
||||
|
||||
- **Email Summarization**: Analyze emails and extract key points, suggested actions, priority levels, and sentiment analysis
|
||||
- **Email Writing Assistance**: Generate professional emails with AI assistance based on context and purpose
|
||||
- **Multi-Model Support**: Compatible with OpenAI GPT models and Google Gemini models
|
||||
- **Flexible Configuration**: Configurable tone, length, and purpose for different use cases
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
pnpm add openai
|
||||
```
|
||||
|
||||
### 2. Environment Variables
|
||||
|
||||
Add the following environment variables to your `.env` file:
|
||||
|
||||
```env
|
||||
# AI Service Configuration
|
||||
AI_BASE_URL=https://api.openai.com/v1
|
||||
AI_API_KEY=your_api_key_here
|
||||
AI_DEFAULT_MODEL=gpt-3.5-turbo
|
||||
AI_TEMPERATURE=0.7
|
||||
AI_MAX_TOKENS=2000
|
||||
AI_TIMEOUT=30000
|
||||
```
|
||||
|
||||
### For Liara AI (Google Gemini):
|
||||
|
||||
```env
|
||||
# Liara AI Configuration
|
||||
AI_BASE_URL=https://api.liara.ir/v1
|
||||
AI_API_KEY=your_liara_api_key
|
||||
AI_DEFAULT_MODEL=google/gemini-2.0-flash-001
|
||||
AI_TEMPERATURE=0.7
|
||||
AI_MAX_TOKENS=2000
|
||||
AI_TIMEOUT=30000
|
||||
```
|
||||
|
||||
### Available Models
|
||||
|
||||
#### OpenAI Models:
|
||||
|
||||
- `gpt-3.5-turbo`
|
||||
- `gpt-4`
|
||||
- `gpt-4-turbo-preview`
|
||||
|
||||
#### Google Gemini Models (via Liara):
|
||||
|
||||
- `google/gemini-2.0-flash-001`
|
||||
- `google/gemini-2.5-pro-preview`
|
||||
- `google/gemini-2.5-flash-preview`
|
||||
- `google/gemini-2.0-flash-lite-001`
|
||||
- `google/gemini-flash-1.5`
|
||||
- `google/gemini-flash-1.5-8b`
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Email Summarization
|
||||
|
||||
```http
|
||||
POST /ai-assistant/summarize-email
|
||||
Content-Type: application/json
|
||||
x-business-id: your-business-id
|
||||
Authorization: Bearer your-token
|
||||
|
||||
{
|
||||
"messageId": 12345,
|
||||
"mailbox": "507f1f77bcf86cd799439012",
|
||||
"length": "medium",
|
||||
"tone": "professional"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"summary": "John is requesting a meeting to discuss the upcoming project timeline and deliverables.",
|
||||
"keyPoints": ["Project timeline discussion", "Meeting request", "Deliverables review"],
|
||||
"suggestedActions": ["Schedule meeting with John", "Prepare project timeline document", "Review current deliverables"],
|
||||
"priority": "medium",
|
||||
"sentiment": "neutral"
|
||||
}
|
||||
```
|
||||
|
||||
### Email Writing Assistance
|
||||
|
||||
```http
|
||||
POST /ai-assistant/write-email
|
||||
Content-Type: application/json
|
||||
x-business-id: your-business-id
|
||||
Authorization: Bearer your-token
|
||||
|
||||
{
|
||||
"purpose": "reply",
|
||||
"mainMessage": "I want to schedule a meeting to discuss the project timeline",
|
||||
"tone": "professional",
|
||||
"length": "medium",
|
||||
"keyPoints": [
|
||||
"Discuss timeline",
|
||||
"Address concerns"
|
||||
],
|
||||
"context": {
|
||||
"originalSubject": "Project Update",
|
||||
"originalSender": "john@example.com",
|
||||
"originalContent": "Hi, I need to discuss the project timeline..."
|
||||
},
|
||||
"additionalInstructions": "Please keep it concise and mention the deadline"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"suggestion": {
|
||||
"subject": "Re: Project Update - Meeting Request",
|
||||
"content": "Hello,\n\nThank you for your email regarding the project timeline...",
|
||||
"alternatives": ["More concise version available", "More detailed version available", "Alternative tone available"]
|
||||
},
|
||||
"tips": ["Consider adding a specific deadline", "You might want to include meeting agenda"],
|
||||
"improvements": ["Consider being more specific about the timeline", "Add context about project urgency"]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Summarization Options
|
||||
|
||||
- **Length**: `short`, `medium`, `detailed`
|
||||
- **Tone**: `formal`, `casual`, `professional`
|
||||
|
||||
### Writing Assistance Options
|
||||
|
||||
- **Purpose**: `reply`, `forward`, `new`, `follow_up`
|
||||
- **Tone**: `formal`, `casual`, `professional`, `friendly`, `urgent`
|
||||
- **Length**: `short`, `medium`, `long`
|
||||
|
||||
## Architecture
|
||||
|
||||
The AI assistant module consists of:
|
||||
|
||||
1. **AiService**: Core service that communicates with AI models
|
||||
2. **AiAssistantService**: Business logic layer that handles email processing
|
||||
3. **AiAssistantController**: API endpoints for client interactions
|
||||
4. **DTOs**: Data transfer objects for request/response validation
|
||||
5. **Interfaces**: Type definitions for AI service interactions
|
||||
|
||||
## Integration with Liara AI
|
||||
|
||||
This module is designed to work seamlessly with [Liara's AI platform](https://docs.liara.ir/ai/google-gemini/), which provides:
|
||||
|
||||
- OpenAI-compatible API
|
||||
- Multiple AI model support
|
||||
- Persian language support
|
||||
- Scalable infrastructure
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module includes comprehensive error handling:
|
||||
|
||||
- **Message Not Found**: Returns 404 when email message doesn't exist
|
||||
- **Empty Content**: Returns 400 when email content is empty
|
||||
- **AI Service Errors**: Logs and re-throws AI service errors
|
||||
- **Authentication**: Validates user permissions and business context
|
||||
|
||||
## Security
|
||||
|
||||
- All endpoints require authentication
|
||||
- Business context validation
|
||||
- Rate limiting (inherited from global configuration)
|
||||
- Input validation and sanitization
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
// In your service
|
||||
import { AiAssistantService } from "./modules/ai-assistant/services/ai-assistant.service";
|
||||
|
||||
@Injectable()
|
||||
export class EmailProcessorService {
|
||||
constructor(private readonly aiAssistant: AiAssistantService) {}
|
||||
|
||||
async processIncomingEmail(messageId: number, mailboxId: string, userId: string) {
|
||||
const summary = await this.aiAssistant.summarizeEmail(
|
||||
{
|
||||
messageId,
|
||||
mailbox: mailboxId,
|
||||
length: "medium",
|
||||
tone: "professional",
|
||||
},
|
||||
userId,
|
||||
);
|
||||
|
||||
// Process the summary...
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new AI features:
|
||||
|
||||
1. Update the appropriate interface in `interfaces/ai-service.interface.ts`
|
||||
2. Implement the logic in `AiService`
|
||||
3. Add business logic in `AiAssistantService`
|
||||
4. Create API endpoints in `AiAssistantController`
|
||||
5. Add proper validation DTOs
|
||||
6. Update this documentation
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Body, Controller, Post } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
import { SummarizeEmailDto } from "./DTO/summarize-email.dto";
|
||||
import { WriteEmailAssistanceDto } from "./DTO/write-email-assistance.dto";
|
||||
import { AiAssistantService } from "./services/ai-assistant.service";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { UserDec } from "../../common/decorators/user.decorator";
|
||||
|
||||
@Controller("ai-assistant")
|
||||
// @ApiHeader({ name: "x-business-id", description: "Business ID" })
|
||||
// @UseInterceptors(BusinessInterceptor)
|
||||
@AuthGuards()
|
||||
export class AiAssistantController {
|
||||
constructor(private readonly aiAssistantService: AiAssistantService) {}
|
||||
|
||||
@Post("summarize-email")
|
||||
@ApiOperation({ summary: "Summarize an email using AI" })
|
||||
@ApiResponse({ status: 201, description: "Email successfully summarized" })
|
||||
@ApiResponse({ status: 400, description: "Bad request - invalid message ID or mailbox" })
|
||||
@ApiResponse({ status: 404, description: "Message not found or access denied" })
|
||||
@ApiResponse({ status: 401, description: "Unauthorized - invalid authentication" })
|
||||
async summarizeEmail(@Body() summarizeDto: SummarizeEmailDto, @UserDec("wildduckUserId") wildduckUserId: string) {
|
||||
return this.aiAssistantService.summarizeEmail(summarizeDto, wildduckUserId);
|
||||
}
|
||||
|
||||
@Post("write-email")
|
||||
@ApiOperation({ summary: "Get AI assistance for writing emails" })
|
||||
@ApiResponse({ status: 201, description: "Email writing assistance generated successfully" })
|
||||
@ApiResponse({ status: 400, description: "Bad request - invalid input data" })
|
||||
@ApiResponse({ status: 401, description: "Unauthorized - invalid authentication" })
|
||||
async assistEmailWriting(@Body() writeDto: WriteEmailAssistanceDto, @UserDec("wildduckUserId") wildduckUserId: string) {
|
||||
return this.aiAssistantService.assistEmailWriting(writeDto, wildduckUserId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { AiAssistantController } from "./ai-assistant.controller";
|
||||
import { AiAssistantService } from "./services/ai-assistant.service";
|
||||
import { AiService } from "./services/ai.service";
|
||||
import { aiConfigProvider } from "../../configs/ai.config";
|
||||
import { MailServerModule } from "../mail-server/mail-server.module";
|
||||
|
||||
@Module({
|
||||
imports: [MailServerModule],
|
||||
controllers: [AiAssistantController],
|
||||
providers: [AiAssistantService, AiService, aiConfigProvider],
|
||||
exports: [AiAssistantService, AiService],
|
||||
})
|
||||
export class AiAssistantModule {}
|
||||
@@ -0,0 +1,32 @@
|
||||
export enum SummaryLength {
|
||||
SHORT = "short",
|
||||
MEDIUM = "medium",
|
||||
DETAILED = "detailed",
|
||||
}
|
||||
|
||||
export enum SummaryTone {
|
||||
FORMAL = "formal",
|
||||
CASUAL = "casual",
|
||||
PROFESSIONAL = "professional",
|
||||
}
|
||||
|
||||
export enum EmailPurpose {
|
||||
REPLY = "reply",
|
||||
FORWARD = "forward",
|
||||
NEW = "new",
|
||||
FOLLOW_UP = "follow_up",
|
||||
}
|
||||
|
||||
export enum WritingTone {
|
||||
FORMAL = "formal",
|
||||
CASUAL = "casual",
|
||||
PROFESSIONAL = "professional",
|
||||
FRIENDLY = "friendly",
|
||||
URGENT = "urgent",
|
||||
}
|
||||
|
||||
export enum EmailLength {
|
||||
SHORT = "short",
|
||||
MEDIUM = "medium",
|
||||
LONG = "long",
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export interface EmailData {
|
||||
subject: string;
|
||||
content: string;
|
||||
htmlContent?: string;
|
||||
from: string;
|
||||
to: string[];
|
||||
date: Date;
|
||||
attachments?: unknown[];
|
||||
}
|
||||
|
||||
export interface SummarizationOptions {
|
||||
length: "short" | "medium" | "detailed";
|
||||
tone: "formal" | "casual" | "professional";
|
||||
language?: "Persian" | "English";
|
||||
}
|
||||
|
||||
export interface WritingAssistanceOptions {
|
||||
purpose: "reply" | "forward" | "new" | "follow_up";
|
||||
tone: "formal" | "casual" | "professional" | "friendly" | "urgent";
|
||||
length: "short" | "medium" | "long";
|
||||
keyPoints?: string[];
|
||||
context?: {
|
||||
originalContent?: string;
|
||||
originalSender?: string;
|
||||
originalSubject?: string;
|
||||
};
|
||||
additionalInstructions?: string;
|
||||
language?: "Persian" | "English";
|
||||
}
|
||||
|
||||
export interface AISummaryResponse {
|
||||
summary: string;
|
||||
keyPoints: string[];
|
||||
suggestedActions: string[];
|
||||
priority: "low" | "medium" | "high";
|
||||
sentiment: "positive" | "neutral" | "negative";
|
||||
}
|
||||
|
||||
export interface AIWritingResponse {
|
||||
subject: string;
|
||||
content: string;
|
||||
alternatives: string[];
|
||||
tips: string[];
|
||||
improvements: string[];
|
||||
}
|
||||
|
||||
export interface AIServiceInterface {
|
||||
summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse>;
|
||||
assistEmailWriting(mainMessage: string, options: WritingAssistanceOptions): Promise<AIWritingResponse>;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AiService } from "./ai.service";
|
||||
import { AiMessage } from "../../../common/enums/message.enum";
|
||||
import { MessageDetails } from "../../mail-server/interfaces/messages-response.interface";
|
||||
import { WildDuckMessagesService } from "../../mail-server/services/wildduck-messages.service";
|
||||
import { SummarizeEmailDto } from "../DTO/summarize-email.dto";
|
||||
import { WriteEmailAssistanceDto } from "../DTO/write-email-assistance.dto";
|
||||
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
||||
import { EmailData, SummarizationOptions, WritingAssistanceOptions } from "../interfaces/ai-service.interface";
|
||||
|
||||
@Injectable()
|
||||
export class AiAssistantService {
|
||||
constructor(
|
||||
private readonly messagesService: WildDuckMessagesService,
|
||||
private readonly aiService: AiService,
|
||||
) {}
|
||||
|
||||
//************************************************* */
|
||||
async summarizeEmail(summarizeDto: SummarizeEmailDto, userId: string) {
|
||||
const message = await firstValueFrom(this.messagesService.getMessage(userId, summarizeDto.mailbox, summarizeDto.messageId));
|
||||
|
||||
if (!message) throw new NotFoundException(AiMessage.MESSAGE_NOT_FOUND_OR_ACCESS_DENIED);
|
||||
|
||||
const emailData = this.extractEmailData(message);
|
||||
|
||||
const summary = await this.generateEmailSummary(emailData, summarizeDto.length, summarizeDto.tone);
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
async assistEmailWriting(writeAssistanceDto: WriteEmailAssistanceDto, userId: string) {
|
||||
const assistance = await this.generateEmailWritingAssistance(writeAssistanceDto, userId);
|
||||
|
||||
return assistance;
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private extractEmailData(message: MessageDetails) {
|
||||
return {
|
||||
subject: message.subject,
|
||||
content: this.extractTextContent(message),
|
||||
htmlContent: this.extractHtmlContent(message),
|
||||
from: this.extractSenderInfo(message),
|
||||
to: this.extractRecipientInfo(message),
|
||||
date: message.date || new Date(),
|
||||
attachments: message.attachments || [],
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private extractTextContent(message: MessageDetails): string {
|
||||
if (message.text) {
|
||||
return typeof message.text === "string" ? message.text : message.text || "";
|
||||
}
|
||||
|
||||
if (message.html) {
|
||||
return this.stripHtmlTags(typeof message.html === "string" ? message.html : message.html[0] || "");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private extractHtmlContent(message: MessageDetails): string {
|
||||
if (message.html) {
|
||||
return typeof message.html === "string" ? message.html : message.html[0] || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private extractSenderInfo(message: MessageDetails): string {
|
||||
if (message.from && message.from.address) {
|
||||
return message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private extractRecipientInfo(message: MessageDetails): string[] {
|
||||
const recipients: string[] = [];
|
||||
|
||||
if (message.to && Array.isArray(message.to)) {
|
||||
recipients.push(...message.to.map((recipient) => (recipient.name ? `${recipient.name} <${recipient.address}>` : recipient.address)));
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private stripHtmlTags(html: string): string {
|
||||
return html
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private async generateEmailSummary(emailData: any, length: SummaryLength = SummaryLength.MEDIUM, _tone: SummaryTone = SummaryTone.PROFESSIONAL) {
|
||||
const content = emailData.content || emailData.htmlContent;
|
||||
|
||||
if (!content.trim()) throw new BadRequestException(AiMessage.EMAIL_CONTENT_IS_EMPTY_OR_COULD_NOT_BE_EXTRACTED);
|
||||
|
||||
const aiEmailData: EmailData = {
|
||||
subject: emailData.subject,
|
||||
content: emailData.content,
|
||||
htmlContent: emailData.htmlContent,
|
||||
from: emailData.from,
|
||||
to: emailData.to,
|
||||
date: emailData.date,
|
||||
attachments: emailData.attachments,
|
||||
};
|
||||
|
||||
const summarizationOptions: SummarizationOptions = {
|
||||
length: length.toLowerCase() as "short" | "medium" | "detailed",
|
||||
tone: _tone.toLowerCase() as "formal" | "casual" | "professional",
|
||||
};
|
||||
|
||||
const aiResponse = await this.aiService.summarizeEmail(aiEmailData, summarizationOptions);
|
||||
|
||||
return {
|
||||
summary: aiResponse.summary,
|
||||
keyPoints: aiResponse.keyPoints,
|
||||
suggestedActions: aiResponse.suggestedActions,
|
||||
priority: aiResponse.priority,
|
||||
sentiment: aiResponse.sentiment,
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private async generateEmailWritingAssistance(writeAssistanceDto: WriteEmailAssistanceDto, _userId: string) {
|
||||
const writingOptions: WritingAssistanceOptions = {
|
||||
purpose: writeAssistanceDto.purpose.toLowerCase() as "reply" | "forward" | "new" | "follow_up",
|
||||
tone: (writeAssistanceDto.tone?.toLowerCase() as "formal" | "casual" | "professional" | "friendly" | "urgent") || "professional",
|
||||
length: (writeAssistanceDto.length?.toLowerCase() as "short" | "medium" | "long") || "medium",
|
||||
keyPoints: writeAssistanceDto.keyPoints,
|
||||
context: writeAssistanceDto.context
|
||||
? {
|
||||
originalContent: writeAssistanceDto.context.originalContent,
|
||||
originalSender: writeAssistanceDto.context.originalSender,
|
||||
originalSubject: writeAssistanceDto.context.originalSubject,
|
||||
}
|
||||
: undefined,
|
||||
additionalInstructions: writeAssistanceDto.additionalInstructions,
|
||||
};
|
||||
|
||||
const aiResponse = await this.aiService.assistEmailWriting(writeAssistanceDto.mainMessage, writingOptions);
|
||||
|
||||
return {
|
||||
suggestion: {
|
||||
subject: aiResponse.subject,
|
||||
content: aiResponse.content,
|
||||
alternatives: aiResponse.alternatives,
|
||||
},
|
||||
tips: aiResponse.tips,
|
||||
improvements: aiResponse.improvements,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import { Inject, Injectable, InternalServerErrorException } from "@nestjs/common";
|
||||
import OpenAI from "openai";
|
||||
|
||||
import { AI_CONFIG } from "../../../common/constants";
|
||||
import { AiMessage } from "../../../common/enums/message.enum";
|
||||
import { AIConfig } from "../../../configs/ai.config";
|
||||
import {
|
||||
AIServiceInterface,
|
||||
AISummaryResponse,
|
||||
AIWritingResponse,
|
||||
EmailData,
|
||||
SummarizationOptions,
|
||||
WritingAssistanceOptions,
|
||||
} from "../interfaces/ai-service.interface";
|
||||
|
||||
@Injectable()
|
||||
export class AiService implements AIServiceInterface {
|
||||
private readonly openai: OpenAI;
|
||||
|
||||
constructor(@Inject(AI_CONFIG) private readonly aiConfig: AIConfig) {
|
||||
this.openai = new OpenAI({
|
||||
baseURL: this.aiConfig.baseUrl,
|
||||
apiKey: this.aiConfig.apiKey,
|
||||
timeout: this.aiConfig.timeout,
|
||||
});
|
||||
}
|
||||
|
||||
async summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse> {
|
||||
const prompt = this.buildSummarizationPrompt(emailData, options);
|
||||
|
||||
const completion = await this.openai.chat.completions.create({
|
||||
model: this.aiConfig.defaultModel,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: this.getSummarizationSystemPrompt(options),
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: this.aiConfig.temperature,
|
||||
max_tokens: this.aiConfig.maxTokens,
|
||||
response_format: { type: "json_object" },
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
if (!response) throw new InternalServerErrorException(AiMessage.NO_RESPONSE_FROM_AI_MODEL);
|
||||
|
||||
const parsedResponse = JSON.parse(response) as AISummaryResponse;
|
||||
|
||||
return parsedResponse;
|
||||
}
|
||||
//************************************************* */
|
||||
async assistEmailWriting(mainMessage: string, options: WritingAssistanceOptions): Promise<AIWritingResponse> {
|
||||
const prompt = this.buildWritingAssistancePrompt(mainMessage, options);
|
||||
|
||||
const completion = await this.openai.chat.completions.create({
|
||||
model: this.aiConfig.defaultModel,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: this.getWritingAssistanceSystemPrompt(options),
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: this.aiConfig.temperature,
|
||||
max_tokens: this.aiConfig.maxTokens,
|
||||
response_format: { type: "json_object" },
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
if (!response) throw new InternalServerErrorException(AiMessage.NO_RESPONSE_FROM_AI_MODEL);
|
||||
|
||||
const parsedResponse = JSON.parse(response) as AIWritingResponse;
|
||||
|
||||
return parsedResponse;
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private getSummarizationSystemPrompt(options: SummarizationOptions): string {
|
||||
const language = options.language || "Persian";
|
||||
const template = this.aiConfig.farsiPrompts.summarization.systemPrompt;
|
||||
|
||||
return template.replace("{tone}", options.tone).replace("{length}", options.length).replace("{language}", language);
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private getWritingAssistanceSystemPrompt(options: WritingAssistanceOptions): string {
|
||||
const language = options.language || "Persian";
|
||||
const template = this.aiConfig.farsiPrompts.writingAssistance.systemPrompt;
|
||||
|
||||
return template
|
||||
.replace("{tone}", options.tone)
|
||||
.replace("{length}", options.length)
|
||||
.replace("{purpose}", options.purpose)
|
||||
.replace("{language}", language);
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private buildSummarizationPrompt(emailData: EmailData, options: SummarizationOptions): string {
|
||||
const template = this.aiConfig.farsiPrompts.summarization.promptTemplate;
|
||||
let prompt = `${template.intro}\n\n`;
|
||||
console.log(emailData);
|
||||
|
||||
prompt += `${template.subjectLabel} ${emailData.subject}\n`;
|
||||
prompt += `${template.fromLabel} ${emailData.from}\n`;
|
||||
prompt += `${template.toLabel} ${emailData.to.join(", ")}\n`;
|
||||
prompt += `${template.dateLabel} ${emailData.date}\n\n`;
|
||||
prompt += `${template.contentLabel}\n${emailData.content}\n\n`;
|
||||
|
||||
if (emailData.attachments && emailData.attachments.length > 0) {
|
||||
prompt += `${template.attachmentsLabel} ${emailData.attachments.length} فایل\n\n`;
|
||||
}
|
||||
|
||||
prompt += `${template.analysisRequirements}\n`;
|
||||
prompt += `${template.summaryLengthLabel} ${options.length}\n`;
|
||||
prompt += `${template.toneLabel} ${options.tone}\n`;
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private buildWritingAssistancePrompt(mainMessage: string, options: WritingAssistanceOptions): string {
|
||||
const template = this.aiConfig.farsiPrompts.writingAssistance.promptTemplate;
|
||||
let prompt = `${template.intro}\n\n`;
|
||||
|
||||
prompt += `${template.mainMessageLabel} ${mainMessage}\n\n`;
|
||||
prompt += `${template.emailDetailsLabel}\n`;
|
||||
prompt += `${template.purposeLabel} ${options.purpose}\n`;
|
||||
prompt += `${template.toneLabel} ${options.tone}\n`;
|
||||
prompt += `${template.lengthLabel} ${options.length}\n\n`;
|
||||
|
||||
if (options.keyPoints && options.keyPoints.length > 0) {
|
||||
prompt += `${template.keyPointsLabel}\n`;
|
||||
options.keyPoints.forEach((point, index) => {
|
||||
prompt += `${index + 1}. ${point}\n`;
|
||||
});
|
||||
prompt += `\n`;
|
||||
}
|
||||
|
||||
if (options.context) {
|
||||
prompt += `${template.contextLabel}\n`;
|
||||
if (options.context.originalSubject) {
|
||||
prompt += `${template.originalSubjectLabel} ${options.context.originalSubject}\n`;
|
||||
}
|
||||
if (options.context.originalSender) {
|
||||
prompt += `${template.originalSenderLabel} ${options.context.originalSender}\n`;
|
||||
}
|
||||
if (options.context.originalContent) {
|
||||
prompt += `${template.originalContentLabel} ${options.context.originalContent}\n`;
|
||||
}
|
||||
prompt += `\n`;
|
||||
}
|
||||
|
||||
if (options.additionalInstructions) {
|
||||
prompt += `${template.additionalInstructionsLabel} ${options.additionalInstructions}\n\n`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
export interface IParameterArray {
|
||||
name: TemplateParams;
|
||||
value: string;
|
||||
}
|
||||
export interface ISmsResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
//----------------------------------------------
|
||||
|
||||
export interface ISmsGetLineResponse extends ISmsResponse {
|
||||
data: string[];
|
||||
}
|
||||
|
||||
export interface ISmsVerifyResponse extends ISmsResponse {
|
||||
data: { MessageId: number; Cost: number };
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
export interface ISmsVerifyBody {
|
||||
Parameters: IParameterArray[];
|
||||
Mobile: string;
|
||||
TemplateId: string;
|
||||
}
|
||||
|
||||
export type TemplateParams =
|
||||
| "VERIFICATIONCODE"
|
||||
| "code"
|
||||
| "invoiceId"
|
||||
| "price"
|
||||
| "items"
|
||||
| "loginDate"
|
||||
| "invoiceDate"
|
||||
| "title"
|
||||
| "description"
|
||||
| "ticketId"
|
||||
| "serviceName"
|
||||
| "date"
|
||||
| "amount"
|
||||
| "newBalance"
|
||||
| "reason"
|
||||
| "dueDate"
|
||||
| "lateFee"
|
||||
| "paidDate"
|
||||
| "user"
|
||||
| "subject"
|
||||
| "planName"
|
||||
| "message"
|
||||
| "blogTitle"
|
||||
| "fullName"
|
||||
| "mobile";
|
||||
@@ -1,542 +0,0 @@
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||
import { AxiosError } from "axios";
|
||||
import { catchError, firstValueFrom } from "rxjs";
|
||||
|
||||
import { SmsMessage } from "../../../common/enums/message.enum";
|
||||
import { ISmsConfigs } from "../../../configs/sms.config";
|
||||
import { SMS_CONFIG } from "../constants";
|
||||
import { ISmsGetLineResponse, ISmsVerifyBody, ISmsVerifyResponse } from "../interfaces/ISms";
|
||||
|
||||
@Injectable()
|
||||
export class SmsService {
|
||||
private readonly logger = new Logger(SmsService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(SMS_CONFIG) private smsConfigs: ISmsConfigs,
|
||||
private readonly httpService: HttpService,
|
||||
) {}
|
||||
//************************************************* */
|
||||
|
||||
async sendSmsVerifyCode(mobile: string, otpCode: string) {
|
||||
//
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [{ name: "code", value: otpCode }],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_OTP,
|
||||
};
|
||||
//
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(
|
||||
`${this.smsConfigs.API_URL}/send/verify`,
|
||||
{ ...smsData },
|
||||
{
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending verify sms", err);
|
||||
throw new InternalServerErrorException("error in sending verify sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException(SmsMessage.SEND_SMS_ERROR);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendInvoiceVerifyCode(mobile: string, otpCode: string, invoiceId: number, price: number, items: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "code", value: otpCode },
|
||||
{ name: "invoiceId", value: invoiceId.toString() },
|
||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||
{ name: "items", value: items },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE,
|
||||
};
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(
|
||||
`${this.smsConfigs.API_URL}/send/verify`,
|
||||
{ ...smsData },
|
||||
{
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending verify sms", err);
|
||||
throw new InternalServerErrorException("error in sending verify sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
throw new InternalServerErrorException(SmsMessage.SEND_SMS_ERROR);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendLoginSms(mobile: string, loginDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [{ name: "loginDate", value: loginDate }],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_LOGIN,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending verify sms", err);
|
||||
throw new InternalServerErrorException("error in sending verify sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string, dueDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||
{ name: "items", value: items },
|
||||
{ name: "invoiceDate", value: invoiceDate },
|
||||
{ name: "dueDate", value: dueDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
console.error(err);
|
||||
|
||||
this.logger.error("error in sending invoice created sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice created sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
async sendAnnouncementSms(mobile: string, title: string, description: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "title", value: title },
|
||||
{ name: "description", value: description },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_ANNOUNCEMENT,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending announcement sms", err);
|
||||
throw new InternalServerErrorException("error in sending announcement sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendCreateTicketSms(mobile: string, ticketId: string, subject: string, date: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "ticketId", value: ticketId },
|
||||
{ name: "subject", value: subject },
|
||||
{ name: "date", value: date },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_CREATED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending ticket created sms", err);
|
||||
throw new InternalServerErrorException("error in sending ticket created sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendAnswerTicketSms(mobile: string, ticketId: string, subject: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "ticketId", value: ticketId },
|
||||
{ name: "subject", value: subject },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ANSWERED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending ticket answered sms", err);
|
||||
throw new InternalServerErrorException("error in sending ticket answered sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendTicketAssignedToAdminSms(mobile: string, ticketId: string, subject: string, user: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "ticketId", value: ticketId },
|
||||
{ name: "subject", value: subject },
|
||||
{ name: "user", value: user },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ASSIGNED_ADMIN,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending ticket assigned sms", err);
|
||||
throw new InternalServerErrorException("error in sending ticket assigned sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
async sendInvoiceApprovedSms(mobile: string, invoiceId: string, price: number, dueDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||
{ name: "dueDate", value: dueDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_APPROVED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending invoice approved sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice approved sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
// throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
async sendInvoicePaidSms(mobile: string, invoiceId: string, amount: number, paidDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "amount", value: amount.toString() },
|
||||
{ name: "paidDate", value: paidDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_PAID,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending invoice paid sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice paid sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
// throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendInvoicePaymentReminderSms(mobile: string, invoiceId: string, amount: number, dueDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "amount", value: amount.toString() },
|
||||
{ name: "dueDate", value: dueDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_REMINDER,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending invoice reminder sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice reminder sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendInvoiceOverdueSms(mobile: string, invoiceId: string, amount: number, lateFee: number, dueDate: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "amount", value: amount.toString() },
|
||||
{ name: "lateFee", value: lateFee.toString() },
|
||||
{ name: "dueDate", value: dueDate },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_OVERDUE,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending invoice overdue sms", err);
|
||||
throw new InternalServerErrorException("error in sending invoice overdue sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendRecurringInvoiceDraftSms(adminMobile: string, invoiceId: string, price: number) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||
],
|
||||
Mobile: adminMobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_RECURRING_INVOICE_DRAFT,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending recurring invoice draft sms", err);
|
||||
throw new InternalServerErrorException("error in sending recurring invoice draft sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
// throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
|
||||
async sendBlockServiceSms(mobile: string, invoiceId: string, planName: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [
|
||||
{ name: "invoiceId", value: invoiceId },
|
||||
{ name: "planName", value: planName },
|
||||
],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_SUBSCRIPTION_CANCELLED,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending subscription cancelled sms", err);
|
||||
throw new InternalServerErrorException("error in sending subscription cancelled sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
// throw new InternalServerErrorException("error in sending sms");
|
||||
}
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
//************************************************* */
|
||||
|
||||
async sendPaymentReminderSms(mobile: string, amount: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [{ name: "amount", value: amount }],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_PAYMENT_REMINDER,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending payment reminder sms", err);
|
||||
throw new InternalServerErrorException("error in sending payment reminder sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
//************************************************* */
|
||||
|
||||
async sendPaymentCancellationSms(mobile: string, amount: string) {
|
||||
const smsData: ISmsVerifyBody = {
|
||||
Parameters: [{ name: "amount", value: amount }],
|
||||
Mobile: mobile,
|
||||
TemplateId: this.smsConfigs.SMS_PATTERN_PAYMENT_CANCELLATION,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in sending payment cancellation sms", err);
|
||||
throw new InternalServerErrorException("error in sending payment cancellation sms");
|
||||
}),
|
||||
),
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in sending sms", error);
|
||||
}
|
||||
}
|
||||
|
||||
async getSmsLines() {
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.get<ISmsGetLineResponse>(`${this.smsConfigs.API_URL}/lines`, {
|
||||
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||
})
|
||||
.pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
this.logger.error("error in getting sms lines", error);
|
||||
throw new InternalServerErrorException("error in getting sms lines");
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error("error in getting sms lines", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,22 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { S3_CONFIG, SMS_CONFIG } from "./constants";
|
||||
import { S3_CONFIG } from "./constants";
|
||||
import { CacheService } from "./services/cache.service";
|
||||
import { OTPService } from "./services/otp.service";
|
||||
import { PasswordService } from "./services/password.service";
|
||||
import { SmsService } from "./services/sms.service";
|
||||
import { S3Configs } from "../../configs/s3.config";
|
||||
import { smsConfigs } from "../../configs/sms.config";
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
OTPService,
|
||||
PasswordService,
|
||||
CacheService,
|
||||
SmsService,
|
||||
{
|
||||
provide: SMS_CONFIG,
|
||||
useFactory: smsConfigs().useFactory,
|
||||
inject: smsConfigs().inject,
|
||||
},
|
||||
{
|
||||
provide: S3_CONFIG,
|
||||
useFactory: S3Configs().useFactory,
|
||||
inject: S3Configs().inject,
|
||||
},
|
||||
],
|
||||
exports: [SMS_CONFIG, S3_CONFIG, OTPService, PasswordService, CacheService, SmsService],
|
||||
exports: [S3_CONFIG, OTPService, PasswordService, CacheService],
|
||||
})
|
||||
export class UtilsModule {}
|
||||
|
||||
Reference in New Issue
Block a user