@programisto/edrm-storage 0.3.1
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/README.md +135 -0
- package/dist/bin/www.d.ts +2 -0
- package/dist/bin/www.js +13 -0
- package/dist/modules/edrm-exams/lib/openai/correctQuestion.txt +9 -0
- package/dist/modules/edrm-exams/lib/openai/createQuestion.txt +6 -0
- package/dist/modules/edrm-exams/lib/openai.d.ts +37 -0
- package/dist/modules/edrm-exams/lib/openai.js +135 -0
- package/dist/modules/edrm-exams/listeners/correct.listener.d.ts +2 -0
- package/dist/modules/edrm-exams/listeners/correct.listener.js +167 -0
- package/dist/modules/edrm-exams/models/candidate.model.d.ts +21 -0
- package/dist/modules/edrm-exams/models/candidate.model.js +75 -0
- package/dist/modules/edrm-exams/models/candidate.models.d.ts +21 -0
- package/dist/modules/edrm-exams/models/candidate.models.js +75 -0
- package/dist/modules/edrm-exams/models/company.model.d.ts +8 -0
- package/dist/modules/edrm-exams/models/company.model.js +34 -0
- package/dist/modules/edrm-exams/models/contact.model.d.ts +14 -0
- package/dist/modules/edrm-exams/models/contact.model.js +60 -0
- package/dist/modules/edrm-exams/models/test-category.models.d.ts +7 -0
- package/dist/modules/edrm-exams/models/test-category.models.js +29 -0
- package/dist/modules/edrm-exams/models/test-job.model.d.ts +7 -0
- package/dist/modules/edrm-exams/models/test-job.model.js +29 -0
- package/dist/modules/edrm-exams/models/test-question.model.d.ts +25 -0
- package/dist/modules/edrm-exams/models/test-question.model.js +70 -0
- package/dist/modules/edrm-exams/models/test-result.model.d.ts +26 -0
- package/dist/modules/edrm-exams/models/test-result.model.js +70 -0
- package/dist/modules/edrm-exams/models/test.model.d.ts +47 -0
- package/dist/modules/edrm-exams/models/test.model.js +133 -0
- package/dist/modules/edrm-exams/models/user.model.d.ts +18 -0
- package/dist/modules/edrm-exams/models/user.model.js +73 -0
- package/dist/modules/edrm-exams/routes/company.router.d.ts +7 -0
- package/dist/modules/edrm-exams/routes/company.router.js +108 -0
- package/dist/modules/edrm-exams/routes/exams-candidate.router.d.ts +7 -0
- package/dist/modules/edrm-exams/routes/exams-candidate.router.js +448 -0
- package/dist/modules/edrm-exams/routes/exams.router.d.ts +8 -0
- package/dist/modules/edrm-exams/routes/exams.router.js +1343 -0
- package/dist/modules/edrm-exams/routes/result.router.d.ts +7 -0
- package/dist/modules/edrm-exams/routes/result.router.js +370 -0
- package/dist/modules/edrm-exams/routes/user.router.d.ts +7 -0
- package/dist/modules/edrm-exams/routes/user.router.js +96 -0
- package/dist/modules/edrm-storage/config/edrm-storage.config.d.ts +29 -0
- package/dist/modules/edrm-storage/config/edrm-storage.config.js +31 -0
- package/dist/modules/edrm-storage/config/environment.example.d.ts +54 -0
- package/dist/modules/edrm-storage/config/environment.example.js +130 -0
- package/dist/modules/edrm-storage/examples/usage.example.d.ts +52 -0
- package/dist/modules/edrm-storage/examples/usage.example.js +156 -0
- package/dist/modules/edrm-storage/index.d.ts +5 -0
- package/dist/modules/edrm-storage/index.js +8 -0
- package/dist/modules/edrm-storage/integration/edrm-storage-integration.d.ts +53 -0
- package/dist/modules/edrm-storage/integration/edrm-storage-integration.js +132 -0
- package/dist/modules/edrm-storage/interfaces/storage-provider.interface.d.ts +35 -0
- package/dist/modules/edrm-storage/interfaces/storage-provider.interface.js +1 -0
- package/dist/modules/edrm-storage/migrations/edrm-storage.migration.d.ts +6 -0
- package/dist/modules/edrm-storage/migrations/edrm-storage.migration.js +151 -0
- package/dist/modules/edrm-storage/models/file.model.d.ts +78 -0
- package/dist/modules/edrm-storage/models/file.model.js +190 -0
- package/dist/modules/edrm-storage/providers/s3-storage.provider.d.ts +18 -0
- package/dist/modules/edrm-storage/providers/s3-storage.provider.js +95 -0
- package/dist/modules/edrm-storage/routes/edrm-storage.router.d.ts +8 -0
- package/dist/modules/edrm-storage/routes/edrm-storage.router.js +155 -0
- package/dist/modules/edrm-storage/scripts/quick-start.d.ts +7 -0
- package/dist/modules/edrm-storage/scripts/quick-start.js +114 -0
- package/dist/modules/edrm-storage/services/edrm-storage.service.d.ts +29 -0
- package/dist/modules/edrm-storage/services/edrm-storage.service.js +188 -0
- package/dist/modules/edrm-storage/tests/edrm-storage.service.test.d.ts +1 -0
- package/dist/modules/edrm-storage/tests/edrm-storage.service.test.js +143 -0
- package/dist/modules/edrm-storage/tests/integration.test.d.ts +1 -0
- package/dist/modules/edrm-storage/tests/integration.test.js +141 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Endurance Template
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Endurance Framework is a highly modular and scalable Node.js project template built on Express.js. It is designed to dynamically load and manage independent modules, making it extremely easy to develop, extend, and maintain web applications. The goal is to create a library of ready-made modules (e.g., login, user management, etc.) that can be easily integrated into any project.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Dynamic Module Loading**: Easily add new modules with their own models and routes, and they will be automatically loaded and exposed by the application.
|
|
10
|
+
- **Express.js**: Fast, unopinionated, minimalist web framework for Node.js.
|
|
11
|
+
- **Modular Structure**: Each module is independent, promoting separation of concerns and better maintainability.
|
|
12
|
+
- **Modules marketplace**: Uses npm packages starting with "EDRM-" to quickly add features to your API.
|
|
13
|
+
- **Lib assets**: Include everything your need to start creating a robust API : events management, CRON, swagger, API versioning, webhooks etc.
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
|
|
19
|
+
- Node.js (v20.x)
|
|
20
|
+
- MongoDB (optional for session management and data management)
|
|
21
|
+
|
|
22
|
+
### Installation
|
|
23
|
+
|
|
24
|
+
1. Install our CLI:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install -g endurance
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Create a project folder and create a new project:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
mkdir newproject
|
|
34
|
+
cd newproject
|
|
35
|
+
|
|
36
|
+
endurance new
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Usage
|
|
40
|
+
|
|
41
|
+
1. **Start the application**:
|
|
42
|
+
|
|
43
|
+
For development:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm start
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For production:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm run prod
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. **Add a new module**:
|
|
56
|
+
|
|
57
|
+
To add a new module, create a new folder under the `modules` directory. Each module should contain its own models and routes.
|
|
58
|
+
|
|
59
|
+
Example structure for a new module:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
modules/
|
|
63
|
+
your-module/
|
|
64
|
+
models/
|
|
65
|
+
YourModel.js
|
|
66
|
+
routes/
|
|
67
|
+
yourModule.router.js
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. **Dynamic Module Loading**:
|
|
71
|
+
|
|
72
|
+
The application will automatically load and expose the routes and models from any new module added to the `modules` directory. There is no need for additional configuration.
|
|
73
|
+
|
|
74
|
+
### Example
|
|
75
|
+
|
|
76
|
+
Here is an example of how to add a simple "login" module:
|
|
77
|
+
|
|
78
|
+
1. **Create the module structure**:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
modules/
|
|
82
|
+
login/
|
|
83
|
+
models/
|
|
84
|
+
User.js
|
|
85
|
+
routes/
|
|
86
|
+
login.router.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
2. **Define the model (`User.js`)**:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const mongoose = require('mongoose');
|
|
93
|
+
|
|
94
|
+
const UserSchema = new mongoose.Schema({
|
|
95
|
+
username: { type: String, required: true },
|
|
96
|
+
password: { type: String, required: true }
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
module.exports = mongoose.model('User', UserSchema);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. **Define the route (`login.router.js`)**:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const router = require('endurance-core/lib/router')();
|
|
106
|
+
|
|
107
|
+
router.post('/login', (req, res) => {
|
|
108
|
+
// Your login logic here
|
|
109
|
+
res.send('Login route');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
module.exports = router;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Testing
|
|
116
|
+
|
|
117
|
+
Run tests using Mocha and Supertest:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
npm test
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Roadmap
|
|
124
|
+
|
|
125
|
+
- **Library of Modules**: Develop a library of ready-made modules (e.g., login, user management) for easy integration.
|
|
126
|
+
- **Enhanced Documentation**: Provide detailed documentation and examples for each module.
|
|
127
|
+
- **Community Contributions**: Encourage community contributions to expand the module library.
|
|
128
|
+
|
|
129
|
+
## Contributing
|
|
130
|
+
|
|
131
|
+
We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING.md) for more details.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
package/dist/bin/www.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const localAppPath = path.join(__dirname, '../lib/app.js');
|
|
8
|
+
if (fs.existsSync(localAppPath)) {
|
|
9
|
+
await import(localAppPath);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
await import('@programisto/endurance-core');
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Question : ${instruction}
|
|
2
|
+
Type de question : ${questionType}
|
|
3
|
+
- si MCQ les réponses possibles étaient : ${possibleResponses}
|
|
4
|
+
|
|
5
|
+
Réponse du candidat à corriger :
|
|
6
|
+
--------------------------------
|
|
7
|
+
${response}
|
|
8
|
+
--------------------------------
|
|
9
|
+
Score à donner : De 0 point (tout faux ou réponse vide) à ${maxScore} points (réponse correcte), avec un commentaire de correction
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface CreateQuestionParams {
|
|
2
|
+
job: string;
|
|
3
|
+
seniority: string;
|
|
4
|
+
questionType: string;
|
|
5
|
+
category: string;
|
|
6
|
+
expertiseLevel: string;
|
|
7
|
+
otherQuestions: string;
|
|
8
|
+
}
|
|
9
|
+
interface CorrectQuestionParams {
|
|
10
|
+
question: {
|
|
11
|
+
_id: string;
|
|
12
|
+
instruction: string;
|
|
13
|
+
maxScore: number;
|
|
14
|
+
possibleResponses: Array<{
|
|
15
|
+
possibleResponse: string;
|
|
16
|
+
valid: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
questionType: string;
|
|
19
|
+
};
|
|
20
|
+
result: {
|
|
21
|
+
responses: Array<{
|
|
22
|
+
questionId: string;
|
|
23
|
+
response: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
interface ContextBuilder {
|
|
28
|
+
createQuestion: (params: CreateQuestionParams) => Promise<Record<string, string>>;
|
|
29
|
+
correctQuestion: (params: CorrectQuestionParams) => Promise<{
|
|
30
|
+
instruction: string;
|
|
31
|
+
response: string;
|
|
32
|
+
maxScore: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare function generateLiveMessage(messageType: keyof ContextBuilder, params: CreateQuestionParams | CorrectQuestionParams, json?: boolean): Promise<string>;
|
|
36
|
+
export declare function generateLiveMessageAssistant(assistantId: string, messageType: keyof ContextBuilder, params: CreateQuestionParams | CorrectQuestionParams, json?: boolean): Promise<string>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const openai = new OpenAI({
|
|
8
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
9
|
+
});
|
|
10
|
+
const contextBuilder = {
|
|
11
|
+
async createQuestion({ job, seniority, questionType, category, expertiseLevel, otherQuestions }) {
|
|
12
|
+
const context = {
|
|
13
|
+
job,
|
|
14
|
+
seniority,
|
|
15
|
+
questionType,
|
|
16
|
+
category,
|
|
17
|
+
expertiseLevel,
|
|
18
|
+
otherQuestions
|
|
19
|
+
};
|
|
20
|
+
return context;
|
|
21
|
+
},
|
|
22
|
+
async correctQuestion({ question, result }) {
|
|
23
|
+
let response = '';
|
|
24
|
+
for (let i = 0; i < result.responses.length; i++) {
|
|
25
|
+
if (result.responses[i].questionId === question._id) {
|
|
26
|
+
response = result.responses[i].response;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const instruction = question.instruction;
|
|
31
|
+
const maxScore = question.maxScore;
|
|
32
|
+
const questionType = question.questionType;
|
|
33
|
+
const possibleResponses = question.possibleResponses.map((response, index) => `réponse ${index + 1} = "${response.possibleResponse}" (${response.valid ? 'correcte' : 'incorrecte'})`).join('\n');
|
|
34
|
+
const context = {
|
|
35
|
+
instruction,
|
|
36
|
+
response,
|
|
37
|
+
maxScore,
|
|
38
|
+
questionType,
|
|
39
|
+
possibleResponses
|
|
40
|
+
};
|
|
41
|
+
return context;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export async function generateLiveMessage(messageType, params, json) {
|
|
45
|
+
const MAX_RETRY = 2;
|
|
46
|
+
let retryCount = 0;
|
|
47
|
+
const context = await contextBuilder[messageType](params);
|
|
48
|
+
const text = fs.readFileSync(path.join(__dirname, 'openai', `${messageType}.txt`), 'utf8');
|
|
49
|
+
const message = text.replace(/\${(.*?)}/g, (_, v) => context[v]);
|
|
50
|
+
while (retryCount <= MAX_RETRY) {
|
|
51
|
+
try {
|
|
52
|
+
const openAIParams = {
|
|
53
|
+
model: 'gpt-4-1106-preview',
|
|
54
|
+
temperature: 0.7,
|
|
55
|
+
messages: [{ role: 'system', content: message }]
|
|
56
|
+
};
|
|
57
|
+
if (json) {
|
|
58
|
+
openAIParams.response_format = { type: 'json_object' };
|
|
59
|
+
}
|
|
60
|
+
const result = await openai.chat.completions.create(openAIParams);
|
|
61
|
+
const content = result.choices[0].message.content;
|
|
62
|
+
if (!content) {
|
|
63
|
+
throw new Error('No content in response');
|
|
64
|
+
}
|
|
65
|
+
return removeQuotes(content);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
retryCount++;
|
|
69
|
+
console.log(error);
|
|
70
|
+
if (retryCount > MAX_RETRY) {
|
|
71
|
+
return 'Brain freezed, I cannot generate a live message right now.';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return 'Brain freezed, I cannot generate a live message right now.';
|
|
76
|
+
}
|
|
77
|
+
export async function generateLiveMessageAssistant(assistantId, messageType, params, json) {
|
|
78
|
+
const MAX_RETRY = 2;
|
|
79
|
+
let retryCount = 0;
|
|
80
|
+
// Construire le contexte pour le message
|
|
81
|
+
const context = await contextBuilder[messageType](params);
|
|
82
|
+
const text = fs.readFileSync(path.join(__dirname, 'openai', `${messageType}.txt`), 'utf8');
|
|
83
|
+
const message = text.replace(/\${(.*?)}/g, (_, v) => context[v]);
|
|
84
|
+
while (retryCount <= MAX_RETRY) {
|
|
85
|
+
try {
|
|
86
|
+
// Créer un thread avec l'assistant
|
|
87
|
+
const thread = await openai.beta.threads.create();
|
|
88
|
+
// Ajouter le message avec le contexte
|
|
89
|
+
await openai.beta.threads.messages.create(thread.id, {
|
|
90
|
+
role: 'user',
|
|
91
|
+
content: message
|
|
92
|
+
});
|
|
93
|
+
// Exécuter l'assistant
|
|
94
|
+
const run = await openai.beta.threads.runs.create(thread.id, {
|
|
95
|
+
assistant_id: assistantId
|
|
96
|
+
});
|
|
97
|
+
// Attendre que l'exécution soit terminée
|
|
98
|
+
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
99
|
+
while (runStatus.status === 'in_progress' || runStatus.status === 'queued') {
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
101
|
+
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
102
|
+
}
|
|
103
|
+
if (runStatus.status === 'failed') {
|
|
104
|
+
throw new Error('Assistant execution failed');
|
|
105
|
+
}
|
|
106
|
+
// Récupérer les messages de réponse
|
|
107
|
+
const messages = await openai.beta.threads.messages.list(thread.id);
|
|
108
|
+
const lastMessage = messages.data[0]; // Le premier message est le plus récent
|
|
109
|
+
if (!lastMessage || !lastMessage.content || lastMessage.content.length === 0) {
|
|
110
|
+
throw new Error('No content in response');
|
|
111
|
+
}
|
|
112
|
+
const content = lastMessage.content[0];
|
|
113
|
+
if (content.type === 'text') {
|
|
114
|
+
return removeQuotes(content.text.value);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
throw new Error('Unexpected content type');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
retryCount++;
|
|
122
|
+
console.log(error);
|
|
123
|
+
if (retryCount > MAX_RETRY) {
|
|
124
|
+
return 'Brain freezed, I cannot generate a live message right now.';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return 'Brain freezed, I cannot generate a live message right now.';
|
|
129
|
+
}
|
|
130
|
+
function removeQuotes(str) {
|
|
131
|
+
if (str.startsWith('"') && str.endsWith('"')) {
|
|
132
|
+
return str.substring(1, str.length - 1);
|
|
133
|
+
}
|
|
134
|
+
return str;
|
|
135
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { enduranceListener, enduranceEventTypes, enduranceEmitter } from '@programisto/endurance-core';
|
|
2
|
+
import TestQuestion from '../models/test-question.model.js';
|
|
3
|
+
import { generateLiveMessageAssistant } from '../lib/openai.js';
|
|
4
|
+
import TestResult, { TestState } from '../models/test-result.model.js';
|
|
5
|
+
import CandidateModel from '../models/candidate.model.js';
|
|
6
|
+
import ContactModel from '../models/contact.model.js';
|
|
7
|
+
import TestModel from '../models/test.model.js';
|
|
8
|
+
async function sendDiscordNotification(message) {
|
|
9
|
+
const discordWebhook = process.env.TEST_CORRECTION_DISCORD_WEBHOOKS;
|
|
10
|
+
if (discordWebhook) {
|
|
11
|
+
try {
|
|
12
|
+
await fetch(discordWebhook, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
content: message,
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error('Error sending Discord notification:', error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function correctTest(options) {
|
|
28
|
+
if (!options.testId)
|
|
29
|
+
throw new Error('TestId is required');
|
|
30
|
+
if (!options.responses)
|
|
31
|
+
throw new Error('Responses are required');
|
|
32
|
+
if (!options.state)
|
|
33
|
+
throw new Error('State is required');
|
|
34
|
+
try {
|
|
35
|
+
// Récupérer le résultat de test
|
|
36
|
+
const result = await TestResult.findById(options._id);
|
|
37
|
+
if (!result) {
|
|
38
|
+
throw new Error('Test result not found');
|
|
39
|
+
}
|
|
40
|
+
let finalscore = 0;
|
|
41
|
+
let maxScore = 0;
|
|
42
|
+
// Pour chaque réponse enregistrée en base, on cherche la correction correspondante
|
|
43
|
+
for (const dbResponse of result.responses) {
|
|
44
|
+
const correction = options.responses.find(r => r.questionId.toString() === dbResponse.questionId.toString());
|
|
45
|
+
if (!correction)
|
|
46
|
+
continue;
|
|
47
|
+
const question = await TestQuestion.findById(dbResponse.questionId);
|
|
48
|
+
if (!question) {
|
|
49
|
+
console.error('Question not found', { questionId: dbResponse.questionId });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
maxScore += question.maxScore;
|
|
53
|
+
const scoreResponse = await generateLiveMessageAssistant(process.env.OPENAI_ASSISTANT_ID_CORRECT_QUESTION || '', 'correctQuestion', {
|
|
54
|
+
question: {
|
|
55
|
+
_id: question._id.toString(),
|
|
56
|
+
instruction: question.instruction,
|
|
57
|
+
possibleResponses: question.possibleResponses,
|
|
58
|
+
questionType: question.questionType,
|
|
59
|
+
maxScore: question.maxScore
|
|
60
|
+
},
|
|
61
|
+
result: {
|
|
62
|
+
responses: [{
|
|
63
|
+
questionId: dbResponse.questionId.toString(),
|
|
64
|
+
response: dbResponse.response
|
|
65
|
+
}]
|
|
66
|
+
}
|
|
67
|
+
}, true);
|
|
68
|
+
console.log('Correction result:', { scoreResponse });
|
|
69
|
+
const parsedResult = JSON.parse(scoreResponse);
|
|
70
|
+
// Valider le score retourné par l'IA
|
|
71
|
+
let validScore = 0;
|
|
72
|
+
if (parsedResult.score !== undefined && parsedResult.score !== null) {
|
|
73
|
+
const score = parseFloat(parsedResult.score.toString());
|
|
74
|
+
if (!isNaN(score) && isFinite(score) && score >= 0) {
|
|
75
|
+
validScore = score;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.warn('Invalid score returned by AI:', parsedResult.score);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finalscore += validScore;
|
|
82
|
+
dbResponse.score = validScore;
|
|
83
|
+
dbResponse.comment = parsedResult.comment || '';
|
|
84
|
+
}
|
|
85
|
+
// S'assurer que finalscore est un nombre valide
|
|
86
|
+
if (isNaN(finalscore) || !isFinite(finalscore)) {
|
|
87
|
+
console.warn('Invalid finalscore calculated, setting to 0:', finalscore);
|
|
88
|
+
finalscore = 0;
|
|
89
|
+
}
|
|
90
|
+
// S'assurer que maxScore est un nombre valide
|
|
91
|
+
if (isNaN(maxScore) || !isFinite(maxScore)) {
|
|
92
|
+
console.warn('Invalid maxScore calculated, setting to 0:', maxScore);
|
|
93
|
+
maxScore = 0;
|
|
94
|
+
}
|
|
95
|
+
// Mettre à jour le score final et l'état
|
|
96
|
+
result.score = finalscore;
|
|
97
|
+
result.state = TestState.Finish;
|
|
98
|
+
// Forcer la sauvegarde des sous-documents responses
|
|
99
|
+
result.markModified('responses');
|
|
100
|
+
// Calculer le pourcentage de score en évitant la division par zéro
|
|
101
|
+
let scorePercentage = 0;
|
|
102
|
+
if (maxScore > 0) {
|
|
103
|
+
scorePercentage = Math.ceil((finalscore / maxScore) * 100);
|
|
104
|
+
}
|
|
105
|
+
else if (finalscore > 0) {
|
|
106
|
+
// Si maxScore est 0 mais qu'il y a un score, on met 100%
|
|
107
|
+
scorePercentage = 100;
|
|
108
|
+
}
|
|
109
|
+
// S'assurer que le score est un nombre valide
|
|
110
|
+
if (isNaN(scorePercentage) || !isFinite(scorePercentage)) {
|
|
111
|
+
scorePercentage = 0;
|
|
112
|
+
}
|
|
113
|
+
// Sauvegarder les modifications avec findByIdAndUpdate pour éviter les conflits de version
|
|
114
|
+
await TestResult.findByIdAndUpdate(result._id, {
|
|
115
|
+
$set: {
|
|
116
|
+
responses: result.responses,
|
|
117
|
+
score: finalscore,
|
|
118
|
+
state: result.state
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const test = await TestModel.findById(result.testId);
|
|
122
|
+
const candidate = await CandidateModel.findById(result.candidateId);
|
|
123
|
+
if (candidate) {
|
|
124
|
+
const contact = await ContactModel.findById(candidate.contact);
|
|
125
|
+
if (contact) {
|
|
126
|
+
const testLink = (process.env.TEST_INVITATION_LINK || '') + contact.email;
|
|
127
|
+
enduranceEmitter.emit(enduranceEventTypes.SEND_EMAIL, {
|
|
128
|
+
template: 'test-result',
|
|
129
|
+
to: contact.email,
|
|
130
|
+
data: {
|
|
131
|
+
firstname: contact.firstname,
|
|
132
|
+
lastname: contact.lastname,
|
|
133
|
+
score: scorePercentage,
|
|
134
|
+
testName: test?.title || '',
|
|
135
|
+
testLink
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// Envoyer une notification Discord
|
|
139
|
+
const discordMessage = `📊 **Nouveau résultat de test**\n` +
|
|
140
|
+
`**Candidat:** ${contact.firstname} ${contact.lastname}\n` +
|
|
141
|
+
`**Test:** ${test?.title || 'Test inconnu'}\n` +
|
|
142
|
+
`**Score:** ${scorePercentage}%\n` +
|
|
143
|
+
`**Score brut:** ${finalscore}/${maxScore}`;
|
|
144
|
+
await sendDiscordNotification(discordMessage);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err instanceof Error) {
|
|
150
|
+
console.error(`Error correcting test: ${err.message}`, { err });
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.error('Unknown error occurred during test correction', { err });
|
|
154
|
+
}
|
|
155
|
+
throw err; // Propager l'erreur pour la gestion en amont
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
enduranceListener.createListener(enduranceEventTypes.CORRECT_TEST, (args) => {
|
|
159
|
+
if (typeof args === 'object' && args !== null) {
|
|
160
|
+
const options = args;
|
|
161
|
+
correctTest(options);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.error('Invalid data type received in correct listener', { args });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
export default enduranceListener;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EnduranceSchema } from '@programisto/endurance-core';
|
|
2
|
+
import { Types } from 'mongoose';
|
|
3
|
+
export declare enum ExperienceLevel {
|
|
4
|
+
JUNIOR = "JUNIOR",
|
|
5
|
+
INTERMEDIATE = "INTERMEDIATE",
|
|
6
|
+
SENIOR = "SENIOR",
|
|
7
|
+
EXPERT = "EXPERT"
|
|
8
|
+
}
|
|
9
|
+
declare class Candidate extends EnduranceSchema {
|
|
10
|
+
contact: Types.ObjectId;
|
|
11
|
+
experienceLevel: string;
|
|
12
|
+
yearsOfExperience: number;
|
|
13
|
+
skills: string[];
|
|
14
|
+
magicLinkToken?: string;
|
|
15
|
+
magicLinkExpiresAt?: Date;
|
|
16
|
+
authToken?: string;
|
|
17
|
+
authTokenExpiresAt?: Date;
|
|
18
|
+
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
19
|
+
}
|
|
20
|
+
declare const CandidateModel: import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
21
|
+
export default CandidateModel;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance-core';
|
|
11
|
+
import { Types } from 'mongoose';
|
|
12
|
+
// Enum pour les niveaux d'expérience
|
|
13
|
+
/* eslint-disable no-unused-vars */
|
|
14
|
+
export var ExperienceLevel;
|
|
15
|
+
(function (ExperienceLevel) {
|
|
16
|
+
ExperienceLevel["JUNIOR"] = "JUNIOR";
|
|
17
|
+
ExperienceLevel["INTERMEDIATE"] = "INTERMEDIATE";
|
|
18
|
+
ExperienceLevel["SENIOR"] = "SENIOR";
|
|
19
|
+
ExperienceLevel["EXPERT"] = "EXPERT";
|
|
20
|
+
})(ExperienceLevel || (ExperienceLevel = {}));
|
|
21
|
+
/* eslint-enable no-unused-vars */
|
|
22
|
+
let Candidate = class Candidate extends EnduranceSchema {
|
|
23
|
+
contact;
|
|
24
|
+
experienceLevel;
|
|
25
|
+
yearsOfExperience;
|
|
26
|
+
skills;
|
|
27
|
+
magicLinkToken;
|
|
28
|
+
magicLinkExpiresAt;
|
|
29
|
+
authToken;
|
|
30
|
+
authTokenExpiresAt;
|
|
31
|
+
static getModel() {
|
|
32
|
+
return CandidateModel;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
__decorate([
|
|
36
|
+
EnduranceModelType.prop({ required: true, ref: 'Contact' }),
|
|
37
|
+
__metadata("design:type", Types.ObjectId)
|
|
38
|
+
], Candidate.prototype, "contact", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
EnduranceModelType.prop({ required: false, enum: ExperienceLevel, default: ExperienceLevel.JUNIOR }),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], Candidate.prototype, "experienceLevel", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
EnduranceModelType.prop({ required: false, type: Number, default: 0 }),
|
|
45
|
+
__metadata("design:type", Number)
|
|
46
|
+
], Candidate.prototype, "yearsOfExperience", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
EnduranceModelType.prop({ type: [String], required: true }),
|
|
49
|
+
__metadata("design:type", Array)
|
|
50
|
+
], Candidate.prototype, "skills", void 0);
|
|
51
|
+
__decorate([
|
|
52
|
+
EnduranceModelType.prop({ required: false, type: String }),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], Candidate.prototype, "magicLinkToken", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
EnduranceModelType.prop({ required: false, type: Date }),
|
|
57
|
+
__metadata("design:type", Date)
|
|
58
|
+
], Candidate.prototype, "magicLinkExpiresAt", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
EnduranceModelType.prop({ required: false, type: String }),
|
|
61
|
+
__metadata("design:type", String)
|
|
62
|
+
], Candidate.prototype, "authToken", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
EnduranceModelType.prop({ required: false, type: Date }),
|
|
65
|
+
__metadata("design:type", Date)
|
|
66
|
+
], Candidate.prototype, "authTokenExpiresAt", void 0);
|
|
67
|
+
Candidate = __decorate([
|
|
68
|
+
EnduranceModelType.modelOptions({
|
|
69
|
+
options: {
|
|
70
|
+
allowMixed: EnduranceModelType.Severity.ALLOW
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
], Candidate);
|
|
74
|
+
const CandidateModel = EnduranceModelType.getModelForClass(Candidate);
|
|
75
|
+
export default CandidateModel;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EnduranceSchema } from '@programisto/endurance-core';
|
|
2
|
+
import { Types } from 'mongoose';
|
|
3
|
+
export declare enum ExperienceLevel {
|
|
4
|
+
JUNIOR = "JUNIOR",
|
|
5
|
+
INTERMEDIATE = "INTERMEDIATE",
|
|
6
|
+
SENIOR = "SENIOR",
|
|
7
|
+
EXPERT = "EXPERT"
|
|
8
|
+
}
|
|
9
|
+
declare class Candidate extends EnduranceSchema {
|
|
10
|
+
contact: Types.ObjectId;
|
|
11
|
+
experienceLevel: string;
|
|
12
|
+
yearsOfExperience: number;
|
|
13
|
+
skills: string[];
|
|
14
|
+
magicLinkToken?: string;
|
|
15
|
+
magicLinkExpiresAt?: Date;
|
|
16
|
+
authToken?: string;
|
|
17
|
+
authTokenExpiresAt?: Date;
|
|
18
|
+
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
19
|
+
}
|
|
20
|
+
declare const CandidateModel: import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
21
|
+
export default CandidateModel;
|