nodejs-quickstart-structure 1.4.3 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +18 -20
- package/bin/index.js +1 -0
- package/docs/generateCase.md +127 -61
- package/docs/generatorFlow.md +15 -3
- package/docs/releaseNoteRule.md +42 -0
- package/lib/generator.js +46 -314
- package/lib/modules/app-setup.js +96 -0
- package/lib/modules/caching-setup.js +56 -0
- package/lib/modules/config-files.js +109 -0
- package/lib/modules/database-setup.js +111 -0
- package/lib/modules/kafka-setup.js +112 -0
- package/lib/modules/project-setup.js +31 -0
- package/lib/prompts.js +12 -4
- package/package.json +4 -3
- package/templates/clean-architecture/js/src/index.js.ejs +19 -6
- package/templates/clean-architecture/js/src/infrastructure/log/logger.js +16 -2
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +18 -6
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -0
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +1 -2
- package/templates/clean-architecture/ts/src/index.ts.ejs +27 -19
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +16 -2
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +17 -6
- package/templates/common/.env.example.ejs +39 -0
- package/templates/common/Dockerfile +3 -0
- package/templates/common/Jenkinsfile.ejs +41 -0
- package/templates/common/README.md.ejs +113 -106
- package/templates/common/caching/clean/js/CreateUser.js.ejs +25 -0
- package/templates/common/caching/clean/js/GetAllUsers.js.ejs +33 -0
- package/templates/common/caching/clean/ts/createUser.ts.ejs +23 -0
- package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +30 -0
- package/templates/common/caching/js/redisClient.js.ejs +71 -0
- package/templates/common/caching/ts/redisClient.ts.ejs +76 -0
- package/templates/common/docker-compose.yml.ejs +156 -116
- package/templates/common/package.json.ejs +13 -2
- package/templates/mvc/js/src/controllers/userController.js.ejs +35 -3
- package/templates/mvc/js/src/index.js.ejs +26 -17
- package/templates/mvc/js/src/utils/logger.js +16 -6
- package/templates/mvc/ts/src/config/swagger.ts.ejs +1 -2
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +35 -3
- package/templates/mvc/ts/src/index.ts.ejs +27 -18
- package/templates/mvc/ts/src/utils/logger.ts +16 -2
- package/templates/mvc/js/src/config/database.js +0 -12
- /package/templates/db/mysql/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
- /package/templates/db/postgres/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import winston from 'winston';
|
|
2
|
+
import 'winston-daily-rotate-file';
|
|
2
3
|
|
|
3
4
|
const logger = winston.createLogger({
|
|
4
5
|
level: 'info',
|
|
@@ -8,8 +9,21 @@ const logger = winston.createLogger({
|
|
|
8
9
|
),
|
|
9
10
|
defaultMeta: { service: 'user-service' },
|
|
10
11
|
transports: [
|
|
11
|
-
new winston.transports.
|
|
12
|
-
|
|
12
|
+
new winston.transports.DailyRotateFile({
|
|
13
|
+
filename: 'logs/error-%DATE%.log',
|
|
14
|
+
datePattern: 'YYYY-MM-DD',
|
|
15
|
+
zippedArchive: true,
|
|
16
|
+
maxSize: '20m',
|
|
17
|
+
maxFiles: '14d',
|
|
18
|
+
level: 'error',
|
|
19
|
+
}),
|
|
20
|
+
new winston.transports.DailyRotateFile({
|
|
21
|
+
filename: 'logs/combined-%DATE%.log',
|
|
22
|
+
datePattern: 'YYYY-MM-DD',
|
|
23
|
+
zippedArchive: true,
|
|
24
|
+
maxSize: '20m',
|
|
25
|
+
maxFiles: '14d',
|
|
26
|
+
}),
|
|
13
27
|
],
|
|
14
28
|
});
|
|
15
29
|
|
package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs
CHANGED
|
@@ -3,25 +3,36 @@ import UserModel from '@/infrastructure/database/models/User';
|
|
|
3
3
|
|
|
4
4
|
export class UserRepository {
|
|
5
5
|
async save(user: UserEntity): Promise<UserEntity> {
|
|
6
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
6
7
|
const newUser = await UserModel.create({ name: user.name, email: user.email });
|
|
7
|
-
|
|
8
|
-
<% } else
|
|
9
|
-
|
|
8
|
+
return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
|
|
9
|
+
<%_ } else if (database === 'None') { -%>
|
|
10
|
+
const newUser = { id: String(UserModel.mockData.length + 1), name: user.name, email: user.email };
|
|
11
|
+
UserModel.mockData.push(newUser);
|
|
12
|
+
return newUser;
|
|
13
|
+
<%_ } else { -%>
|
|
14
|
+
const newUser = await UserModel.create({ name: user.name, email: user.email });
|
|
15
|
+
return { id: newUser.id, name: newUser.name, email: newUser.email };
|
|
16
|
+
<%_ } -%>
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
async getUsers(): Promise<UserEntity[]> {
|
|
13
|
-
<% if (database === 'MongoDB') {
|
|
20
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
21
|
+
const users = await UserModel.find();
|
|
14
22
|
return users.map(user => ({
|
|
15
23
|
id: user._id.toString(),
|
|
16
24
|
name: user.name,
|
|
17
25
|
email: user.email
|
|
18
26
|
}));
|
|
19
|
-
<% } else
|
|
27
|
+
<%_ } else if (database === 'None') { -%>
|
|
28
|
+
return UserModel.mockData;
|
|
29
|
+
<%_ } else { -%>
|
|
30
|
+
const users = await UserModel.findAll();
|
|
20
31
|
return users.map(user => ({
|
|
21
32
|
id: user.id,
|
|
22
33
|
name: user.name,
|
|
23
34
|
email: user.email
|
|
24
35
|
}));
|
|
25
|
-
<% } -%>
|
|
36
|
+
<%_ } -%>
|
|
26
37
|
}
|
|
27
38
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
PORT=3000
|
|
3
|
+
NODE_ENV=development
|
|
4
|
+
|
|
5
|
+
<%_ if (database !== 'None') { -%>
|
|
6
|
+
# Database
|
|
7
|
+
<%_ if (database === 'MySQL') { -%>
|
|
8
|
+
DB_HOST=localhost
|
|
9
|
+
DB_PORT=3306
|
|
10
|
+
DB_USER=root
|
|
11
|
+
DB_PASSWORD=root
|
|
12
|
+
DB_NAME=<%= dbName %>
|
|
13
|
+
<%_ } -%>
|
|
14
|
+
<%_ if (database === 'PostgreSQL') { -%>
|
|
15
|
+
DB_HOST=localhost
|
|
16
|
+
DB_PORT=5432
|
|
17
|
+
DB_USER=postgres
|
|
18
|
+
DB_PASSWORD=root
|
|
19
|
+
DB_NAME=<%= dbName %>
|
|
20
|
+
<%_ } -%>
|
|
21
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
22
|
+
DB_URI=mongodb://localhost:27017/<%= dbName %>
|
|
23
|
+
<%_ } -%>
|
|
24
|
+
<%_ } -%>
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
28
|
+
# Communication
|
|
29
|
+
KAFKA_BROKER=localhost:9092
|
|
30
|
+
KAFKA_CLIENT_ID=<%= projectName %>
|
|
31
|
+
KAFKA_GROUP_ID=<%= projectName %>-group
|
|
32
|
+
<%_ } -%>
|
|
33
|
+
|
|
34
|
+
<%_ if (caching === 'Redis') { -%>
|
|
35
|
+
# Caching
|
|
36
|
+
REDIS_HOST=localhost
|
|
37
|
+
REDIS_PORT=6379
|
|
38
|
+
REDIS_PASSWORD=
|
|
39
|
+
<%_ } -%>
|
|
@@ -24,6 +24,47 @@ pipeline {
|
|
|
24
24
|
sh 'npm test'
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
// stage('Build') {
|
|
29
|
+
// steps {
|
|
30
|
+
// sh 'npm run build'
|
|
31
|
+
// }
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// stage('SonarQube Analysis') {
|
|
35
|
+
// environment {
|
|
36
|
+
// scannerHome = tool 'SonarScanner'
|
|
37
|
+
// }
|
|
38
|
+
// steps {
|
|
39
|
+
// withSonarQubeEnv('SonarQube') {
|
|
40
|
+
// sh "${scannerHome}/bin/sonar-scanner"
|
|
41
|
+
// }
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// stage('Security Scan') {
|
|
46
|
+
// steps {
|
|
47
|
+
// sh 'npm audit --audit-level=high'
|
|
48
|
+
// }
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
// stage('Docker Build & Push') {
|
|
52
|
+
// steps {
|
|
53
|
+
// script {
|
|
54
|
+
// docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') {
|
|
55
|
+
// def appImage = docker.build("my-image:${env.BUILD_ID}")
|
|
56
|
+
// appImage.push()
|
|
57
|
+
// appImage.push("latest")
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
// }
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
// stage('Deploy to Staging') {
|
|
64
|
+
// steps {
|
|
65
|
+
// sh './scripts/deploy.sh staging'
|
|
66
|
+
// }
|
|
67
|
+
// }
|
|
27
68
|
}
|
|
28
69
|
|
|
29
70
|
post {
|
|
@@ -1,107 +1,114 @@
|
|
|
1
|
-
# <%= projectName %>
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
<% if (language === 'TypeScript') { %><% } else { %><% } %>
|
|
6
|
-
|
|
7
|
-
A production-ready Node.js microservice generated with **<%= architecture %>** and **<%= database %>**.
|
|
8
|
-
This project comes pre-configured with industry-standard tooling for **Code Quality**, **Testing**, and **Security**.
|
|
9
|
-
|
|
10
|
-
## 🚀 Key Features
|
|
11
|
-
|
|
12
|
-
- **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
|
|
13
|
-
- **Database**: <%= database %> <% if (database !== 'MongoDB') { %>with **Flyway** migrations<% } else { %>with **Mongoose** schemas<% } %>.
|
|
14
|
-
- **Security**: Helmet, CORS, Rate Limiting, HPP.
|
|
15
|
-
- **Quality**: Eslint, Prettier, Husky, Lint-Staged.
|
|
16
|
-
- **Testing**: Jest (Unit & Integration).
|
|
17
|
-
- **DevOps**: Multi-stage Docker build, CI/CD ready.
|
|
18
|
-
|
|
19
|
-
## 🔄 CI/CD Pipeline
|
|
20
|
-
<% if (ciProvider === 'GitHub Actions') { -%>
|
|
21
|
-
This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
|
|
22
|
-
It automatically runs:
|
|
23
|
-
- Linting
|
|
24
|
-
- Tests
|
|
25
|
-
- Builds
|
|
26
|
-
<% } else if (ciProvider === 'Jenkins') { -%>
|
|
27
|
-
This project includes a **Jenkinsfile** for comprehensive CI/CD.
|
|
28
|
-
Pipeline stages:
|
|
29
|
-
- Install Dependencies
|
|
30
|
-
- Lint
|
|
31
|
-
- Test
|
|
32
|
-
<% } else { -%>
|
|
33
|
-
CI/CD is not currently configured, but the project is ready for integration.
|
|
34
|
-
<% } -%>
|
|
35
|
-
|
|
36
|
-
## 🛠️ Getting Started
|
|
37
|
-
|
|
38
|
-
### 1. Prerequisites
|
|
39
|
-
- Node.js (v18+)
|
|
40
|
-
- Docker & Docker Compose
|
|
41
|
-
|
|
42
|
-
### 2. Quick Start
|
|
43
|
-
```bash
|
|
44
|
-
# Initialize Git (Required for Husky)
|
|
45
|
-
git init
|
|
46
|
-
|
|
47
|
-
# Install dependencies
|
|
48
|
-
npm install
|
|
49
|
-
|
|
50
|
-
# Setup Git Hooks (Husky)
|
|
51
|
-
npm run prepare
|
|
52
|
-
|
|
53
|
-
# Start Infrastructure (DB, etc.)
|
|
54
|
-
docker-compose up -d
|
|
55
|
-
|
|
56
|
-
# Run Development Server
|
|
57
|
-
npm run dev
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### 3. Development Standards
|
|
61
|
-
Ensure your code meets quality standards before committing:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Run Linter
|
|
65
|
-
npm run lint
|
|
66
|
-
|
|
67
|
-
# Run Tests
|
|
68
|
-
npm test
|
|
69
|
-
|
|
70
|
-
# Format Code
|
|
71
|
-
npm run format
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## 📂 Project Structure
|
|
75
|
-
|
|
76
|
-
The project follows **<%= architecture %>** principles.
|
|
77
|
-
<% if (communication === 'Kafka') { -%>
|
|
78
|
-
Microservices communication handled via **Kafka**.
|
|
79
|
-
<% } else { -%>
|
|
80
|
-
API is exposed via **REST**.
|
|
81
|
-
A Swagger UI for API documentation is available at:
|
|
82
|
-
- **URL**: `http://localhost:3000/api-docs` (Dynamic based on PORT)
|
|
83
|
-
<% } -%>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- **
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
##
|
|
93
|
-
This project uses
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
1
|
+
# <%= projectName %>
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
<% if (language === 'TypeScript') { %><% } else { %><% } %>
|
|
6
|
+
|
|
7
|
+
A production-ready Node.js microservice generated with **<%= architecture %>** and **<%= database %>**.
|
|
8
|
+
This project comes pre-configured with industry-standard tooling for **Code Quality**, **Testing**, and **Security**.
|
|
9
|
+
|
|
10
|
+
## 🚀 Key Features
|
|
11
|
+
|
|
12
|
+
- **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
|
|
13
|
+
- **Database**: <%= database %> <% if (database !== 'MongoDB') { %>with **Flyway** migrations<% } else { %>with **Mongoose** schemas<% } %>.
|
|
14
|
+
- **Security**: Helmet, CORS, Rate Limiting, HPP.
|
|
15
|
+
- **Quality**: Eslint, Prettier, Husky, Lint-Staged.
|
|
16
|
+
- **Testing**: Jest (Unit & Integration).
|
|
17
|
+
- **DevOps**: Multi-stage Docker build, CI/CD ready.
|
|
18
|
+
|
|
19
|
+
## 🔄 CI/CD Pipeline
|
|
20
|
+
<% if (ciProvider === 'GitHub Actions') { -%>
|
|
21
|
+
This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
|
|
22
|
+
It automatically runs:
|
|
23
|
+
- Linting
|
|
24
|
+
- Tests
|
|
25
|
+
- Builds
|
|
26
|
+
<% } else if (ciProvider === 'Jenkins') { -%>
|
|
27
|
+
This project includes a **Jenkinsfile** for comprehensive CI/CD.
|
|
28
|
+
Pipeline stages:
|
|
29
|
+
- Install Dependencies
|
|
30
|
+
- Lint
|
|
31
|
+
- Test
|
|
32
|
+
<% } else { -%>
|
|
33
|
+
CI/CD is not currently configured, but the project is ready for integration.
|
|
34
|
+
<% } -%>
|
|
35
|
+
|
|
36
|
+
## 🛠️ Getting Started
|
|
37
|
+
|
|
38
|
+
### 1. Prerequisites
|
|
39
|
+
- Node.js (v18+)
|
|
40
|
+
- Docker & Docker Compose
|
|
41
|
+
|
|
42
|
+
### 2. Quick Start
|
|
43
|
+
```bash
|
|
44
|
+
# Initialize Git (Required for Husky)
|
|
45
|
+
git init
|
|
46
|
+
|
|
47
|
+
# Install dependencies
|
|
48
|
+
npm install
|
|
49
|
+
|
|
50
|
+
# Setup Git Hooks (Husky)
|
|
51
|
+
npm run prepare
|
|
52
|
+
|
|
53
|
+
# Start Infrastructure (DB, etc.)
|
|
54
|
+
docker-compose up -d
|
|
55
|
+
|
|
56
|
+
# Run Development Server
|
|
57
|
+
npm run dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Development Standards
|
|
61
|
+
Ensure your code meets quality standards before committing:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Run Linter
|
|
65
|
+
npm run lint
|
|
66
|
+
|
|
67
|
+
# Run Tests
|
|
68
|
+
npm test
|
|
69
|
+
|
|
70
|
+
# Format Code
|
|
71
|
+
npm run format
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 📂 Project Structure
|
|
75
|
+
|
|
76
|
+
The project follows **<%= architecture %>** principles.
|
|
77
|
+
<% if (communication === 'Kafka') { -%>
|
|
78
|
+
Microservices communication handled via **Kafka**.
|
|
79
|
+
<% } else { -%>
|
|
80
|
+
API is exposed via **REST**.
|
|
81
|
+
A Swagger UI for API documentation is available at:
|
|
82
|
+
- **URL**: `http://localhost:3000/api-docs` (Dynamic based on PORT)
|
|
83
|
+
<% } -%>
|
|
84
|
+
|
|
85
|
+
<% if (caching === 'Redis') { -%>
|
|
86
|
+
## ⚡ Caching
|
|
87
|
+
This project uses **Redis** for caching.
|
|
88
|
+
- **Client**: `ioredis`
|
|
89
|
+
- **Connection**: Configured via `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` in `.env`.
|
|
90
|
+
<% } -%>
|
|
91
|
+
|
|
92
|
+
## 📝 Logging
|
|
93
|
+
This project uses **Winston** for structured logging.
|
|
94
|
+
- **Development**: Logs are printed to the console.
|
|
95
|
+
- **Production**: Logs are saved to files:
|
|
96
|
+
- `error.log`: Only error level logs.
|
|
97
|
+
- `combined.log`: All logs.
|
|
98
|
+
|
|
99
|
+
## 🐳 Docker Deployment
|
|
100
|
+
This project uses a **Multi-Stage Dockerfile** for optimized production images.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Build Production Image
|
|
104
|
+
docker build -t <%= projectName %> .
|
|
105
|
+
|
|
106
|
+
# Run Container
|
|
107
|
+
docker run -p 3000:3000 -e DATABASE_URL=... <%= projectName %>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 🔒 Security Features
|
|
111
|
+
- **Helmet**: Sets secure HTTP headers.
|
|
112
|
+
- **CORS**: Configured for cross-origin requests.
|
|
113
|
+
- **Rate Limiting**: Protects against DDoS / Brute-force.
|
|
107
114
|
- **HPP**: Prevents HTTP Parameter Pollution attacks.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const User = require('../domain/models/User');
|
|
2
|
+
const redis = require('../infrastructure/caching/redisClient');
|
|
3
|
+
const logger = require('../infrastructure/log/logger');
|
|
4
|
+
|
|
5
|
+
class CreateUser {
|
|
6
|
+
constructor(userRepository) {
|
|
7
|
+
this.userRepository = userRepository;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async execute(name, email) {
|
|
11
|
+
const user = new User(null, name, email);
|
|
12
|
+
const savedUser = await this.userRepository.save(user);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await redis.del('users:all');
|
|
16
|
+
logger.info('Invalidated users:all cache');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error('Redis error (del):', error);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return savedUser;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = CreateUser;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const redis = require('../infrastructure/caching/redisClient');
|
|
2
|
+
const logger = require('../infrastructure/log/logger');
|
|
3
|
+
|
|
4
|
+
class GetAllUsers {
|
|
5
|
+
constructor(userRepository) {
|
|
6
|
+
this.userRepository = userRepository;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async execute() {
|
|
10
|
+
const cacheKey = 'users:all';
|
|
11
|
+
try {
|
|
12
|
+
const cachedUsers = await redis.get(cacheKey);
|
|
13
|
+
if (cachedUsers) {
|
|
14
|
+
logger.info('Serving users from cache');
|
|
15
|
+
return cachedUsers;
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error('Redis error (get):', error);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const users = await this.userRepository.getUsers();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await redis.set(cacheKey, users, 60);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.error('Redis error (set):', error);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return users;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = GetAllUsers;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { User } from '@/domain/user';
|
|
2
|
+
|
|
3
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
4
|
+
import redis from '@/infrastructure/caching/redisClient';
|
|
5
|
+
import logger from '@/infrastructure/log/logger';
|
|
6
|
+
|
|
7
|
+
export default class CreateUser {
|
|
8
|
+
constructor(private userRepository: UserRepository) {}
|
|
9
|
+
|
|
10
|
+
async execute(name: string, email: string) {
|
|
11
|
+
const user = new User(null, name, email);
|
|
12
|
+
const savedUser = await this.userRepository.save(user);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await redis.del('users:all');
|
|
16
|
+
logger.info('Invalidated users:all cache');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error('Redis error (del):', error);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return savedUser;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
2
|
+
import redis from '@/infrastructure/caching/redisClient';
|
|
3
|
+
import logger from '@/infrastructure/log/logger';
|
|
4
|
+
|
|
5
|
+
export default class GetAllUsers {
|
|
6
|
+
constructor(private userRepository: UserRepository) {}
|
|
7
|
+
|
|
8
|
+
async execute() {
|
|
9
|
+
const cacheKey = 'users:all';
|
|
10
|
+
try {
|
|
11
|
+
const cachedUsers = await redis.get(cacheKey);
|
|
12
|
+
if (cachedUsers) {
|
|
13
|
+
logger.info('Serving users from cache');
|
|
14
|
+
return cachedUsers;
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {
|
|
17
|
+
logger.error('Redis error (get):', error);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const users = await this.userRepository.getUsers();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await redis.set(cacheKey, users, 60);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.error('Redis error (set):', error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return users;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const Redis = require('ioredis');
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
const logger = require('<%- loggerPath %>');
|
|
4
|
+
|
|
5
|
+
class RedisService {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.client = new Redis({
|
|
8
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
9
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
10
|
+
password: process.env.REDIS_PASSWORD || undefined,
|
|
11
|
+
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
this.client.on('connect', () => {
|
|
15
|
+
logger.info('Redis connected');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
this.client.on('error', (err) => {
|
|
19
|
+
logger.error('Redis error:', err);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static getInstance() {
|
|
24
|
+
if (!RedisService.instance) {
|
|
25
|
+
RedisService.instance = new RedisService();
|
|
26
|
+
}
|
|
27
|
+
return RedisService.instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async get(key) {
|
|
31
|
+
try {
|
|
32
|
+
const data = await this.client.get(key);
|
|
33
|
+
return data ? JSON.parse(data) : null;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.error(`Redis get error for key ${key}:`, error);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async set(key, value, ttl) {
|
|
41
|
+
try {
|
|
42
|
+
const data = JSON.stringify(value);
|
|
43
|
+
if (ttl) {
|
|
44
|
+
await this.client.set(key, data, 'EX', ttl);
|
|
45
|
+
} else {
|
|
46
|
+
await this.client.set(key, data);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logger.error(`Redis set error for key ${key}:`, error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async del(key) {
|
|
54
|
+
try {
|
|
55
|
+
await this.client.del(key);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error(`Redis del error for key ${key}:`, error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getOrSet(key, fetcher, ttl = 3600) {
|
|
62
|
+
const cached = await this.get(key);
|
|
63
|
+
if (cached) return cached;
|
|
64
|
+
|
|
65
|
+
const data = await fetcher();
|
|
66
|
+
if (data) await this.set(key, data, ttl);
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = RedisService.getInstance();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import logger from '<%- loggerPath %>';
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
class RedisService {
|
|
8
|
+
private client: Redis;
|
|
9
|
+
private static instance: RedisService;
|
|
10
|
+
|
|
11
|
+
private constructor() {
|
|
12
|
+
this.client = new Redis({
|
|
13
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
14
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
15
|
+
password: process.env.REDIS_PASSWORD || undefined,
|
|
16
|
+
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
this.client.on('connect', () => {
|
|
20
|
+
logger.info('Redis connected');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.client.on('error', (err) => {
|
|
24
|
+
logger.error('Redis error:', err);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static getInstance(): RedisService {
|
|
29
|
+
if (!RedisService.instance) {
|
|
30
|
+
RedisService.instance = new RedisService();
|
|
31
|
+
}
|
|
32
|
+
return RedisService.instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async get<T>(key: string): Promise<T | null> {
|
|
36
|
+
try {
|
|
37
|
+
const data = await this.client.get(key);
|
|
38
|
+
return data ? JSON.parse(data) : null;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error(`Redis get error for key ${key}:`, error);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async set(key: string, value: unknown, ttl?: number): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.stringify(value);
|
|
48
|
+
if (ttl) {
|
|
49
|
+
await this.client.set(key, data, 'EX', ttl);
|
|
50
|
+
} else {
|
|
51
|
+
await this.client.set(key, data);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error(`Redis set error for key ${key}:`, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async del(key: string): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
await this.client.del(key);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error(`Redis del error for key ${key}:`, error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async getOrSet<T>(key: string, fetcher: () => Promise<T>, ttl: number = 3600): Promise<T> {
|
|
67
|
+
const cached = await this.get<T>(key);
|
|
68
|
+
if (cached) return cached;
|
|
69
|
+
|
|
70
|
+
const data = await fetcher();
|
|
71
|
+
if (data) await this.set(key, data, ttl);
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default RedisService.getInstance();
|