Emojy reactions

This commit is contained in:
2025-03-25 19:12:47 +03:00
parent c02cf6dfc9
commit b2121cc133
12 changed files with 983 additions and 14 deletions

View File

@@ -1,6 +1,7 @@
const router = require('express').Router()
const fs = require('node:fs')
const path = require('node:path')
const mockGenerator = require('./mock-generator')
const timer =
(time = 1000) =>
@@ -56,19 +57,21 @@ router.post('/lesson', (req, res) => {
})
router.post('/lesson/access-code', (req, res) => {
const answer = fs.readFileSync(
path.resolve(__dirname, '../mocks/lessons/access-code/create/success.json'),
)
// res.send(require('../mocks/lessons/access-code/create/success.json'))
res.send(answer)
// Generate random students and reactions dynamically
const dynamicData = mockGenerator.generateDynamicAccessCodeResponse();
res.send(dynamicData);
})
router.get('/lesson/access-code/:accessCode', (req, res) => {
res.status(400).send(require('../mocks/lessons/access-code/get/error.json'))
// Generate dynamic data for the access code lookup
const dynamicData = mockGenerator.generateDynamicAccessLookupResponse(req.params.accessCode);
res.send(dynamicData);
})
router.get('/lesson/:lessonId', (req, res) => {
res.send(require('../mocks/lessons/byid/success.json'))
// Generate dynamic lesson data using the same helpers
const dynamicData = mockGenerator.generateDynamicLessonResponse(req.params.lessonId);
res.send(dynamicData);
})
router.delete('/lesson/:lessonId', (req, res) => {
@@ -79,4 +82,291 @@ router.put('/lesson', (req, res) => {
res.send({ success: true, body: req.body })
})
router.post('/lesson/reaction/:lessonId', (req, res) => {
// Simulate processing a new reaction
const { reaction } = req.body;
const lessonId = req.params.lessonId;
// Log the reaction for debugging
console.log(`Received reaction "${reaction}" for lesson ${lessonId}`);
// Return success response
res.send({
success: true,
body: {
_id: `r-${Date.now()}-${Math.floor(Math.random() * 1000)}`,
reaction,
lessonId,
created: new Date().toISOString()
}
});
});
module.exports = router
// Database of potential students
const potentialStudents = [
{
"sub": "fcde3f22-d9ba-412a-a572-c59e515a290f",
"email_verified": true,
"name": "Мария Капитанова",
"preferred_username": "maryaKapitan@gmail.com",
"given_name": "Мария",
"family_name": "Капитанова",
"email": "maryaKapitan@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJgIjjOFD2YUSyRF5kH4jaysE6X5p-kq0Cg0CFncfMi=s96-c"
},
{
"sub": "8555885b-715c-4dee-a7c5-9563a6a05211",
"email_verified": true,
"name": "Евгения Жужова",
"preferred_username": "zhuzhova@gmail.com",
"given_name": "Евгения",
"family_name": "Жужова",
"email": "zhuzhova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJUtJBAVBm642AxoGpMDDMV8CPu3MEoLjU3hmO7oisG=s96-c"
},
{
"sub": "a723b8c2-f1d7-4620-9c35-1d48c821afb7",
"email_verified": true,
"name": "Иван Петров",
"preferred_username": "ivan.petrov@gmail.com",
"given_name": "Иван",
"family_name": "Петров",
"email": "ivan.petrov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJKHmMLFXY1s0Lkj_KKf9ZEsHl-rW6FnDs4vPHUl2aF=s96-c"
},
{
"sub": "e4f9d328-7b2e-49c1-b5e8-12f78c54a63d",
"email_verified": true,
"name": "Алексей Смирнов",
"preferred_username": "alexey.smirnov@gmail.com",
"given_name": "Алексей",
"family_name": "Смирнов",
"email": "alexey.smirnov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK9Nfj_jT4DLjG5hVQWS2bz8_QTZ3cHVJ6K8mD8aqWr=s96-c"
},
{
"sub": "b9d7e1f5-6a3c-47d0-9bce-3c54e28a0ef2",
"email_verified": true,
"name": "Ольга Иванова",
"preferred_username": "olga.ivanova@gmail.com",
"given_name": "Ольга",
"family_name": "Иванова",
"email": "olga.ivanova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocI48DY7C2ZXbMvHrEjKmY6w9JdF5PLKwEDgTR9x1jY2=s96-c"
},
{
"sub": "c5e8d4f3-2b1a-4c9d-8e7f-6a5b4c3d2e1f",
"email_verified": true,
"name": "Дмитрий Кузнецов",
"preferred_username": "dmitry.kuznetsov@gmail.com",
"given_name": "Дмитрий",
"family_name": "Кузнецов",
"email": "dmitry.kuznetsov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLqZD7KjXy3B1P2VsRn6Z9tY8XMhCJ6F5gK7sD1qB3t=s96-c"
},
{
"sub": "d6f9e8d7-3c2b-4a1d-9e8f-7a6b5c4d3e2f",
"email_verified": true,
"name": "Анна Соколова",
"preferred_username": "anna.sokolova@gmail.com",
"given_name": "Анна",
"family_name": "Соколова",
"email": "anna.sokolova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK3dN5mYwLjE1qFvX9pZ8rY1hJ5L2mN3oP6gR7tUb4s=s96-c"
},
{
"sub": "e7f8g9h0-4d3c-2b1a-0f9e-8d7c6b5a4e3d",
"email_verified": true,
"name": "Сергей Новиков",
"preferred_username": "sergey.novikov@gmail.com",
"given_name": "Сергей",
"family_name": "Новиков",
"email": "sergey.novikov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocI7P2dF3vQ5wR9jH6tN8bZ1cM4kD6yL2jN5oR8tYb5r=s96-c"
},
{
"sub": "f8g9h0i1-5e4d-3c2b-1a0f-9e8d7c6b5a4e",
"email_verified": true,
"name": "Екатерина Морозова",
"preferred_username": "ekaterina.morozova@gmail.com",
"given_name": "Екатерина",
"family_name": "Морозова",
"email": "ekaterina.morozova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJ6N5oR7sD8tUb4rY1hJ5L2mN3oP6gR7tUb4s9pZ8=s96-c"
},
{
"sub": "g9h0i1j2-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
"email_verified": true,
"name": "Андрей Волков",
"preferred_username": "andrey.volkov@gmail.com",
"given_name": "Андрей",
"family_name": "Волков",
"email": "andrey.volkov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK4e3d2c1b0a9f8e7d6c5b4a3e2d1c0b9a8f7e6d5=s96-c"
}
];
// Available reaction types
const reactionTypes = ['thumbs_up', 'heart', 'laugh', 'wow', 'clap'];
// Function to generate a random integer between min and max (inclusive)
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Function to generate a random subset of students
function getRandomStudents() {
const totalStudents = potentialStudents.length;
const count = getRandomInt(1, Math.min(8, totalStudents));
// Shuffle array and take a subset
const shuffled = [...potentialStudents].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
// Function to generate a random reaction
function generateReaction(studentSub, index) {
const reactionType = reactionTypes[getRandomInt(0, reactionTypes.length - 1)];
return {
"_id": `r-${Date.now()}-${index}`,
"sub": studentSub,
"reaction": reactionType,
"created": new Date().toISOString()
};
}
// Function to generate random reactions for each student
function generateReactions(students) {
const reactions = [];
let reactionIndex = 0;
students.forEach(student => {
// Small chance (20%) of a "reaction burst" - multiple reactions in rapid succession
const hasBurst = Math.random() < 0.2;
if (hasBurst) {
// Generate a burst of 2-5 rapid reactions
const burstCount = getRandomInt(2, 5);
const now = Date.now();
for (let i = 0; i < burstCount; i++) {
// Reactions spaced 0.5-2 seconds apart
const timeOffset = i * getRandomInt(500, 2000);
const reactionTime = new Date(now - timeOffset);
reactions.push({
"_id": `r-burst-${now}-${i}-${reactionIndex++}`,
"sub": student.sub,
"reaction": reactionTypes[getRandomInt(0, reactionTypes.length - 1)],
"created": reactionTime.toISOString()
});
}
} else {
// Each student may have 0-3 random reactions
const reactionCount = getRandomInt(0, 3);
for (let i = 0; i < reactionCount; i++) {
// Space out regular reactions by 5-30 seconds
const timeOffset = getRandomInt(5000, 30000);
const reactionTime = new Date(Date.now() - timeOffset);
reactions.push({
"_id": `r-${Date.now()}-${reactionIndex++}`,
"sub": student.sub,
"reaction": reactionTypes[getRandomInt(0, reactionTypes.length - 1)],
"created": reactionTime.toISOString()
});
}
}
});
// Sort reactions by creation time (newest first)
return reactions.sort((a, b) => new Date(b.created) - new Date(a.created));
}
// Function to generate the entire dynamic response
function generateDynamicAccessCodeResponse() {
// Base template from the static file
const baseTemplate = {
"success": true,
"body": {
"expires": new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour from now
"lesson": {
"_id": "65df996c584b172772d69706",
"name": "ВВОДНАЯ ПО JS.ПРИМЕНЕНИЕ И СПОСОБЫ ПОДКЛЮЧЕНИЯ НА СТРАНИЦЕ. LET, CONST. БАЗОВЫЕ ТИПЫ ДАННЫХ, ПРИВЕДЕНИЕ ТИПОВ. ПЕРЕМЕННЫЕ, ОБЛАСТЬ ВИДИМОСТИ ПЕРЕМЕННЫХ",
"date": new Date().toISOString(),
"created": new Date().toISOString(),
"__v": 0
},
"_id": `access-${Date.now()}`,
"created": new Date().toISOString(),
"__v": 0
}
};
// Generate random students
const students = getRandomStudents();
baseTemplate.body.lesson.students = students;
// Generate random reactions for those students
baseTemplate.body.lesson.reactions = generateReactions(students);
return baseTemplate;
}
// Function to generate a dynamic lesson response
function generateDynamicLessonResponse(lessonId) {
// Base template for lesson response
const baseTemplate = {
"success": true,
"body": {
"_id": lessonId || "65df996c584b172772d69706",
"name": "ВВОДНАЯ ПО JS.ПРИМЕНЕНИЕ И СПОСОБЫ ПОДКЛЮЧЕНИЯ НА СТРАНИЦЕ. LET, CONST. БАЗОВЫЕ ТИПЫ ДАННЫХ, ПРИВЕДЕНИЕ ТИПОВ. ПЕРЕМЕННЫЕ, ОБЛАСТЬ ВИДИМОСТИ ПЕРЕМЕННЫХ",
"date": new Date().toISOString(),
"created": new Date().toISOString(),
"__v": 0
}
};
// Generate random students
const students = getRandomStudents();
baseTemplate.body.students = students;
// Generate random reactions for those students
baseTemplate.body.reactions = generateReactions(students);
return baseTemplate;
}
// Function to generate a dynamic access code lookup response
function generateDynamicAccessLookupResponse(accessCode) {
// Generate a lesson with students and reactions
const lessonData = generateDynamicLessonResponse();
// Create a mock user
const mockUser = {
sub: `user-${Date.now()}`,
email_verified: true,
name: "Текущий Пользователь",
preferred_username: "current.user@example.com",
email: "current.user@example.com"
};
// Combine into the expected format
return {
"success": true,
"body": {
"user": mockUser,
"accessCode": {
"expires": new Date(Date.now() + 60 * 60 * 1000).toISOString(),
"lesson": lessonData.body,
"_id": accessCode || `access-${Date.now()}`,
"created": new Date().toISOString(),
"__v": 0
}
}
};
}

264
stubs/api/mock-generator.js Normal file
View File

@@ -0,0 +1,264 @@
// Database of potential students
const potentialStudents = [
{
"sub": "fcde3f22-d9ba-412a-a572-c59e515a290f",
"email_verified": true,
"name": "Мария Капитанова",
"preferred_username": "maryaKapitan@gmail.com",
"given_name": "Мария",
"family_name": "Капитанова",
"email": "maryaKapitan@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJgIjjOFD2YUSyRF5kH4jaysE6X5p-kq0Cg0CFncfMi=s96-c"
},
{
"sub": "8555885b-715c-4dee-a7c5-9563a6a05211",
"email_verified": true,
"name": "Евгения Жужова",
"preferred_username": "zhuzhova@gmail.com",
"given_name": "Евгения",
"family_name": "Жужова",
"email": "zhuzhova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJUtJBAVBm642AxoGpMDDMV8CPu3MEoLjU3hmO7oisG=s96-c"
},
{
"sub": "a723b8c2-f1d7-4620-9c35-1d48c821afb7",
"email_verified": true,
"name": "Иван Петров",
"preferred_username": "ivan.petrov@gmail.com",
"given_name": "Иван",
"family_name": "Петров",
"email": "ivan.petrov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJKHmMLFXY1s0Lkj_KKf9ZEsHl-rW6FnDs4vPHUl2aF=s96-c"
},
{
"sub": "e4f9d328-7b2e-49c1-b5e8-12f78c54a63d",
"email_verified": true,
"name": "Алексей Смирнов",
"preferred_username": "alexey.smirnov@gmail.com",
"given_name": "Алексей",
"family_name": "Смирнов",
"email": "alexey.smirnov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK9Nfj_jT4DLjG5hVQWS2bz8_QTZ3cHVJ6K8mD8aqWr=s96-c"
},
{
"sub": "b9d7e1f5-6a3c-47d0-9bce-3c54e28a0ef2",
"email_verified": true,
"name": "Ольга Иванова",
"preferred_username": "olga.ivanova@gmail.com",
"given_name": "Ольга",
"family_name": "Иванова",
"email": "olga.ivanova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocI48DY7C2ZXbMvHrEjKmY6w9JdF5PLKwEDgTR9x1jY2=s96-c"
},
{
"sub": "c5e8d4f3-2b1a-4c9d-8e7f-6a5b4c3d2e1f",
"email_verified": true,
"name": "Дмитрий Кузнецов",
"preferred_username": "dmitry.kuznetsov@gmail.com",
"given_name": "Дмитрий",
"family_name": "Кузнецов",
"email": "dmitry.kuznetsov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLqZD7KjXy3B1P2VsRn6Z9tY8XMhCJ6F5gK7sD1qB3t=s96-c"
},
{
"sub": "d6f9e8d7-3c2b-4a1d-9e8f-7a6b5c4d3e2f",
"email_verified": true,
"name": "Анна Соколова",
"preferred_username": "anna.sokolova@gmail.com",
"given_name": "Анна",
"family_name": "Соколова",
"email": "anna.sokolova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK3dN5mYwLjE1qFvX9pZ8rY1hJ5L2mN3oP6gR7tUb4s=s96-c"
},
{
"sub": "e7f8g9h0-4d3c-2b1a-0f9e-8d7c6b5a4e3d",
"email_verified": true,
"name": "Сергей Новиков",
"preferred_username": "sergey.novikov@gmail.com",
"given_name": "Сергей",
"family_name": "Новиков",
"email": "sergey.novikov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocI7P2dF3vQ5wR9jH6tN8bZ1cM4kD6yL2jN5oR8tYb5r=s96-c"
},
{
"sub": "f8g9h0i1-5e4d-3c2b-1a0f-9e8d7c6b5a4e",
"email_verified": true,
"name": "Екатерина Морозова",
"preferred_username": "ekaterina.morozova@gmail.com",
"given_name": "Екатерина",
"family_name": "Морозова",
"email": "ekaterina.morozova@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocJ6N5oR7sD8tUb4rY1hJ5L2mN3oP6gR7tUb4s9pZ8=s96-c"
},
{
"sub": "g9h0i1j2-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
"email_verified": true,
"name": "Андрей Волков",
"preferred_username": "andrey.volkov@gmail.com",
"given_name": "Андрей",
"family_name": "Волков",
"email": "andrey.volkov@gmail.com",
"picture": "https://lh3.googleusercontent.com/a/ACg8ocK4e3d2c1b0a9f8e7d6c5b4a3e2d1c0b9a8f7e6d5=s96-c"
}
];
// Available reaction types
const reactionTypes = ['thumbs_up', 'heart', 'laugh', 'wow', 'clap'];
// Function to generate a random integer between min and max (inclusive)
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Function to generate a random subset of students
function getRandomStudents() {
const totalStudents = potentialStudents.length;
const count = getRandomInt(1, Math.min(8, totalStudents));
// Shuffle array and take a subset
const shuffled = [...potentialStudents].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
// Function to generate random reactions for each student
function generateReactions(students) {
const reactions = [];
let reactionIndex = 0;
students.forEach(student => {
// Small chance (20%) of a "reaction burst" - multiple reactions in rapid succession
const hasBurst = Math.random() < 0.2;
if (hasBurst) {
// Generate a burst of 2-5 rapid reactions
const burstCount = getRandomInt(2, 5);
const now = Date.now();
for (let i = 0; i < burstCount; i++) {
// Reactions spaced 0.5-2 seconds apart
const timeOffset = i * getRandomInt(500, 2000);
const reactionTime = new Date(now - timeOffset);
reactions.push({
"_id": `r-burst-${now}-${i}-${reactionIndex++}`,
"sub": student.sub,
"reaction": reactionTypes[getRandomInt(0, reactionTypes.length - 1)],
"created": reactionTime.toISOString()
});
}
} else {
// Each student may have 0-3 random reactions
const reactionCount = getRandomInt(0, 3);
for (let i = 0; i < reactionCount; i++) {
// Space out regular reactions by 5-30 seconds
const timeOffset = getRandomInt(5000, 30000);
const reactionTime = new Date(Date.now() - timeOffset);
reactions.push({
"_id": `r-${Date.now()}-${reactionIndex++}`,
"sub": student.sub,
"reaction": reactionTypes[getRandomInt(0, reactionTypes.length - 1)],
"created": reactionTime.toISOString()
});
}
}
});
// Sort reactions by creation time (newest first)
return reactions.sort((a, b) => new Date(b.created) - new Date(a.created));
}
// Function to generate the entire dynamic response
function generateDynamicAccessCodeResponse() {
// Base template from the static file
const baseTemplate = {
"success": true,
"body": {
"expires": new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour from now
"lesson": {
"_id": "65df996c584b172772d69706",
"name": "ВВОДНАЯ ПО JS.ПРИМЕНЕНИЕ И СПОСОБЫ ПОДКЛЮЧЕНИЯ НА СТРАНИЦЕ. LET, CONST. БАЗОВЫЕ ТИПЫ ДАННЫХ, ПРИВЕДЕНИЕ ТИПОВ. ПЕРЕМЕННЫЕ, ОБЛАСТЬ ВИДИМОСТИ ПЕРЕМЕННЫХ",
"date": new Date().toISOString(),
"created": new Date().toISOString(),
"__v": 0
},
"_id": `access-${Date.now()}`,
"created": new Date().toISOString(),
"__v": 0
}
};
// Generate random students
const students = getRandomStudents();
baseTemplate.body.lesson.students = students;
// Generate random reactions for those students
baseTemplate.body.lesson.reactions = generateReactions(students);
return baseTemplate;
}
// Function to generate a dynamic lesson response
function generateDynamicLessonResponse(lessonId) {
// Base template for lesson response
const baseTemplate = {
"success": true,
"body": {
"_id": lessonId || "65df996c584b172772d69706",
"name": "ВВОДНАЯ ПО JS.ПРИМЕНЕНИЕ И СПОСОБЫ ПОДКЛЮЧЕНИЯ НА СТРАНИЦЕ. LET, CONST. БАЗОВЫЕ ТИПЫ ДАННЫХ, ПРИВЕДЕНИЕ ТИПОВ. ПЕРЕМЕННЫЕ, ОБЛАСТЬ ВИДИМОСТИ ПЕРЕМЕННЫХ",
"date": new Date().toISOString(),
"created": new Date().toISOString(),
"__v": 0
}
};
// Generate random students
const students = getRandomStudents();
baseTemplate.body.students = students;
// Generate random reactions for those students
baseTemplate.body.reactions = generateReactions(students);
return baseTemplate;
}
// Function to generate a dynamic access code lookup response
function generateDynamicAccessLookupResponse(accessCode) {
// Generate a lesson with students and reactions
const lessonData = generateDynamicLessonResponse();
// Create a mock user
const mockUser = {
sub: `user-${Date.now()}`,
email_verified: true,
name: "Текущий Пользователь",
preferred_username: "current.user@example.com",
email: "current.user@example.com"
};
// Combine into the expected format
return {
"success": true,
"body": {
"user": mockUser,
"accessCode": {
"expires": new Date(Date.now() + 60 * 60 * 1000).toISOString(),
"lesson": lessonData.body,
"_id": accessCode || `access-${Date.now()}`,
"created": new Date().toISOString(),
"__v": 0
}
}
};
}
// Export all the necessary functions
module.exports = {
getRandomStudents,
generateReactions,
generateDynamicAccessCodeResponse,
generateDynamicLessonResponse,
generateDynamicAccessLookupResponse,
reactionTypes
};