email set-up complete

This commit is contained in:
dimitar 2024-11-02 18:54:11 +01:00
parent befb521604
commit 609475271e
15 changed files with 8441 additions and 89 deletions

View File

@ -10,4 +10,10 @@ AWS_REGION=EU2
AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6 AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4 AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
AWS_S3_BUCKET_NAME=imk-data AWS_S3_BUCKET_NAME=imk-data
AWS_ENDPOINT_URL=https://eu2.contabostorage.com AWS_ENDPOINT_URL=https://eu2.contabostorage.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

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
"@aws-sdk/client-s3": "^3.679.0", "@aws-sdk/client-s3": "^3.679.0",
"@aws-sdk/lib-storage": "^3.679.0", "@aws-sdk/lib-storage": "^3.679.0",
"@aws-sdk/s3-request-presigner": "^3.679.0", "@aws-sdk/s3-request-presigner": "^3.679.0",
"@nestjs-modules/mailer": "^1.6.1",
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0", "@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
@ -36,6 +37,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"nodemailer": "^6.9.16",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
@ -45,13 +47,14 @@
"typeorm": "^0.3.20" "typeorm": "^0.3.20"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.0", "@nestjs/cli": "^10.4.5",
"@nestjs/schematics": "^10.0.0", "@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0", "@nestjs/testing": "^10.0.0",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/nodemailer": "^6.4.16",
"@types/passport-jwt": "^4.0.1", "@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38", "@types/passport-local": "^1.0.38",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",

View File

@ -3,10 +3,11 @@ import { AdminController } from './admin.controller';
import { AdminService } from './admin.service'; import { AdminService } from './admin.service';
import { PrismaModule } from '../prisma/prisma.module'; import { PrismaModule } from '../prisma/prisma.module';
import { S3Module } from '../s3/s3.module'; import { S3Module } from '../s3/s3.module';
import { EmailModule } from '../email/email.module';
@Module({ @Module({
controllers: [AdminController], controllers: [AdminController],
providers: [AdminService], providers: [AdminService],
imports: [PrismaModule, S3Module], imports: [PrismaModule, S3Module, EmailModule],
}) })
export class AdminModule {} export class AdminModule {}

View File

