Merge branch 'mailfunc'
This commit is contained in:
commit
a0eb8e1ed8
14
backend/.env
14
backend/.env
@ -12,3 +12,17 @@ AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||
AWS_S3_BUCKET_NAME=imk-data
|
||||
AWS_ENDPOINT_URL=https://eu2.contabostorage.com
|
||||
|
||||
#Email Configuration
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=taratur@gmail.com
|
||||
SMTP_PASS=dziy nccc svgg bovb
|
||||
EMAIL_FROM=taratur@gmail.com
|
||||
|
||||
# SMTP_HOST=imk.mk
|
||||
# SMTP_PORT=465
|
||||
# SMTP_USER=mailer@imk.mk
|
||||
# SMTP_PASSWORD=76Avtostoperski76
|
||||
# SMTP_FROM=mailer@imk.mk
|
||||
# FRONTEND_URL=https://imk.mk
|
||||
# ADMIN_EMAIL=petrovskidimitar@yandex.com
|
||||
|
||||
20
backend/package-lock.json
generated
20
backend/package-lock.json
generated
@ -22,9 +22,11 @@
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"nodemailer": "^6.10.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
@ -3860,6 +3862,15 @@
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport": {
|
||||
"version": "1.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz",
|
||||
@ -8679,6 +8690,15 @@
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
|
||||
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
|
||||
@ -33,9 +33,11 @@
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"nodemailer": "^6.10.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
@ -80,25 +80,7 @@ async uploadDocument(
|
||||
return this.adminService.getAllUsers();
|
||||
}
|
||||
|
||||
// @Post('test-document')
|
||||
// async testDocumentCreation() {
|
||||
// try {
|
||||
// const document = await this.prisma.document.create({
|
||||
// data: {
|
||||
// title: 'Test Document',
|
||||
// s3Key: 'test-key',
|
||||
// status: 'completed',
|
||||
// sharedWith: {
|
||||
// connect: { id: 2 } // ID of 'pero' user
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// return document;
|
||||
// } catch (error) {
|
||||
// console.error('Test document creation error:', error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@Post('users')
|
||||
async createUser(@Body() createUserDto: CreateUserDto) {
|
||||
|
||||
@ -3,10 +3,11 @@ import { AdminController } from './admin.controller';
|
||||
import { AdminService } from './admin.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { S3Module } from '../s3/s3.module';
|
||||
import { EmailModule } from '../email/email.module';
|
||||
|
||||
@Module({
|
||||
controllers: [AdminController],
|
||||
providers: [AdminService],
|
||||
imports: [PrismaModule, S3Module],
|
||||
imports: [PrismaModule, S3Module, EmailModule],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { S3Service } from '../s3/s3.service';
|
||||
import { UpdateDocumentDto } from '../dto/update-document.dto';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { EmailService } from '../email/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
private readonly logger = new Logger(AdminService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly s3Service: S3Service,
|
||||
private readonly emailService: EmailService,
|
||||
) {}
|
||||
|
||||
async getAllDocuments() {
|
||||
@ -71,14 +75,124 @@ export class AdminService {
|
||||
}
|
||||
|
||||
async shareDocument(documentId: number, userId: number) {
|
||||
return this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: {
|
||||
sharedWith: {
|
||||
connect: { id: userId },
|
||||
this.logger.log('=== Starting document share process ===');
|
||||
console.log('=== Starting document share process ===');
|
||||
this.logger.debug('Share request:', { documentId, userId });
|
||||
console.log('Share request:', { documentId, userId });
|
||||
|
||||
try {
|
||||
// Get the document with its current data
|
||||
const document = await this.prisma.document.findUnique({
|
||||
where: { id: documentId },
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
this.logger.error('Document not found:', { documentId });
|
||||
console.error('Document not found:', { documentId });
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
// Get the user we're sharing with
|
||||
const shareUser = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!shareUser) {
|
||||
this.logger.error('User not found:', { userId });
|
||||
console.error('User not found:', { userId });
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Update the document sharing
|
||||
const updatedDocument = await this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: {
|
||||
sharedWith: {
|
||||
connect: { id: userId },
|
||||
},
|
||||
},
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
sharedWith: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Send email notification to the user we're sharing with
|
||||
this.logger.log('Sending email notification to shared user:', {
|
||||
userId: shareUser.id,
|
||||
email: shareUser.email,
|
||||
name: shareUser.name,
|
||||
documentTitle: document.title
|
||||
});
|
||||
console.log('Sending email notification to shared user:', {
|
||||
userId: shareUser.id,
|
||||
email: shareUser.email,
|
||||
name: shareUser.name,
|
||||
documentTitle: document.title
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('Attempting to send email notification...');
|
||||
await this.emailService.sendDocumentNotification(
|
||||
shareUser.email,
|
||||
shareUser.name,
|
||||
document.title,
|
||||
'shared'
|
||||
);
|
||||
console.log('Email notification sent successfully');
|
||||
this.logger.log('Email notification sent successfully');
|
||||
} catch (emailError) {
|
||||
// Log the full error details
|
||||
console.error('Failed to send email notification:', emailError);
|
||||
this.logger.error('Failed to send email notification:', {
|
||||
error: emailError.message,
|
||||
code: emailError.code,
|
||||
command: emailError.command,
|
||||
response: emailError.response,
|
||||
responseCode: emailError.responseCode,
|
||||
stack: emailError.stack,
|
||||
});
|
||||
// Don't throw the error, just log it and continue
|
||||
}
|
||||
|
||||
this.logger.log('=== Document share process completed ===');
|
||||
console.log('=== Document share process completed ===');
|
||||
return updatedDocument;
|
||||
} catch (error) {
|
||||
this.logger.error('Error in shareDocument:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
stack: error.stack,
|
||||
});
|
||||
console.error('Error in shareDocument:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateDocumentStatus(documentId: number, status: string) {
|
||||
@ -94,36 +208,139 @@ export class AdminService {
|
||||
sharedWithId: number,
|
||||
uploadedById: number
|
||||
) {
|
||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
status: 'pending',
|
||||
sharedWith: {
|
||||
connect: { id: sharedWithId }
|
||||
},
|
||||
uploadedBy: {
|
||||
connect: { id: uploadedById }
|
||||
}
|
||||
},
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
sharedWith: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
this.logger.log('=== Starting document upload process ===');
|
||||
this.logger.debug('Upload parameters:', {
|
||||
title,
|
||||
sharedWithId,
|
||||
uploadedById,
|
||||
fileName: file.originalname,
|
||||
fileSize: file.size,
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.debug('Uploading file to S3...');
|
||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
this.logger.debug(`File uploaded to S3 successfully with key: ${s3Key}`);
|
||||
|
||||
this.logger.debug('Creating document record in database...');
|
||||
const document = await this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
status: 'pending',
|
||||
sharedWith: {
|
||||
connect: { id: sharedWithId }
|
||||
},
|
||||
uploadedBy: {
|
||||
connect: { id: uploadedById }
|
||||
}
|
||||
},
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
sharedWith: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.logger.debug('Document record created:', {
|
||||
id: document.id,
|
||||
title: document.title,
|
||||
uploadedBy: document.uploadedBy,
|
||||
sharedWith: document.sharedWith,
|
||||
});
|
||||
|
||||
// Send email notifications
|
||||
this.logger.log('=== Starting email notification process ===');
|
||||
|
||||
// Notify the user who the document is shared with
|
||||
const sharedUser = document.sharedWith[0];
|
||||
if (sharedUser) {
|
||||
this.logger.debug(`Preparing to send notification to shared user:`, {
|
||||
id: sharedUser.id,
|
||||
email: sharedUser.email,
|
||||
name: sharedUser.name,
|
||||
});
|
||||
try {
|
||||
this.logger.debug('Calling EmailService.sendDocumentNotification for shared user...');
|
||||
await this.emailService.sendDocumentNotification(
|
||||
sharedUser.email,
|
||||
sharedUser.name,
|
||||
document.title,
|
||||
'shared'
|
||||
);
|
||||
this.logger.debug('Shared user notification sent successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send notification to shared user:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Log the email service instance to verify it's properly injected
|
||||
this.logger.debug('EmailService instance:', {
|
||||
exists: !!this.emailService,
|
||||
type: typeof this.emailService,
|
||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('No shared user found in the document record');
|
||||
}
|
||||
|
||||
// Notify the uploader
|
||||
const uploader = document.uploadedBy;
|
||||
if (uploader) {
|
||||
this.logger.debug(`Preparing to send notification to uploader:`, {
|
||||
id: uploader.id,
|
||||
email: uploader.email,
|
||||
name: uploader.name,
|
||||
});
|
||||
try {
|
||||
this.logger.debug('Calling EmailService.sendDocumentNotification for uploader...');
|
||||
await this.emailService.sendDocumentNotification(
|
||||
uploader.email,
|
||||
uploader.name,
|
||||
document.title,
|
||||
'uploaded'
|
||||
);
|
||||
this.logger.debug('Uploader notification sent successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send notification to uploader:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Log the email service instance to verify it's properly injected
|
||||
this.logger.debug('EmailService instance:', {
|
||||
exists: !!this.emailService,
|
||||
type: typeof this.emailService,
|
||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('No uploader found in the document record');
|
||||
}
|
||||
|
||||
this.logger.log('=== Document upload process completed ===');
|
||||
return document;
|
||||
} catch (error) {
|
||||
this.logger.error('Error in uploadDocument:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthController } from './auth/auth.controller';
|
||||
import { DocumentsController } from './documents/documents.controller';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { EmailModule } from './email/email.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -39,6 +40,7 @@ import { JwtModule } from '@nestjs/jwt';
|
||||
ClientModule,
|
||||
S3Module,
|
||||
PrismaModule,
|
||||
EmailModule,
|
||||
],
|
||||
controllers: [AppController, AuthController, DocumentsController],
|
||||
providers: [
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
UseGuards,
|
||||
Get,
|
||||
Request,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginDto } from '../dto/login.dto';
|
||||
@ -15,29 +16,137 @@ import { AdminGuard } from './admin.guard';
|
||||
//@UseGuards(JwtAuthGuard, AdminGuard)
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
private readonly logger = new Logger(AuthController.name);
|
||||
|
||||
constructor(private authService: AuthService) {
|
||||
this.logger.log('AuthController initialized');
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() loginDto: LoginDto) {
|
||||
const user = await this.authService.validateUser(
|
||||
loginDto.username,
|
||||
loginDto.password,
|
||||
);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
this.logger.log('=== Login endpoint hit ===');
|
||||
this.logger.debug('Raw request body:', {
|
||||
username: loginDto.username,
|
||||
email: loginDto.email,
|
||||
hasPassword: !!loginDto.password,
|
||||
});
|
||||
|
||||
const email = loginDto.getEmail();
|
||||
this.logger.debug('Normalized login request:', {
|
||||
email,
|
||||
hasPassword: !!loginDto.password,
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.debug('Calling AuthService.validateUser...');
|
||||
const user = await this.authService.validateUser(
|
||||
email,
|
||||
loginDto.password,
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
this.logger.warn(`Login failed: Invalid credentials for ${email}`);
|
||||
throw new UnauthorizedException('Invalid email or password');
|
||||
}
|
||||
|
||||
this.logger.debug('User validated successfully:', {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
});
|
||||
|
||||
this.logger.debug('Calling AuthService.login...');
|
||||
const result = await this.authService.login(user);
|
||||
|
||||
this.logger.debug('Login successful, returning response:', {
|
||||
hasAccessToken: !!result.access_token,
|
||||
user: {
|
||||
id: result.user.id,
|
||||
email: result.user.email,
|
||||
name: result.user.name,
|
||||
isAdmin: result.user.isAdmin,
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof UnauthorizedException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.error('Login failed:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
body: {
|
||||
username: loginDto.username,
|
||||
email: loginDto.email,
|
||||
hasPassword: !!loginDto.password,
|
||||
},
|
||||
});
|
||||
throw new UnauthorizedException('Invalid email or password');
|
||||
}
|
||||
return this.authService.login(user);
|
||||
}
|
||||
|
||||
@Post('register')
|
||||
async register(@Body() createUserDto: CreateUserDto) {
|
||||
return this.authService.createUser(createUserDto);
|
||||
console.log('=== Registration endpoint hit ===');
|
||||
this.logger.log('=== Registration endpoint hit ===');
|
||||
console.log('Registration request received:', createUserDto);
|
||||
this.logger.log('Registration request received:', {
|
||||
email: createUserDto.email,
|
||||
name: createUserDto.name,
|
||||
hasPassword: !!createUserDto.password
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('Calling AuthService.createUser...');
|
||||
this.logger.log('Calling AuthService.createUser...');
|
||||
const result = await this.authService.createUser(createUserDto);
|
||||
console.log('Registration successful:', result);
|
||||
this.logger.log('Registration successful:', {
|
||||
id: result.id,
|
||||
email: result.email,
|
||||
name: result.name,
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error);
|
||||
this.logger.error('Registration failed:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@Post('create-admin')
|
||||
async createAdmin(@Body() createUserDto: CreateUserDto) {
|
||||
return this.authService.createUser(createUserDto, true);
|
||||
this.logger.log('=== Create admin endpoint hit ===');
|
||||
this.logger.debug('Admin creation request received:', {
|
||||
email: createUserDto.email,
|
||||
name: createUserDto.name,
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.debug('Calling AuthService.createUser with isAdmin=true...');
|
||||
const result = await this.authService.createUser(createUserDto, true);
|
||||
this.logger.debug('Admin creation successful:', {
|
||||
id: result.id,
|
||||
email: result.email,
|
||||
name: result.name,
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error('Admin creation failed:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
||||
@ -1,23 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LocalStrategy } from './local.strategy';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
//import { UsersModule } from '../users/users.module';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { jwtConstants } from './constants';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import { EmailModule } from '../email/email.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule,
|
||||
PrismaModule,
|
||||
JwtModule.register({
|
||||
secret: jwtConstants.secret,
|
||||
signOptions: { expiresIn: '60m' },
|
||||
ConfigModule,
|
||||
EmailModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
secret: configService.get('JWT_SECRET'),
|
||||
signOptions: { expiresIn: '1h' },
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@ -1,71 +1,201 @@
|
||||
import { Injectable, ConflictException } from '@nestjs/common';
|
||||
import { Injectable, ConflictException, Logger } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { EmailService } from '../email/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
private emailService: EmailService,
|
||||
) {
|
||||
this.logger.log('AuthService initialized with EmailService');
|
||||
}
|
||||
|
||||
async validateUser(email: string, password: string): Promise<any> {
|
||||
this.logger.debug('Validating user:', { email });
|
||||
|
||||
try {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
password: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.debug('Database query result:', {
|
||||
userFound: !!user,
|
||||
userData: user ? {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
} : null,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
this.logger.debug('User not found:', { email });
|
||||
return null;
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
this.logger.debug('Password validation result:', { isPasswordValid });
|
||||
|
||||
if (!isPasswordValid) {
|
||||
this.logger.debug('Invalid password for user:', { email });
|
||||
return null;
|
||||
}
|
||||
|
||||
async validateUser(username: string, password: string): Promise<any> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email: username },
|
||||
});
|
||||
if (user && (await bcrypt.compare(password, user.password))) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { password, ...result } = user;
|
||||
const { password: _, ...result } = user;
|
||||
this.logger.debug('User validated successfully:', {
|
||||
id: result.id,
|
||||
email: result.email,
|
||||
name: result.name,
|
||||
isAdmin: result.isAdmin,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error('Error validating user:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
// const payload = { username: user.email, sub: user.id };
|
||||
// return {
|
||||
// access_token: this.jwtService.sign(payload),
|
||||
// };
|
||||
const payload = { username: user.username, sub: user.id };
|
||||
console.log(payload);
|
||||
return {
|
||||
access_token: this.jwtService.sign(payload, {
|
||||
this.logger.debug('Login called with user:', {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
email: user.email,
|
||||
sub: user.id,
|
||||
};
|
||||
|
||||
this.logger.debug('Generated JWT payload:', payload);
|
||||
|
||||
const token = this.jwtService.sign(payload, {
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
this.logger.debug('JWT token generated successfully');
|
||||
|
||||
return {
|
||||
access_token: token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error generating JWT token:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(
|
||||
createUserDto: CreateUserDto,
|
||||
isAdmin: boolean = false,
|
||||
): Promise<any> {
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { email: createUserDto.email },
|
||||
});
|
||||
console.log('=== Starting user creation process ===');
|
||||
this.logger.log('=== Starting user creation process ===');
|
||||
console.log('Creating user:', { ...createUserDto, isAdmin, password: '[REDACTED]' });
|
||||
this.logger.log('Creating user:', { ...createUserDto, isAdmin, password: '[REDACTED]' });
|
||||
|
||||
if (existingUser) {
|
||||
throw new ConflictException('Email already exists');
|
||||
try {
|
||||
// Check for existing user
|
||||
console.log('Checking for existing user...');
|
||||
this.logger.log('Checking for existing user...');
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { email: createUserDto.email },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
console.log('User already exists:', createUserDto.email);
|
||||
this.logger.warn('User already exists:', createUserDto.email);
|
||||
throw new ConflictException('Email already exists');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
console.log('Hashing password...');
|
||||
this.logger.log('Hashing password...');
|
||||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
||||
|
||||
// Create user
|
||||
console.log('Creating user in database...');
|
||||
this.logger.log('Creating user in database...');
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: {
|
||||
email: createUserDto.email,
|
||||
password: hashedPassword,
|
||||
name: createUserDto.name,
|
||||
isAdmin: isAdmin,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('User created successfully:', { id: newUser.id, email: newUser.email });
|
||||
this.logger.log('User created successfully:', { id: newUser.id, email: newUser.email });
|
||||
|
||||
// Send welcome email
|
||||
console.log('Attempting to send welcome email...');
|
||||
this.logger.log('Attempting to send welcome email...');
|
||||
|
||||
try {
|
||||
console.log('Calling EmailService.sendWelcomeEmail...');
|
||||
this.logger.log('Calling EmailService.sendWelcomeEmail...');
|
||||
await this.emailService.sendWelcomeEmail(newUser.email, newUser.name);
|
||||
console.log('Welcome email sent successfully');
|
||||
this.logger.log('Welcome email sent successfully');
|
||||
} catch (emailError) {
|
||||
console.error('Failed to send welcome email:', emailError);
|
||||
this.logger.error('Failed to send welcome email:', {
|
||||
error: emailError.message,
|
||||
code: emailError.code,
|
||||
command: emailError.command,
|
||||
response: emailError.response,
|
||||
stack: emailError.stack,
|
||||
});
|
||||
// Don't throw the error, just log it
|
||||
}
|
||||
|
||||
// Return user data
|
||||
const { password, ...result } = newUser;
|
||||
console.log('=== User creation completed ===');
|
||||
this.logger.log('=== User creation completed ===');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error in createUser:', error);
|
||||
this.logger.error('Error in createUser:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
||||
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: {
|
||||
email: createUserDto.email,
|
||||
password: hashedPassword,
|
||||
name: createUserDto.name,
|
||||
isAdmin: isAdmin,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { password, ...result } = newUser;
|
||||
console.log(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// async getUserInfo(userId: number) {
|
||||
|
||||
@ -1,19 +1,63 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(configService: ConfigService) {
|
||||
private readonly logger = new Logger(JwtStrategy.name);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private prisma: PrismaService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get('JWT_SECRET'),
|
||||
});
|
||||
this.logger.log('JwtStrategy initialized');
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return { userId: payload.sub, username: payload.username };
|
||||
this.logger.debug('Validating JWT payload:', payload);
|
||||
|
||||
try {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: payload.sub },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
this.logger.warn('JWT validation failed: User not found:', { userId: payload.sub });
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
|
||||
this.logger.debug('JWT validation successful:', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
});
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('JWT validation error:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { DocumentsService } from './documents.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { S3Service } from '../s3/s3.service';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
|
||||
interface S3File {
|
||||
buffer: Buffer;
|
||||
@ -18,7 +19,51 @@ export class DocumentsController {
|
||||
constructor(
|
||||
private readonly documentsService: DocumentsService,
|
||||
private readonly s3Service: S3Service
|
||||
) {}
|
||||
) {
|
||||
this.logger.log('DocumentsController initialized');
|
||||
}
|
||||
|
||||
@Post('upload')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadDocument(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Body('title') title: string,
|
||||
@Body('sharedWithId') sharedWithId: string,
|
||||
@Request() req,
|
||||
) {
|
||||
this.logger.log('=== Document upload endpoint hit ===');
|
||||
this.logger.debug('Upload request received:', {
|
||||
fileName: file?.originalname,
|
||||
fileSize: file?.size,
|
||||
title,
|
||||
sharedWithId,
|
||||
uploadedById: req.user.id,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await this.documentsService.uploadDocument(
|
||||
file,
|
||||
title,
|
||||
Number(sharedWithId),
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.logger.debug('Document upload successful:', {
|
||||
documentId: result.id,
|
||||
title: result.title,
|
||||
s3Key: result.s3Key,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error('Document upload failed:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Get('shared/:userId')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
||||
14
backend/src/documents/documents.module.ts
Normal file
14
backend/src/documents/documents.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DocumentsService } from './documents.service';
|
||||
import { DocumentsController } from './documents.controller';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { S3Module } from '../s3/s3.module';
|
||||
import { EmailModule } from '../email/email.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, S3Module, EmailModule],
|
||||
controllers: [DocumentsController],
|
||||
providers: [DocumentsService],
|
||||
exports: [DocumentsService],
|
||||
})
|
||||
export class DocumentsModule {}
|
||||
@ -2,6 +2,7 @@ import { Injectable, Logger, NotFoundException, ForbiddenException } from '@nest
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { S3Service } from '../s3/s3.service';
|
||||
import { Document, User, Prisma } from '@prisma/client';
|
||||
import { EmailService } from '../email/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentsService {
|
||||
@ -9,8 +10,16 @@ export class DocumentsService {
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly s3Service: S3Service
|
||||
) {}
|
||||
private readonly s3Service: S3Service,
|
||||
private readonly emailService: EmailService,
|
||||
) {
|
||||
this.logger.log('DocumentsService initialized with EmailService');
|
||||
this.logger.debug('EmailService instance:', {
|
||||
exists: !!this.emailService,
|
||||
type: typeof this.emailService,
|
||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||
});
|
||||
}
|
||||
|
||||
async findDocumentByS3Key(s3Key: string) {
|
||||
return this.prisma.document.findFirst({
|
||||
@ -85,36 +94,139 @@ export class DocumentsService {
|
||||
sharedWithId: number,
|
||||
uploadedById: number
|
||||
) {
|
||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
status: 'pending',
|
||||
uploadedBy: {
|
||||
connect: { id: uploadedById }
|
||||
},
|
||||
sharedWith: {
|
||||
connect: { id: sharedWithId }
|
||||
}
|
||||
},
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
sharedWith: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
this.logger.log('=== Starting document upload process ===');
|
||||
this.logger.debug('Upload parameters:', {
|
||||
title,
|
||||
sharedWithId,
|
||||
uploadedById,
|
||||
fileName: file.originalname,
|
||||
fileSize: file.size,
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.debug('Uploading file to S3...');
|
||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
this.logger.debug(`File uploaded to S3 successfully with key: ${s3Key}`);
|
||||
|
||||
this.logger.debug('Creating document record in database...');
|
||||
const document = await this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
status: 'pending',
|
||||
uploadedBy: {
|
||||
connect: { id: uploadedById }
|
||||
},
|
||||
sharedWith: {
|
||||
connect: { id: sharedWithId }
|
||||
}
|
||||
},
|
||||
include: {
|
||||
uploadedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
sharedWith: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.logger.debug('Document record created:', {
|
||||
id: document.id,
|
||||
title: document.title,
|
||||
uploadedBy: document.uploadedBy,
|
||||
sharedWith: document.sharedWith,
|
||||
});
|
||||
|
||||
// Send email notifications
|
||||
this.logger.log('=== Starting email notification process ===');
|
||||
|
||||
// Notify the user who the document is shared with
|
||||
const sharedUser = document.sharedWith[0];
|
||||
if (sharedUser) {
|
||||
this.logger.debug(`Preparing to send notification to shared user:`, {
|
||||
id: sharedUser.id,
|
||||
email: sharedUser.email,
|
||||
name: sharedUser.name,
|
||||
});
|
||||
try {
|
||||
this.logger.debug('Calling EmailService.sendDocumentNotification for shared user...');
|
||||
await this.emailService.sendDocumentNotification(
|
||||
sharedUser.email,
|
||||
sharedUser.name,
|
||||
document.title,
|
||||
'shared'
|
||||
);
|
||||
this.logger.debug('Shared user notification sent successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send notification to shared user:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Log the email service instance to verify it's properly injected
|
||||
this.logger.debug('EmailService instance:', {
|
||||
exists: !!this.emailService,
|
||||
type: typeof this.emailService,
|
||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('No shared user found in the document record');
|
||||
}
|
||||
|
||||
// Notify the uploader
|
||||
const uploader = document.uploadedBy;
|
||||
if (uploader) {
|
||||
this.logger.debug(`Preparing to send notification to uploader:`, {
|
||||
id: uploader.id,
|
||||
email: uploader.email,
|
||||
name: uploader.name,
|
||||
});
|
||||
try {
|
||||
this.logger.debug('Calling EmailService.sendDocumentNotification for uploader...');
|
||||
await this.emailService.sendDocumentNotification(
|
||||
uploader.email,
|
||||
uploader.name,
|
||||
document.title,
|
||||
'uploaded'
|
||||
);
|
||||
this.logger.debug('Uploader notification sent successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send notification to uploader:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Log the email service instance to verify it's properly injected
|
||||
this.logger.debug('EmailService instance:', {
|
||||
exists: !!this.emailService,
|
||||
type: typeof this.emailService,
|
||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('No uploader found in the document record');
|
||||
}
|
||||
|
||||
this.logger.log('=== Document upload process completed ===');
|
||||
return document;
|
||||
} catch (error) {
|
||||
this.logger.error('Error in uploadDocument:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { IsString, IsEmail, MinLength, IsBoolean } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsString()
|
||||
@ -7,6 +8,7 @@ export class CreateUserDto {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@Transform(({ value }) => value?.trim())
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
import { IsString, IsNotEmpty, IsEmail, ValidateIf } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class LoginDto {
|
||||
@ValidateIf(o => !o.email)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
username?: string;
|
||||
|
||||
@ValidateIf(o => !o.username)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsEmail()
|
||||
email?: string;
|
||||
|
||||
@Transform(({ value }) => value?.trim())
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
|
||||
// Helper method to get the email (either from email or username field)
|
||||
getEmail(): string {
|
||||
return this.email || this.username;
|
||||
}
|
||||
}
|
||||
|
||||
10
backend/src/email/email.module.ts
Normal file
10
backend/src/email/email.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { EmailService } from './email.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [EmailService],
|
||||
exports: [EmailService],
|
||||
})
|
||||
export class EmailModule {}
|
||||
187
backend/src/email/email.service.ts
Normal file
187
backend/src/email/email.service.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
private transporter: nodemailer.Transporter;
|
||||
private readonly logger = new Logger(EmailService.name);
|
||||
private readonly from: string;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
console.log('Initializing EmailService...'); // Direct console log for debugging
|
||||
this.logger.log('Initializing EmailService...');
|
||||
|
||||
// Load config
|
||||
const host = this.configService.get<string>('SMTP_HOST');
|
||||
const port = this.configService.get<number>('SMTP_PORT');
|
||||
const user = this.configService.get<string>('SMTP_USER');
|
||||
const pass = this.configService.get<string>('SMTP_PASS');
|
||||
this.from = this.configService.get<string>('EMAIL_FROM');
|
||||
|
||||
console.log('Email Config:', { host, port, user, from: this.from }); // Direct console log
|
||||
this.logger.log('Email Config:', { host, port, user, from: this.from });
|
||||
|
||||
// Create transporter with Gmail settings
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587, // Use STARTTLS port
|
||||
secure: false, // Use STARTTLS
|
||||
auth: { user, pass },
|
||||
debug: true, // Enable debug logs
|
||||
logger: true // Enable transport level logging
|
||||
});
|
||||
|
||||
// Verify connection
|
||||
this.verifyConnection();
|
||||
}
|
||||
|
||||
private async verifyConnection() {
|
||||
try {
|
||||
console.log('Verifying SMTP connection...'); // Direct console log
|
||||
this.logger.log('Verifying SMTP connection...');
|
||||
|
||||
const verification = await this.transporter.verify();
|
||||
|
||||
console.log('SMTP connection verified successfully!', verification); // Direct console log
|
||||
this.logger.log('SMTP connection verified successfully!', verification);
|
||||
} catch (error) {
|
||||
console.error('SMTP connection failed:', error); // Direct console log
|
||||
this.logger.error('SMTP connection failed:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
response: error.response,
|
||||
responseCode: error.responseCode,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendWelcomeEmail(userEmail: string, username: string): Promise<void> {
|
||||
console.log(`Sending welcome email to ${userEmail}...`); // Direct console log
|
||||
this.logger.log(`Sending welcome email to ${userEmail}...`);
|
||||
|
||||
const mailOptions = {
|
||||
from: `"IMK Platform" <${this.from}>`,
|
||||
to: userEmail,
|
||||
subject: 'Welcome to IMK Platform!',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2>Welcome to IMK Platform!</h2>
|
||||
<p>Dear ${username},</p>
|
||||
<p>Thank you for joining IMK Platform. We're excited to have you on board!</p>
|
||||
<p>You can now start using our platform to manage and share your documents securely.</p>
|
||||
<p>If you have any questions or need assistance, please don't hesitate to contact our support team.</p>
|
||||
<p>Best regards,<br>The IMK Team</p>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('Attempting to send email with options:', mailOptions); // Direct console log
|
||||
this.logger.log('Attempting to send email with options:', {
|
||||
to: mailOptions.to,
|
||||
from: mailOptions.from,
|
||||
subject: mailOptions.subject
|
||||
});
|
||||
|
||||
const info = await this.transporter.sendMail(mailOptions);
|
||||
|
||||
console.log('Email sent successfully:', info); // Direct console log
|
||||
this.logger.log('Email sent successfully:', {
|
||||
messageId: info.messageId,
|
||||
response: info.response,
|
||||
accepted: info.accepted,
|
||||
rejected: info.rejected,
|
||||
envelope: info.envelope,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to send email:', error); // Direct console log
|
||||
this.logger.error('Failed to send email:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
response: error.response,
|
||||
responseCode: error.responseCode,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendDocumentNotification(
|
||||
userEmail: string,
|
||||
username: string,
|
||||
documentName: string,
|
||||
action: 'uploaded' | 'shared',
|
||||
): Promise<void> {
|
||||
console.log('=== Starting document notification email process ===');
|
||||
this.logger.log('=== Starting document notification email process ===');
|
||||
console.log(`Preparing notification for ${userEmail}, document: ${documentName}, action: ${action}`);
|
||||
this.logger.log(`Preparing notification for ${userEmail}, document: ${documentName}, action: ${action}`);
|
||||
|
||||
const actionText = action === 'uploaded' ? 'uploaded' : 'shared with you';
|
||||
const mailOptions = {
|
||||
from: `"IMK Platform" <${this.from}>`,
|
||||
to: userEmail,
|
||||
subject: `New Document ${actionText} - IMK Platform`,
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2>New Document Notification</h2>
|
||||
<p>Dear ${username},</p>
|
||||
<p>A new document "${documentName}" has been ${actionText} on the IMK Platform.</p>
|
||||
<p>You can access this document by logging into your account.</p>
|
||||
<p>If you have any questions or concerns, please contact our support team.</p>
|
||||
<p>Best regards,<br>The IMK Team</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('Sending document notification email with options:', {
|
||||
to: mailOptions.to,
|
||||
from: mailOptions.from,
|
||||
subject: mailOptions.subject,
|
||||
documentName,
|
||||
action
|
||||
});
|
||||
this.logger.log('Sending document notification email with options:', {
|
||||
to: mailOptions.to,
|
||||
from: mailOptions.from,
|
||||
subject: mailOptions.subject,
|
||||
documentName,
|
||||
action
|
||||
});
|
||||
|
||||
const info = await this.transporter.sendMail(mailOptions);
|
||||
|
||||
console.log('Document notification email sent successfully:', {
|
||||
messageId: info.messageId,
|
||||
response: info.response,
|
||||
accepted: info.accepted,
|
||||
rejected: info.rejected,
|
||||
envelope: info.envelope,
|
||||
});
|
||||
this.logger.log('Document notification email sent successfully:', {
|
||||
messageId: info.messageId,
|
||||
response: info.response,
|
||||
accepted: info.accepted,
|
||||
rejected: info.rejected,
|
||||
envelope: info.envelope,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to send document notification email:', error);
|
||||
this.logger.error('Failed to send document notification email:', {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
response: error.response,
|
||||
responseCode: error.responseCode,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,46 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
await app.listen(3000);
|
||||
const logger = new Logger('Bootstrap');
|
||||
logger.log('Starting application...');
|
||||
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: ['error', 'warn', 'log', 'debug', 'verbose'], // Enable all log levels
|
||||
});
|
||||
|
||||
logger.log('Configuring application...');
|
||||
|
||||
// Enable validation with detailed error messages
|
||||
app.useGlobalPipes(new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
enableDebugMessages: true, // Add detailed validation error messages
|
||||
validationError: {
|
||||
target: false,
|
||||
value: false,
|
||||
},
|
||||
}));
|
||||
|
||||
// Enable CORS with credentials
|
||||
app.enableCors({
|
||||
origin: true, // or specify your frontend URL
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
logger.log(`Starting server on port ${port}...`);
|
||||
|
||||
await app.listen(port);
|
||||
logger.log(`Application is running on: ${await app.getUrl()}`);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
|
||||
bootstrap();
|
||||
|
||||
@ -61,7 +61,7 @@ export const downloadDocument = async (key) => {
|
||||
|
||||
|
||||
export const createUser = (userData) => {
|
||||
return api.post('/admin/users', {
|
||||
return api.post('/auth/register', {
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
@ -69,7 +69,7 @@ export const createUser = (userData) => {
|
||||
});
|
||||
};
|
||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
||||
export const shareDocument = (documentId, userIds) => api.post(`/admin/documents/${documentId}/share`, { userIds });
|
||||
export const shareDocument = (documentId, userId) => api.post(`/admin/documents/${documentId}/share`, { userId });
|
||||
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
|
||||
|
||||
export const uploadDocument = async (formData) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user