@runnerpro/backend 1.32.0 → 1.33.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/lib/cjs/types/workout/garmin.d.ts.map +1 -1
- package/lib/cjs/types/workout/saveWorkoutAplication.d.ts.map +1 -1
- package/lib/cjs/types/workout/sendToWatch.d.ts.map +1 -1
- package/lib/cjs/workout/garmin.js +34 -5
- package/lib/cjs/workout/saveWorkoutAplication.js +18 -11
- package/lib/cjs/workout/sendToWatch.js +4 -0
- package/package.json +1 -1
- package/lib/cjs/translation/titleDescriptionTranslated.js +0 -38
- package/lib/cjs/types/translation/titleDescriptionTranslated.d.ts +0 -8
- package/lib/cjs/types/translation/titleDescriptionTranslated.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"garmin.d.ts","sourceRoot":"","sources":["../../../../src/workout/garmin.ts"],"names":[],"mappings":"AAoBA,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"garmin.d.ts","sourceRoot":"","sources":["../../../../src/workout/garmin.ts"],"names":[],"mappings":"AAoBA,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAgHD,QAAA,MAAM,sBAAsB,iCA4D3B,CAAC;AAmFF,QAAA,MAAM,mBAAmB;;;EAcxB,CAAC;AAEF,QAAA,MAAM,mBAAmB,6CASxB,CAAC;AAEF,QAAA,MAAM,mBAAmB,6CAMxB,CAAC;AAEF,QAAA,MAAM,qBAAqB,6CAU1B,CAAC;AAEF,QAAA,MAAM,2BAA2B,4DAWhC,CAAC;AAIF,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,GAC5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saveWorkoutAplication.d.ts","sourceRoot":"","sources":["../../../../src/workout/saveWorkoutAplication.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"saveWorkoutAplication.d.ts","sourceRoot":"","sources":["../../../../src/workout/saveWorkoutAplication.ts"],"names":[],"mappings":"AAsTA;;;;;;;;;;GAUG;AACH,QAAA,MAAM,qBAAqB,SACnB,GAAG,UAED,GAAG,KACV,QAAQ,MAAM,GAAG,IAAI,CAqFvB,CAAC;AA0GF,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sendToWatch.d.ts","sourceRoot":"","sources":["../../../../src/workout/sendToWatch.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,kBAAkB;;;;;;
|
|
1
|
+
{"version":3,"file":"sendToWatch.d.ts","sourceRoot":"","sources":["../../../../src/workout/sendToWatch.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,kBAAkB;;;;;;EAuBvB,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -65,22 +65,40 @@ const garminOauthActivity = ({ url, httpMethod, userid, params = {} }) => __awai
|
|
|
65
65
|
const secondsPerKmToMetersPerSecond = (secondsPerKm) => {
|
|
66
66
|
return Math.round((1000 / secondsPerKm) * 100) / 100;
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Devuelve la metrica planificada como numero, o null si no la hay.
|
|
70
|
+
*
|
|
71
|
+
* OJO: Postgres devuelve las columnas `numeric` como TEXTO, asi que una distancia
|
|
72
|
+
* de 0 llega como la cadena "0", que en JS es truthy. Los entrenos de bici y
|
|
73
|
+
* natacion se planifican por tiempo y guardan `DISTANCE PLANNED` = 0: comparar
|
|
74
|
+
* en crudo hacia colar entrenos de 0 metros a Garmin.
|
|
75
|
+
*/
|
|
76
|
+
const metricaPlanificada = (valor) => {
|
|
77
|
+
const n = Number(valor);
|
|
78
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
79
|
+
};
|
|
68
80
|
const getWorkoutGarminDuration = (workout) => {
|
|
81
|
+
const distancia = metricaPlanificada(workout.distancePlanned);
|
|
82
|
+
const duracion = metricaPlanificada(workout.durationPlanned);
|
|
69
83
|
const rangePlanning = (0, common_1.getWorkoutRangePlanning)({
|
|
70
84
|
workoutType: workout.type,
|
|
71
85
|
distancePlanned: workout.distancePlanned,
|
|
72
86
|
durationPlanned: workout.durationPlanned,
|
|
73
87
|
zonePlanned: workout.zonePlanned,
|
|
74
88
|
});
|
|
75
|
-
|
|
89
|
+
// `fixedMetric` solo se calcula cuando hay rango de ritmo, y eso solo pasa en
|
|
90
|
+
// CORRER/COMPETICION. Para bici y natacion siempre es null, asi que sin este
|
|
91
|
+
// fallback caian SIEMPRE por distancia (y se enviaba un paso de 0 metros).
|
|
92
|
+
const prefiereTiempo = rangePlanning.fixedMetric === common_1.WORKOUT_FIXED_METRIC.TIME;
|
|
93
|
+
if (duracion && (prefiereTiempo || !distancia)) {
|
|
76
94
|
return {
|
|
77
95
|
type: 'TIME',
|
|
78
|
-
value:
|
|
96
|
+
value: duracion,
|
|
79
97
|
};
|
|
80
98
|
}
|
|
81
99
|
return {
|
|
82
100
|
type: 'DISTANCE',
|
|
83
|
-
value:
|
|
101
|
+
value: distancia,
|
|
84
102
|
valueType: 'METER',
|
|
85
103
|
};
|
|
86
104
|
};
|
|
@@ -126,7 +144,10 @@ const translateWorkoutGarmin = (workout) => {
|
|
|
126
144
|
});
|
|
127
145
|
}
|
|
128
146
|
else if (!workout.estructura) {
|
|
129
|
-
|
|
147
|
+
// Basta con una de las dos: bici/natacion se planifican por tiempo y correr
|
|
148
|
+
// por distancia. Exigir las dos (comparando ademas en crudo) era lo que
|
|
149
|
+
// dejaba fuera a los entrenos por tiempo.
|
|
150
|
+
if (!metricaPlanificada(workout.distancePlanned) && !metricaPlanificada(workout.durationPlanned))
|
|
130
151
|
return null;
|
|
131
152
|
const targetType = getWorkoutGarminTargetType(workout, null);
|
|
132
153
|
const targetValue = getWorkoutGarminTargetValue(workout, null);
|
|
@@ -203,7 +224,11 @@ const getWorkoutGarminTargetValue = (workout, row) => {
|
|
|
203
224
|
// } else {
|
|
204
225
|
// El resto calcular el ritmo
|
|
205
226
|
// No se puede añadir potencia ya que el valor que hay que dar es una zona, no absoluto
|
|
206
|
-
|
|
227
|
+
const distancia = metricaPlanificada(workout.distancePlanned);
|
|
228
|
+
const duracion = metricaPlanificada(workout.durationPlanned);
|
|
229
|
+
// Sin las dos metricas no hay ritmo objetivo: el paso va sin target (que es
|
|
230
|
+
// lo que ya se hace hoy en natacion y Garmin acepta).
|
|
231
|
+
value = distancia && duracion ? distancia / duracion : null;
|
|
207
232
|
paceRange = (0, common_1.getWorkoutRangePlanning)({
|
|
208
233
|
workoutType: workout.type,
|
|
209
234
|
distancePlanned: workout.distancePlanned,
|
|
@@ -247,6 +272,10 @@ const getWorkoutGarminTargetValue = (workout, row) => {
|
|
|
247
272
|
};
|
|
248
273
|
const createWorkoutGarmin = (userid, workout) => __awaiter(void 0, void 0, void 0, function* () {
|
|
249
274
|
const workoutGarmin = translateWorkoutGarmin(workout);
|
|
275
|
+
// Sin payload no se manda nada: antes se hacia el POST con body null y Garmin
|
|
276
|
+
// respondia un error opaco.
|
|
277
|
+
if (!workoutGarmin)
|
|
278
|
+
throw new Error(`Workout ${workout.id} no es traducible a Garmin (sin estructura y sin distancia ni duracion)`);
|
|
250
279
|
const data = yield garminOauthActivity({
|
|
251
280
|
url: 'https://apis.garmin.com/training-api/workout',
|
|
252
281
|
userid,
|
|
@@ -14,7 +14,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.saveWorkoutAplication = void 0;
|
|
16
16
|
const db_1 = require("../db");
|
|
17
|
-
const titleDescriptionTranslated_1 = require("../translation/titleDescriptionTranslated");
|
|
18
17
|
const storage_1 = require("@google-cloud/storage");
|
|
19
18
|
const path_1 = __importDefault(require("path"));
|
|
20
19
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -316,16 +315,24 @@ _data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
316
315
|
}
|
|
317
316
|
const dateStr = (0, moment_1.default)(opts.date).format('YYYY-MM-DD');
|
|
318
317
|
const pt = predicadoTipo(opts);
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
// se
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
318
|
+
// SIN traducción en la ingesta: título y descripción se guardan tal cual llegan
|
|
319
|
+
// del proveedor (Garmin/Strava/Apple). Antes se traducían al español con Google
|
|
320
|
+
// Cloud Translation, lo que metía una llamada de red —y una dependencia de las
|
|
321
|
+
// credenciales de GCP— en el camino crítico del guardado: si Translate fallaba,
|
|
322
|
+
// el entreno NO se guardaba y el webhook devolvía 5xx. Con la migración a Azure
|
|
323
|
+
// eso se rompió (sin metadata server, las ADC resuelven a la service account de
|
|
324
|
+
// Storage, que no tiene permiso de Translate → PERMISSION_DENIED) y dos
|
|
325
|
+
// actividades envenenadas bastaron para atascar la cola de reintentos de Garmin
|
|
326
|
+
// y frenar la ingesta del resto.
|
|
327
|
+
// Consecuencia asumida: para clientes no-ES el "TITLE" queda en el idioma del
|
|
328
|
+
// dispositivo en vez de normalizado a español —lo notan entrenadores y la IA, no
|
|
329
|
+
// el cliente: la app cae a "TITLE" cuando "TITLE PREFERRED LANGUAGE" es null.
|
|
330
|
+
const translated = {
|
|
331
|
+
title: opts.title,
|
|
332
|
+
description: opts.description,
|
|
333
|
+
titlePreferredLanguage: null,
|
|
334
|
+
descriptionPreferredLanguage: null,
|
|
335
|
+
};
|
|
329
336
|
const idWorkout = yield withTx((q) => __awaiter(void 0, void 0, void 0, function* () {
|
|
330
337
|
var _e;
|
|
331
338
|
// Lock anti-carrera: serializa ingestas del mismo cliente/día/bucket-tipo
|
|
@@ -28,6 +28,10 @@ const sendWorkoutToWatch = (clientId, workoutId) => __awaiter(void 0, void 0, vo
|
|
|
28
28
|
const estructura = yield (0, db_1.query)('SELECT * FROM [WORKOUT STRUCTURE] WHERE [ID WORKOUT] = ? ORDER BY [INDEX]', [workoutId]);
|
|
29
29
|
if (estructura.length > 0)
|
|
30
30
|
workout.estructura = estructura;
|
|
31
|
+
// Si el entreno no se puede traducir, se avisa con un 400 legible en vez de
|
|
32
|
+
// dejar que el POST a Garmin reviente con un 500 opaco.
|
|
33
|
+
if (!(0, garmin_1.translateWorkoutGarmin)(workout))
|
|
34
|
+
return { error: 'Este entrenamiento no se puede enviar al reloj' };
|
|
31
35
|
// Llamar a la función que envía el workout al Garmin
|
|
32
36
|
const { workoutGarminId } = yield (0, garmin_1.createWorkoutGarmin)(clientId, workout);
|
|
33
37
|
yield (0, garmin_1.scheduleWorkoutGarmin)(clientId, { date: (0, moment_1.default)(workout.date).format('YYYY-MM-DD'), workoutGarminId });
|
package/package.json
CHANGED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.getTitleDescriptionTranslated = void 0;
|
|
13
|
-
const db_1 = require("../db");
|
|
14
|
-
const index_1 = require("./index");
|
|
15
|
-
const common_1 = require("@runnerpro/common");
|
|
16
|
-
const getTitleDescriptionTranslated = (idCliente, titleOriginal, descriptionOriginal) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
-
let title;
|
|
18
|
-
let description;
|
|
19
|
-
let titlePreferredLanguage;
|
|
20
|
-
let descriptionPreferredLanguage;
|
|
21
|
-
const [cliente] = yield (0, db_1.query)('SELECT [PREFERRED LANGUAGE] FROM [CLIENTE] WHERE [ID] = ?', [idCliente]);
|
|
22
|
-
if ((cliente === null || cliente === void 0 ? void 0 : cliente.preferredLanguage) && cliente.preferredLanguage !== common_1.LANGUAGES.ES) {
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
title = titleOriginal ? yield (0, index_1.translate)(titleOriginal, { fromLanguage: cliente.preferredLanguage, toLanguage: common_1.LANGUAGES.ES }) : null;
|
|
25
|
-
description = descriptionOriginal
|
|
26
|
-
? // @ts-ignore
|
|
27
|
-
yield (0, index_1.translate)(descriptionOriginal, { fromLanguage: cliente.preferredLanguage, toLanguage: common_1.LANGUAGES.ES })
|
|
28
|
-
: null;
|
|
29
|
-
titlePreferredLanguage = titleOriginal;
|
|
30
|
-
descriptionPreferredLanguage = descriptionOriginal;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
title = titleOriginal;
|
|
34
|
-
description = descriptionOriginal;
|
|
35
|
-
}
|
|
36
|
-
return { title, description, titlePreferredLanguage, descriptionPreferredLanguage };
|
|
37
|
-
});
|
|
38
|
-
exports.getTitleDescriptionTranslated = getTitleDescriptionTranslated;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
declare const getTitleDescriptionTranslated: (idCliente: any, titleOriginal: any, descriptionOriginal: any) => Promise<{
|
|
2
|
-
title: any;
|
|
3
|
-
description: any;
|
|
4
|
-
titlePreferredLanguage: any;
|
|
5
|
-
descriptionPreferredLanguage: any;
|
|
6
|
-
}>;
|
|
7
|
-
export { getTitleDescriptionTranslated };
|
|
8
|
-
//# sourceMappingURL=titleDescriptionTranslated.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"titleDescriptionTranslated.d.ts","sourceRoot":"","sources":["../../../../src/translation/titleDescriptionTranslated.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,6BAA6B;;;;;EAsBlC,CAAC;AAEF,OAAO,EAAE,6BAA6B,EAAE,CAAC"}
|