chore: add uploader and user profile picture
This commit is contained in:
@@ -30,6 +30,8 @@
|
|||||||
"seed:run": "mikro-orm seeder:run --config=database/mikro-orm.config.ts"
|
"seed:run": "mikro-orm seeder:run --config=database/mikro-orm.config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.850.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.850.0",
|
||||||
"@fastify/static": "^8.2.0",
|
"@fastify/static": "^8.2.0",
|
||||||
"@keyv/redis": "^4.5.0",
|
"@keyv/redis": "^4.5.0",
|
||||||
"@mikro-orm/cli": "^6.4.16",
|
"@mikro-orm/cli": "^6.4.16",
|
||||||
|
|||||||
Generated
+1217
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ import { QuotaSyncModule } from "./modules/quota-sync/quota-sync.module";
|
|||||||
import { SettingModule } from "./modules/settings/settings.module";
|
import { SettingModule } from "./modules/settings/settings.module";
|
||||||
import { EmailSignaturesModule } from "./modules/signatures/email-signatures.module";
|
import { EmailSignaturesModule } from "./modules/signatures/email-signatures.module";
|
||||||
import { TemplatesModule } from "./modules/templates/templates.module";
|
import { TemplatesModule } from "./modules/templates/templates.module";
|
||||||
|
import { UploaderModule } from "./modules/uploader/uploader.module";
|
||||||
import { UsersModule } from "./modules/users/users.module";
|
import { UsersModule } from "./modules/users/users.module";
|
||||||
import { UtilsModule } from "./modules/utils/utils.module";
|
import { UtilsModule } from "./modules/utils/utils.module";
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
|||||||
TemplatesModule,
|
TemplatesModule,
|
||||||
SettingModule,
|
SettingModule,
|
||||||
NotificationModule,
|
NotificationModule,
|
||||||
|
UploaderModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { UseGuards, applyDecorators } from "@nestjs/common";
|
import { UseGuards, applyDecorators } from "@nestjs/common";
|
||||||
import { ApiBearerAuth } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiHeader } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { AdminRouteGuard } from "../../modules/auth/guards/admin.guard";
|
import { AdminRouteGuard } from "../../modules/auth/guards/admin.guard";
|
||||||
import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard";
|
import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard";
|
||||||
|
|
||||||
export function AuthGuards() {
|
export function AuthGuards() {
|
||||||
return applyDecorators(UseGuards(JwtAuthGuard, AdminRouteGuard), ApiBearerAuth("authorization"));
|
return applyDecorators(
|
||||||
|
UseGuards(JwtAuthGuard, AdminRouteGuard),
|
||||||
|
ApiBearerAuth("authorization"),
|
||||||
|
ApiHeader({ name: "x-business-id", description: "Business ID" }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ export const enum UserMessage {
|
|||||||
FORWARDERS_EMAIL_VALID = "آدرس ایمیل فوروارد باید معتبر باشد",
|
FORWARDERS_EMAIL_VALID = "آدرس ایمیل فوروارد باید معتبر باشد",
|
||||||
FORWARDERS_ARRAY = "آدرس های فوروارد باید یک آرایه باشد",
|
FORWARDERS_ARRAY = "آدرس های فوروارد باید یک آرایه باشد",
|
||||||
FORWARDERS_STRING = "آدرس های فوروارد باید یک رشته باشد",
|
FORWARDERS_STRING = "آدرس های فوروارد باید یک رشته باشد",
|
||||||
|
PROFILE_PICTURE_URL = "آدرس تصویر پروفایل باید یک آدرس اینترنتی معتبر باشد",
|
||||||
|
USER_UPDATED_SUCCESSFULLY = "کاربر با موفقیت به روز رسانی شد",
|
||||||
|
PROFILE_PICTURE_NOT_EMPTY = "تصویر پروفایل نمیتواند خالی باشد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CommonMessage {
|
export const enum CommonMessage {
|
||||||
|
|||||||
@@ -1,105 +1,15 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { Type } from "class-transformer";
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
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 {
|
export class WriteEmailAssistanceDto {
|
||||||
@IsNotEmpty({ message: "Email purpose is required" })
|
@IsNotEmpty({ message: "Prompt is required" })
|
||||||
@IsEnum(EmailPurpose, { message: "Email purpose must be one of: reply, forward, new, follow_up" })
|
@IsString({ message: "Prompt must be a string" })
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: "Purpose of the email",
|
description: "User prompt describing what kind of email they want to write",
|
||||||
enum: EmailPurpose,
|
example:
|
||||||
example: EmailPurpose.REPLY,
|
"Write a professional email to schedule a meeting with my team to discuss the Q4 project timeline. I want to address concerns about resource allocation and set a deadline for next Friday.",
|
||||||
})
|
})
|
||||||
purpose: EmailPurpose;
|
prompt: string;
|
||||||
|
|
||||||
@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 {
|
export class EmailSuggestionDto {
|
||||||
@@ -122,25 +32,3 @@ export class EmailSuggestionDto {
|
|||||||
})
|
})
|
||||||
alternatives: string[];
|
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[];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
|||||||
import { UserDec } from "../../common/decorators/user.decorator";
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
|
|
||||||
@Controller("ai-assistant")
|
@Controller("ai-assistant")
|
||||||
// @ApiHeader({ name: "x-business-id", description: "Business ID" })
|
|
||||||
// @UseInterceptors(BusinessInterceptor)
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
export class AiAssistantController {
|
export class AiAssistantController {
|
||||||
constructor(private readonly aiAssistantService: AiAssistantService) {}
|
constructor(private readonly aiAssistantService: AiAssistantService) {}
|
||||||
|
|||||||
@@ -14,20 +14,6 @@ export interface SummarizationOptions {
|
|||||||
language?: "Persian" | "English";
|
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 {
|
export interface AISummaryResponse {
|
||||||
summary: string;
|
summary: string;
|
||||||
keyPoints: string[];
|
keyPoints: string[];
|
||||||
@@ -46,5 +32,5 @@ export interface AIWritingResponse {
|
|||||||
|
|
||||||
export interface AIServiceInterface {
|
export interface AIServiceInterface {
|
||||||
summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse>;
|
summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse>;
|
||||||
assistEmailWriting(mainMessage: string, options: WritingAssistanceOptions): Promise<AIWritingResponse>;
|
assistEmailWriting(prompt: string): Promise<AIWritingResponse>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { WildDuckMessagesService } from "../../mail-server/services/wildduck-mes
|
|||||||
import { SummarizeEmailDto } from "../DTO/summarize-email.dto";
|
import { SummarizeEmailDto } from "../DTO/summarize-email.dto";
|
||||||
import { WriteEmailAssistanceDto } from "../DTO/write-email-assistance.dto";
|
import { WriteEmailAssistanceDto } from "../DTO/write-email-assistance.dto";
|
||||||
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
||||||
import { EmailData, SummarizationOptions, WritingAssistanceOptions } from "../interfaces/ai-service.interface";
|
import { EmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AiAssistantService {
|
export class AiAssistantService {
|
||||||
@@ -132,22 +132,7 @@ export class AiAssistantService {
|
|||||||
|
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
private async generateEmailWritingAssistance(writeAssistanceDto: WriteEmailAssistanceDto, _userId: string) {
|
private async generateEmailWritingAssistance(writeAssistanceDto: WriteEmailAssistanceDto, _userId: string) {
|
||||||
const writingOptions: WritingAssistanceOptions = {
|
const aiResponse = await this.aiService.assistEmailWriting(writeAssistanceDto.prompt);
|
||||||
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 {
|
return {
|
||||||
suggestion: {
|
suggestion: {
|
||||||
|
|||||||
@@ -4,18 +4,15 @@ import OpenAI from "openai";
|
|||||||
import { AI_CONFIG } from "../../../common/constants";
|
import { AI_CONFIG } from "../../../common/constants";
|
||||||
import { AiMessage } from "../../../common/enums/message.enum";
|
import { AiMessage } from "../../../common/enums/message.enum";
|
||||||
import { AIConfig } from "../../../configs/ai.config";
|
import { AIConfig } from "../../../configs/ai.config";
|
||||||
import {
|
import { AIServiceInterface, AISummaryResponse, AIWritingResponse, EmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||||
AIServiceInterface,
|
|
||||||
AISummaryResponse,
|
|
||||||
AIWritingResponse,
|
|
||||||
EmailData,
|
|
||||||
SummarizationOptions,
|
|
||||||
WritingAssistanceOptions,
|
|
||||||
} from "../interfaces/ai-service.interface";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AiService implements AIServiceInterface {
|
export class AiService implements AIServiceInterface {
|
||||||
private readonly openai: OpenAI;
|
private readonly openai: OpenAI;
|
||||||
|
private readonly language: "Persian" | "English" = "Persian";
|
||||||
|
private readonly defaultTone: "formal" | "casual" | "professional" = "professional";
|
||||||
|
private readonly defaultLength: "short" | "medium" | "detailed" = "medium";
|
||||||
|
private readonly defaultPurpose: "general" | "reply" | "forward" | "new" | "follow_up" = "general";
|
||||||
|
|
||||||
constructor(@Inject(AI_CONFIG) private readonly aiConfig: AIConfig) {
|
constructor(@Inject(AI_CONFIG) private readonly aiConfig: AIConfig) {
|
||||||
this.openai = new OpenAI({
|
this.openai = new OpenAI({
|
||||||
@@ -25,6 +22,7 @@ export class AiService implements AIServiceInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//************************************************* */
|
||||||
async summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse> {
|
async summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse> {
|
||||||
const prompt = this.buildSummarizationPrompt(emailData, options);
|
const prompt = this.buildSummarizationPrompt(emailData, options);
|
||||||
|
|
||||||
@@ -52,16 +50,15 @@ export class AiService implements AIServiceInterface {
|
|||||||
|
|
||||||
return parsedResponse;
|
return parsedResponse;
|
||||||
}
|
}
|
||||||
//************************************************* */
|
|
||||||
async assistEmailWriting(mainMessage: string, options: WritingAssistanceOptions): Promise<AIWritingResponse> {
|
|
||||||
const prompt = this.buildWritingAssistancePrompt(mainMessage, options);
|
|
||||||
|
|
||||||
|
//************************************************* */
|
||||||
|
async assistEmailWriting(prompt: string): Promise<AIWritingResponse> {
|
||||||
const completion = await this.openai.chat.completions.create({
|
const completion = await this.openai.chat.completions.create({
|
||||||
model: this.aiConfig.defaultModel,
|
model: this.aiConfig.defaultModel,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
content: this.getWritingAssistanceSystemPrompt(options),
|
content: this.getWritingAssistanceSystemPrompt(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -80,24 +77,23 @@ export class AiService implements AIServiceInterface {
|
|||||||
|
|
||||||
return parsedResponse;
|
return parsedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
private getSummarizationSystemPrompt(options: SummarizationOptions): string {
|
private getSummarizationSystemPrompt(options: SummarizationOptions): string {
|
||||||
const language = options.language || "Persian";
|
const language = options.language || this.language;
|
||||||
const template = this.aiConfig.farsiPrompts.summarization.systemPrompt;
|
const template = this.aiConfig.farsiPrompts.summarization.systemPrompt;
|
||||||
|
|
||||||
return template.replace("{tone}", options.tone).replace("{length}", options.length).replace("{language}", language);
|
return template.replace("{tone}", options.tone).replace("{length}", options.length).replace("{language}", language);
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
private getWritingAssistanceSystemPrompt(options: WritingAssistanceOptions): string {
|
private getWritingAssistanceSystemPrompt(): string {
|
||||||
const language = options.language || "Persian";
|
const language = this.language;
|
||||||
const template = this.aiConfig.farsiPrompts.writingAssistance.systemPrompt;
|
const template = this.aiConfig.farsiPrompts.writingAssistance.systemPrompt;
|
||||||
|
|
||||||
return template
|
return template
|
||||||
.replace("{tone}", options.tone)
|
.replace("{tone}", this.defaultTone)
|
||||||
.replace("{length}", options.length)
|
.replace("{length}", this.defaultLength)
|
||||||
.replace("{purpose}", options.purpose)
|
.replace("{purpose}", this.defaultPurpose)
|
||||||
.replace("{language}", language);
|
.replace("{language}", language);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +101,6 @@ export class AiService implements AIServiceInterface {
|
|||||||
private buildSummarizationPrompt(emailData: EmailData, options: SummarizationOptions): string {
|
private buildSummarizationPrompt(emailData: EmailData, options: SummarizationOptions): string {
|
||||||
const template = this.aiConfig.farsiPrompts.summarization.promptTemplate;
|
const template = this.aiConfig.farsiPrompts.summarization.promptTemplate;
|
||||||
let prompt = `${template.intro}\n\n`;
|
let prompt = `${template.intro}\n\n`;
|
||||||
console.log(emailData);
|
|
||||||
|
|
||||||
prompt += `${template.subjectLabel} ${emailData.subject}\n`;
|
prompt += `${template.subjectLabel} ${emailData.subject}\n`;
|
||||||
prompt += `${template.fromLabel} ${emailData.from}\n`;
|
prompt += `${template.fromLabel} ${emailData.from}\n`;
|
||||||
@@ -123,44 +118,4 @@ export class AiService implements AIServiceInterface {
|
|||||||
|
|
||||||
return prompt;
|
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,5 +1,5 @@
|
|||||||
import { Body, Controller, Get, Param, Patch, Post, UseInterceptors } from "@nestjs/common";
|
import { Body, Controller, Get, Param, Patch, Post, UseInterceptors } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { BusinessQuotaResponseDto } from "./DTO/business-quota-response.dto";
|
import { BusinessQuotaResponseDto } from "./DTO/business-quota-response.dto";
|
||||||
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
|
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
|
||||||
@@ -12,7 +12,6 @@ import { BusinessDec } from "../../common/decorators/business.decorator";
|
|||||||
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||||
|
|
||||||
@Controller("business")
|
@Controller("business")
|
||||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
|
||||||
export class BusinessesController {
|
export class BusinessesController {
|
||||||
constructor(private readonly businessesService: BusinessesService) {}
|
constructor(private readonly businessesService: BusinessesService) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateDomainDto } from "./DTO/create-domain.dto";
|
import { CreateDomainDto } from "./DTO/create-domain.dto";
|
||||||
import { UpdateDomainDto } from "./DTO/update-domain.dto";
|
import { UpdateDomainDto } from "./DTO/update-domain.dto";
|
||||||
@@ -12,7 +12,6 @@ import { ParamDto } from "../../common/DTO/param.dto";
|
|||||||
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||||
|
|
||||||
@Controller("domains")
|
@Controller("domains")
|
||||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
|
||||||
@UseInterceptors(BusinessInterceptor)
|
@UseInterceptors(BusinessInterceptor)
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
export class DomainsController {
|
export class DomainsController {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateTemplateDto } from "./DTO/create-template.dto";
|
import { CreateTemplateDto } from "./DTO/create-template.dto";
|
||||||
import { TemplateParamDto } from "./DTO/template-param.dto";
|
import { TemplateParamDto } from "./DTO/template-param.dto";
|
||||||
@@ -12,7 +12,6 @@ import { BusinessDec } from "../../common/decorators/business.decorator";
|
|||||||
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||||
|
|
||||||
@Controller("templates")
|
@Controller("templates")
|
||||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
|
||||||
@UseInterceptors(BusinessInterceptor)
|
@UseInterceptors(BusinessInterceptor)
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
export class TemplatesController {
|
export class TemplatesController {
|
||||||
|
|||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { File } from "@nest-lab/fastify-multer";
|
||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export class UploadSingleFileDto {
|
||||||
|
@ApiProperty({ type: "string", format: "binary", nullable: false })
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UploadMultipleFileDto {
|
||||||
|
@ApiProperty({
|
||||||
|
required: true,
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "string",
|
||||||
|
format: "binary",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
files: File[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
export interface S3UploadResult {
|
||||||
|
key: string;
|
||||||
|
url: string;
|
||||||
|
bucket: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3UploadOptions {
|
||||||
|
contentType: string;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
acl?: "private" | "public-read" | "public-read-write";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3DownloadOptions {
|
||||||
|
expiresIn?: number;
|
||||||
|
responseContentType?: string;
|
||||||
|
responseContentDisposition?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3FileInfo {
|
||||||
|
key: string;
|
||||||
|
bucket: string;
|
||||||
|
size: number;
|
||||||
|
lastModified: Date;
|
||||||
|
contentType: string;
|
||||||
|
etag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3DeleteResult {
|
||||||
|
deleted: boolean;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3ListResult {
|
||||||
|
files: S3FileInfo[];
|
||||||
|
continuationToken?: string;
|
||||||
|
isTruncated: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
|
import { S3UploadResult } from "../interfaces/s3.interface";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class S3Service {
|
||||||
|
private readonly logger = new Logger(S3Service.name);
|
||||||
|
private readonly s3Client: S3Client;
|
||||||
|
private readonly bucketName: string;
|
||||||
|
private readonly region: string;
|
||||||
|
private readonly url: string;
|
||||||
|
|
||||||
|
constructor(private readonly configService: ConfigService) {
|
||||||
|
this.bucketName = this.configService.getOrThrow<string>("BUCKET_NAME");
|
||||||
|
this.region = this.configService.getOrThrow<string>("BUCKET_REGION");
|
||||||
|
this.url = this.configService.getOrThrow<string>("BUCKET_URL");
|
||||||
|
|
||||||
|
this.s3Client = new S3Client({
|
||||||
|
region: this.region,
|
||||||
|
endpoint: this.url,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: this.configService.getOrThrow<string>("BUCKET_ACCESS_KEY"),
|
||||||
|
secretAccessKey: this.configService.getOrThrow<string>("BUCKET_SECRET_KEY"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload file to S3
|
||||||
|
*/
|
||||||
|
async uploadFile(buffer: Buffer, key: string, contentType: string, metadata?: Record<string, string>): Promise<S3UploadResult> {
|
||||||
|
try {
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: this.bucketName,
|
||||||
|
Key: key,
|
||||||
|
Body: buffer,
|
||||||
|
ACL: "public-read",
|
||||||
|
ContentType: contentType,
|
||||||
|
Metadata: metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.s3Client.send(command);
|
||||||
|
|
||||||
|
// const url = `https://${this.bucketName}.s3.${this.region}.amazonaws.com/${key}`;
|
||||||
|
const url = `https://${this.bucketName}.s3.ir-thr-at1.arvanstorage.ir/${key}`;
|
||||||
|
|
||||||
|
this.logger.log(`File uploaded to S3: ${key}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
url,
|
||||||
|
bucket: this.bucketName,
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.logger.error(`Failed to upload file to S3: ${(error as Error).message}`, (error as Error).stack);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file stream from S3
|
||||||
|
*/
|
||||||
|
async getFileStream(key: string): Promise<Readable> {
|
||||||
|
try {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: this.bucketName,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.s3Client.send(command);
|
||||||
|
return response.Body as Readable;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.logger.error(`Failed to get file from S3: ${(error as Error).message}`, (error as Error).stack);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate presigned URL for file download
|
||||||
|
*/
|
||||||
|
async getPresignedUrl(key: string, expiresIn: number = 3600): Promise<string> {
|
||||||
|
try {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: this.bucketName,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.logger.error(`Failed to generate presigned URL: ${(error as Error).message}`, (error as Error).stack);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete file from S3
|
||||||
|
*/
|
||||||
|
async deleteFile(key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const command = new DeleteObjectCommand({
|
||||||
|
Bucket: this.bucketName,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.s3Client.send(command);
|
||||||
|
this.logger.log(`File deleted from S3: ${key}`);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.logger.error(`Failed to delete file from S3: ${(error as Error).message}`, (error as Error).stack);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate S3 key for file
|
||||||
|
*/
|
||||||
|
generateFileKey(originalName: string): string {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const randomSuffix = Math.round(Math.random() * 1e9);
|
||||||
|
const extension = originalName.split(".").pop();
|
||||||
|
|
||||||
|
const basePath = `images`;
|
||||||
|
return `${basePath}/${timestamp}-${randomSuffix}.${extension}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { File } from "@nest-lab/fastify-multer";
|
||||||
|
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
|
import { S3Service } from "./s3.service";
|
||||||
|
import { UploaderMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UploaderService {
|
||||||
|
private readonly logger = new Logger(UploaderService.name);
|
||||||
|
private readonly maxFileSize: number;
|
||||||
|
private readonly allowedMimeTypes: string[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly s3Service: S3Service,
|
||||||
|
) {
|
||||||
|
this.maxFileSize = this.configService.get("MAX_FILE_SIZE", 1024 * 1024 * 100); // 100MB
|
||||||
|
this.allowedMimeTypes = [
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm",
|
||||||
|
"video/ogg",
|
||||||
|
"video/avi",
|
||||||
|
"video/mov",
|
||||||
|
"video/mkv",
|
||||||
|
"audio/mp3",
|
||||||
|
"audio/wav",
|
||||||
|
"audio/ogg",
|
||||||
|
"audio/webm",
|
||||||
|
"audio/m4a",
|
||||||
|
"audio/aac",
|
||||||
|
"audio/flac",
|
||||||
|
"audio/m4a",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/tiff",
|
||||||
|
"image/bmp",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadMultiple(files: File[]) {
|
||||||
|
return await Promise.all(files.map((file) => this.uploadFile(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle file upload
|
||||||
|
*/
|
||||||
|
async uploadFile(file: File) {
|
||||||
|
if (!file || !file.buffer || !file.size) {
|
||||||
|
throw new BadRequestException(UploaderMessage.UPLOAD_FILE_INVALID);
|
||||||
|
}
|
||||||
|
this.logger.log(`Uploading file: ${file.originalname}`);
|
||||||
|
|
||||||
|
// Validate file
|
||||||
|
this.validateFile(file);
|
||||||
|
|
||||||
|
// Determine file type
|
||||||
|
this.determineFileType(file.mimetype);
|
||||||
|
|
||||||
|
// Generate S3 key
|
||||||
|
const s3Key = this.s3Service.generateFileKey(file.originalname);
|
||||||
|
|
||||||
|
// Upload to S3
|
||||||
|
const s3Result = await this.s3Service.uploadFile(file.buffer, s3Key, file.mimetype, {
|
||||||
|
originalName: file.originalname,
|
||||||
|
uploadedBy: "admin",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`File uploaded successfully: `);
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: s3Result,
|
||||||
|
url: s3Result.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate uploaded file
|
||||||
|
*/
|
||||||
|
private validateFile(file: File): void {
|
||||||
|
if (!file) throw new BadRequestException(UploaderMessage.UPLOAD_FILE_INVALID);
|
||||||
|
|
||||||
|
if (file.size && file.size > this.maxFileSize) throw new BadRequestException(`File too large. Maximum size is ${this.maxFileSize} bytes`);
|
||||||
|
|
||||||
|
if (!this.allowedMimeTypes.includes(file.mimetype)) throw new BadRequestException(`File type not supported: ${file.mimetype}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine file type based on mimetype
|
||||||
|
*/
|
||||||
|
private determineFileType(mimetype: string) {
|
||||||
|
if (mimetype.startsWith("video/")) {
|
||||||
|
return "video";
|
||||||
|
} else if (mimetype.startsWith("audio/")) {
|
||||||
|
return "audio";
|
||||||
|
} else if (mimetype.startsWith("image/")) {
|
||||||
|
return "image";
|
||||||
|
} else if (mimetype.startsWith("application/")) {
|
||||||
|
return "application";
|
||||||
|
} else if (mimetype.startsWith("text/")) {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
// Default to video for unknown types
|
||||||
|
return "video";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { File, FileInterceptor, FilesInterceptor } from "@nest-lab/fastify-multer";
|
||||||
|
import { Controller, Post, UploadedFile, UploadedFiles, UseInterceptors } from "@nestjs/common";
|
||||||
|
import { ApiBody, ApiConsumes, ApiOperation } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { UploadMultipleFileDto, UploadSingleFileDto } from "./DTO/upload-file.dto";
|
||||||
|
import { UploaderService } from "./providers/uploader.service";
|
||||||
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
|
|
||||||
|
@Controller("uploader")
|
||||||
|
@AuthGuards()
|
||||||
|
export class UploaderController {
|
||||||
|
constructor(private readonly uploaderService: UploaderService) {}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Upload a file (admin)" })
|
||||||
|
@ApiConsumes("multipart/form-data")
|
||||||
|
@UseInterceptors(FileInterceptor("file"))
|
||||||
|
@ApiBody({ type: UploadSingleFileDto })
|
||||||
|
@Post("single-file")
|
||||||
|
async uploadFile(@UploadedFile() file: File) {
|
||||||
|
return this.uploaderService.uploadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Uploads multiple files" })
|
||||||
|
@ApiConsumes("multipart/form-data")
|
||||||
|
@UseInterceptors(FilesInterceptor("files"))
|
||||||
|
@ApiBody({ type: UploadMultipleFileDto })
|
||||||
|
@Post("multi-file")
|
||||||
|
uploadFiles(@UploadedFiles() files: File[]) {
|
||||||
|
return this.uploaderService.uploadMultiple(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { S3Service } from "./providers/s3.service";
|
||||||
|
import { UploaderService } from "./providers/uploader.service";
|
||||||
|
import { UploaderController } from "./uploader.controller";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [UploaderController],
|
||||||
|
providers: [UploaderService, S3Service],
|
||||||
|
exports: [UploaderService],
|
||||||
|
})
|
||||||
|
export class UploaderModule {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsOptional, IsUrl } from "class-validator";
|
||||||
|
|
||||||
|
import { UserMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class UpdateUserProfileDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: UserMessage.PROFILE_PICTURE_NOT_EMPTY })
|
||||||
|
@IsUrl({ protocols: ["https"] }, { message: UserMessage.PROFILE_PICTURE_URL })
|
||||||
|
@ApiPropertyOptional({ description: "Profile picture URL", example: "https://example.com/profile.jpg" })
|
||||||
|
profilePic?: string;
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ export class User extends BaseEntity {
|
|||||||
@Property({ type: "varchar", length: 150, nullable: false })
|
@Property({ type: "varchar", length: 150, nullable: false })
|
||||||
password!: string;
|
password!: string;
|
||||||
|
|
||||||
|
@Property({ type: "varchar", length: 255, nullable: true })
|
||||||
|
profilePic?: string;
|
||||||
|
|
||||||
@Property({ type: "varchar", length: 255, nullable: false })
|
@Property({ type: "varchar", length: 255, nullable: false })
|
||||||
emailAddress!: string;
|
emailAddress!: string;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Template } from "../../templates/entities/template.entity";
|
|||||||
import { PasswordService } from "../../utils/services/password.service";
|
import { PasswordService } from "../../utils/services/password.service";
|
||||||
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
|
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
|
||||||
import { UpdateEmailUserDto } from "../DTO/update-email-user.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";
|
import { UserListQueryDto } from "../DTO/user-list-query.dto";
|
||||||
import { Role } from "../entities/role.entity";
|
import { Role } from "../entities/role.entity";
|
||||||
import { User } from "../entities/user.entity";
|
import { User } from "../entities/user.entity";
|
||||||
@@ -80,6 +81,16 @@ export class UsersService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
/*******************************/
|
/*******************************/
|
||||||
|
async updateProfile(userId: string, updateMeDto: UpdateUserProfileDto) {
|
||||||
|
const user = await this.userRepository.findOne({ id: userId, deletedAt: null });
|
||||||
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
|
||||||
|
this.userRepository.assign(user, updateMeDto);
|
||||||
|
await this.em.flush();
|
||||||
|
|
||||||
|
return { message: UserMessage.USER_UPDATED_SUCCESSFULLY, user };
|
||||||
|
}
|
||||||
|
/*******************************/
|
||||||
|
|
||||||
async findOneByIdWithEntityManager(id: string, em: EntityManager) {
|
async findOneByIdWithEntityManager(id: string, em: EntityManager) {
|
||||||
const user = await em.findOne(User, { id }, { populate: ["role"] });
|
const user = await em.findOne(User, { id }, { populate: ["role"] });
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateEmailUserDto } from "./DTO/create-email-user.dto";
|
import { CreateEmailUserDto } from "./DTO/create-email-user.dto";
|
||||||
import { UpdateEmailUserDto } from "./DTO/update-email-user.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";
|
import { UserListQueryDto } from "./DTO/user-list-query.dto";
|
||||||
import { UsersService } from "./services/users.service";
|
import { UsersService } from "./services/users.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
@@ -12,7 +13,6 @@ import { ParamDto } from "../../common/DTO/param.dto";
|
|||||||
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||||
|
|
||||||
@Controller("users")
|
@Controller("users")
|
||||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor(private readonly usersService: UsersService) {}
|
constructor(private readonly usersService: UsersService) {}
|
||||||
@@ -24,6 +24,21 @@ export class UsersController {
|
|||||||
return this.usersService.getMe(userId);
|
return this.usersService.getMe(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Patch(":id")
|
||||||
|
@UseInterceptors(BusinessInterceptor)
|
||||||
|
@ApiOperation({ summary: "Update email user" })
|
||||||
|
@ApiResponse({ status: 200, description: "Email user updated successfully" })
|
||||||
|
updateEmailUser(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: UpdateEmailUserDto) {
|
||||||
|
return this.usersService.updateEmailUser(paramDto.id, businessId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch("profile")
|
||||||
|
@ApiOperation({ summary: "Update current user" })
|
||||||
|
@ApiResponse({ status: 200, description: "Current user updated successfully" })
|
||||||
|
updateProfile(@UserDec("id") userId: string, @Body() updateMeDto: UpdateUserProfileDto) {
|
||||||
|
return this.usersService.updateProfile(userId, updateMeDto);
|
||||||
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@UseInterceptors(BusinessInterceptor)
|
@UseInterceptors(BusinessInterceptor)
|
||||||
@ApiOperation({ summary: "Create a new email user" })
|
@ApiOperation({ summary: "Create a new email user" })
|
||||||
@@ -48,14 +63,6 @@ export class UsersController {
|
|||||||
return this.usersService.deleteEmailUser(paramDto.id, businessId);
|
return this.usersService.deleteEmailUser(paramDto.id, businessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(":id")
|
|
||||||
@UseInterceptors(BusinessInterceptor)
|
|
||||||
@ApiOperation({ summary: "Update email user" })
|
|
||||||
@ApiResponse({ status: 200, description: "Email user updated successfully" })
|
|
||||||
updateEmailUser(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: UpdateEmailUserDto) {
|
|
||||||
return this.usersService.updateEmailUser(paramDto.id, businessId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch(":id/quota")
|
@Patch(":id/quota")
|
||||||
@UseInterceptors(BusinessInterceptor)
|
@UseInterceptors(BusinessInterceptor)
|
||||||
@ApiOperation({ summary: "Update email user quota" })
|
@ApiOperation({ summary: "Update email user quota" })
|
||||||
|
|||||||
Reference in New Issue
Block a user