express-genix 4.2.1 → 4.4.0
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/index.js +1 -1
- package/lib/features.js +5 -0
- package/package.json +1 -1
- package/templates/agents/graph.js.ejs +0 -1
- package/templates/config/ai.js.ejs +34 -41
- package/templates/config/queue.js.ejs +1 -1
- package/templates/config/redis.js.ejs +1 -1
- package/templates/controllers/aiController.js.ejs +10 -12
- package/templates/controllers/authController.js.ejs +4 -4
- package/templates/core/Dockerfile.ejs +16 -6
- package/templates/core/README.md.ejs +12 -12
- package/templates/core/docker-compose.yml.ejs +5 -5
- package/templates/core/package.json.ejs +10 -7
- package/templates/jobs/worker.js.ejs +1 -1
- package/templates/mcp/server.js.ejs +3 -3
- package/templates/middleware/auth.js.ejs +2 -14
- package/templates/routes/aiRoutes.js.ejs +4 -13
- package/templates/routes/index.js.ejs +1 -1
- package/templates/services/aiService.js.ejs +2 -2
- package/templates/services/emailService.js.ejs +1 -1
- package/templates/tests/example.test.js.ejs +3 -3
- package/templates/tests/setup.js.ejs +1 -1
package/index.js
CHANGED
|
@@ -54,7 +54,7 @@ async function promptCodaExtension() {
|
|
|
54
54
|
} catch {
|
|
55
55
|
console.log('\n⚠️ Could not install automatically. Install it manually:');
|
|
56
56
|
console.log(' 1. Download from: https://github.com/LambdaAI001/coda/releases');
|
|
57
|
-
console.log(' 2. Then run: code --install-extension coda-ai-0.
|
|
57
|
+
console.log(' 2. Then run: code --install-extension coda-ai-0.2.0.vsix');
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
package/lib/features.js
CHANGED
|
@@ -242,6 +242,11 @@ const inferConfig = (projectDir, pkg) => {
|
|
|
242
242
|
hasGraphQL: !!(pkg.dependencies && pkg.dependencies['@apollo/server']),
|
|
243
243
|
hasMCP: !!(pkg.dependencies && pkg.dependencies['@modelcontextprotocol/sdk']),
|
|
244
244
|
hasAI: !!(pkg.dependencies && pkg.dependencies['@langchain/core']),
|
|
245
|
+
aiProvider: pkg.dependencies && pkg.dependencies['@langchain/openai'] ? 'openai'
|
|
246
|
+
: pkg.dependencies && pkg.dependencies['@langchain/anthropic'] ? 'anthropic'
|
|
247
|
+
: pkg.dependencies && pkg.dependencies['@langchain/google-genai'] ? 'gemini'
|
|
248
|
+
: pkg.dependencies && pkg.dependencies['@langchain/ollama'] ? 'ollama'
|
|
249
|
+
: 'openai',
|
|
245
250
|
logger: pkg.dependencies && pkg.dependencies.pino ? 'pino' : 'winston',
|
|
246
251
|
};
|
|
247
252
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-genix",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Production-grade CLI to generate Express apps with JWT, RBAC, GraphQL, TypeScript, Prisma, MongoDB, PostgreSQL, file uploads, email, background jobs, and more",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,7 +45,6 @@ const calculatorTool = tool(
|
|
|
45
45
|
* @param {Object} options
|
|
46
46
|
* @param {Array} options.tools - Additional @langchain/core tools to register
|
|
47
47
|
* @param {string} options.systemPrompt - System instruction for the agent
|
|
48
|
-
* @param {string} options.provider - AI provider (openai, anthropic, ollama)
|
|
49
48
|
* @param {string} options.model - Model name override
|
|
50
49
|
*/
|
|
51
50
|
const createAgent = (options = {}) => {
|
|
@@ -1,54 +1,47 @@
|
|
|
1
|
+
<% if (aiProvider === 'openai') { %>
|
|
1
2
|
const { ChatOpenAI } = require('@langchain/openai');
|
|
3
|
+
<% } else if (aiProvider === 'anthropic') { %>
|
|
2
4
|
const { ChatAnthropic } = require('@langchain/anthropic');
|
|
3
|
-
|
|
5
|
+
<% } else if (aiProvider === 'gemini') { %>
|
|
4
6
|
const { ChatGoogleGenerativeAI } = require('@langchain/google-genai');
|
|
7
|
+
<% } else if (aiProvider === 'ollama') { %>
|
|
8
|
+
const { ChatOllama } = require('@langchain/ollama');
|
|
9
|
+
<% } %>
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Create a LangChain chat model based on the configured provider.
|
|
8
|
-
* Supports OpenAI, Anthropic, Google Gemini, and Ollama (local).
|
|
9
13
|
*/
|
|
10
14
|
const getModel = (options = {}) => {
|
|
11
|
-
const provider = (options.provider || process.env.AI_PROVIDER || 'openai').toLowerCase();
|
|
12
15
|
const temperature = options.temperature ?? parseFloat(process.env.AI_TEMPERATURE || '0.7');
|
|
13
16
|
const maxTokens = options.maxTokens ?? parseInt(process.env.AI_MAX_TOKENS || '2048', 10);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
model: options.model || process.env.AI_MODEL || 'llama3',
|
|
43
|
-
temperature,
|
|
44
|
-
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
default:
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Unsupported AI provider: "${provider}". Supported: openai, anthropic, gemini, ollama`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
17
|
+
<% if (aiProvider === 'openai') { %>
|
|
18
|
+
return new ChatOpenAI({
|
|
19
|
+
modelName: options.model || process.env.AI_MODEL || 'gpt-4o-mini',
|
|
20
|
+
temperature,
|
|
21
|
+
maxTokens,
|
|
22
|
+
openAIApiKey: process.env.OPENAI_API_KEY,
|
|
23
|
+
});
|
|
24
|
+
<% } else if (aiProvider === 'anthropic') { %>
|
|
25
|
+
return new ChatAnthropic({
|
|
26
|
+
modelName: options.model || process.env.AI_MODEL || 'claude-sonnet-4-20250514',
|
|
27
|
+
temperature,
|
|
28
|
+
maxTokens,
|
|
29
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
30
|
+
});
|
|
31
|
+
<% } else if (aiProvider === 'gemini') { %>
|
|
32
|
+
return new ChatGoogleGenerativeAI({
|
|
33
|
+
model: options.model || process.env.AI_MODEL || 'gemini-2.0-flash',
|
|
34
|
+
temperature,
|
|
35
|
+
maxOutputTokens: maxTokens,
|
|
36
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
37
|
+
});
|
|
38
|
+
<% } else if (aiProvider === 'ollama') { %>
|
|
39
|
+
return new ChatOllama({
|
|
40
|
+
model: options.model || process.env.AI_MODEL || 'llama3',
|
|
41
|
+
temperature,
|
|
42
|
+
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
|
43
|
+
});
|
|
44
|
+
<% } %>
|
|
52
45
|
};
|
|
53
46
|
|
|
54
47
|
module.exports = { getModel };
|
|
@@ -7,16 +7,15 @@ const { success, error } = require('../utils/response');
|
|
|
7
7
|
*/
|
|
8
8
|
const chatHandler = async (req, res, next) => {
|
|
9
9
|
try {
|
|
10
|
-
const { message, systemPrompt, history,
|
|
10
|
+
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
11
11
|
|
|
12
12
|
if (!message) {
|
|
13
|
-
return
|
|
13
|
+
return error(res, 'Message is required', 400);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const result = await aiService.chat(message, {
|
|
17
17
|
systemPrompt,
|
|
18
18
|
history,
|
|
19
|
-
provider,
|
|
20
19
|
model,
|
|
21
20
|
temperature,
|
|
22
21
|
maxTokens,
|
|
@@ -33,10 +32,10 @@ const chatHandler = async (req, res, next) => {
|
|
|
33
32
|
*/
|
|
34
33
|
const streamHandler = async (req, res, next) => {
|
|
35
34
|
try {
|
|
36
|
-
const { message, systemPrompt, history,
|
|
35
|
+
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
37
36
|
|
|
38
37
|
if (!message) {
|
|
39
|
-
return
|
|
38
|
+
return error(res, 'Message is required', 400);
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -46,7 +45,6 @@ const streamHandler = async (req, res, next) => {
|
|
|
46
45
|
const generator = aiService.stream(message, {
|
|
47
46
|
systemPrompt,
|
|
48
47
|
history,
|
|
49
|
-
provider,
|
|
50
48
|
model,
|
|
51
49
|
temperature,
|
|
52
50
|
maxTokens,
|
|
@@ -68,13 +66,13 @@ const streamHandler = async (req, res, next) => {
|
|
|
68
66
|
*/
|
|
69
67
|
const chainHandler = async (req, res, next) => {
|
|
70
68
|
try {
|
|
71
|
-
const { template, variables,
|
|
69
|
+
const { template, variables, model } = req.body;
|
|
72
70
|
|
|
73
71
|
if (!template) {
|
|
74
|
-
return
|
|
72
|
+
return error(res, 'Template is required', 400);
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
const result = await aiService.chain(template, variables || {}, {
|
|
75
|
+
const result = await aiService.chain(template, variables || {}, { model });
|
|
78
76
|
res.json(success({ content: result }));
|
|
79
77
|
} catch (err) {
|
|
80
78
|
next(err);
|
|
@@ -86,13 +84,13 @@ const chainHandler = async (req, res, next) => {
|
|
|
86
84
|
*/
|
|
87
85
|
const agentHandler = async (req, res, next) => {
|
|
88
86
|
try {
|
|
89
|
-
const { message, systemPrompt,
|
|
87
|
+
const { message, systemPrompt, model } = req.body;
|
|
90
88
|
|
|
91
89
|
if (!message) {
|
|
92
|
-
return
|
|
90
|
+
return error(res, 'Message is required', 400);
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
const result = await runAgent(message, { systemPrompt,
|
|
93
|
+
const result = await runAgent(message, { systemPrompt, model });
|
|
96
94
|
res.json(success(result));
|
|
97
95
|
} catch (err) {
|
|
98
96
|
next(err);
|
|
@@ -65,7 +65,7 @@ const refreshToken = async (req, res, next) => {
|
|
|
65
65
|
throw new AppError('Refresh token is required', 400);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (authService.isTokenBlacklisted(token)) {
|
|
68
|
+
if (await authService.isTokenBlacklisted(token)) {
|
|
69
69
|
throw new AppError('Token has been revoked', 401);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -77,7 +77,7 @@ const refreshToken = async (req, res, next) => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Blacklist old refresh token (rotation)
|
|
80
|
-
authService.blacklistToken(token);
|
|
80
|
+
await authService.blacklistToken(token);
|
|
81
81
|
|
|
82
82
|
const tokens = authService.generateTokens(user);
|
|
83
83
|
return success(res, tokens);
|
|
@@ -92,12 +92,12 @@ const logout = async (req, res, next) => {
|
|
|
92
92
|
const accessToken = authHeader && authHeader.split(' ')[1];
|
|
93
93
|
|
|
94
94
|
if (accessToken) {
|
|
95
|
-
authService.blacklistToken(accessToken);
|
|
95
|
+
await authService.blacklistToken(accessToken);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const { refreshToken: token } = req.body;
|
|
99
99
|
if (token) {
|
|
100
|
-
authService.blacklistToken(token);
|
|
100
|
+
await authService.blacklistToken(token);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
return success(res, { message: 'Logout successful' });
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
FROM node:20-alpine
|
|
1
|
+
FROM node:20-alpine<% if (isTypescript) { %> AS builder
|
|
2
|
+
|
|
3
|
+
# Build stage for TypeScript
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY package*.json ./
|
|
6
|
+
<% if (isPrisma) { %>COPY prisma ./prisma/<% } %>
|
|
7
|
+
RUN npm ci
|
|
8
|
+
<% if (isPrisma) { %>RUN npx prisma generate<% } %>
|
|
9
|
+
COPY tsconfig.json ./
|
|
10
|
+
COPY src ./src/
|
|
11
|
+
RUN npx tsc
|
|
12
|
+
|
|
13
|
+
FROM node:20-alpine<% } %>
|
|
2
14
|
|
|
3
15
|
# Set working directory
|
|
4
16
|
WORKDIR /app
|
|
@@ -9,16 +21,14 @@ COPY package*.json ./
|
|
|
9
21
|
# Copy Prisma schema for generation
|
|
10
22
|
COPY prisma ./prisma/
|
|
11
23
|
<% } %>
|
|
12
|
-
# Install dependencies
|
|
24
|
+
# Install production dependencies
|
|
13
25
|
RUN npm ci --only=production && npm cache clean --force
|
|
14
26
|
<% if (isPrisma) { %>
|
|
15
27
|
# Generate Prisma client
|
|
16
28
|
RUN npx prisma generate
|
|
17
29
|
<% } %><% if (isTypescript) { %>
|
|
18
|
-
# Copy
|
|
19
|
-
COPY
|
|
20
|
-
COPY src ./src/
|
|
21
|
-
RUN npx tsc
|
|
30
|
+
# Copy compiled output from builder
|
|
31
|
+
COPY --from=builder /app/dist ./dist/
|
|
22
32
|
<% } else { %>
|
|
23
33
|
# Copy application code
|
|
24
34
|
COPY . .
|
|
@@ -126,27 +126,27 @@ Interactive docs available at [http://localhost:3000/api-docs](http://localhost:
|
|
|
126
126
|
|
|
127
127
|
| Method | Endpoint | Description |
|
|
128
128
|
|--------|----------|-------------|
|
|
129
|
-
| POST | `/api
|
|
130
|
-
| POST | `/api
|
|
131
|
-
| POST | `/api
|
|
132
|
-
| POST | `/api
|
|
129
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/register` | Register a new user |
|
|
130
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/login` | Login and receive tokens |
|
|
131
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/refresh` | Refresh access token |
|
|
132
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/logout` | Logout (blacklist token) |
|
|
133
133
|
|
|
134
134
|
### Users (protected)
|
|
135
135
|
|
|
136
136
|
| Method | Endpoint | Description |
|
|
137
137
|
|--------|----------|-------------|
|
|
138
|
-
| GET | `/api
|
|
139
|
-
| PUT | `/api
|
|
140
|
-
| DELETE | `/api
|
|
138
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Get current user profile |
|
|
139
|
+
| PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Update profile |
|
|
140
|
+
| DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Delete account |
|
|
141
141
|
<% } else { %>### Examples
|
|
142
142
|
|
|
143
143
|
| Method | Endpoint | Description |
|
|
144
144
|
|--------|----------|-------------|
|
|
145
|
-
| GET | `/api
|
|
146
|
-
| GET | `/api
|
|
147
|
-
| POST | `/api
|
|
148
|
-
| PUT | `/api
|
|
149
|
-
| DELETE | `/api
|
|
145
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | List all (paginated) |
|
|
146
|
+
| GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Get by ID |
|
|
147
|
+
| POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | Create |
|
|
148
|
+
| PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Update |
|
|
149
|
+
| DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Delete |
|
|
150
150
|
<% } %>
|
|
151
151
|
### Health
|
|
152
152
|
|
|
@@ -11,7 +11,7 @@ services:
|
|
|
11
11
|
env_file:
|
|
12
12
|
- .env<% if (hasDatabase) { %>
|
|
13
13
|
depends_on:
|
|
14
|
-
- <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis) { %>
|
|
14
|
+
- <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis || hasBackgroundJobs) { %>
|
|
15
15
|
- redis<% } %>
|
|
16
16
|
restart: unless-stopped
|
|
17
17
|
networks:
|
|
@@ -27,7 +27,7 @@ services:
|
|
|
27
27
|
- mongodb_data:/data/db
|
|
28
28
|
restart: unless-stopped
|
|
29
29
|
networks:
|
|
30
|
-
- app-network<% } else if (db === 'postgresql') { %> postgres:
|
|
30
|
+
- app-network<% } else if (db === 'postgresql' || db === 'prisma') { %> postgres:
|
|
31
31
|
image: postgres:15-alpine
|
|
32
32
|
ports:
|
|
33
33
|
- "5432:5432"
|
|
@@ -41,7 +41,7 @@ services:
|
|
|
41
41
|
networks:
|
|
42
42
|
- app-network<% } %>
|
|
43
43
|
|
|
44
|
-
<% if (hasRedis) { %> redis:
|
|
44
|
+
<% if (hasRedis || hasBackgroundJobs) { %> redis:
|
|
45
45
|
image: redis:7-alpine
|
|
46
46
|
ports:
|
|
47
47
|
- "6379:6379"
|
|
@@ -51,9 +51,9 @@ services:
|
|
|
51
51
|
networks:
|
|
52
52
|
- app-network
|
|
53
53
|
<% } %>
|
|
54
|
-
<% if (hasDatabase || hasRedis) { %>volumes:<% } %>
|
|
54
|
+
<% if (hasDatabase || hasRedis || hasBackgroundJobs) { %>volumes:<% } %>
|
|
55
55
|
<% if (hasDatabase) { %> <%= db === 'mongodb' ? 'mongodb_data:' : 'postgres_data:' %><% } %>
|
|
56
|
-
<% if (hasRedis) { %> redis_data:<% } %>
|
|
56
|
+
<% if (hasRedis || hasBackgroundJobs) { %> redis_data:<% } %>
|
|
57
57
|
|
|
58
58
|
networks:
|
|
59
59
|
app-network:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"db:migrate:undo": "npx sequelize-cli db:migrate:undo",
|
|
20
20
|
"db:seed": "npx sequelize-cli db:seed:all",
|
|
21
21
|
"db:create": "npx sequelize-cli db:create"<% } %><% if (hasMCP) { %>,
|
|
22
|
-
"mcp:start": "node src/mcp/server.js"<% } %>
|
|
22
|
+
"mcp:start": "<% if (isTypescript) { %>tsx src/mcp/server.ts<% } else { %>node src/mcp/server.js<% } %>"<% } %>
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"express": "^4.21.0",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"bullmq": "^5.12.0"<% } %><% if (hasMCP) { %>,
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.12.0"<% } %><% if (hasAI) { %>,
|
|
52
52
|
"@langchain/core": "^0.3.0",
|
|
53
|
-
"@langchain/openai": "^0.5.0",
|
|
54
|
-
"@langchain/anthropic": "^0.3.0",
|
|
55
|
-
"@langchain/google-genai": "^0.2.0",
|
|
56
|
-
"@langchain/ollama": "^0.1.0",
|
|
57
|
-
"@langchain/langgraph": "^0.2.0"<% } %><% if (logger === 'winston') { %>,
|
|
53
|
+
<% if (aiProvider === 'openai') { %> "@langchain/openai": "^0.5.0",
|
|
54
|
+
<% } else if (aiProvider === 'anthropic') { %> "@langchain/anthropic": "^0.3.0",
|
|
55
|
+
<% } else if (aiProvider === 'gemini') { %> "@langchain/google-genai": "^0.2.0",
|
|
56
|
+
<% } else if (aiProvider === 'ollama') { %> "@langchain/ollama": "^0.1.0",
|
|
57
|
+
<% } %> "@langchain/langgraph": "^0.2.0"<% } %><% if (logger === 'winston') { %>,
|
|
58
58
|
"winston": "^3.15.0"<% } %><% if (logger === 'pino') { %>,
|
|
59
59
|
"pino": "^9.5.0",
|
|
60
60
|
"pino-pretty": "^13.0.0"<% } %>
|
|
@@ -73,7 +73,10 @@
|
|
|
73
73
|
"@types/swagger-jsdoc": "^6.0.4",
|
|
74
74
|
"@types/swagger-ui-express": "^4.1.6"<% } %><% if (hasRequestId) { %>,
|
|
75
75
|
"@types/uuid": "^10.0.0"<% } %>,<% } %>
|
|
76
|
-
"jest": "^29.7.0"
|
|
76
|
+
"jest": "^29.7.0",<% if (isTypescript) { %>
|
|
77
|
+
"ts-jest": "^29.2.0",
|
|
78
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",<% } %>
|
|
77
80
|
"supertest": "^7.0.0",
|
|
78
81
|
"eslint": "^8.57.0",
|
|
79
82
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
@@ -5,7 +5,7 @@ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio
|
|
|
5
5
|
const { z } = require('zod');
|
|
6
6
|
<% if (hasDatabase) { %>const db = require('../config/database');
|
|
7
7
|
<% } %><% if (hasAuth) { %>const userService = require('../services/userService');
|
|
8
|
-
<% } %><% if (!hasAuth
|
|
8
|
+
<% } %><% if (!hasAuth) { %>const exampleService = require('../services/exampleService');
|
|
9
9
|
<% } %><% if (hasEmail) { %>const emailService = require('../services/emailService');
|
|
10
10
|
<% } %>
|
|
11
11
|
|
|
@@ -82,7 +82,7 @@ server.tool(
|
|
|
82
82
|
limit: z.number().optional().describe('Items per page (default: 20)'),
|
|
83
83
|
},
|
|
84
84
|
async ({ page = 1, limit = 20 }) => {
|
|
85
|
-
const result = await exampleService.
|
|
85
|
+
const result = await exampleService.getAllExamples(page, limit);
|
|
86
86
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
87
87
|
}
|
|
88
88
|
);
|
|
@@ -95,7 +95,7 @@ server.tool(
|
|
|
95
95
|
description: z.string().optional().describe('Item description'),
|
|
96
96
|
},
|
|
97
97
|
async ({ title, description }) => {
|
|
98
|
-
const item = await exampleService.
|
|
98
|
+
const item = await exampleService.createExample({ title, description });
|
|
99
99
|
return { content: [{ type: 'text', text: JSON.stringify(item, null, 2) }] };
|
|
100
100
|
}
|
|
101
101
|
);
|
|
@@ -27,7 +27,7 @@ const authenticateToken = <% if (hasRedis) { %>async <% } %>(req, res, next) =>
|
|
|
27
27
|
|
|
28
28
|
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
|
29
29
|
if (err) {
|
|
30
|
-
return next(new AppError('Invalid or expired token',
|
|
30
|
+
return next(new AppError('Invalid or expired token', 401));
|
|
31
31
|
}
|
|
32
32
|
req.user = decoded;
|
|
33
33
|
next();
|
|
@@ -65,16 +65,4 @@ const optionalAuth = <% if (hasRedis) { %>async <% } %>(req, res, next) => {
|
|
|
65
65
|
next();
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
return (req, res, next) => {
|
|
70
|
-
if (!req.user) {
|
|
71
|
-
return next(new AppError('Authentication required', 401));
|
|
72
|
-
}
|
|
73
|
-
if (req.user.role !== role) {
|
|
74
|
-
return next(new AppError('Insufficient permissions', 403));
|
|
75
|
-
}
|
|
76
|
-
next();
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
module.exports = { authenticateToken, optionalAuth, requireRole };
|
|
68
|
+
module.exports = { authenticateToken, optionalAuth };
|
|
@@ -5,7 +5,7 @@ const router = express.Router();
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @swagger
|
|
8
|
-
*
|
|
8
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chat:
|
|
9
9
|
* post:
|
|
10
10
|
* summary: Send a chat message to the AI
|
|
11
11
|
* tags: [AI]
|
|
@@ -33,9 +33,6 @@ const router = express.Router();
|
|
|
33
33
|
* enum: [user, assistant]
|
|
34
34
|
* content:
|
|
35
35
|
* type: string
|
|
36
|
-
* provider:
|
|
37
|
-
* type: string
|
|
38
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
39
36
|
* model:
|
|
40
37
|
* type: string
|
|
41
38
|
* responses:
|
|
@@ -46,7 +43,7 @@ router.post('/chat', chatHandler);
|
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
45
|
* @swagger
|
|
49
|
-
*
|
|
46
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/stream:
|
|
50
47
|
* post:
|
|
51
48
|
* summary: Stream an AI response via Server-Sent Events
|
|
52
49
|
* tags: [AI]
|
|
@@ -68,7 +65,7 @@ router.post('/stream', streamHandler);
|
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
67
|
* @swagger
|
|
71
|
-
*
|
|
68
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chain:
|
|
72
69
|
* post:
|
|
73
70
|
* summary: Run a prompt template chain with variable substitution
|
|
74
71
|
* tags: [AI]
|
|
@@ -86,9 +83,6 @@ router.post('/stream', streamHandler);
|
|
|
86
83
|
* variables:
|
|
87
84
|
* type: object
|
|
88
85
|
* example: { "text": "Node.js is a JavaScript runtime..." }
|
|
89
|
-
* provider:
|
|
90
|
-
* type: string
|
|
91
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
92
86
|
* responses:
|
|
93
87
|
* 200:
|
|
94
88
|
* description: Chain result
|
|
@@ -97,7 +91,7 @@ router.post('/chain', chainHandler);
|
|
|
97
91
|
|
|
98
92
|
/**
|
|
99
93
|
* @swagger
|
|
100
|
-
*
|
|
94
|
+
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/agent:
|
|
101
95
|
* post:
|
|
102
96
|
* summary: Run the LangGraph ReAct agent with tool calling
|
|
103
97
|
* tags: [AI]
|
|
@@ -114,9 +108,6 @@ router.post('/chain', chainHandler);
|
|
|
114
108
|
* example: "What time is it right now?"
|
|
115
109
|
* systemPrompt:
|
|
116
110
|
* type: string
|
|
117
|
-
* provider:
|
|
118
|
-
* type: string
|
|
119
|
-
* enum: [openai, anthropic, gemini, ollama]
|
|
120
111
|
* responses:
|
|
121
112
|
* 200:
|
|
122
113
|
* description: Agent response with tool-use steps
|
|
@@ -2,7 +2,7 @@ const { getModel } = require('../config/ai');
|
|
|
2
2
|
const { HumanMessage, SystemMessage, AIMessage } = require('@langchain/core/messages');
|
|
3
3
|
const { StringOutputParser } = require('@langchain/core/output_parsers');
|
|
4
4
|
const { ChatPromptTemplate } = require('@langchain/core/prompts');
|
|
5
|
-
const logger = require('../utils/logger');
|
|
5
|
+
const { logger } = require('../utils/logger');
|
|
6
6
|
|
|
7
7
|
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful AI assistant.';
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ const chat = async (message, options = {}) => {
|
|
|
27
27
|
const response = await model.invoke(messages);
|
|
28
28
|
|
|
29
29
|
logger.info('AI chat completed', {
|
|
30
|
-
provider:
|
|
30
|
+
provider: process.env.AI_PROVIDER,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
return {
|
|
@@ -17,9 +17,9 @@ describe('Example Endpoints', () => {
|
|
|
17
17
|
.get('/api/examples?page=1&limit=1');
|
|
18
18
|
|
|
19
19
|
expect(res.statusCode).toBe(200);
|
|
20
|
-
expect(res.body.meta).toHaveProperty('page', 1);
|
|
21
|
-
expect(res.body.meta).toHaveProperty('limit', 1);
|
|
22
|
-
expect(res.body.meta).toHaveProperty('total');
|
|
20
|
+
expect(res.body.meta.pagination).toHaveProperty('page', 1);
|
|
21
|
+
expect(res.body.meta.pagination).toHaveProperty('limit', 1);
|
|
22
|
+
expect(res.body.meta.pagination).toHaveProperty('total');
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// templates/tests/setup.js.ejs
|
|
2
2
|
// Global test setup
|
|
3
3
|
process.env.NODE_ENV = 'test';
|
|
4
|
-
<% if (
|
|
4
|
+
<% if (hasAuth) { %>process.env.JWT_SECRET = 'test-secret';
|
|
5
5
|
process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';<% } %>
|
|
6
6
|
|
|
7
7
|
// Suppress console logs during tests
|