@runnerpro/backend 1.31.1 → 1.31.2
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/lib/cjs/chat/api/conversation.js +49 -35
- package/lib/cjs/image/generateShareMap.js +6 -8
- package/lib/cjs/image/reduceSizeImage.js +12 -22
- package/lib/cjs/types/chat/api/conversation.d.ts.map +1 -1
- package/lib/cjs/types/image/generateShareMap.d.ts.map +1 -1
- package/lib/cjs/types/image/reduceSizeImage.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -476,47 +476,61 @@ const sendFile = (req, res, { sendNotification, firebaseMessaging, isClient, buc
|
|
|
476
476
|
let thumbnail;
|
|
477
477
|
const { userid } = req.session;
|
|
478
478
|
const filePath = path_1.default.join('./uploads', req.file.filename);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Logros: adjunto enviado por el cliente también cuenta como mensaje. Await
|
|
485
|
-
// acotado para devolver los recién desbloqueados al FE en la respuesta (push).
|
|
479
|
+
// El adjunto de multer vive en ./uploads, que en Cloud Run es tmpfs: disco que es RAM.
|
|
480
|
+
// El unlink va en un finally porque antes solo se alcanzaba si TODO lo anterior salía
|
|
481
|
+
// bien: una subida a Storage o una query que fallara dejaban la foto, el audio o el
|
|
482
|
+
// vídeo —decenas de MB— ocupando memoria hasta el reinicio (la fuga de T-211).
|
|
483
|
+
let idFile;
|
|
486
484
|
let logrosDesbloqueados = [];
|
|
487
|
-
|
|
488
|
-
|
|
485
|
+
try {
|
|
486
|
+
if (req.file.mimetype.includes('video')) {
|
|
487
|
+
duration = yield getVideoDuration(filePath);
|
|
488
|
+
thumbnail = yield getThumbnailFromVideo(filePath, duration);
|
|
489
|
+
}
|
|
490
|
+
[{ id: idFile }] = yield (0, index_1.query)('INSERT INTO [CHAT MESSAGE] ([ID CLIENTE], [ID SENDER], [TEXT], [MIMETYPE], [DURATION], [TYPE]) VALUES (?, ?, ?, ?, ?, ?) RETURNING [ID]', [isClient ? userid : idCliente, userid, req.file.originalname, req.file.mimetype, duration || null, type || 2]);
|
|
491
|
+
// Logros: adjunto enviado por el cliente también cuenta como mensaje. Await
|
|
492
|
+
// acotado para devolver los recién desbloqueados al FE en la respuesta (push).
|
|
493
|
+
if (isClient) {
|
|
494
|
+
logrosDesbloqueados = yield (0, index_1.evaluateAchievementsBounded)(userid, 'chat', {}, 1500);
|
|
495
|
+
}
|
|
496
|
+
const fileData = fs_1.default.readFileSync(filePath);
|
|
497
|
+
const files = [];
|
|
498
|
+
if (req.file.mimetype.includes('image')) {
|
|
499
|
+
try {
|
|
500
|
+
const fileDataResize = yield resizeImage(fileData, filePath, req.file.mimetype);
|
|
501
|
+
const fileDataOriented = yield rotateOrientationImage(fileData, filePath, null, req.file.mimetype);
|
|
502
|
+
files.push({ data: fileDataResize, id: idFile });
|
|
503
|
+
files.push({ data: fileDataOriented, id: `${idFile}-original` });
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
(0, index_1.err)(null, null, error, null);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else if (req.file.mimetype.includes('video')) {
|
|
510
|
+
files.push({ data: fileData, id: idFile });
|
|
511
|
+
files.push({ data: thumbnail, id: `${idFile}-thumbnail` });
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
files.push({ data: fileData, id: idFile });
|
|
515
|
+
}
|
|
516
|
+
res.send({ idFile, logrosDesbloqueados });
|
|
517
|
+
// Procesar el adjunto en background, sea del tipo que sea: transcripción, descripción, resumen
|
|
518
|
+
// o ficha del archivo. El router de mediaProcessing decide qué toca. Va ANTES de subir a Storage
|
|
519
|
+
// a propósito: si la subida falla, el mensaje se quedaría sin FILE TEXT de forma permanente y
|
|
520
|
+
// eso bloquea al entrenador, que no distingue "sin texto" de "aún sin procesar".
|
|
521
|
+
(0, mediaProcessing_1.processMediaFile)(idFile, fileData, req.file.mimetype, req.file.originalname);
|
|
522
|
+
for (const file of files) {
|
|
523
|
+
yield bucket.file(`Chat/${file.id}`).save(file.data);
|
|
524
|
+
}
|
|
489
525
|
}
|
|
490
|
-
|
|
491
|
-
const files = [];
|
|
492
|
-
if (req.file.mimetype.includes('image')) {
|
|
526
|
+
finally {
|
|
493
527
|
try {
|
|
494
|
-
|
|
495
|
-
const fileDataOriented = yield rotateOrientationImage(fileData, filePath, null, req.file.mimetype);
|
|
496
|
-
files.push({ data: fileDataResize, id: idFile });
|
|
497
|
-
files.push({ data: fileDataOriented, id: `${idFile}-original` });
|
|
528
|
+
fs_1.default.unlinkSync(filePath);
|
|
498
529
|
}
|
|
499
|
-
catch (
|
|
500
|
-
(
|
|
530
|
+
catch (cleanupError) {
|
|
531
|
+
// Ya no está (o nunca llegó a escribirse): no hay nada que limpiar.
|
|
501
532
|
}
|
|
502
533
|
}
|
|
503
|
-
else if (req.file.mimetype.includes('video')) {
|
|
504
|
-
files.push({ data: fileData, id: idFile });
|
|
505
|
-
files.push({ data: thumbnail, id: `${idFile}-thumbnail` });
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
files.push({ data: fileData, id: idFile });
|
|
509
|
-
}
|
|
510
|
-
res.send({ idFile, logrosDesbloqueados });
|
|
511
|
-
// Procesar el adjunto en background, sea del tipo que sea: transcripción, descripción, resumen
|
|
512
|
-
// o ficha del archivo. El router de mediaProcessing decide qué toca. Va ANTES de subir a Storage
|
|
513
|
-
// a propósito: si la subida falla, el mensaje se quedaría sin FILE TEXT de forma permanente y
|
|
514
|
-
// eso bloquea al entrenador, que no distingue "sin texto" de "aún sin procesar".
|
|
515
|
-
(0, mediaProcessing_1.processMediaFile)(idFile, fileData, req.file.mimetype, req.file.originalname);
|
|
516
|
-
for (const file of files) {
|
|
517
|
-
yield bucket.file(`Chat/${file.id}`).save(file.data);
|
|
518
|
-
}
|
|
519
|
-
fs_1.default.unlinkSync(filePath);
|
|
520
534
|
if (!isClient) {
|
|
521
535
|
let textFile = 'Archivo adjunto';
|
|
522
536
|
if (Number(type) === 4)
|
|
@@ -20,8 +20,6 @@ const common_1 = require("@runnerpro/common");
|
|
|
20
20
|
const translation_1 = require("../translation");
|
|
21
21
|
const path_1 = __importDefault(require("path"));
|
|
22
22
|
const jimp_1 = __importDefault(require("jimp"));
|
|
23
|
-
const uuidv4_1 = require("uuidv4");
|
|
24
|
-
const fs_1 = __importDefault(require("fs"));
|
|
25
23
|
// install fonts
|
|
26
24
|
canvas_1.GlobalFonts.registerFromPath(path_1.default.join(__dirname, '../../..', 'static/fonts', 'SofiaSans-Bold.ttf'), 'SofiaSansBold');
|
|
27
25
|
canvas_1.GlobalFonts.registerFromPath(path_1.default.join(__dirname, '../../..', 'static/fonts', 'SofiaSans-Regular.ttf'), 'SofiaSans');
|
|
@@ -159,13 +157,13 @@ const addShadowShareWorkoutmap = (context, useDefaultPhoto) => __awaiter(void 0,
|
|
|
159
157
|
context.fillRect(0, 1080 - shadowHeight, 1080, shadowHeight);
|
|
160
158
|
});
|
|
161
159
|
const addImageShareWorkoutmap = (context, data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
160
|
+
// Sin fichero temporal: antes el JPEG intermedio pasaba por ./uploads y el unlink
|
|
161
|
+
// solo llegaba si el render entero salía bien — un fallo por medio lo dejaba
|
|
162
|
+
// huérfano, y en Cloud Run ./uploads es tmpfs, o sea RAM que no se recupera hasta
|
|
163
|
+
// el reinicio (la fuga de T-211). loadImage acepta el Buffer directamente.
|
|
162
164
|
const image = yield jimp_1.default.read(data);
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
yield image.cover(1080, 1080).writeAsync(path_1.default.join(uploadsDir, filename2));
|
|
166
|
-
const imagePath2 = path_1.default.join(uploadsDir, filename2);
|
|
167
|
-
const image2 = yield (0, canvas_1.loadImage)(imagePath2);
|
|
165
|
+
const buffer = yield image.cover(1080, 1080).getBufferAsync(jimp_1.default.MIME_JPEG);
|
|
166
|
+
const image2 = yield (0, canvas_1.loadImage)(buffer);
|
|
168
167
|
context.drawImage(image2, 0, 0, 1080, 1080);
|
|
169
168
|
context.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
170
|
-
yield fs_1.default.promises.unlink(imagePath2);
|
|
171
169
|
});
|
|
@@ -13,40 +13,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.reduceSizeImage = void 0;
|
|
16
|
-
const util_1 = require("util");
|
|
17
16
|
const jimp_1 = __importDefault(require("jimp"));
|
|
18
17
|
const fs_1 = __importDefault(require("fs"));
|
|
19
|
-
const uuid_1 = require("uuid");
|
|
20
|
-
const path_1 = __importDefault(require("path"));
|
|
21
18
|
const exifr_1 = __importDefault(require("exifr"));
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
+
const sizeOf = require('image-size');
|
|
22
21
|
const reduceSizeImage = (file, newWidth = 100, quality = 80) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
22
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// If it's not an object, it's already a file path
|
|
33
|
-
filePath = file;
|
|
34
|
-
}
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
36
|
-
const sizeOf = (0, util_1.promisify)(require('image-size'));
|
|
37
|
-
const dimensions = yield sizeOf(filePath);
|
|
23
|
+
// Todo en memoria, sin fichero temporal. Antes esto escribía el buffer en ./uploads
|
|
24
|
+
// para medirlo y leerlo desde disco, y solo lo borraba si TODO salía bien: con un
|
|
25
|
+
// formato que Jimp no lee (el HEIC de los iPhone, sin ir más lejos) se saltaba al
|
|
26
|
+
// catch y el fichero quedaba huérfano. En Cloud Run ./uploads es tmpfs —disco que
|
|
27
|
+
// es RAM— así que cada foto fallida eran MiB de memoria perdidos hasta el reinicio
|
|
28
|
+
// (la fuga de T-211). Jimp e image-size aceptan el Buffer directamente.
|
|
29
|
+
const source = typeof file === 'object' ? file : fs_1.default.readFileSync(file);
|
|
30
|
+
const dimensions = sizeOf(source);
|
|
38
31
|
const imageWidth = dimensions.width;
|
|
39
32
|
const imageHeight = dimensions.height;
|
|
40
33
|
const newHeight = Math.round((imageHeight * newWidth) / imageWidth);
|
|
41
|
-
let image = yield jimp_1.default.read(
|
|
34
|
+
let image = yield jimp_1.default.read(source);
|
|
42
35
|
image = yield rotateOrientationImage(image, file);
|
|
43
36
|
image = yield image.resize(newWidth, newHeight);
|
|
44
37
|
image = yield image.quality(quality);
|
|
45
38
|
// @ts-ignore
|
|
46
|
-
|
|
47
|
-
if (typeof file === 'object')
|
|
48
|
-
fs_1.default.unlinkSync(filePath);
|
|
49
|
-
return image;
|
|
39
|
+
return yield image.getBufferAsync(jimp_1.default.MIME_JPEG);
|
|
50
40
|
}
|
|
51
41
|
catch (error) {
|
|
52
42
|
if (typeof file === 'object')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../../../src/chat/api/conversation.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,iBAAiB,0BAA2B,GAAG,SA0BpD,CAAC;AAoGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,uBAAuB,qCAAkC,GAAG,iBAwCjE,CAAC;AAmGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,WAAW,0EAAuE,GAAG,kBA0E1F,CAAC;AAEF,QAAA,MAAM,gBAAgB;;;;mBAqBrB,CAAC;
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../../../src/chat/api/conversation.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,iBAAiB,0BAA2B,GAAG,SA0BpD,CAAC;AAoGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,uBAAuB,qCAAkC,GAAG,iBAwCjE,CAAC;AAmGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,WAAW,0EAAuE,GAAG,kBA0E1F,CAAC;AAEF,QAAA,MAAM,gBAAgB;;;;mBAqBrB,CAAC;AAuUF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateShareMap.d.ts","sourceRoot":"","sources":["../../../../src/image/generateShareMap.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"generateShareMap.d.ts","sourceRoot":"","sources":["../../../../src/image/generateShareMap.ts"],"names":[],"mappings":";AAYA;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,gBAAgB,+DA+BrB,CAAC;AA0HF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reduceSizeImage.d.ts","sourceRoot":"","sources":["../../../../src/image/reduceSizeImage.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"reduceSizeImage.d.ts","sourceRoot":"","sources":["../../../../src/image/reduceSizeImage.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,eAAe,kEA4BpB,CAAC;AAaF,OAAO,EAAE,eAAe,EAAE,CAAC"}
|