From 88807d05236fbc0935bc429331a81af11b542e1c Mon Sep 17 00:00:00 2001 From: Bruno Besson Date: Sat, 28 Oct 2023 22:50:55 +0200 Subject: [PATCH 1/2] WIP --- .eslintrc.cjs | 126 ++++++++++++++++++++++++----------------- Dockerfile | 4 +- jest.config.common.mjs | 21 +++++++ jest.config.mjs | 8 +-- jest.config.psql.mjs | 8 +-- jest.config.s3.mjs | 9 ++- package-lock.json | 14 +++++ package.json | 5 +- tsconfig.eslint.json | 2 +- tsconfig.json | 8 ++- 10 files changed, 132 insertions(+), 73 deletions(-) create mode 100644 jest.config.common.mjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1a51bfb9..43be6549 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,70 +1,92 @@ module.exports = { - env: { - node: true, - es2022: true, - }, - parser: '@typescript-eslint/parser', // Specifies the ESLint parser + root: true, extends: [ - 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin - 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'eslint:recommended', 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 'plugin:node/recommended', - 'plugin:import/recommended', - 'plugin:import/typescript', 'plugin:security/recommended', 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. ], + env: { + node: true, + es2022: true, + }, parserOptions: { ecmaVersion: 'latest', - project: './tsconfig.eslint.json', - sourceType: 'module', // Allows for the use of imports, - }, - rules: { - 'node/no-extraneous-import': ['error', { allowModules: ['pino'] }], - 'node/no-missing-import': 'off', - 'node/no-unpublished-import': ['error', { allowModules: ['type-fest'] }], - 'node/no-unsupported-features/es-syntax': 'off', - 'import/no-named-as-default-member': 'off', - 'import/order': [ - 'error', - { - 'newlines-between': 'always', - alphabetize: { - order: 'asc', - caseInsensitive: false, - }, - }, - ], - '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true, destructuredArrayIgnorePattern: '^_' }], - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/explicit-function-return-type': 'error', - '@typescript-eslint/explicit-member-accessibility': ['error', { overrides: { constructors: 'no-public' } }], + sourceType: 'module', }, overrides: [ { - files: ['./**/*.ts]'], - rules: { - '@typescript-eslint/no-throw-literal': 'error', + files: ['./**/*.ts'], + parser: '@typescript-eslint/parser', // Specifies the ESLint parser + extends: [ + 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + 'plugin:node/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:security/recommended', + 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + parserOptions: { + ecmaVersion: 'latest', + project: './tsconfig.eslint.json', + sourceType: 'module', // Allows for the use of imports, }, - }, - { - files: ['test/**/*.ts'], - plugins: ['jest'], - extends: ['plugin:jest/recommended'], rules: { - 'node/no-unpublished-import': 'off', - 'node/no-extraneous-import': 'off', - '@typescript-eslint/unbound-method': 'off', - 'jest/unbound-method': 'error', + 'node/no-extraneous-import': ['error', { allowModules: ['pino'] }], + 'node/no-missing-import': 'off', + 'node/no-unpublished-import': ['error', { allowModules: ['type-fest'] }], + 'node/no-unsupported-features/es-syntax': 'off', + 'import/no-named-as-default-member': 'off', + 'import/order': [ + 'error', + { + 'newlines-between': 'always', + alphabetize: { + order: 'asc', + caseInsensitive: false, + }, + }, + ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { ignoreRestSiblings: true, destructuredArrayIgnorePattern: '^_' }, + ], + '@typescript-eslint/camelcase': 'off', + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/explicit-member-accessibility': ['error', { overrides: { constructors: 'no-public' } }], }, - }, - ], - settings: { - 'import/resolvers': { - typescript: { - alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` - project: '.', + overrides: [ + { + files: ['./**/*.ts]'], + rules: { + '@typescript-eslint/no-throw-literal': 'error', + }, + }, + { + files: ['test/**/*.ts'], + plugins: ['jest'], + extends: ['plugin:jest/recommended'], + rules: { + 'node/no-unpublished-import': 'off', + 'node/no-extraneous-import': 'off', + '@typescript-eslint/unbound-method': 'off', + 'jest/unbound-method': 'error', + }, + }, + ], + settings: { + 'import/resolvers': { + typescript: { + alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` + project: '.', + }, + }, }, }, - }, + ], }; diff --git a/Dockerfile b/Dockerfile index 56199db8..df1420a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build stage -FROM node:20-slim as build-stage +FROM node:lts-slim as build-stage RUN apt-get update && apt-get install -y --no-install-recommends dumb-init WORKDIR /usr COPY package*.json ./ @@ -10,7 +10,7 @@ RUN npm run build RUN npm prune --omit=dev # production stage -FROM node:20-slim +FROM node:lts-slim ARG version ENV npm_package_version=${version} ENV NODE_ENV production diff --git a/jest.config.common.mjs b/jest.config.common.mjs new file mode 100644 index 00000000..bd1cf4a1 --- /dev/null +++ b/jest.config.common.mjs @@ -0,0 +1,21 @@ +export default { + rootDir: '.', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + testMatch: ['**/*.spec.ts'], + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + // workaround for OOM issue, but lowers type checking + // see https://github.com/kulshekhar/ts-jest/issues/2015 + isolatedModules: true, + tsconfig: '/tsconfig.json', + }, + ], + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, +}; diff --git a/jest.config.mjs b/jest.config.mjs index e48d4599..c5fa0ffb 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -1,9 +1,7 @@ +import baseConfig from './jest.config.common.mjs'; + export default { + ...baseConfig, roots: ['/test/unit'], - testEnvironment: 'node', - testMatch: ['**/*.spec.ts'], - transform: { - '^.+\\.tsx?$': ['ts-jest'], - }, setupFiles: ['/test/environment/env-vars.mjs'], }; diff --git a/jest.config.psql.mjs b/jest.config.psql.mjs index 44fdfb97..6272a74c 100644 --- a/jest.config.psql.mjs +++ b/jest.config.psql.mjs @@ -1,9 +1,7 @@ +import baseConfig from './jest.config.common.mjs'; + export default { + ...baseConfig, roots: ['/test/psql'], - testEnvironment: 'node', - testMatch: ['**/*.spec.ts'], - transform: { - '^.+\\.tsx?$': ['ts-jest'], - }, setupFiles: ['/test/environment/env-vars.mjs'], }; diff --git a/jest.config.s3.mjs b/jest.config.s3.mjs index a973453e..e965bed0 100644 --- a/jest.config.s3.mjs +++ b/jest.config.s3.mjs @@ -1,9 +1,8 @@ +import baseConfig from './jest.config.common.mjs'; + export default { + ...baseConfig, + displayName: 'S3', roots: ['/test/s3'], - testEnvironment: 'node', - testMatch: ['**/*.spec.ts'], - transform: { - '^.+\\.tsx?$': ['ts-jest'], - }, setupFiles: ['/test/environment/env-vars.s3.mjs'], }; diff --git a/package-lock.json b/package-lock.json index 750a69fb..dae7fc3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@shopify/jest-koa-mocks": "5.1.1", + "@tsconfig/esm": "1.0.5", "@tsconfig/node20": "20.1.2", "@tsconfig/strictest": "2.0.2", "@types/async": "3.2.22", @@ -2875,6 +2876,13 @@ "node": ">=14.0.0" } }, + "node_modules/@tsconfig/esm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/esm/-/esm-1.0.5.tgz", + "integrity": "sha512-JzoZ0h299JRLPfV5VBsMq1TuMy+OmU9bdV/7NcjfRojL0eIcA1k5ESrtjWrDwJRJnk9B0QmgR0rq04LERbdfWw==", + "deprecated": "this package has been deprecated", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -13888,6 +13896,12 @@ "tslib": "^2.5.0" } }, + "@tsconfig/esm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/esm/-/esm-1.0.5.tgz", + "integrity": "sha512-JzoZ0h299JRLPfV5VBsMq1TuMy+OmU9bdV/7NcjfRojL0eIcA1k5ESrtjWrDwJRJnk9B0QmgR0rq04LERbdfWw==", + "dev": true + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", diff --git a/package.json b/package.json index 0a96f430..84c108bf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "node": ">=20.0.0" }, "description": "Tiny server to handle connection with activity trackers such as Strava, Garmin or Suunto", - "type": "commonjs", + "type": "module", "main": "dist/index.js", "scripts": { "start": "ts-node src/index.ts | pino-pretty -c -t", @@ -20,7 +20,7 @@ "test:psql": "jest --config jest.config.psql.mjs --runInBand", "test:ci:s3": "jest --config jest.config.s3.ci.mjs --runInBand", "test:ci:psql": "npm run test:ci:psql:init && jest --config jest.config.psql.ci.mjs --runInBand", - "test:ci:psql:init": "ts-node test/environment/init-db.ts" + "test:ci:psql:init": "node test/environment/init-db.mjs" }, "repository": { "type": "git", @@ -70,6 +70,7 @@ }, "devDependencies": { "@shopify/jest-koa-mocks": "5.1.1", + "@tsconfig/esm": "1.0.5", "@tsconfig/node20": "20.1.2", "@tsconfig/strictest": "2.0.2", "@types/async": "3.2.22", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 550ef80f..2c82d68e 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -4,6 +4,6 @@ "rootDir": ".", "outDir": "./dist" }, - "include": ["src/**/*.ts", "test/**/*.ts", "test/**/*.js"], + "include": ["src/**/*.ts", "test/**/*.ts"], "exclude": ["node_modules"] } diff --git a/tsconfig.json b/tsconfig.json index fd54f8c7..653f9d9a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "outDir": "./dist", "resolveJsonModule": true, "useUnknownInCatchVariables": true, + "sourceMap": true, // strictest until tsnode support extends arrays "strict": true, @@ -26,7 +27,12 @@ "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + //esm until tsnode supports extends arrays + "module": "es2022", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] From eed253dfed557ea222cb2f7dc8999d163e8dedb5 Mon Sep 17 00:00:00 2001 From: Bruno Besson Date: Sun, 29 Oct 2023 00:16:39 +0200 Subject: [PATCH 2/2] WIP --- .eslintrc.cjs | 12 +++--- src/app.ts | 32 +++++++------- src/auth/c2c-jwt-extractor.ts | 2 +- src/auth/index.ts | 8 ++-- src/config.ts | 13 +++--- src/db/index.ts | 6 +-- ...ma.ts => 20210506103300_create_schema.mjs} | 14 +++++-- ...00_garmin.ts => 20220802100000_garmin.mjs} | 14 +++++-- ...athlon.ts => 20221020000000_decathlon.mjs} | 14 +++++-- ...ts => 20221104000000_activity_details.mjs} | 14 +++++-- ...0000_polar.ts => 20221121000000_polar.mjs} | 14 +++++-- src/errors.ts | 2 +- src/health.service.ts | 4 +- src/helpers/error.ts | 8 ++-- src/helpers/i18n/index.ts | 34 ++++++++++----- src/helpers/utils.ts | 8 ++-- src/index.ts | 18 ++++---- src/metrics/index.ts | 2 +- src/metrics/prometheus.ts | 2 +- src/miniature.service.ts | 14 +++---- src/repository/activity.repository.ts | 10 ++--- src/repository/activity.ts | 6 +-- src/repository/polar.repository.ts | 8 ++-- src/repository/strava.repository.ts | 6 +-- src/repository/user.repository.ts | 10 ++--- src/server/activities/activity.controller.ts | 6 +-- src/server/activities/activity.service.ts | 10 ++--- src/server/activities/activity.validators.ts | 4 +- src/server/activities/index.ts | 6 +-- src/server/coros/coros.api.ts | 4 +- src/server/coros/coros.controller.ts | 4 +- src/server/coros/coros.service.ts | 36 +++++++++------- src/server/coros/coros.validators.ts | 4 +- src/server/coros/index.ts | 8 ++-- src/server/decathlon/decathlon.api.ts | 8 ++-- src/server/decathlon/decathlon.controller.ts | 4 +- src/server/decathlon/decathlon.service.ts | 30 +++++++------ src/server/decathlon/decathlon.validators.ts | 4 +- src/server/decathlon/index.ts | 8 ++-- src/server/error-handler.ts | 8 ++-- src/server/garmin/garmin.api.ts | 8 ++-- src/server/garmin/garmin.controller.ts | 6 +-- src/server/garmin/garmin.service.ts | 28 ++++++------- src/server/garmin/garmin.validators.ts | 4 +- src/server/garmin/index.ts | 8 ++-- src/server/health/health.controller.ts | 2 +- src/server/health/index.ts | 4 +- src/server/polar/index.ts | 8 ++-- src/server/polar/polar.api.ts | 22 +++++----- src/server/polar/polar.controller.ts | 4 +- src/server/polar/polar.service.ts | 28 ++++++------- src/server/polar/polar.validators.ts | 4 +- src/server/strava/index.ts | 8 ++-- src/server/strava/strava.api.ts | 14 +++---- src/server/strava/strava.controller.ts | 4 +- src/server/strava/strava.service.ts | 32 +++++++------- src/server/strava/strava.validators.ts | 4 +- src/server/suunto/index.ts | 8 ++-- src/server/suunto/suunto.api.ts | 6 +-- src/server/suunto/suunto.controller.ts | 4 +- src/server/suunto/suunto.service.ts | 34 ++++++++------- src/server/suunto/suunto.validators.ts | 2 +- src/server/users/index.ts | 2 +- src/server/users/user.controller.ts | 2 +- src/server/validator.ts | 4 +- src/storage/storage.ts | 10 ++--- src/user.service.ts | 42 +++++++++---------- test/environment/env-vars.mjs | 2 +- test/environment/{init-db.ts => init-db.mjs} | 15 ++++--- .../repository/activity.repository.spec.ts | 4 +- test/psql/repository/polar.repository.spec.ts | 4 +- .../psql/repository/strava.repository.spec.ts | 4 +- test/psql/repository/user.repository.spec.ts | 6 +-- test/s3/storage/storage.spec.ts | 8 ++-- test/unit/auth/c2c-jwt-extractor.spec.ts | 2 +- test/unit/auth/c2c-jwt-verify.spec.ts | 2 +- test/unit/auth/index.spec.ts | 6 +-- test/unit/health.service.spec.ts | 6 +-- test/unit/miniature.service.spec.ts | 4 +- .../activities/activity.controller.spec.ts | 8 ++-- .../activities/activity.service.spec.ts | 12 +++--- test/unit/server/coros/coros.api.spec.ts | 4 +- .../server/coros/coros.controller.spec.ts | 10 ++--- test/unit/server/coros/coros.service.spec.ts | 20 ++++----- .../decathlon/decathlon.controller.spec.ts | 10 ++--- .../decathlon/decathlon.service.spec.ts | 14 +++---- test/unit/server/garmin/garmin.api.spec.ts | 4 +- .../server/garmin/garmin.controller.spec.ts | 12 +++--- .../unit/server/garmin/garmin.service.spec.ts | 14 +++---- .../server/health/health.controller.spec.ts | 6 +-- test/unit/server/polar/polar.api.spec.ts | 10 ++++- .../server/polar/polar.controller.spec.ts | 10 ++--- test/unit/server/polar/polar.service.spec.ts | 18 ++++---- .../server/strava/strava.controller.spec.ts | 10 ++--- .../unit/server/strava/strava.service.spec.ts | 16 +++---- test/unit/server/suunto/suunto.api.spec.ts | 4 +- .../server/suunto/suunto.controller.spec.ts | 8 ++-- .../unit/server/suunto/suunto.service.spec.ts | 20 ++++----- test/unit/server/user/user.controller.spec.ts | 8 ++-- test/unit/storage/storage.spec.ts | 8 ++-- test/unit/user.service.spec.ts | 26 ++++++------ tsconfig.json | 2 +- 102 files changed, 550 insertions(+), 488 deletions(-) rename src/db/migrations/{20210506103300_create_schema.ts => 20210506103300_create_schema.mjs} (84%) rename src/db/migrations/{20220802100000_garmin.ts => 20220802100000_garmin.mjs} (70%) rename src/db/migrations/{20221020000000_decathlon.ts => 20221020000000_decathlon.mjs} (72%) rename src/db/migrations/{20221104000000_activity_details.ts => 20221104000000_activity_details.mjs} (64%) rename src/db/migrations/{20221121000000_polar.ts => 20221121000000_polar.mjs} (69%) rename test/environment/{init-db.ts => init-db.mjs} (73%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 43be6549..2e121647 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -42,6 +42,7 @@ module.exports = { 'node/no-unpublished-import': ['error', { allowModules: ['type-fest'] }], 'node/no-unsupported-features/es-syntax': 'off', 'import/no-named-as-default-member': 'off', + 'import/no-unresolved': 'off', 'import/order': [ 'error', { @@ -59,14 +60,9 @@ module.exports = { '@typescript-eslint/camelcase': 'off', '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/explicit-member-accessibility': ['error', { overrides: { constructors: 'no-public' } }], + '@typescript-eslint/no-throw-literal': 'error', }, overrides: [ - { - files: ['./**/*.ts]'], - rules: { - '@typescript-eslint/no-throw-literal': 'error', - }, - }, { files: ['test/**/*.ts'], plugins: ['jest'], @@ -76,10 +72,14 @@ module.exports = { 'node/no-extraneous-import': 'off', '@typescript-eslint/unbound-method': 'off', 'jest/unbound-method': 'error', + 'security/detect-non-literal-fs-filename': 'off', }, }, ], settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, 'import/resolvers': { typescript: { alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` diff --git a/src/app.ts b/src/app.ts index 0074ee3a..1b58bb48 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,27 +3,27 @@ import type { ServerResponse } from 'http'; import cors from '@koa/cors'; import Router from '@koa/router'; import rTracer from 'cls-rtracer'; -import Koa, { Context } from 'koa'; +import Koa, { type Context } from 'koa'; import bodyParser from 'koa-bodyparser'; import helmet from 'koa-helmet'; import logger from 'koa-pino-logger'; import type { LevelWithSilent } from 'pino'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser, passport } from './auth'; -import config from './config'; -import enabledIf from './helpers/enabled-if'; -import log from './helpers/logger'; -import { promResponseTimeSummary } from './metrics/prometheus'; -import activities from './server/activities'; -import coros from './server/coros'; -import decathlon from './server/decathlon'; -import { defaultErrorHandler } from './server/error-handler'; -import garmin from './server/garmin'; -import health from './server/health'; -import polar from './server/polar'; -import strava from './server/strava'; -import suunto from './server/suunto'; -import users from './server/users'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser, passport } from './auth/index.js'; +import config from './config.js'; +import enabledIf from './helpers/enabled-if.js'; +import log from './helpers/logger.js'; +import { promResponseTimeSummary } from './metrics/prometheus.js'; +import activities from './server/activities/index.js'; +import coros from './server/coros/index.js'; +import decathlon from './server/decathlon/index.js'; +import { defaultErrorHandler } from './server/error-handler.js'; +import garmin from './server/garmin/index.js'; +import health from './server/health/index.js'; +import polar from './server/polar/index.js'; +import strava from './server/strava/index.js'; +import suunto from './server/suunto/index.js'; +import users from './server/users/index.js'; const app = new Koa(); const router = new Router(); diff --git a/src/auth/c2c-jwt-extractor.ts b/src/auth/c2c-jwt-extractor.ts index 97620851..4a317917 100644 --- a/src/auth/c2c-jwt-extractor.ts +++ b/src/auth/c2c-jwt-extractor.ts @@ -6,7 +6,7 @@ const c2cJwtExtractor = (request: Request): string | null => { return null; } return ( - [/JWT token="([\w-\.]+)"/, /Bearer ([\w-\.]+)/] + [/JWT token="([\w-.]+)"/, /Bearer ([\w-.]+)/] .map((regex) => regex.exec(authHeader)) .find((found) => !!found && found.length === 2)?.[1] ?? null ); diff --git a/src/auth/index.ts b/src/auth/index.ts index 973e2ee5..f0384335 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -2,10 +2,10 @@ import type { Context, Middleware } from 'koa'; import passport from 'koa-passport'; import { ExtractJwt, Strategy as JwtStrategy } from 'passport-jwt'; -import config from '../config'; +import config from '../config.js'; -import c2cJwtExtractor from './c2c-jwt-extractor'; -import verify from './c2c-jwt-verify'; +import c2cJwtExtractor from './c2c-jwt-extractor.js'; +import verify from './c2c-jwt-verify.js'; const ensureAuthenticated: Middleware = (ctx: Context, next: () => Promise): Promise => passport.authenticate('jwt', { session: false })(ctx, next); @@ -33,4 +33,4 @@ passport.use( ), ); -export { passport, ensureAuthenticated, ensureUserFromParamsMatchesAuthUser }; +export { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser, passport }; diff --git a/src/config.ts b/src/config.ts index cecd1826..aeb31959 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,15 +1,12 @@ import convict from 'convict'; -import isBase64 from 'validator/lib/isBase64'; -import isStrongPassword from 'validator/lib/isStrongPassword'; -import isUrl from 'validator/lib/isURL'; -import isUUID from 'validator/lib/isUUID'; +import validator from 'validator'; convict.addFormats({ baseUrl: { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call coerce: (v: any) => v.toString(), validate: (value: string) => { - if (!isUrl(value, { require_tld: false, require_protocol: false })) { + if (!validator.isURL(value, { require_tld: false, require_protocol: false })) { throw new Error('must be a URL'); } if (!value.endsWith('/')) { @@ -21,7 +18,7 @@ convict.addFormats({ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call coerce: (v: any) => v.toString(), validate: (value: string) => { - if (!isUUID(value, 4)) { + if (!validator.isUUID(value, 4)) { throw new Error('must be a UUID version 4'); } }, @@ -30,7 +27,7 @@ convict.addFormats({ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call coerce: (v: any) => v.toString(), validate: (value: string) => { - if (!value.trim().length || !isBase64(value)) { + if (!value.trim().length || !validator.isBase64(value)) { throw new Error('must be a non-empty base64 string'); } }, @@ -39,7 +36,7 @@ convict.addFormats({ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call coerce: (v: any) => v.toString(), validate: (value: string) => { - if (!isStrongPassword(value)) { + if (!validator.isStrongPassword(value)) { throw new Error('must be a strong password'); } }, diff --git a/src/db/index.ts b/src/db/index.ts index 556140e2..8b9560d4 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,9 +1,9 @@ -import path from 'path'; +import path from 'node:path'; -import { AsyncResultCallback, retry } from 'async'; +import { retry, type AsyncResultCallback } from 'async'; import knex, { Knex } from 'knex'; -import config from '../config'; +import config from '../config.js'; export type Configuration = { host: string; diff --git a/src/db/migrations/20210506103300_create_schema.ts b/src/db/migrations/20210506103300_create_schema.mjs similarity index 84% rename from src/db/migrations/20210506103300_create_schema.ts rename to src/db/migrations/20210506103300_create_schema.mjs index 372c08a6..0a9d862f 100644 --- a/src/db/migrations/20210506103300_create_schema.ts +++ b/src/db/migrations/20210506103300_create_schema.mjs @@ -1,8 +1,10 @@ -import type { Knex } from 'knex'; - const schema = process.env['DB_SCHEMA'] || 'public'; -export function up(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function up(db) { return db.schema .withSchema(schema) .createTable('users', (table) => { @@ -37,6 +39,10 @@ export function up(db: Knex): Knex.SchemaBuilder { }); } -export function down(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function down(db) { return db.schema.withSchema(schema).dropTable('users').dropTable('activities').dropTable('strava'); } diff --git a/src/db/migrations/20220802100000_garmin.ts b/src/db/migrations/20220802100000_garmin.mjs similarity index 70% rename from src/db/migrations/20220802100000_garmin.ts rename to src/db/migrations/20220802100000_garmin.mjs index b4b19b3a..93f756fe 100644 --- a/src/db/migrations/20220802100000_garmin.ts +++ b/src/db/migrations/20220802100000_garmin.mjs @@ -1,8 +1,10 @@ -import type { Knex } from 'knex'; - const schema = process.env['DB_SCHEMA'] || 'public'; -export function up(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function up(db) { return db.schema .withSchema(schema) .alterTable('users', (table) => { @@ -14,7 +16,11 @@ export function up(db: Knex): Knex.SchemaBuilder { }); } -export function down(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function down(db) { return db.schema .withSchema(schema) .alterTable('users', (table) => { diff --git a/src/db/migrations/20221020000000_decathlon.ts b/src/db/migrations/20221020000000_decathlon.mjs similarity index 72% rename from src/db/migrations/20221020000000_decathlon.ts rename to src/db/migrations/20221020000000_decathlon.mjs index 573acebb..b8dad3f0 100644 --- a/src/db/migrations/20221020000000_decathlon.ts +++ b/src/db/migrations/20221020000000_decathlon.mjs @@ -1,8 +1,10 @@ -import type { Knex } from 'knex'; - const schema = process.env['DB_SCHEMA'] || 'public'; -export function up(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function up(db) { return db.schema.withSchema(schema).alterTable('users', (table) => { table.string('decathlon_id'); table.string('decathlon_access_token', 4096); @@ -12,7 +14,11 @@ export function up(db: Knex): Knex.SchemaBuilder { }); } -export function down(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function down(db) { return db.schema.withSchema(schema).alterTable('users', (table) => { table.dropColumns( 'decathlon_id', diff --git a/src/db/migrations/20221104000000_activity_details.ts b/src/db/migrations/20221104000000_activity_details.mjs similarity index 64% rename from src/db/migrations/20221104000000_activity_details.ts rename to src/db/migrations/20221104000000_activity_details.mjs index 80bc327d..30aebd96 100644 --- a/src/db/migrations/20221104000000_activity_details.ts +++ b/src/db/migrations/20221104000000_activity_details.mjs @@ -1,8 +1,10 @@ -import type { Knex } from 'knex'; - const schema = process.env['DB_SCHEMA'] || 'public'; -export function up(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function up(db) { return db.schema.withSchema(schema).alterTable('activities', (table) => { table.integer('length').nullable(); table.integer('duration').nullable(); @@ -10,7 +12,11 @@ export function up(db: Knex): Knex.SchemaBuilder { }); } -export function down(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function down(db) { return db.schema.withSchema(schema).alterTable('activities', (table) => { table.dropColumns('length', 'duration', 'height_diff_up'); }); diff --git a/src/db/migrations/20221121000000_polar.ts b/src/db/migrations/20221121000000_polar.mjs similarity index 69% rename from src/db/migrations/20221121000000_polar.ts rename to src/db/migrations/20221121000000_polar.mjs index da122c3d..bcd08b99 100644 --- a/src/db/migrations/20221121000000_polar.ts +++ b/src/db/migrations/20221121000000_polar.mjs @@ -1,8 +1,10 @@ -import type { Knex } from 'knex'; - const schema = process.env['DB_SCHEMA'] || 'public'; -export function up(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function up(db) { return db.schema .withSchema(schema) .alterTable('users', (table) => { @@ -15,7 +17,11 @@ export function up(db: Knex): Knex.SchemaBuilder { }); } -export function down(db: Knex): Knex.SchemaBuilder { +/** + * @param {import('knex').Knex} db + * @returns {import('knex').Knex.SchemaBuilder} + */ +export function down(db) { return db.schema .withSchema(schema) .alterTable('users', (table) => { diff --git a/src/errors.ts b/src/errors.ts index 3b154ab2..a25ffc5e 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,4 +1,4 @@ -import log from './helpers/logger'; +import log from './helpers/logger.js'; export class AppError extends Error { constructor( diff --git a/src/health.service.ts b/src/health.service.ts index 598c83b6..d3f0a509 100644 --- a/src/health.service.ts +++ b/src/health.service.ts @@ -1,7 +1,7 @@ import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; +import relativeTime from 'dayjs/plugin/relativeTime.js'; -import config from './config'; +import config from './config.js'; dayjs.extend(relativeTime); diff --git a/src/helpers/error.ts b/src/helpers/error.ts index 0e0e6504..cf52518c 100644 --- a/src/helpers/error.ts +++ b/src/helpers/error.ts @@ -1,10 +1,10 @@ import axios from 'axios'; -import { AppError, ExternalApiError } from '../errors'; -import { promApiErrorsCounter } from '../metrics/prometheus'; -import type { Vendor } from '../repository/activity'; +import { AppError, ExternalApiError } from '../errors.js'; +import { promApiErrorsCounter } from '../metrics/prometheus.js'; +import type { Vendor } from '../repository/activity.js'; -import log from './logger'; +import log from './logger.js'; export function handleExternalApiError(vendor: Vendor, message: string, error: unknown): AppError { if (error instanceof AppError) { diff --git a/src/helpers/i18n/index.ts b/src/helpers/i18n/index.ts index ac3c1429..6df48379 100644 --- a/src/helpers/i18n/index.ts +++ b/src/helpers/i18n/index.ts @@ -1,16 +1,28 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { z } from 'zod'; -import ca from './ca.json'; -import de from './de.json'; -import en from './en.json'; -import es from './es.json'; -import eu from './eu.json'; -import fr from './fr.json'; -import hu from './hu.json'; -import it from './it.json'; -import ru from './ru.json'; -import sl from './sl.json'; -import zh_CN from './zh_CN.json'; +// @ts-ignore +import ca from './ca.json' assert { type: 'json' }; +// @ts-ignore +import de from './de.json' assert { type: 'json' }; +// @ts-ignore +import en from './en.json' assert { type: 'json' }; +// @ts-ignore +import es from './es.json' assert { type: 'json' }; +// @ts-ignore +import eu from './eu.json' assert { type: 'json' }; +// @ts-ignore +import fr from './fr.json' assert { type: 'json' }; +// @ts-ignore +import hu from './hu.json' assert { type: 'json' }; +// @ts-ignore +import it from './it.json' assert { type: 'json' }; +// @ts-ignore +import ru from './ru.json' assert { type: 'json' }; +// @ts-ignore +import sl from './sl.json' assert { type: 'json' }; +// @ts-ignore +import zh_CN from './zh_CN.json' assert { type: 'json' }; export const Lang = z.enum(['fr', 'en', 'ca', 'eu', 'it', 'de', 'es', 'hu', 'ru', 'sl', 'zh_CN']); export type Lang = z.infer; diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 8d591b9d..d30038ee 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -1,11 +1,11 @@ import { extractGeometry } from '@c2corg/fit-parser-extract-geometry'; -import aes from 'crypto-js/aes'; -import encUtf8 from 'crypto-js/enc-utf8'; +import aes from 'crypto-js/aes.js'; +import encUtf8 from 'crypto-js/enc-utf8.js'; import { parse } from 'iso8601-duration'; import type { Except } from 'type-fest'; -import config from '../config'; -import type { LineString } from '../repository/geojson'; +import config from '../config.js'; +import type { LineString } from '../repository/geojson.js'; export type Optional = Pick, K> & Except; diff --git a/src/index.ts b/src/index.ts index fbbb977a..7a54f504 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,17 @@ -import './dotenv'; +import './dotenv.js'; import type { Server } from 'http'; -import { ErrorCallback, retry } from 'async'; +import { retry, type ErrorCallback } from 'async'; import watch from 'node-watch'; -import { app } from './app'; -import config from './config'; -import { database as db } from './db'; -import log from './helpers/logger'; -import { metricsKoa } from './metrics'; -import { polarService } from './server/polar/polar.service'; -import { stravaService } from './server/strava/strava.service'; +import { app } from './app.js'; +import config from './config.js'; +import { database as db } from './db/index.js'; +import log from './helpers/logger.js'; +import { metricsKoa } from './metrics/index.js'; +import { polarService } from './server/polar/polar.service.js'; +import { stravaService } from './server/strava/strava.service.js'; async function closeServer(server: Server): Promise { const checkPendingRequests = (callback: ErrorCallback): void => { diff --git a/src/metrics/index.ts b/src/metrics/index.ts index b641ef07..b88a1922 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import Koa from 'koa'; import { register } from 'prom-client'; -import config from '../config'; +import config from '../config.js'; const koa = new Koa(); const router = new Router(); diff --git a/src/metrics/prometheus.ts b/src/metrics/prometheus.ts index bd5bc1fd..c98c0adb 100644 --- a/src/metrics/prometheus.ts +++ b/src/metrics/prometheus.ts @@ -1,6 +1,6 @@ import { collectDefaultMetrics, Counter, Gauge, register, Summary } from 'prom-client'; -import config from '../config'; +import config from '../config.js'; register.setDefaultLabels({ service: 'c2c_tracking' }); diff --git a/src/miniature.service.ts b/src/miniature.service.ts index 34864730..125fc1b3 100644 --- a/src/miniature.service.ts +++ b/src/miniature.service.ts @@ -1,11 +1,11 @@ -import { encode } from '@mapbox/polyline'; +import polyline from '@mapbox/polyline'; import { createId } from '@paralleldrive/cuid2'; import axios from 'axios'; -import config from './config'; -import { simplify } from './helpers/simplify'; -import type { LineString } from './repository/geojson'; -import { storage } from './storage/storage'; +import config from './config.js'; +import { simplify } from './helpers/simplify.js'; +import type { LineString } from './repository/geojson.js'; +import { storage } from './storage/storage.js'; const miniatureSize = config.get('miniatures.size'); const mapboxToken = config.get('miniatures.mapbox.token'); @@ -23,8 +23,8 @@ export class MiniatureService { } private mapboxUrl(geometry: LineString): string { - const polyline = encode(this.simplifiedCoordinates(geometry).map(([lng, lat]) => [lat!, lng!])); - const uriEncodedPolyline = encodeURIComponent(polyline); + const poly = polyline.encode(this.simplifiedCoordinates(geometry).map(([lng, lat]) => [lat!, lng!])); + const uriEncodedPolyline = encodeURIComponent(poly); return `https://api.mapbox.com/styles/v1/mapbox/outdoors-v11/static/path-2+f00(${uriEncodedPolyline})/auto/${miniatureSize}x${miniatureSize}?access_token=${mapboxToken}&attribution=false&logo=false&padding=20`; } diff --git a/src/repository/activity.repository.ts b/src/repository/activity.repository.ts index 7b274317..6d0d6382 100644 --- a/src/repository/activity.repository.ts +++ b/src/repository/activity.repository.ts @@ -1,11 +1,11 @@ import type { Except } from 'type-fest'; -import config from '../config'; -import { database as db } from '../db'; -import { IOError, NotFoundError } from '../errors'; +import config from '../config.js'; +import { database as db } from '../db/index.js'; +import { IOError, NotFoundError } from '../errors.js'; -import type { Activity, Vendor } from './activity'; -import type { LineString } from './geojson'; +import type { Activity, Vendor } from './activity.js'; +import type { LineString } from './geojson.js'; type ActivityRow = { id: number; diff --git a/src/repository/activity.ts b/src/repository/activity.ts index 0564dda7..4ef3547d 100644 --- a/src/repository/activity.ts +++ b/src/repository/activity.ts @@ -1,8 +1,8 @@ import type { Except, SetOptional, SetRequired } from 'type-fest'; -import isISO8601 from 'validator/lib/isISO8601'; +import validator from 'validator'; import { z } from 'zod'; -import { LineString } from './geojson'; +import { LineString } from './geojson.js'; export const Vendor = z.enum(['strava', 'suunto', 'garmin', 'decathlon', 'polar', 'coros']); export type Vendor = z.infer; @@ -12,7 +12,7 @@ export const Activity = z.object({ userId: z.number().int().positive(), vendor: Vendor, vendorId: z.string().min(1), - date: z.string().refine(isISO8601, { + date: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), name: z.string().min(1).optional(), diff --git a/src/repository/polar.repository.ts b/src/repository/polar.repository.ts index 560c1af8..c15bb0bb 100644 --- a/src/repository/polar.repository.ts +++ b/src/repository/polar.repository.ts @@ -1,7 +1,7 @@ -import config from '../config'; -import { database as db } from '../db'; -import { IOError } from '../errors'; -import { encrypt, decrypt } from '../helpers/utils'; +import config from '../config.js'; +import { database as db } from '../db/index.js'; +import { IOError } from '../errors.js'; +import { decrypt, encrypt } from '../helpers/utils.js'; type PolarRow = { id: number; diff --git a/src/repository/strava.repository.ts b/src/repository/strava.repository.ts index d6faa1a7..140d0238 100644 --- a/src/repository/strava.repository.ts +++ b/src/repository/strava.repository.ts @@ -1,6 +1,6 @@ -import config from '../config'; -import { database as db } from '../db'; -import { IOError } from '../errors'; +import config from '../config.js'; +import { database as db } from '../db/index.js'; +import { IOError } from '../errors.js'; type StravaRow = { id: number; diff --git a/src/repository/user.repository.ts b/src/repository/user.repository.ts index 6bc069c3..d1b9af85 100644 --- a/src/repository/user.repository.ts +++ b/src/repository/user.repository.ts @@ -1,11 +1,11 @@ import dayjs from 'dayjs'; -import config from '../config'; -import { database as db } from '../db'; -import { IOError } from '../errors'; -import { decrypt, encrypt } from '../helpers/utils'; +import config from '../config.js'; +import { database as db } from '../db/index.js'; +import { IOError } from '../errors.js'; +import { decrypt, encrypt } from '../helpers/utils.js'; -import type { User } from './user'; +import type { User } from './user.js'; type AbstractUserRow = { c2c_id: number; diff --git a/src/server/activities/activity.controller.ts b/src/server/activities/activity.controller.ts index 18c13849..ad032f25 100644 --- a/src/server/activities/activity.controller.ts +++ b/src/server/activities/activity.controller.ts @@ -1,9 +1,9 @@ import type { Context } from 'koa'; -import { NotFoundError } from '../../errors'; -import type { Lang } from '../../helpers/i18n'; +import { NotFoundError } from '../../errors.js'; +import type { Lang } from '../../helpers/i18n/index.js'; -import { activityService as service } from './activity.service'; +import { activityService as service } from './activity.service.js'; class ActivityController { public async getUserActivities(ctx: Context): Promise { diff --git a/src/server/activities/activity.service.ts b/src/server/activities/activity.service.ts index 7d09f90f..c9f9ce6c 100644 --- a/src/server/activities/activity.service.ts +++ b/src/server/activities/activity.service.ts @@ -1,10 +1,10 @@ import type { Except } from 'type-fest'; -import { NotFoundError } from '../../errors'; -import { Lang, translations } from '../../helpers/i18n'; -import type { Activity } from '../../repository/activity'; -import type { LineString } from '../../repository/geojson'; -import { userService } from '../../user.service'; +import { NotFoundError } from '../../errors.js'; +import { Lang, translations } from '../../helpers/i18n/index.js'; +import type { Activity } from '../../repository/activity.js'; +import type { LineString } from '../../repository/geojson.js'; +import { userService } from '../../user.service.js'; export class ActivityService { public async getActivities( diff --git a/src/server/activities/activity.validators.ts b/src/server/activities/activity.validators.ts index e31c9ef5..0bd8449e 100644 --- a/src/server/activities/activity.validators.ts +++ b/src/server/activities/activity.validators.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { Lang } from '../../helpers/i18n'; -import type { ValidationSchema } from '../validator'; +import { Lang } from '../../helpers/i18n/index.js'; +import type { ValidationSchema } from '../validator.js'; export const activities: ValidationSchema = { query: z.object({ lang: Lang.optional() }) }; diff --git a/src/server/activities/index.ts b/src/server/activities/index.ts index 25f0907b..f473f5ae 100644 --- a/src/server/activities/index.ts +++ b/src/server/activities/index.ts @@ -1,9 +1,9 @@ import Router from '@koa/router'; -import { validate } from '../validator'; +import { validate } from '../validator.js'; -import { controller } from './activity.controller'; -import { activities } from './activity.validators'; +import { controller } from './activity.controller.js'; +import { activities } from './activity.validators.js'; const router = new Router(); diff --git a/src/server/coros/coros.api.ts b/src/server/coros/coros.api.ts index 07ac1595..207e8ef4 100644 --- a/src/server/coros/coros.api.ts +++ b/src/server/coros/coros.api.ts @@ -1,8 +1,8 @@ import axios from 'axios'; import { z } from 'zod'; -import config from '../../config'; -import { handleExternalApiError } from '../../helpers/error'; +import config from '../../config.js'; +import { handleExternalApiError } from '../../helpers/error.js'; export const CorosAuth = z.object({ access_token: z.string().min(5).max(50), diff --git a/src/server/coros/coros.controller.ts b/src/server/coros/coros.controller.ts index 51ef6b3e..a5cebb24 100644 --- a/src/server/coros/coros.controller.ts +++ b/src/server/coros/coros.controller.ts @@ -1,7 +1,7 @@ import type { Context } from 'koa'; -import type { WebhookEvent } from './coros.api'; -import { corosService as service } from './coros.service'; +import type { WebhookEvent } from './coros.api.js'; +import { corosService as service } from './coros.service.js'; class CorosController { public async exchangeToken(ctx: Context): Promise { diff --git a/src/server/coros/coros.service.ts b/src/server/coros/coros.service.ts index 3d4bab28..37577664 100644 --- a/src/server/coros/coros.service.ts +++ b/src/server/coros/coros.service.ts @@ -1,22 +1,26 @@ import dayjs from 'dayjs'; -import dayjsPluginTimezone from 'dayjs/plugin/timezone'; -import dayjsPluginUtc from 'dayjs/plugin/utc'; +import dayjsPluginTimezone from 'dayjs/plugin/timezone.js'; +import dayjsPluginUtc from 'dayjs/plugin/utc.js'; import type { SetNonNullable, SetRequired } from 'type-fest'; -import config from '../../config'; -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { fitToGeoJSON } from '../../helpers/utils'; -import { promTokenRenewalErrorsCounter, promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import type { NewActivityWithGeometry, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import type { User } from '../../repository/user'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; - -import { corosApi, CorosAuth, WebhookEvent, Workout, WorkoutRecords } from './coros.api'; +import config from '../../config.js'; +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { fitToGeoJSON } from '../../helpers/utils.js'; +import { + promTokenRenewalErrorsCounter, + promWebhookCounter, + promWebhookErrorsCounter, +} from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import type { NewActivityWithGeometry, Vendor } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import type { User } from '../../repository/user.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; + +import { corosApi, CorosAuth, WebhookEvent, Workout, WorkoutRecords } from './coros.api.js'; dayjs.extend(dayjsPluginUtc); dayjs.extend(dayjsPluginTimezone); diff --git a/src/server/coros/coros.validators.ts b/src/server/coros/coros.validators.ts index be356a50..e11d81f2 100644 --- a/src/server/coros/coros.validators.ts +++ b/src/server/coros/coros.validators.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; -import { WebhookEvent } from './coros.api'; +import { WebhookEvent } from './coros.api.js'; export const exchangeToken: ValidationSchema = { query: z.object({ diff --git a/src/server/coros/index.ts b/src/server/coros/index.ts index 2f7ce7eb..5d06da90 100644 --- a/src/server/coros/index.ts +++ b/src/server/coros/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './coros.controller'; -import { exchangeToken, webhook } from './coros.validators'; +import { controller } from './coros.controller.js'; +import { exchangeToken, webhook } from './coros.validators.js'; const router = new Router(); diff --git a/src/server/decathlon/decathlon.api.ts b/src/server/decathlon/decathlon.api.ts index 31b870cb..6dd8ad28 100644 --- a/src/server/decathlon/decathlon.api.ts +++ b/src/server/decathlon/decathlon.api.ts @@ -1,9 +1,9 @@ import axios from 'axios'; -import isISO8601 from 'validator/lib/isISO8601'; +import validator from 'validator'; import { z } from 'zod'; -import config from '../../config'; -import { handleExternalApiError } from '../../helpers/error'; +import config from '../../config.js'; +import { handleExternalApiError } from '../../helpers/error.js'; export const DecathlonAuth = z.object({ access_token: z.string().min(10).max(5000), @@ -20,7 +20,7 @@ export const Activity = z.object({ id: z.string().min(5).max(100), name: z.string().max(100).optional(), sport: z.string().regex(/^\/v2\/sports\/\d+$/), - startdate: z.string().refine(isISO8601, { + startdate: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), duration: z.number().nonnegative().optional(), diff --git a/src/server/decathlon/decathlon.controller.ts b/src/server/decathlon/decathlon.controller.ts index f6a1edea..f7c0f5c7 100644 --- a/src/server/decathlon/decathlon.controller.ts +++ b/src/server/decathlon/decathlon.controller.ts @@ -1,7 +1,7 @@ import type { Context } from 'koa'; -import type { WebhookEvent } from './decathlon.api'; -import { decathlonService as service } from './decathlon.service'; +import type { WebhookEvent } from './decathlon.api.js'; +import { decathlonService as service } from './decathlon.service.js'; class DecathlonController { public async exchangeToken(ctx: Context): Promise { diff --git a/src/server/decathlon/decathlon.service.ts b/src/server/decathlon/decathlon.service.ts index b6fbfa4b..132e93f9 100644 --- a/src/server/decathlon/decathlon.service.ts +++ b/src/server/decathlon/decathlon.service.ts @@ -1,18 +1,22 @@ import dayjs from 'dayjs'; -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { promTokenRenewalErrorsCounter, promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import type { NewActivityWithGeometry, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import type { DecathlonInfo } from '../../repository/user'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; - -import { Activity, decathlonApi, DecathlonAuth, WebhookEvent } from './decathlon.api'; -import { sports } from './sports'; +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { + promTokenRenewalErrorsCounter, + promWebhookCounter, + promWebhookErrorsCounter, +} from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import type { NewActivityWithGeometry, Vendor } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import type { DecathlonInfo } from '../../repository/user.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; + +import { Activity, decathlonApi, DecathlonAuth, WebhookEvent } from './decathlon.api.js'; +import { sports } from './sports.js'; export class DecathlonService { public async requestShortLivedAccessTokenAndSetupUser(c2cId: number, authorizationCode: string): Promise { diff --git a/src/server/decathlon/decathlon.validators.ts b/src/server/decathlon/decathlon.validators.ts index 729d9384..241415b0 100644 --- a/src/server/decathlon/decathlon.validators.ts +++ b/src/server/decathlon/decathlon.validators.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; -import { WebhookEvent } from './decathlon.api'; +import { WebhookEvent } from './decathlon.api.js'; export const exchangeToken: ValidationSchema = { query: z diff --git a/src/server/decathlon/index.ts b/src/server/decathlon/index.ts index 031d01ca..28dfc150 100644 --- a/src/server/decathlon/index.ts +++ b/src/server/decathlon/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './decathlon.controller'; -import { exchangeToken, webhook } from './decathlon.validators'; +import { controller } from './decathlon.controller.js'; +import { exchangeToken, webhook } from './decathlon.validators.js'; const router = new Router(); diff --git a/src/server/error-handler.ts b/src/server/error-handler.ts index 0403786a..94e4de43 100644 --- a/src/server/error-handler.ts +++ b/src/server/error-handler.ts @@ -1,9 +1,9 @@ import type { Middleware } from '@koa/router'; -import { Context, HttpError } from 'koa'; +import { HttpError, type Context } from 'koa'; -import { AppError } from '../errors'; -import log from '../helpers/logger'; -import { promUnhandledErrorsCounter } from '../metrics/prometheus'; +import { AppError } from '../errors.js'; +import log from '../helpers/logger.js'; +import { promUnhandledErrorsCounter } from '../metrics/prometheus.js'; export function defaultErrorHandler(): Middleware { return async (ctx: Context, next: () => Promise): Promise => { diff --git a/src/server/garmin/garmin.api.ts b/src/server/garmin/garmin.api.ts index bb69fcc2..40ed1d4f 100644 --- a/src/server/garmin/garmin.api.ts +++ b/src/server/garmin/garmin.api.ts @@ -2,12 +2,12 @@ import { createHmac, randomBytes } from 'crypto'; import axios from 'axios'; import dayjs from 'dayjs'; -import dayjsPluginUTC from 'dayjs/plugin/utc'; +import dayjsPluginUTC from 'dayjs/plugin/utc.js'; import { z } from 'zod'; -import config from '../../config'; -import { ExternalApiError } from '../../errors'; -import { handleExternalApiError } from '../../helpers/error'; +import config from '../../config.js'; +import { ExternalApiError } from '../../errors.js'; +import { handleExternalApiError } from '../../helpers/error.js'; dayjs.extend(dayjsPluginUTC); diff --git a/src/server/garmin/garmin.controller.ts b/src/server/garmin/garmin.controller.ts index 7cf95561..d2a29d1a 100644 --- a/src/server/garmin/garmin.controller.ts +++ b/src/server/garmin/garmin.controller.ts @@ -2,10 +2,10 @@ import KeyvRedis from '@keyv/redis'; import Keyv from 'keyv'; import type { Context } from 'koa'; -import config from '../../config'; +import config from '../../config.js'; -import type { GarminActivity } from './garmin.api'; -import { garminService as service } from './garmin.service'; +import type { GarminActivity } from './garmin.api.js'; +import { garminService as service } from './garmin.service.js'; class GarminController { private readonly keyv; diff --git a/src/server/garmin/garmin.service.ts b/src/server/garmin/garmin.service.ts index 9eafa7a9..dfdc2f0b 100644 --- a/src/server/garmin/garmin.service.ts +++ b/src/server/garmin/garmin.service.ts @@ -1,18 +1,18 @@ import dayjs from 'dayjs'; -import dayjsPluginUTC from 'dayjs/plugin/utc'; - -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import { hasGeometry, NewActivity, NewActivityWithGeometry, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import type { GarminInfo } from '../../repository/user'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; - -import { GarminActivity, garminApi, GarminAuth, GarminSample } from './garmin.api'; +import dayjsPluginUTC from 'dayjs/plugin/utc.js'; + +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import { hasGeometry, Vendor, type NewActivity, type NewActivityWithGeometry } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import type { GarminInfo } from '../../repository/user.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; + +import { GarminActivity, garminApi, GarminAuth, GarminSample } from './garmin.api.js'; dayjs.extend(dayjsPluginUTC); diff --git a/src/server/garmin/garmin.validators.ts b/src/server/garmin/garmin.validators.ts index ecd3afaa..20961a17 100644 --- a/src/server/garmin/garmin.validators.ts +++ b/src/server/garmin/garmin.validators.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; -import { GarminActivity } from './garmin.api'; +import { GarminActivity } from './garmin.api.js'; export const exchangeToken: ValidationSchema = { query: z diff --git a/src/server/garmin/index.ts b/src/server/garmin/index.ts index 114c7952..fc2bcda7 100644 --- a/src/server/garmin/index.ts +++ b/src/server/garmin/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './garmin.controller'; -import { activityWebhook, deauthorizeWebhook, exchangeToken } from './garmin.validators'; +import { controller } from './garmin.controller.js'; +import { activityWebhook, deauthorizeWebhook, exchangeToken } from './garmin.validators.js'; const router = new Router(); diff --git a/src/server/health/health.controller.ts b/src/server/health/health.controller.ts index dc7a01bb..0c3b2121 100644 --- a/src/server/health/health.controller.ts +++ b/src/server/health/health.controller.ts @@ -1,6 +1,6 @@ import type { Context } from 'koa'; -import type { HealthService } from '../../health.service'; +import type { HealthService } from '../../health.service.js'; export default class HealthController { constructor(private readonly health: HealthService) {} diff --git a/src/server/health/index.ts b/src/server/health/index.ts index ec787d28..8dc49bec 100644 --- a/src/server/health/index.ts +++ b/src/server/health/index.ts @@ -1,8 +1,8 @@ import Router from '@koa/router'; -import { healthService } from '../../health.service'; +import { healthService } from '../../health.service.js'; -import HealthController from './health.controller'; +import HealthController from './health.controller.js'; const healthController = new HealthController(healthService); const router = new Router(); diff --git a/src/server/polar/index.ts b/src/server/polar/index.ts index 2c8a62c1..9d436c36 100644 --- a/src/server/polar/index.ts +++ b/src/server/polar/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './polar.controller'; -import { exchangeToken, webhook } from './polar.validators'; +import { controller } from './polar.controller.js'; +import { exchangeToken, webhook } from './polar.validators.js'; const router = new Router(); diff --git a/src/server/polar/polar.api.ts b/src/server/polar/polar.api.ts index 9636993a..deae2d71 100644 --- a/src/server/polar/polar.api.ts +++ b/src/server/polar/polar.api.ts @@ -1,12 +1,10 @@ import axios from 'axios'; -import isISO8601 from 'validator/lib/isISO8601'; -import isURL from 'validator/lib/isURL'; -import isUUID from 'validator/lib/isUUID'; +import validator from 'validator'; import { z } from 'zod'; -import config from '../../config'; -import { handleExternalApiError } from '../../helpers/error'; -import { isISO8601Duration } from '../../helpers/utils'; +import config from '../../config.js'; +import { handleExternalApiError } from '../../helpers/error.js'; +import { isISO8601Duration } from '../../helpers/utils.js'; const PolarAuth = z.object({ access_token: z.string().min(10).max(100), @@ -34,10 +32,10 @@ const CreatedWebhookInfo = z.object({ data: z.object({ id: z.string().min(1).max(50), events: z.array(WebhookType), - url: z.string().refine(isURL, { + url: z.string().refine(validator.isURL, { message: 'String must be an URL', }), - signature_secret_key: z.string().refine(isUUID, { + signature_secret_key: z.string().refine(validator.isUUID, { message: 'String must be an UUID', }), }), @@ -50,7 +48,7 @@ const WebhookInfo = z.object({ z.object({ id: z.string(), events: z.array(WebhookType), - url: z.string().refine(isURL, { + url: z.string().refine(validator.isURL, { message: 'String must be an URL', }), }), @@ -61,7 +59,7 @@ export type WebhookInfo = z.infer; const WebhookPingEvent = z.object({ event: z.literal('PING'), - timestamp: z.string().refine(isISO8601, { + timestamp: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), }); @@ -82,7 +80,7 @@ const WebhookExerciseEvent = z.object({ }) .pipe(z.bigint()), entity_id: z.string().min(1).max(50), - timestamp: z.string().refine(isISO8601, { + timestamp: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), }); @@ -257,7 +255,7 @@ export type SportType = z.infer; const Exercise = z.object({ id: z.string().min(1).max(50), - start_time: z.string().refine(isISO8601, { + start_time: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), start_time_utc_offset: z.number().int(), diff --git a/src/server/polar/polar.controller.ts b/src/server/polar/polar.controller.ts index 7fc7795d..46328df1 100644 --- a/src/server/polar/polar.controller.ts +++ b/src/server/polar/polar.controller.ts @@ -1,7 +1,7 @@ import type { Context } from 'koa'; -import type { WebhookEvent } from './polar.api'; -import { polarService as service } from './polar.service'; +import type { WebhookEvent } from './polar.api.js'; +import { polarService as service } from './polar.service.js'; class PolarController { public async exchangeToken(ctx: Context): Promise { diff --git a/src/server/polar/polar.service.ts b/src/server/polar/polar.service.ts index 15f5e8c3..60d07493 100644 --- a/src/server/polar/polar.service.ts +++ b/src/server/polar/polar.service.ts @@ -1,23 +1,23 @@ import { createHmac } from 'crypto'; import dayjs from 'dayjs'; -import duration from 'dayjs/plugin/duration'; +import duration from 'dayjs/plugin/duration.js'; import { parse } from 'iso8601-duration'; import invariant from 'tiny-invariant'; -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { fitToGeoJSON } from '../../helpers/utils'; -import { promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import type { NewActivityWithGeometry, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import { polarRepository } from '../../repository/polar.repository'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; - -import { Exercise, polarApi, WebhookEvent, isWebhookPingEvent } from './polar.api'; +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { fitToGeoJSON } from '../../helpers/utils.js'; +import { promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import type { NewActivityWithGeometry, Vendor } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import { polarRepository } from '../../repository/polar.repository.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; + +import { WebhookEvent, isWebhookPingEvent, polarApi, type Exercise } from './polar.api.js'; dayjs.extend(duration); diff --git a/src/server/polar/polar.validators.ts b/src/server/polar/polar.validators.ts index de9e8d01..cd4f4c9a 100644 --- a/src/server/polar/polar.validators.ts +++ b/src/server/polar/polar.validators.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; -import { WebhookEvent } from './polar.api'; +import { WebhookEvent } from './polar.api.js'; export const exchangeToken: ValidationSchema = { query: z diff --git a/src/server/strava/index.ts b/src/server/strava/index.ts index 9d071f22..697ed6b1 100644 --- a/src/server/strava/index.ts +++ b/src/server/strava/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './strava.controller'; -import { exchangeToken, webhook, webhookSubscription } from './strava.validators'; +import { controller } from './strava.controller.js'; +import { exchangeToken, webhook, webhookSubscription } from './strava.validators.js'; const router = new Router(); diff --git a/src/server/strava/strava.api.ts b/src/server/strava/strava.api.ts index 4f447f1f..ba5b904c 100644 --- a/src/server/strava/strava.api.ts +++ b/src/server/strava/strava.api.ts @@ -1,9 +1,9 @@ import axios from 'axios'; -import isISO8601 from 'validator/lib/isISO8601'; +import validator from 'validator'; import { z } from 'zod'; -import config from '../../config'; -import { handleExternalApiError } from '../../helpers/error'; +import config from '../../config.js'; +import { handleExternalApiError } from '../../helpers/error.js'; export const Athlete = z.object({ id: z.number().int().positive(), @@ -87,10 +87,10 @@ export const Activity = z.object({ id: z.number().int().positive(), name: z.string(), sport_type: SportType, - start_date: z.string().refine(isISO8601, { + start_date: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), - start_date_local: z.string().refine(isISO8601, { + start_date_local: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), distance: z.number().nonnegative(), // in meters @@ -138,10 +138,10 @@ export const Subscription = z.object({ id: z.number().int().positive(), application_id: z.number().int().positive(), callback_url: z.string().url(), - created_at: z.string().refine(isISO8601, { + created_at: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), - updated_at: z.string().refine(isISO8601, { + updated_at: z.string().refine(validator.isISO8601, { message: 'String must be an ISO-8601 date', }), }); diff --git a/src/server/strava/strava.controller.ts b/src/server/strava/strava.controller.ts index 56aad3da..0fa2bb91 100644 --- a/src/server/strava/strava.controller.ts +++ b/src/server/strava/strava.controller.ts @@ -1,7 +1,7 @@ import type { Context } from 'koa'; -import type { WebhookEvent, WebhookSubscription } from './strava.api'; -import { stravaService as service } from './strava.service'; +import type { WebhookEvent, WebhookSubscription } from './strava.api.js'; +import { stravaService as service } from './strava.service.js'; class StravaController { public async exchangeToken(ctx: Context): Promise { diff --git a/src/server/strava/strava.service.ts b/src/server/strava/strava.service.ts index 5ec08cb4..4c9802b0 100644 --- a/src/server/strava/strava.service.ts +++ b/src/server/strava/strava.service.ts @@ -1,17 +1,21 @@ import dayjs from 'dayjs'; -import dayjsPluginUTC from 'dayjs/plugin/utc'; - -import config from '../../config'; -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { promTokenRenewalErrorsCounter, promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import type { NewActivityWithGeometry, UpdateActivity, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import { stravaRepository } from '../../repository/strava.repository'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; +import dayjsPluginUTC from 'dayjs/plugin/utc.js'; + +import config from '../../config.js'; +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { + promTokenRenewalErrorsCounter, + promWebhookCounter, + promWebhookErrorsCounter, +} from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import type { NewActivityWithGeometry, UpdateActivity, Vendor } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import { stravaRepository } from '../../repository/strava.repository.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; import { Activity, @@ -167,7 +171,7 @@ export class StravaService { throw new NotFoundError('Available data cannot be converted to a valid geometry'); } - const layout = !!altStream ? (!!timeStream ? 'XYZM' : 'XYZ') : !!timeStream ? 'XYM' : 'XY'; + const layout = altStream ? (timeStream ? 'XYZM' : 'XYZ') : timeStream ? 'XYM' : 'XY'; const coordinates: number[][] = []; for (let i = 0; i < distanceStream.original_size; i++) { // eslint-disable-next-line security/detect-object-injection diff --git a/src/server/strava/strava.validators.ts b/src/server/strava/strava.validators.ts index f2c3f462..9dca0e75 100644 --- a/src/server/strava/strava.validators.ts +++ b/src/server/strava/strava.validators.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; -import { WebhookEvent } from './strava.api'; +import { WebhookEvent } from './strava.api.js'; export const exchangeToken: ValidationSchema = { query: z diff --git a/src/server/suunto/index.ts b/src/server/suunto/index.ts index ba1c2e14..ebb0b9c3 100644 --- a/src/server/suunto/index.ts +++ b/src/server/suunto/index.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth'; -import { validate } from '../validator'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser } from '../../auth/index.js'; +import { validate } from '../validator.js'; -import { controller } from './suunto.controller'; -import { exchangeToken, webhook } from './suunto.validators'; +import { controller } from './suunto.controller.js'; +import { exchangeToken, webhook } from './suunto.validators.js'; const router = new Router(); diff --git a/src/server/suunto/suunto.api.ts b/src/server/suunto/suunto.api.ts index 21f491e6..e0e47268 100644 --- a/src/server/suunto/suunto.api.ts +++ b/src/server/suunto/suunto.api.ts @@ -1,9 +1,9 @@ import axios from 'axios'; import { z } from 'zod'; -import config from '../../config'; -import { handleExternalApiError } from '../../helpers/error'; -import log from '../../helpers/logger'; +import config from '../../config.js'; +import { handleExternalApiError } from '../../helpers/error.js'; +import log from '../../helpers/logger.js'; export const SuuntoAuth = z.object({ access_token: z.string().min(10).max(5000), diff --git a/src/server/suunto/suunto.controller.ts b/src/server/suunto/suunto.controller.ts index d9cb5e5c..852f014a 100644 --- a/src/server/suunto/suunto.controller.ts +++ b/src/server/suunto/suunto.controller.ts @@ -1,7 +1,7 @@ import type { Context } from 'koa'; -import type { WebhookEvent } from './suunto.api'; -import { suuntoService as service } from './suunto.service'; +import type { WebhookEvent } from './suunto.api.js'; +import { suuntoService as service } from './suunto.service.js'; class SuuntoController { public async exchangeToken(ctx: Context): Promise { diff --git a/src/server/suunto/suunto.service.ts b/src/server/suunto/suunto.service.ts index 56e49b21..19eb9bdd 100644 --- a/src/server/suunto/suunto.service.ts +++ b/src/server/suunto/suunto.service.ts @@ -1,19 +1,23 @@ import dayjs from 'dayjs'; -import dayjsPluginUTC from 'dayjs/plugin/utc'; - -import config from '../../config'; -import { NotFoundError } from '../../errors'; -import log from '../../helpers/logger'; -import { fitToGeoJSON } from '../../helpers/utils'; -import { promTokenRenewalErrorsCounter, promWebhookCounter, promWebhookErrorsCounter } from '../../metrics/prometheus'; -import { miniatureService } from '../../miniature.service'; -import type { NewActivityWithGeometry, Vendor } from '../../repository/activity'; -import { activityRepository } from '../../repository/activity.repository'; -import type { LineString } from '../../repository/geojson'; -import { userRepository } from '../../repository/user.repository'; -import { userService } from '../../user.service'; - -import { SuuntoAuth, WebhookEvent, Workout, WorkoutSummary, Workouts, suuntoApi, workoutTypes } from './suunto.api'; +import dayjsPluginUTC from 'dayjs/plugin/utc.js'; + +import config from '../../config.js'; +import { NotFoundError } from '../../errors.js'; +import log from '../../helpers/logger.js'; +import { fitToGeoJSON } from '../../helpers/utils.js'; +import { + promTokenRenewalErrorsCounter, + promWebhookCounter, + promWebhookErrorsCounter, +} from '../../metrics/prometheus.js'; +import { miniatureService } from '../../miniature.service.js'; +import type { NewActivityWithGeometry, Vendor } from '../../repository/activity.js'; +import { activityRepository } from '../../repository/activity.repository.js'; +import type { LineString } from '../../repository/geojson.js'; +import { userRepository } from '../../repository/user.repository.js'; +import { userService } from '../../user.service.js'; + +import { SuuntoAuth, WebhookEvent, Workout, WorkoutSummary, Workouts, suuntoApi, workoutTypes } from './suunto.api.js'; dayjs.extend(dayjsPluginUTC); diff --git a/src/server/suunto/suunto.validators.ts b/src/server/suunto/suunto.validators.ts index 1ba9da4b..571a2100 100644 --- a/src/server/suunto/suunto.validators.ts +++ b/src/server/suunto/suunto.validators.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import type { ValidationSchema } from '../validator'; +import type { ValidationSchema } from '../validator.js'; export const exchangeToken: ValidationSchema = { query: z diff --git a/src/server/users/index.ts b/src/server/users/index.ts index d0bf7287..43f4bf5b 100644 --- a/src/server/users/index.ts +++ b/src/server/users/index.ts @@ -1,6 +1,6 @@ import Router from '@koa/router'; -import { controller } from './user.controller'; +import { controller } from './user.controller.js'; const router = new Router(); diff --git a/src/server/users/user.controller.ts b/src/server/users/user.controller.ts index 65ffdcca..e34a99d5 100644 --- a/src/server/users/user.controller.ts +++ b/src/server/users/user.controller.ts @@ -1,6 +1,6 @@ import type { Context } from 'koa'; -import { userService } from '../../user.service'; +import { userService } from '../../user.service.js'; class UserController { public async getStatus(ctx: Context): Promise { diff --git a/src/server/validator.ts b/src/server/validator.ts index 0facf824..795e6817 100644 --- a/src/server/validator.ts +++ b/src/server/validator.ts @@ -2,8 +2,8 @@ import type { Middleware } from '@koa/router'; import type { Context } from 'koa'; import type { Schema } from 'zod'; -import { FieldValidationError } from '../errors'; -import log from '../helpers/logger'; +import { FieldValidationError } from '../errors.js'; +import log from '../helpers/logger.js'; export type ValidationSchema = { query?: Schema; diff --git a/src/storage/storage.ts b/src/storage/storage.ts index ce9f3cba..546100c2 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -1,6 +1,6 @@ -import fs, { constants, promises as fsPromises } from 'fs'; -import path from 'path'; -import type { Readable } from 'stream'; +import fs, { constants, promises as fsPromises } from 'node:fs'; +import path from 'node:path'; +import type { Readable } from 'node:stream'; import { DeleteObjectCommand, @@ -9,12 +9,12 @@ import { ObjectCannedACL, PutObjectCommand, S3Client, - S3ClientConfig, + type S3ClientConfig, } from '@aws-sdk/client-s3'; import sanitize from 'sanitize-filename'; import type { SetRequired } from 'type-fest'; -import config from '../config'; +import config from '../config.js'; export abstract class Storage { public abstract exists(key: string): Promise; diff --git a/src/user.service.ts b/src/user.service.ts index 68ba343b..81bef62d 100644 --- a/src/user.service.ts +++ b/src/user.service.ts @@ -1,20 +1,20 @@ import dayjs from 'dayjs'; -import config from './config'; -import { NotFoundError } from './errors'; -import log from './helpers/logger'; -import type { Optional } from './helpers/utils'; -import { miniatureService } from './miniature.service'; -import type { Activity, NewActivityWithGeometry, UpdateActivity, Vendor } from './repository/activity'; -import { activityRepository } from './repository/activity.repository'; -import type { DecathlonInfo, GarminInfo, StravaInfo, SuuntoInfo, User } from './repository/user'; -import { userRepository } from './repository/user.repository'; -import type { CorosAuth } from './server/coros/coros.api'; -import type { DecathlonAuth } from './server/decathlon/decathlon.api'; -import type { GarminAuth } from './server/garmin/garmin.api'; -import type { PolarAuth } from './server/polar/polar.api'; -import type { StravaAuth, StravaRefreshAuth } from './server/strava/strava.api'; -import type { SuuntoAuth, SuuntoRefreshAuth } from './server/suunto/suunto.api'; +import config from './config.js'; +import { NotFoundError } from './errors.js'; +import log from './helpers/logger.js'; +import type { Optional } from './helpers/utils.js'; +import { miniatureService } from './miniature.service.js'; +import type { Activity, NewActivityWithGeometry, UpdateActivity, Vendor } from './repository/activity.js'; +import { activityRepository } from './repository/activity.repository.js'; +import type { DecathlonInfo, GarminInfo, StravaInfo, SuuntoInfo, User } from './repository/user.js'; +import { userRepository } from './repository/user.repository.js'; +import type { CorosAuth } from './server/coros/coros.api.js'; +import type { DecathlonAuth } from './server/decathlon/decathlon.api.js'; +import type { GarminAuth } from './server/garmin/garmin.api.js'; +import type { PolarAuth } from './server/polar/polar.api.js'; +import type { StravaAuth, StravaRefreshAuth } from './server/strava/strava.api.js'; +import type { SuuntoAuth, SuuntoRefreshAuth } from './server/suunto/suunto.api.js'; type Status = 'not-configured' | 'configured' | 'token-lost'; @@ -48,17 +48,17 @@ export class UserService { const { strava, suunto, garmin, decathlon, polar, coros } = (await userRepository.findById(c2cId)) || {}; return { ...(stravaEnabled && { - strava: !!strava ? (strava.refreshToken ? 'configured' : 'token-lost') : 'not-configured', + strava: strava ? (strava.refreshToken ? 'configured' : 'token-lost') : 'not-configured', }), ...(suuntoEnabled && { - suunto: !!suunto ? (suunto.refreshToken ? 'configured' : 'token-lost') : 'not-configured', + suunto: suunto ? (suunto.refreshToken ? 'configured' : 'token-lost') : 'not-configured', }), - ...(garminEnabled && { garmin: !!garmin ? 'configured' : 'not-configured' }), + ...(garminEnabled && { garmin: garmin ? 'configured' : 'not-configured' }), ...(decathlonEnabled && { - decathlon: !!decathlon ? (decathlon.refreshToken ? 'configured' : 'token-lost') : 'not-configured', + decathlon: decathlon ? (decathlon.refreshToken ? 'configured' : 'token-lost') : 'not-configured', }), - ...(polarEnabled && { polar: !!polar ? 'configured' : 'not-configured' }), - ...(corosEnabled && { coros: !!coros ? 'configured' : 'not-configured' }), + ...(polarEnabled && { polar: polar ? 'configured' : 'not-configured' }), + ...(corosEnabled && { coros: coros ? 'configured' : 'not-configured' }), }; } diff --git a/test/environment/env-vars.mjs b/test/environment/env-vars.mjs index 8fea7d51..870162e0 100644 --- a/test/environment/env-vars.mjs +++ b/test/environment/env-vars.mjs @@ -1,5 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const os = require('os'); +const os = require('node:os'); process.env['SERVER_BASE_URL'] = 'http://localhost:3000/'; process.env['DB_CRYPTO'] = 'secret'; diff --git a/test/environment/init-db.ts b/test/environment/init-db.mjs similarity index 73% rename from test/environment/init-db.ts rename to test/environment/init-db.mjs index 94a850b9..43cd8bd3 100644 --- a/test/environment/init-db.ts +++ b/test/environment/init-db.mjs @@ -1,9 +1,9 @@ -import path from 'path'; +import path from 'node:path'; -import knex, { Knex } from 'knex'; +import knex from 'knex'; -void (async (): Promise => { - let connection: Knex | undefined; +void (async () => { + let connection; try { connection = knex({ client: 'pg', @@ -20,12 +20,15 @@ void (async (): Promise => { await connection.schema.dropSchemaIfExists('public', true); await connection.schema.createSchema('public'); - await connection.migrate.latest({ directory: path.resolve(__dirname, '../../src/db/migrations') }); + await connection.migrate.latest({ + directory: path.resolve(__dirname, '../../src/db/migrations'), + loadExtensions: ['.mjs'], + }); await connection('users').insert({ c2c_id: 1 }); await connection('users').insert({ c2c_id: 2 }); console.log('DB initialized 🚀'); - } catch (error: unknown) { + } catch (error) { console.error(error); } finally { await connection?.destroy(); diff --git a/test/psql/repository/activity.repository.spec.ts b/test/psql/repository/activity.repository.spec.ts index 269460bf..c2a7f1ce 100644 --- a/test/psql/repository/activity.repository.spec.ts +++ b/test/psql/repository/activity.repository.spec.ts @@ -1,5 +1,5 @@ -import { database as db } from '../../../src/db'; -import { ActivityRepository } from '../../../src/repository/activity.repository'; +import { database as db } from '../../../src/db/index.js'; +import { ActivityRepository } from '../../../src/repository/activity.repository.js'; describe('Activity Repository', () => { afterEach(async () => { diff --git a/test/psql/repository/polar.repository.spec.ts b/test/psql/repository/polar.repository.spec.ts index ca6bef8a..032db9f5 100644 --- a/test/psql/repository/polar.repository.spec.ts +++ b/test/psql/repository/polar.repository.spec.ts @@ -1,5 +1,5 @@ -import { database as db } from '../../../src/db'; -import { PolarRepository } from '../../../src/repository/polar.repository'; +import { database as db } from '../../../src/db/index.js'; +import { PolarRepository } from '../../../src/repository/polar.repository.js'; describe('Polar Repository', () => { afterEach(async () => { diff --git a/test/psql/repository/strava.repository.spec.ts b/test/psql/repository/strava.repository.spec.ts index 167cfa2c..44514307 100644 --- a/test/psql/repository/strava.repository.spec.ts +++ b/test/psql/repository/strava.repository.spec.ts @@ -1,5 +1,5 @@ -import { database as db } from '../../../src/db'; -import { StravaRepository } from '../../../src/repository/strava.repository'; +import { database as db } from '../../../src/db/index.js'; +import { StravaRepository } from '../../../src/repository/strava.repository.js'; describe('Strava Repository', () => { afterEach(async () => { diff --git a/test/psql/repository/user.repository.spec.ts b/test/psql/repository/user.repository.spec.ts index 7b8b88d2..f0cde246 100644 --- a/test/psql/repository/user.repository.spec.ts +++ b/test/psql/repository/user.repository.spec.ts @@ -1,6 +1,6 @@ -import { database as db } from '../../../src/db'; -import type { User } from '../../../src/repository/user'; -import { UserRepository } from '../../../src/repository/user.repository'; +import { database as db } from '../../../src/db/index.js'; +import type { User } from '../../../src/repository/user.js'; +import { UserRepository } from '../../../src/repository/user.repository.js'; describe('User Repository', () => { afterEach(async () => { diff --git a/test/s3/storage/storage.spec.ts b/test/s3/storage/storage.spec.ts index 4f2dbea2..cb2fde77 100644 --- a/test/s3/storage/storage.spec.ts +++ b/test/s3/storage/storage.spec.ts @@ -1,11 +1,11 @@ -import { readFileSync } from 'fs'; -import { resolve } from 'path'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; import { ObjectCannedACL } from '@aws-sdk/client-s3'; import request from 'supertest'; -import config from '../../../src/config'; -import { S3Storage } from '../../../src/storage/storage'; +import config from '../../../src/config.js'; +import { S3Storage } from '../../../src/storage/storage.js'; const key = 'mtctivk0hjf1wkbckcnyz2rd.png'; const buffer = readFileSync(resolve(__dirname, '../../resources/piano.png')); diff --git a/test/unit/auth/c2c-jwt-extractor.spec.ts b/test/unit/auth/c2c-jwt-extractor.spec.ts index 79e8fbd2..59712777 100644 --- a/test/unit/auth/c2c-jwt-extractor.spec.ts +++ b/test/unit/auth/c2c-jwt-extractor.spec.ts @@ -1,6 +1,6 @@ import { createRequest } from 'node-mocks-http'; -import c2cJwtExtractor from '../../../src/auth/c2c-jwt-extractor'; +import c2cJwtExtractor from '../../../src/auth/c2c-jwt-extractor.js'; describe('c2cJwtExtractor', () => { it('retrieves token from header (v6_api way)', () => { diff --git a/test/unit/auth/c2c-jwt-verify.spec.ts b/test/unit/auth/c2c-jwt-verify.spec.ts index b53b56f2..a18818df 100644 --- a/test/unit/auth/c2c-jwt-verify.spec.ts +++ b/test/unit/auth/c2c-jwt-verify.spec.ts @@ -1,4 +1,4 @@ -import verify from '../../../src/auth/c2c-jwt-verify'; +import verify from '../../../src/auth/c2c-jwt-verify.js'; describe('verify', () => { it('accepts payload with sub', () => { diff --git a/test/unit/auth/index.spec.ts b/test/unit/auth/index.spec.ts index 877a17fd..f77d80b6 100644 --- a/test/unit/auth/index.spec.ts +++ b/test/unit/auth/index.spec.ts @@ -1,8 +1,8 @@ import { createMockContext } from '@shopify/jest-koa-mocks'; -import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser, passport } from '../../../src/auth'; -import log from '../../../src/helpers/logger'; -import { AuthenticatedUserStrategy } from '../../utils'; +import { ensureAuthenticated, ensureUserFromParamsMatchesAuthUser, passport } from '../../../src/auth/index.js'; +import log from '../../../src/helpers/logger.js'; +import { AuthenticatedUserStrategy } from '../../utils.js'; describe('ensureAuthenticated', () => { beforeEach(() => { diff --git a/test/unit/health.service.spec.ts b/test/unit/health.service.spec.ts index 9d79f1d9..1fefd77b 100644 --- a/test/unit/health.service.spec.ts +++ b/test/unit/health.service.spec.ts @@ -1,6 +1,6 @@ -import config from '../../src/config'; -import { HealthService } from '../../src/health.service'; -import { semverRegex } from '../../src/helpers/utils'; +import config from '../../src/config.js'; +import { HealthService } from '../../src/health.service.js'; +import { semverRegex } from '../../src/helpers/utils.js'; jest.mock('../../src/config'); diff --git a/test/unit/miniature.service.spec.ts b/test/unit/miniature.service.spec.ts index d607f272..22a5e878 100644 --- a/test/unit/miniature.service.spec.ts +++ b/test/unit/miniature.service.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { MiniatureService } from '../../src/miniature.service'; -import { storage } from '../../src/storage/storage'; +import { MiniatureService } from '../../src/miniature.service.js'; +import { storage } from '../../src/storage/storage.js'; jest.mock('axios'); diff --git a/test/unit/server/activities/activity.controller.spec.ts b/test/unit/server/activities/activity.controller.spec.ts index c49bee26..bc6df81c 100644 --- a/test/unit/server/activities/activity.controller.spec.ts +++ b/test/unit/server/activities/activity.controller.spec.ts @@ -1,9 +1,9 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import { activityService } from '../../../../src/server/activities/activity.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import { activityService } from '../../../../src/server/activities/activity.service.js'; +import { authenticated } from '../../../utils.js'; describe('Activities Controller', () => { beforeEach(() => { diff --git a/test/unit/server/activities/activity.service.spec.ts b/test/unit/server/activities/activity.service.spec.ts index feefaca2..5df54d58 100644 --- a/test/unit/server/activities/activity.service.spec.ts +++ b/test/unit/server/activities/activity.service.spec.ts @@ -1,9 +1,9 @@ -import { NotFoundError } from '../../../../src/errors'; -import log from '../../../../src/helpers/logger'; -import { ActivityService } from '../../../../src/server/activities/activity.service'; -import { stravaService } from '../../../../src/server/strava/strava.service'; -import { suuntoService } from '../../../../src/server/suunto/suunto.service'; -import { userService } from '../../../../src/user.service'; +import { NotFoundError } from '../../../../src/errors.js'; +import log from '../../../../src/helpers/logger.js'; +import { ActivityService } from '../../../../src/server/activities/activity.service.js'; +import { stravaService } from '../../../../src/server/strava/strava.service.js'; +import { suuntoService } from '../../../../src/server/suunto/suunto.service.js'; +import { userService } from '../../../../src/user.service.js'; jest.mock('../../../../src/helpers/utils'); diff --git a/test/unit/server/coros/coros.api.spec.ts b/test/unit/server/coros/coros.api.spec.ts index cb9a60bd..2c94db8a 100644 --- a/test/unit/server/coros/coros.api.spec.ts +++ b/test/unit/server/coros/coros.api.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import log from '../../../../src/helpers/logger'; -import { CorosApi, CorosAuth, WorkoutRecord, WorkoutRecords } from '../../../../src/server/coros/coros.api'; +import log from '../../../../src/helpers/logger.js'; +import { CorosApi, CorosAuth, WorkoutRecord, WorkoutRecords } from '../../../../src/server/coros/coros.api.js'; jest.mock('axios'); diff --git a/test/unit/server/coros/coros.controller.spec.ts b/test/unit/server/coros/coros.controller.spec.ts index 6ff33d5b..7cd592d6 100644 --- a/test/unit/server/coros/coros.controller.spec.ts +++ b/test/unit/server/coros/coros.controller.spec.ts @@ -1,10 +1,10 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import type { WebhookEvent } from '../../../../src/server/coros/coros.api'; -import { corosService } from '../../../../src/server/coros/coros.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import type { WebhookEvent } from '../../../../src/server/coros/coros.api.js'; +import { corosService } from '../../../../src/server/coros/coros.service.js'; +import { authenticated } from '../../../utils.js'; describe('Coros Controller', () => { beforeEach(() => { diff --git a/test/unit/server/coros/coros.service.spec.ts b/test/unit/server/coros/coros.service.spec.ts index f356bf57..bd16a878 100644 --- a/test/unit/server/coros/coros.service.spec.ts +++ b/test/unit/server/coros/coros.service.spec.ts @@ -1,13 +1,13 @@ -import { readFileSync } from 'fs'; -import { resolve } from 'path'; - -import log from '../../../../src/helpers/logger'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { corosApi, WebhookEvent } from '../../../../src/server/coros/coros.api'; -import { CorosService } from '../../../../src/server/coros/coros.service'; -import { userService } from '../../../../src/user.service'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import log from '../../../../src/helpers/logger.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { corosApi, WebhookEvent } from '../../../../src/server/coros/coros.api.js'; +import { CorosService } from '../../../../src/server/coros/coros.service.js'; +import { userService } from '../../../../src/user.service.js'; const FAR_FUTURE = 10000000000; diff --git a/test/unit/server/decathlon/decathlon.controller.spec.ts b/test/unit/server/decathlon/decathlon.controller.spec.ts index 3be67edc..129999e5 100644 --- a/test/unit/server/decathlon/decathlon.controller.spec.ts +++ b/test/unit/server/decathlon/decathlon.controller.spec.ts @@ -1,10 +1,10 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import type { WebhookEvent } from '../../../../src/server/decathlon/decathlon.api'; -import { decathlonService } from '../../../../src/server/decathlon/decathlon.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import type { WebhookEvent } from '../../../../src/server/decathlon/decathlon.api.js'; +import { decathlonService } from '../../../../src/server/decathlon/decathlon.service.js'; +import { authenticated } from '../../../utils.js'; describe('Decathlon Controller', () => { beforeEach(() => { diff --git a/test/unit/server/decathlon/decathlon.service.spec.ts b/test/unit/server/decathlon/decathlon.service.spec.ts index 89c3a72d..aa602aec 100644 --- a/test/unit/server/decathlon/decathlon.service.spec.ts +++ b/test/unit/server/decathlon/decathlon.service.spec.ts @@ -1,10 +1,10 @@ -import log from '../../../../src/helpers/logger'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { decathlonApi } from '../../../../src/server/decathlon/decathlon.api'; -import { DecathlonService } from '../../../../src/server/decathlon/decathlon.service'; -import { userService } from '../../../../src/user.service'; +import log from '../../../../src/helpers/logger.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { decathlonApi } from '../../../../src/server/decathlon/decathlon.api.js'; +import { DecathlonService } from '../../../../src/server/decathlon/decathlon.service.js'; +import { userService } from '../../../../src/user.service.js'; describe('Decathlon Service', () => { beforeEach(() => { diff --git a/test/unit/server/garmin/garmin.api.spec.ts b/test/unit/server/garmin/garmin.api.spec.ts index cb241b02..f3f7c1da 100644 --- a/test/unit/server/garmin/garmin.api.spec.ts +++ b/test/unit/server/garmin/garmin.api.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import log from '../../../../src/helpers/logger'; -import { GarminApi } from '../../../../src/server/garmin/garmin.api'; +import log from '../../../../src/helpers/logger.js'; +import { GarminApi } from '../../../../src/server/garmin/garmin.api.js'; jest.mock('axios'); diff --git a/test/unit/server/garmin/garmin.controller.spec.ts b/test/unit/server/garmin/garmin.controller.spec.ts index 1cc140cc..bec42faa 100644 --- a/test/unit/server/garmin/garmin.controller.spec.ts +++ b/test/unit/server/garmin/garmin.controller.spec.ts @@ -2,12 +2,12 @@ import { AxiosError } from 'axios'; import type Keyv from 'keyv'; import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import type { GarminActivity } from '../../../../src/server/garmin/garmin.api'; -import { controller as garminController } from '../../../../src/server/garmin/garmin.controller'; -import { garminService } from '../../../../src/server/garmin/garmin.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import type { GarminActivity } from '../../../../src/server/garmin/garmin.api.js'; +import { controller as garminController } from '../../../../src/server/garmin/garmin.controller.js'; +import { garminService } from '../../../../src/server/garmin/garmin.service.js'; +import { authenticated } from '../../../utils.js'; describe('Garmin Controller', () => { beforeEach(async () => { diff --git a/test/unit/server/garmin/garmin.service.spec.ts b/test/unit/server/garmin/garmin.service.spec.ts index 0e50590b..53a57ef2 100644 --- a/test/unit/server/garmin/garmin.service.spec.ts +++ b/test/unit/server/garmin/garmin.service.spec.ts @@ -1,10 +1,10 @@ -import log from '../../../../src/helpers/logger'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { garminApi, type GarminAuth } from '../../../../src/server/garmin/garmin.api'; -import { GarminService } from '../../../../src/server/garmin/garmin.service'; -import { userService } from '../../../../src/user.service'; +import log from '../../../../src/helpers/logger.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { garminApi, type GarminAuth } from '../../../../src/server/garmin/garmin.api.js'; +import { GarminService } from '../../../../src/server/garmin/garmin.service.js'; +import { userService } from '../../../../src/user.service.js'; describe('Garmin service', () => { beforeEach(() => { diff --git a/test/unit/server/health/health.controller.spec.ts b/test/unit/server/health/health.controller.spec.ts index 1ac25fc7..a93e3533 100644 --- a/test/unit/server/health/health.controller.spec.ts +++ b/test/unit/server/health/health.controller.spec.ts @@ -1,8 +1,8 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import type { Status } from '../../../../src/health.service'; -import { semverRegex } from '../../../../src/helpers/utils'; +import { app } from '../../../../src/app.js'; +import type { Status } from '../../../../src/health.service.js'; +import { semverRegex } from '../../../../src/helpers/utils.js'; describe('GET /health', () => { it('responds without authentication', async () => { diff --git a/test/unit/server/polar/polar.api.spec.ts b/test/unit/server/polar/polar.api.spec.ts index ccf08421..995f3163 100644 --- a/test/unit/server/polar/polar.api.spec.ts +++ b/test/unit/server/polar/polar.api.spec.ts @@ -1,7 +1,13 @@ import axios from 'axios'; -import log from '../../../../src/helpers/logger'; -import { CreatedWebhookInfo, Exercise, PolarApi, PolarAuth, WebhookInfo } from '../../../../src/server/polar/polar.api'; +import log from '../../../../src/helpers/logger.js'; +import { + CreatedWebhookInfo, + Exercise, + PolarApi, + PolarAuth, + WebhookInfo, +} from '../../../../src/server/polar/polar.api.js'; jest.mock('axios'); diff --git a/test/unit/server/polar/polar.controller.spec.ts b/test/unit/server/polar/polar.controller.spec.ts index ee9052f1..653bb079 100644 --- a/test/unit/server/polar/polar.controller.spec.ts +++ b/test/unit/server/polar/polar.controller.spec.ts @@ -1,10 +1,10 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import type { WebhookEvent } from '../../../../src/server/polar/polar.api'; -import { polarService } from '../../../../src/server/polar/polar.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import type { WebhookEvent } from '../../../../src/server/polar/polar.api.js'; +import { polarService } from '../../../../src/server/polar/polar.service.js'; +import { authenticated } from '../../../utils.js'; describe('Polar Controller', () => { beforeEach(() => { diff --git a/test/unit/server/polar/polar.service.spec.ts b/test/unit/server/polar/polar.service.spec.ts index e7eade2c..78b6935a 100644 --- a/test/unit/server/polar/polar.service.spec.ts +++ b/test/unit/server/polar/polar.service.spec.ts @@ -1,14 +1,14 @@ import JSONBig from 'json-bigint'; -import log from '../../../../src/helpers/logger'; -import * as utils from '../../../../src/helpers/utils'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { polarRepository } from '../../../../src/repository/polar.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { polarApi, WebhookEvent } from '../../../../src/server/polar/polar.api'; -import { PolarService } from '../../../../src/server/polar/polar.service'; -import { userService } from '../../../../src/user.service'; +import log from '../../../../src/helpers/logger.js'; +import * as utils from '../../../../src/helpers/utils.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { polarRepository } from '../../../../src/repository/polar.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { polarApi, WebhookEvent } from '../../../../src/server/polar/polar.api.js'; +import { PolarService } from '../../../../src/server/polar/polar.service.js'; +import { userService } from '../../../../src/user.service.js'; jest.mock('../../../../src/helpers/utils'); diff --git a/test/unit/server/strava/strava.controller.spec.ts b/test/unit/server/strava/strava.controller.spec.ts index 54f5bef4..88f57e38 100644 --- a/test/unit/server/strava/strava.controller.spec.ts +++ b/test/unit/server/strava/strava.controller.spec.ts @@ -1,10 +1,10 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import type { WebhookEvent } from '../../../../src/server/strava/strava.api'; -import { stravaService } from '../../../../src/server/strava/strava.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import type { WebhookEvent } from '../../../../src/server/strava/strava.api.js'; +import { stravaService } from '../../../../src/server/strava/strava.service.js'; +import { authenticated } from '../../../utils.js'; describe('Strava Controller', () => { beforeEach(() => { diff --git a/test/unit/server/strava/strava.service.spec.ts b/test/unit/server/strava/strava.service.spec.ts index 981b1439..9f872cd6 100644 --- a/test/unit/server/strava/strava.service.spec.ts +++ b/test/unit/server/strava/strava.service.spec.ts @@ -1,11 +1,11 @@ -import log from '../../../../src/helpers/logger'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { stravaRepository } from '../../../../src/repository/strava.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { stravaApi } from '../../../../src/server/strava/strava.api'; -import { StravaService } from '../../../../src/server/strava/strava.service'; -import { userService } from '../../../../src/user.service'; +import log from '../../../../src/helpers/logger.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { stravaRepository } from '../../../../src/repository/strava.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { stravaApi } from '../../../../src/server/strava/strava.api.js'; +import { StravaService } from '../../../../src/server/strava/strava.service.js'; +import { userService } from '../../../../src/user.service.js'; describe('Strava Service', () => { beforeEach(() => { diff --git a/test/unit/server/suunto/suunto.api.spec.ts b/test/unit/server/suunto/suunto.api.spec.ts index f66af7db..1a0252f7 100644 --- a/test/unit/server/suunto/suunto.api.spec.ts +++ b/test/unit/server/suunto/suunto.api.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import log from '../../../../src/helpers/logger'; -import { SuuntoApi } from '../../../../src/server/suunto/suunto.api'; +import log from '../../../../src/helpers/logger.js'; +import { SuuntoApi } from '../../../../src/server/suunto/suunto.api.js'; jest.mock('axios'); diff --git a/test/unit/server/suunto/suunto.controller.spec.ts b/test/unit/server/suunto/suunto.controller.spec.ts index 44c0d093..d2111d8c 100644 --- a/test/unit/server/suunto/suunto.controller.spec.ts +++ b/test/unit/server/suunto/suunto.controller.spec.ts @@ -1,9 +1,9 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import { suuntoService } from '../../../../src/server/suunto/suunto.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import { suuntoService } from '../../../../src/server/suunto/suunto.service.js'; +import { authenticated } from '../../../utils.js'; describe('Suunto Controller', () => { beforeEach(() => { diff --git a/test/unit/server/suunto/suunto.service.spec.ts b/test/unit/server/suunto/suunto.service.spec.ts index faca147d..b87746dd 100644 --- a/test/unit/server/suunto/suunto.service.spec.ts +++ b/test/unit/server/suunto/suunto.service.spec.ts @@ -1,13 +1,13 @@ -import { readFileSync } from 'fs'; -import { resolve } from 'path'; - -import log from '../../../../src/helpers/logger'; -import { miniatureService } from '../../../../src/miniature.service'; -import { activityRepository } from '../../../../src/repository/activity.repository'; -import { userRepository } from '../../../../src/repository/user.repository'; -import { suuntoApi } from '../../../../src/server/suunto/suunto.api'; -import { SuuntoService } from '../../../../src/server/suunto/suunto.service'; -import { userService } from '../../../../src/user.service'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import log from '../../../../src/helpers/logger.js'; +import { miniatureService } from '../../../../src/miniature.service.js'; +import { activityRepository } from '../../../../src/repository/activity.repository.js'; +import { userRepository } from '../../../../src/repository/user.repository.js'; +import { suuntoApi } from '../../../../src/server/suunto/suunto.api.js'; +import { SuuntoService } from '../../../../src/server/suunto/suunto.service.js'; +import { userService } from '../../../../src/user.service.js'; describe('Suunto Service', () => { beforeEach(() => { diff --git a/test/unit/server/user/user.controller.spec.ts b/test/unit/server/user/user.controller.spec.ts index c9a0c7fb..806448e7 100644 --- a/test/unit/server/user/user.controller.spec.ts +++ b/test/unit/server/user/user.controller.spec.ts @@ -1,9 +1,9 @@ import request from 'supertest'; -import { app } from '../../../../src/app'; -import log from '../../../../src/helpers/logger'; -import { userService } from '../../../../src/user.service'; -import { authenticated } from '../../../utils'; +import { app } from '../../../../src/app.js'; +import log from '../../../../src/helpers/logger.js'; +import { userService } from '../../../../src/user.service.js'; +import { authenticated } from '../../../utils.js'; describe('User Controller', () => { beforeEach(() => { diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index 64cf0581..0e35016f 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -1,8 +1,8 @@ -import { readFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { resolve } from 'path'; +import { readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { resolve } from 'node:path'; -import { LocalStorage } from '../../../src/storage/storage'; +import { LocalStorage } from '../../../src/storage/storage.js'; const key = 'mtctivk0hjf1wkbckcnyz2rd.png'; const buffer = readFileSync(resolve(__dirname, '../../resources/piano.png')); diff --git a/test/unit/user.service.spec.ts b/test/unit/user.service.spec.ts index e04cbe6f..e1764149 100644 --- a/test/unit/user.service.spec.ts +++ b/test/unit/user.service.spec.ts @@ -1,16 +1,16 @@ -import { NotFoundError } from '../../src/errors'; -import log from '../../src/helpers/logger'; -import { miniatureService } from '../../src/miniature.service'; -import type { Activity } from '../../src/repository/activity'; -import { activityRepository } from '../../src/repository/activity.repository'; -import { userRepository } from '../../src/repository/user.repository'; -import type { CorosAuth } from '../../src/server/coros/coros.api'; -import type { DecathlonAuth } from '../../src/server/decathlon/decathlon.api'; -import type { GarminAuth } from '../../src/server/garmin/garmin.api'; -import type { PolarAuth } from '../../src/server/polar/polar.api'; -import type { StravaAuth } from '../../src/server/strava/strava.api'; -import type { SuuntoAuth } from '../../src/server/suunto/suunto.api'; -import { UserService } from '../../src/user.service'; +import { NotFoundError } from '../../src/errors.js'; +import log from '../../src/helpers/logger.js'; +import { miniatureService } from '../../src/miniature.service.js'; +import type { Activity } from '../../src/repository/activity.js'; +import { activityRepository } from '../../src/repository/activity.repository.js'; +import { userRepository } from '../../src/repository/user.repository.js'; +import type { CorosAuth } from '../../src/server/coros/coros.api.js'; +import type { DecathlonAuth } from '../../src/server/decathlon/decathlon.api.js'; +import type { GarminAuth } from '../../src/server/garmin/garmin.api.js'; +import type { PolarAuth } from '../../src/server/polar/polar.api.js'; +import type { StravaAuth } from '../../src/server/strava/strava.api.js'; +import type { SuuntoAuth } from '../../src/server/suunto/suunto.api.js'; +import { UserService } from '../../src/user.service.js'; describe('User service', () => { beforeEach(() => { diff --git a/tsconfig.json b/tsconfig.json index 653f9d9a..a148a438 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ "forceConsistentCasingInFileNames": true, //esm until tsnode supports extends arrays "module": "es2022", - "moduleResolution": "bundler", + "moduleResolution": "node", "verbatimModuleSyntax": true },