This commit is contained in:
dimitar 2025-03-29 17:08:01 +01:00
parent fd03804038
commit 86254bf7eb
11 changed files with 309 additions and 170 deletions

View File

@ -25,7 +25,13 @@ SMTP_USER=mailer@imk.mk
SMTP_PASS=76Avtostoperski76
SMTP_FROM=mailer@imk.mk
# FRONTEND_URL=https://imk.mk
# EMAIL_FROM=petrovskidimitar@yandex.com
EMAIL_FROM=mailer@yandex.com
ADMIN_EMAIL=taratur@gmail.com
# default app ADMIN
DEFAULT_ADMIN_EMAIL=taratur@gmail.com
DEFAULT_ADMIN_PASSWORD=irina7654321
DEFAULT_ADMIN_NAME=admin

View File

@ -1,73 +1,47 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
Add the necessary environment variables to your `.env` file:
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```env
# Default Admin Configuration
DEFAULT_ADMIN_EMAIL=admin@imk.com
DEFAULT_ADMIN_PASSWORD=admin123456
DEFAULT_ADMIN_NAME=System Admin
```
## Running the app
To use this setup:
1. The seed script will run automatically during deployment when you run:
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
npx prisma db push
npx prisma db seed
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
2. Alternatively, you can manually trigger the initialization by making a POST request to:
```
POST /init/system
```
## Support
This gives you two ways to ensure the default admin user is created:
1. Automatically during deployment via the seed script
2. Manually via the initialization endpoint
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
To deploy, you would:
## Stay in touch
1. Set up your environment variables
2. Run the database migrations
3. Run the seed script
```bash
npm run prisma:deploy
npm run prisma:seed
```
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
The default admin credentials will be:
- Email: admin@imk.com (or whatever you set in env)
- Password: admin123456 (or whatever you set in env)
## License
Make sure to:
1. Change the default password after first login
2. Use strong passwords in production
3. Properly secure the initialization endpoint in production
4. Keep your environment variables secure
Nest is [MIT licensed](LICENSE).
This setup ensures you always have an admin user available after deployment while maintaining security and flexibility.

View File

@ -17,7 +17,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"prisma:seed": "ts-node prisma/seed.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.679.0",

View File

@ -1,23 +1,47 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
// backend/prisma/seed.ts
import { PrismaClient } from "@prisma/client";
import * as bcrypt from "bcrypt";
const prisma = new PrismaClient();
async function main() {
const hashedPassword = await bcrypt.hash('admin123', 10);
const defaultAdminEmail = process.env.DEFAULT_ADMIN_EMAIL || "admin@imk.com";
const defaultAdminPassword =
process.env.DEFAULT_ADMIN_PASSWORD || "admin123456";
const defaultAdminName = process.env.DEFAULT_ADMIN_NAME || "System Admin";
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
try {
// Check if admin already exists
const existingAdmin = await prisma.user.findUnique({
where: { email: defaultAdminEmail },
});
if (!existingAdmin) {
// Hash the password
const hashedPassword = await bcrypt.hash(defaultAdminPassword, 10);
// Create the admin user
const admin = await prisma.user.create({
data: {
email: defaultAdminEmail,
name: defaultAdminName,
password: hashedPassword,
isAdmin: true,
},
});
console.log({ admin });
console.log("Default admin user created:", {
id: admin.id,
email: admin.email,
name: admin.name,
});
} else {
console.log("Default admin user already exists");
}
} catch (error) {
console.error("Error creating default admin:", error);
throw error;
}
}
main()

View File