@ -4,12 +4,14 @@ import { S3Service } from '../s3/s3.service';
import { UpdateDocumentDto } from '../dto/update-document.dto'; import { UpdateDocumentDto } from '../dto/update-document.dto';
import { CreateUserDto } from '../dto/create-user.dto'; import { CreateUserDto } from '../dto/create-user.dto';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { EmailService } from 'src/email/email.service';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly s3Service: S3Service, private readonly s3Service: S3Service,
private readonly emailService: EmailService,
) {} ) {}
async getAllDocuments() { async getAllDocuments() {
@ -47,6 +49,7 @@ export class AdminService {
data: { data: {
...createUserDto, ...createUserDto,
password: hashedPassword, password: hashedPassword,
isAdmin: true,
}, },
}); });
} }
@ -71,14 +74,31 @@ export class AdminService {
} }
async shareDocument(documentId: number, userId: number) { async shareDocument(documentId: number, userId: number) {
return this.prisma.document.update({ const document = await this.prisma.document.update({
where: { id: documentId }, where: { id: documentId },
data: { data: {
sharedWith: { sharedWith: {
connect: { id: userId }, connect: { id: userId },
}, },
}, },
include: {
uploadedBy: true,
sharedWith: true,
},
}); });
const sharedWithUser = await this.prisma.user.findUnique({
where: { id: userId },
});
// Send email notification
await this.emailService.sendDocumentSharedNotification(
sharedWithUser.email,
document.title,
document.uploadedBy.name,
);
return document;
} }
async updateDocumentStatus(documentId: number, status: string) { async updateDocumentStatus(documentId: number, status: string) {

View File

@ -15,18 +15,11 @@ import { ConfigModule } from '@nestjs/config';
import { AuthController } from './auth/auth.controller'; import { AuthController } from './auth/auth.controller';
import { DocumentsController } from './documents/documents.controller'; import { DocumentsController } from './documents/documents.controller';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { EmailModule } from './email/email.module';
@Module({ @Module({
imports: [ imports: [
// TypeOrmModule.forRoot({ EmailModule,
// type: 'postgres',
// host: 'localhost',
// port: 5432,
// username: 'root',
// password: 'admin',
// database: 'imk',
// synchronize: true,
// }),
ConfigModule.forRoot({ ConfigModule.forRoot({
isGlobal: true, isGlobal: true,
}), }),

View File

@ -37,7 +37,7 @@ export class AuthController {
//@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@Post('create-admin') @Post('create-admin')
async createAdmin(@Body() createUserDto: CreateUserDto) { async createAdmin(@Body() createUserDto: CreateUserDto) {
return this.authService.createUser(createUserDto, true); return this.authService.createUser(createUserDto);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)

View File

@ -7,6 +7,7 @@ import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants'; import { jwtConstants } from './constants';
import { PrismaModule } from 'src/prisma/prisma.module'; import { PrismaModule } from 'src/prisma/prisma.module';
import { EmailModule } from 'src/email/email.module';
@Module({ @Module({
imports: [ imports: [
@ -16,6 +17,7 @@ import { PrismaModule } from 'src/prisma/prisma.module';
secret: jwtConstants.secret, secret: jwtConstants.secret,
signOptions: { expiresIn: '60m' }, signOptions: { expiresIn: '60m' },
}), }),
EmailModule,
], ],
providers: [AuthService, LocalStrategy, JwtStrategy], providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService], exports: [AuthService],

View File

@ -4,13 +4,15 @@ import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { CreateUserDto } from '../dto/create-user.dto'; import { CreateUserDto } from '../dto/create-user.dto';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { EmailService } from 'src/email/email.service';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
constructor( constructor(
private prisma: PrismaService, private prisma: PrismaService,
private jwtService: JwtService, private jwtService: JwtService,
private configService: ConfigService, private configService: ConfigService,
private emailService: EmailService,
) {} ) {}
async validateUser(username: string, password: string): Promise<any> { async validateUser(username: string, password: string): Promise<any> {
@ -39,46 +41,59 @@ export class AuthService {
}; };
} }
async createUser( // async createUser(
createUserDto: CreateUserDto, // createUserDto: CreateUserDto,
isAdmin: boolean = false, // isAdmin: boolean = false,
): Promise<any> { // ): Promise<any> {
const existingUser = await this.prisma.user.findUnique({ // const existingUser = await this.prisma.user.findUnique({
where: { email: createUserDto.email }, // where: { email: createUserDto.email },
}); // });
if (existingUser) { // if (existingUser) {
throw new ConflictException('Email already exists'); // throw new ConflictException('Email already exists');
} // }
const hashedPassword = await bcrypt.hash(createUserDto.password, 10); // const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
const newUser = await this.prisma.user.create({ // 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 createUser(createUserDto: CreateUserDto) {
const user = await this.prisma.user.create({
data: { data: {
email: createUserDto.email, ...createUserDto,
password: hashedPassword, password: await bcrypt.hash(createUserDto.password, 10),
name: createUserDto.name,
isAdmin: isAdmin,
}, },
}); });
// eslint-disable-next-line @typescript-eslint/no-unused-vars // Send welcome email
const { password, ...result } = newUser; await this.emailService.sendWelcomeEmail(user.email, user.name);
console.log(result); return user;
return result;
} }
// async getUserInfo(userId: number) { async requestPasswordReset(email: string) {
// return this.prisma.user.findUnique({ const user = await this.prisma.user.findUnique({ where: { email } });
// where: { id: userId }, if (!user) return;
// select: {
// id: true, const resetToken = this.jwtService.sign(
// name: true, { email },
// email: true, { expiresIn: '1h', secret: process.env.JWT_RESET_SECRET },
// isAdmin: true, );
// },
// }); await this.emailService.sendPasswordResetEmail(email, resetToken);
// } }
async getUserInfo(userId: number) { async getUserInfo(userId: number) {
if (!userId) { if (!userId) {
throw new Error('User ID is required'); throw new Error('User ID is required');

View File

@ -0,0 +1,28 @@
import { Controller, Post, Body } from '@nestjs/common';
import { EmailService } from './email.service';
import { MailerService } from '@nestjs-modules/mailer';
@Controller('contact')
export class EmailController {
constructor(private emailService: MailerService) {}
@Post()
async sendContactEmail(@Body() contactData: {
name: string;
email: string;
message: string;
}) {
await this.emailService.sendMail({
to: process.env.CONTACT_EMAIL,
subject: `Contact Form: ${contactData.name}`,
html: `
<h1>New Contact Form Submission</h1>
<p><strong>From:</strong> ${contactData.name}</p>
<p><strong>Email:</strong> ${contactData.email}</p>
<p><strong>Message:</strong></p>
<p>${contactData.message}</p>
`,
});
return { message: 'Contact form submitted successfully' };
}
}

View File

@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { EmailService } from './email.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
MailerModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => ({
transport: {
host: config.get('SMTP_HOST'),
port: config.get('SMTP_PORT'),
secure: true,
auth: {
user: config.get('SMTP_USER'),
pass: config.get('SMTP_PASSWORD'),
},
},
defaults: {
from: `"No Reply" <${config.get('SMTP_FROM')}>`,
},
}),
inject: [ConfigService],
}),
],
providers: [EmailService],
exports: [EmailService],
})
export class EmailModule {}

View File

@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';
@Injectable()
export class EmailService {
constructor(private mailerService: MailerService) {}
async sendWelcomeEmail(email: string, name: string) {
await this.mailerService.sendMail({
to: email,
subject: 'Welcome to IMK Platform',
html: `
<h1>Welcome ${name}!</h1>
<p>Your account has been created successfully.</p>
<p>You can now login to access your documents.</p>
`,
});
}
async sendPasswordResetEmail(email: string, resetToken: string) {
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
await this.mailerService.sendMail({
to: email,
subject: 'Password Reset Request',
html: `
<h1>Password Reset</h1>
<p>Click the link below to reset your password:</p>
<a href="${resetLink}">Reset Password</a>
<p>This link will expire in 1 hour.</p>
`,
});
}
async sendDocumentSharedNotification(email: string, documentTitle: string, sharedByName: string) {
await this.mailerService.sendMail({
to: email,
subject: 'New Document Shared With You',
html: `
<h1>New Document Available</h1>
<p>${sharedByName} has shared a document with you: "${documentTitle}"</p>
<p>Login to your account to view the document.</p>
`,
});
}
}

2962
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

2977
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
{ {
"dependencies": { "dependencies": {
"@nestjs/cli": "^10.4.5",
"axios": "^1.7.7", "axios": "^1.7.7",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.11.11", "framer-motion": "^11.11.11",