email set-up complete
This commit is contained in:
parent
befb521604
commit
609475271e
@ -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
|
||||||
|
|||||||
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/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",
|
||||||
|
|||||||
@ -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 {}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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],
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
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": {
|
"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",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user