@ -1,39 +1,31 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { AuthModule } from "./auth/auth.module";
//import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminModule } from './admin/admin.module';
import { ClientModule } from './client/client.module';
import { UploadService } from './upload/upload.service';
import { DocumentsService } from './documents/documents.service';
import { S3Service } from './s3/s3.service';
import { S3Module } from './s3/s3.module';
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
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';
import { AdminModule } from "./admin/admin.module";
import { ClientModule } from "./client/client.module";
import { UploadService } from "./upload/upload.service";
import { DocumentsService } from "./documents/documents.service";
import { S3Service } from "./s3/s3.service";
import { S3Module } from "./s3/s3.module";
import { PrismaService } from "./prisma/prisma.service";
import { PrismaModule } from "./prisma/prisma.module";
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";
import { InitModule } from "./init/init.module";
@Module({
imports: [
// TypeOrmModule.forRoot({
// type: 'postgres',
// host: 'localhost',
// port: 5432,
// username: 'root',
// password: 'admin',
// database: 'imk',
// synchronize: true,
// }),
ConfigModule.forRoot({
isGlobal: true,
}),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
signOptions: { expiresIn: "1h" },
}),
AuthModule,
AdminModule,
@ -41,6 +33,7 @@ import { EmailModule } from './email/email.module';
S3Module,
PrismaModule,
EmailModule,
InitModule,
],
controllers: [AppController, AuthController, DocumentsController],
providers: [

View File

@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as nodemailer from 'nodemailer';
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as nodemailer from "nodemailer";
@Injectable()
export class EmailService {
@ -9,29 +9,26 @@ export class EmailService {
private readonly from: string;
constructor(private configService: ConfigService) {
console.log('Initializing EmailService...'); // Direct console log for debugging
this.logger.log('Initializing EmailService...');
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');
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 });
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
host: host,
port: port,
auth: { user, pass },
debug: true, // Enable debug logs
logger: true // Enable transport level logging
logger: true, // Enable transport level logging
});
// Verify connection
@ -40,16 +37,16 @@ export class EmailService {
private async verifyConnection() {
try {
console.log('Verifying SMTP connection...'); // Direct console log
this.logger.log('Verifying SMTP connection...');
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);
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:', {
console.error("SMTP connection failed:", error); // Direct console log
this.logger.error("SMTP connection failed:", {
error: error.message,
code: error.code,
command: error.command,
@ -68,7 +65,7 @@ export class EmailService {
const mailOptions = {
from: `"IMK Platform" <${this.from}>`,
to: userEmail,
subject: 'Welcome to IMK Platform!',
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>
@ -78,21 +75,21 @@ export class EmailService {
<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:', {
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
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:', {
console.log("Email sent successfully:", info); // Direct console log
this.logger.log("Email sent successfully:", {
messageId: info.messageId,
response: info.response,
accepted: info.accepted,
@ -100,8 +97,8 @@ export class EmailService {
envelope: info.envelope,
});
} catch (error) {
console.error('Failed to send email:', error); // Direct console log
this.logger.error('Failed to send email:', {
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,
@ -117,14 +114,18 @@ export class EmailService {
userEmail: string,
username: string,
documentName: string,
action: 'uploaded' | 'shared',
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}`);
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 actionText = action === "uploaded" ? "uploaded" : "shared with you";
const mailOptions = {
from: `"IMK Platform" <${this.from}>`,
to: userEmail,
@ -142,31 +143,31 @@ export class EmailService {
};
try {
console.log('Sending document notification email with options:', {
console.log("Sending document notification email with options:", {
to: mailOptions.to,
from: mailOptions.from,
subject: mailOptions.subject,
documentName,
action
action,
});
this.logger.log('Sending document notification email with options:', {
this.logger.log("Sending document notification email with options:", {
to: mailOptions.to,
from: mailOptions.from,
subject: mailOptions.subject,
documentName,
action
action,
});
const info = await this.transporter.sendMail(mailOptions);
console.log('Document notification email sent successfully:', {
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:', {
this.logger.log("Document notification email sent successfully:", {
messageId: info.messageId,
response: info.response,
accepted: info.accepted,
@ -174,8 +175,8 @@ export class EmailService {
envelope: info.envelope,
});
} catch (error) {
console.error('Failed to send document notification email:', error);
this.logger.error('Failed to send document notification email:', {
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,
@ -188,12 +189,12 @@ export class EmailService {
}
async sendPasswordResetNotification(email: string, name: string) {
this.logger.log('Sending password reset notification email to:', email);
this.logger.log("Sending password reset notification email to:", email);
const mailOptions = {
from: this.from,
to: email,
subject: 'Your Password Has Been Reset',
subject: "Your Password Has Been Reset",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2>Password Reset Notification</h2>
@ -206,12 +207,15 @@ export class EmailService {
};
try {
this.logger.debug('Attempting to send password reset notification...');
this.logger.debug("Attempting to send password reset notification...");
const info = await this.transporter.sendMail(mailOptions);
this.logger.log('Password reset notification sent successfully:', info.response);
this.logger.log(
"Password reset notification sent successfully:",
info.response,
);
return info;
} catch (error) {
this.logger.error('Failed to send password reset notification:', {
this.logger.error("Failed to send password reset notification:", {
error: error.message,
code: error.code,
command: error.command,
@ -222,13 +226,13 @@ export class EmailService {
}
async sendPasswordResetEmail(email: string, name: string, token: string) {
this.logger.log('Sending password reset email to:', email);
this.logger.log("Sending password reset email to:", email);
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
const mailOptions = {
from: this.from,
to: email,
subject: 'Reset Your Password - IMK Platform',
subject: "Reset Your Password - IMK Platform",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2>Password Reset Request</h2>
@ -252,12 +256,12 @@ export class EmailService {
};
try {
this.logger.debug('Attempting to send password reset email...');
this.logger.debug("Attempting to send password reset email...");
const info = await this.transporter.sendMail(mailOptions);
this.logger.log('Password reset email sent successfully:', info.response);
this.logger.log("Password reset email sent successfully:", info.response);
return info;
} catch (error) {
this.logger.error('Failed to send password reset email:', {
this.logger.error("Failed to send password reset email:", {
error: error.message,
code: error.code,
command: error.command,
@ -268,12 +272,12 @@ export class EmailService {
}
async sendPasswordChangeConfirmation(email: string, name: string) {
this.logger.log('Sending password change confirmation to:', email);
this.logger.log("Sending password change confirmation to:", email);
const mailOptions = {
from: this.from,
to: email,
subject: 'Password Changed Successfully - IMK Platform',
subject: "Password Changed Successfully - IMK Platform",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2>Password Changed Successfully</h2>
@ -286,12 +290,15 @@ export class EmailService {
};
try {
this.logger.debug('Attempting to send password change confirmation...');
this.logger.debug("Attempting to send password change confirmation...");
const info = await this.transporter.sendMail(mailOptions);
this.logger.log('Password change confirmation sent successfully:', info.response);
this.logger.log(
"Password change confirmation sent successfully:",
info.response,
);
return info;
} catch (error) {
this.logger.error('Failed to send password change confirmation:', {
this.logger.error("Failed to send password change confirmation:", {
error: error.message,
code: error.code,
command: error.command,

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { InitController } from './init.controller';
describe('InitController', () => {
let controller: InitController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [InitController],
}).compile();
controller = module.get<InitController>(InitController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,15 @@
import { Controller, Post, Logger } from "@nestjs/common";
import { InitService } from "./init.service";
@Controller("init")
export class InitController {
private readonly logger = new Logger(InitController.name);
constructor(private readonly initService: InitService) {}
@Post("system")
async initializeSystem() {
this.logger.log("Received system initialization request");
return this.initService.initializeSystem();
}
}

View File

@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { InitController } from "./init.controller";
import { PrismaModule } from "../prisma/prisma.module";
import { ConfigModule } from "@nestjs/config";
import { InitService } from "./init.service";
@Module({
imports: [PrismaModule, ConfigModule],
providers: [InitService],
controllers: [InitController],
})
export class InitModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { InitService } from './init.service';
describe('InitService', () => {
let service: InitService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [InitService],
}).compile();
service = module.get<InitService>(InitService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,71 @@
import { Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { ConfigService } from "@nestjs/config";
import * as bcrypt from "bcrypt";
@Injectable()
export class InitService {
private readonly logger = new Logger(InitService.name);
constructor(
private readonly prisma: PrismaService,
private readonly configService: ConfigService,
) {}
async initializeSystem() {
this.logger.log("Starting system initialization...");
const defaultAdminEmail =
this.configService.get("DEFAULT_ADMIN_EMAIL") || "taratur@gmail";
const defaultAdminPassword =
this.configService.get("DEFAULT_ADMIN_PASSWORD") || "irina7654321";
const defaultAdminName =
this.configService.get("DEFAULT_ADMIN_NAME") || "System Admin";
try {
// Check if admin already exists
const existingAdmin = await this.prisma.user.findUnique({
where: { email: defaultAdminEmail },
});
if (!existingAdmin) {
// Hash the password
const hashedPassword = await bcrypt.hash(defaultAdminPassword, 10);
// Create the admin user
const admin = await this.prisma.user.create({
data: {
email: defaultAdminEmail,
name: defaultAdminName,
password: hashedPassword,
isAdmin: true,
},
});
this.logger.log("Default admin user created successfully");
return {
success: true,
message: "System initialized successfully",
admin: {
id: admin.id,
email: admin.email,
name: admin.name,
},
};
}
return {
success: true,
message: "System already initialized",
admin: {
id: existingAdmin.id,
email: existingAdmin.email,
name: existingAdmin.name,
},
};
} catch (error) {
this.logger.error("Error during system initialization:", error);
throw error;
}
}
}