email set-up complete
This commit is contained in:
parent
befb521604
commit
609475271e
@ -11,3 +11,9 @@ AWS_ACCESS_KEY_ID=4d2f5655369a02100375e3247d7e1fe6
|
||||
AWS_SECRET_ACCESS_KEY=6d4723e14c0d799b89948c24dbe983e4
|
||||
AWS_S3_BUCKET_NAME=imk-data
|
||||
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
|
||||
|
||||
2355
backend/imk-backend/package-lock.json
generated
2355
backend/imk-backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
||||
"@aws-sdk/client-s3": "^3.679.0",
|
||||
"@aws-sdk/lib-storage": "^3.679.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
||||
"@nestjs-modules/mailer": "^1.6.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
@ -36,6 +37,7 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"nodemailer": "^6.9.16",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
@ -45,13 +47,14 @@
|
||||
"typeorm": "^0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/cli": "^10.4.5",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/supertest": "^2.0.12",
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -4,12 +4,14 @@ 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 'src/email/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly s3Service: S3Service,
|
||||
private readonly emailService: EmailService,
|
||||
) {}
|
||||
|
||||
async getAllDocuments() {
|
||||
@ -47,6 +49,7 @@ export class AdminService {
|
||||
data: {
|
||||
...createUserDto,
|
||||
password: hashedPassword,
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -71,14 +74,31 @@ export class AdminService {
|
||||
}
|
||||
|
||||
async shareDocument(documentId: number, userId: number) {
|
||||
return this.prisma.document.update({
|
||||
const document = await this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: {
|
||||
sharedWith: {
|
||||
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) {
|
||||
|
||||
@ -15,18 +15,11 @@ 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: [
|
||||
// TypeOrmModule.forRoot({
|
||||
// type: 'postgres',
|
||||
// host: 'localhost',
|
||||
// port: 5432,
|
||||
// username: 'root',
|
||||
// password: 'admin',
|
||||
// database: 'imk',
|
||||
// synchronize: true,
|
||||
// }),
|
||||
EmailModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
|
||||
@ -37,7 +37,7 @@ export class AuthController {
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@Post('create-admin')
|
||||
async createAdmin(@Body() createUserDto: CreateUserDto) {
|
||||
return this.authService.createUser(createUserDto, true);
|
||||
return this.authService.createUser(createUserDto);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
||||
@ -7,6 +7,7 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { jwtConstants } from './constants';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { EmailModule } from 'src/email/email.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -16,6 +17,7 @@ import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
secret: jwtConstants.secret,
|
||||
signOptions: { expiresIn: '60m' },
|
||||
}),
|
||||
EmailModule,
|
||||
],
|
||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
||||
exports: [AuthService],
|
||||
|
||||
@ -4,6 +4,7 @@ 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 'src/email/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@ -11,6 +12,7 @@ export class AuthService {
|
||||
private prisma: PrismaService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
private emailService: EmailService,
|
||||
) {}
|
||||
|
||||
async validateUser(username: string, password: string): Promise<any> {
|
||||
@ -39,46 +41,59 @@ export class AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
async createUser(
|
||||
createUserDto: CreateUserDto,
|
||||
isAdmin: boolean = false,
|
||||
): Promise<any> {
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { email: createUserDto.email },
|
||||
});
|
||||
// async createUser(
|
||||
// createUserDto: CreateUserDto,
|
||||
// isAdmin: boolean = false,
|
||||
// ): Promise<any> {
|
||||
// const existingUser = await this.prisma.user.findUnique({
|
||||
// where: { email: createUserDto.email },
|
||||
// });
|
||||
|
||||
if (existingUser) {
|
||||
throw new ConflictException('Email already exists');
|
||||
}
|
||||
// if (existingUser) {
|
||||
// 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: {
|
||||
email: createUserDto.email,
|
||||
password: hashedPassword,
|
||||
name: createUserDto.name,
|
||||
isAdmin: isAdmin,
|
||||
...createUserDto,
|
||||
password: await bcrypt.hash(createUserDto.password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { password, ...result } = newUser;
|
||||
console.log(result);
|
||||
return result;
|
||||
// Send welcome email
|
||||
await this.emailService.sendWelcomeEmail(user.email, user.name);
|
||||
return user;
|
||||
}
|
||||
|
||||
// async getUserInfo(userId: number) {
|
||||
// return this.prisma.user.findUnique({
|
||||
// where: { id: userId },
|
||||
// select: {
|
||||
// id: true,
|
||||
// name: true,
|
||||
// email: true,
|
||||
// isAdmin: true,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
async requestPasswordReset(email: string) {
|
||||
const user = await this.prisma.user.findUnique({ where: { email } });
|
||||
if (!user) return;
|
||||
|
||||
const resetToken = this.jwtService.sign(
|
||||
{ email },
|
||||
{ expiresIn: '1h', secret: process.env.JWT_RESET_SECRET },
|
||||
);
|
||||
|
||||
await this.emailService.sendPasswordResetEmail(email, resetToken);
|
||||
}
|
||||
async getUserInfo(userId: number) {
|
||||
if (!userId) {
|
||||
throw new Error('User ID is required');
|
||||
|
||||
28
backend/imk-backend/src/email/email.controller.ts
Normal file
28
backend/imk-backend/src/email/email.controller.ts
Normal 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' };
|
||||
}
|
||||
}
|
||||
30
backend/imk-backend/src/email/email.module.ts
Normal file
30
backend/imk-backend/src/email/email.module.ts
Normal 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 {}
|
||||
45
backend/imk-backend/src/email/email.service.ts
Normal file
45
backend/imk-backend/src/email/email.service.ts
Normal 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
2962
node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
2977
package-lock.json
generated
2977
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@nestjs/cli": "^10.4.5",
|
||||
"axios": "^1.7.7",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^11.11.11",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user