diff --git a/idp/deno.json b/idp/deno.json index a76d21c0d..c702df5b3 100644 --- a/idp/deno.json +++ b/idp/deno.json @@ -2,7 +2,7 @@ "tasks": { "start": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-net src/app.ts", "dev": "deno run --reload src/app.ts", - "format": "prettier --write .", + "format": "deno fmt .", "swagger-autogen": "deno run ./swagger.js", "lint": "eslint" }, @@ -46,5 +46,10 @@ "rules": { "exclude": ["no-explicit-any"] } + }, + "fmt": { + "semiColons": false, + "singleQuote": true, + "exclude": ["./docs"] } } diff --git a/idp/deno.lock b/idp/deno.lock index 30b66356c..c44603081 100644 --- a/idp/deno.lock +++ b/idp/deno.lock @@ -15,6 +15,7 @@ "npm:nodemailer@6.9.15": "6.9.15", "npm:passport-jwt@4.0.1": "4.0.1", "npm:passport@0.7.0": "0.7.0", + "npm:swagger-autogen@2.23.7": "2.23.7", "npm:uuid@10.0.0": "10.0.0" }, "npm": { @@ -25,6 +26,9 @@ "negotiator" ] }, + "acorn@7.4.1": { + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, "append-field@1.0.0": { "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, @@ -34,6 +38,9 @@ "array-flatten@3.0.0": { "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "basic-auth@2.0.1": { "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "dependencies": [ @@ -55,6 +62,13 @@ "type-is@1.6.18" ] }, + "brace-expansion@1.1.11": { + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, "buffer-equal-constant-time@1.0.1": { "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, @@ -93,6 +107,9 @@ "get-intrinsic" ] }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "concat-stream@1.6.2": { "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dependencies": [ @@ -145,6 +162,9 @@ "ms@2.1.2" ] }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, "define-data-property@1.1.4": { "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": [ @@ -268,6 +288,9 @@ "fresh@2.0.0": { "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, + "fs.realpath@1.0.0": { + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "function-bind@1.1.2": { "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, @@ -286,6 +309,17 @@ "math-intrinsics" ] }, + "glob@7.2.3": { + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": [ + "fs.realpath", + "inflight", + "inherits", + "minimatch", + "once", + "path-is-absolute" + ] + }, "gopd@1.2.0": { "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, @@ -339,6 +373,13 @@ "safer-buffer" ] }, + "inflight@1.0.6": { + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": [ + "once", + "wrappy" + ] + }, "inherits@2.0.4": { "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, @@ -360,6 +401,9 @@ "argparse" ] }, + "json5@2.2.3": { + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + }, "jsonwebtoken@9.0.2": { "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dependencies": [ @@ -450,6 +494,12 @@ "mime-db@1.53.0" ] }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion" + ] + }, "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, @@ -547,6 +597,9 @@ "utils-merge" ] }, + "path-is-absolute@1.0.1": { + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, "path-to-regexp@8.2.0": { "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" }, @@ -708,6 +761,15 @@ "safe-buffer@5.1.2" ] }, + "swagger-autogen@2.23.7": { + "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", + "dependencies": [ + "acorn", + "deepmerge", + "glob", + "json5" + ] + }, "toidentifier@1.0.1": { "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, diff --git a/idp/src/account/router.ts b/idp/src/account/router.ts index 3419b40df..b87936bc6 100644 --- a/idp/src/account/router.ts +++ b/idp/src/account/router.ts @@ -7,20 +7,20 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Router, Request, Response } from 'express' +import { Request, Response, Router } from 'express' import { body, validationResult } from 'express-validator' import { getConfig } from '@/config/config.ts' import { parseValidationError } from '@/infra/error/index.ts' import { - confirmEmail, - createUser, - resetPassword, - sendResetPasswordEmail, AccountConfirmEmailOptions, AccountCreateOptions, AccountResetPasswordOptions, AccountSendResetPasswordEmailOptions, + confirmEmail, + createUser, getPasswordRequirements, + resetPassword, + sendResetPasswordEmail, } from './service.ts' const router = Router() diff --git a/idp/src/account/service.ts b/idp/src/account/service.ts index 06d913b45..de19d4b25 100644 --- a/idp/src/account/service.ts +++ b/idp/src/account/service.ts @@ -19,7 +19,7 @@ import { hashPassword } from '@/infra/password.ts' import search, { USER_SEARCH_INDEX } from '@/infra/search.ts' import { User } from '@/user/model.ts' import userRepo from '@/user/repo.ts' -import { UserDTO, mapEntity, getUserCount } from '@/user/service.ts' +import { getUserCount, mapEntity, UserDTO } from '@/user/service.ts' export type AccountCreateOptions = { email: string diff --git a/idp/src/config/config.ts b/idp/src/config/config.ts index f379fdf5d..97f611d2b 100644 --- a/idp/src/config/config.ts +++ b/idp/src/config/config.ts @@ -141,4 +141,4 @@ export function readSecurity(config: Config) { function newEnvironmentVariableNotSetError(variable: string) { return new Error(`${variable} environment variable is not set.`) -} \ No newline at end of file +} diff --git a/idp/src/health/router.ts b/idp/src/health/router.ts index 72eb86f34..543ea8d34 100644 --- a/idp/src/health/router.ts +++ b/idp/src/health/router.ts @@ -7,14 +7,14 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Router, Request, Response } from 'express' +import { Request, Response, Router } from 'express' import { Client as PgClient } from 'https://deno.land/x/postgres@v0.19.3/mod.ts' import { getConfig } from '@/config/config.ts' const router = Router() router.get('', async (_: Request, res: Response) => { - let pg: PgClient|undefined + let pg: PgClient | undefined try { pg = new PgClient(getConfig().databaseURL) await pg.connect() diff --git a/idp/src/infra/error/core.ts b/idp/src/infra/error/core.ts index e531e4222..1a249aa9c 100644 --- a/idp/src/infra/error/core.ts +++ b/idp/src/infra/error/core.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Request, Response, NextFunction } from 'express' +import { NextFunction, Request, Response } from 'express' export enum ErrorCode { InternalServerError = 'internal_server_error', diff --git a/idp/src/infra/error/validation.ts b/idp/src/infra/error/validation.ts index 8c7afa439..6e7f4753b 100644 --- a/idp/src/infra/error/validation.ts +++ b/idp/src/infra/error/validation.ts @@ -2,8 +2,8 @@ import { ErrorCode, ErrorData, newError } from './core.ts' /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export function parseValidationError(result: any): ErrorData { - let message: string|undefined - let userMessage: string|undefined + let message: string | undefined + let userMessage: string | undefined if (result.errors) { message = result.errors /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ diff --git a/idp/src/infra/mail.ts b/idp/src/infra/mail.ts index 34d6e9b3c..a5bd4e288 100644 --- a/idp/src/infra/mail.ts +++ b/idp/src/infra/mail.ts @@ -24,13 +24,12 @@ const transporter = nodemailer.createTransport({ host: config.host, port: config.port, secure: config.secure, - auth: - config.username || config.password - ? { - user: config.username, - pass: config.password, - } - : null, + auth: config.username || config.password + ? { + user: config.username, + pass: config.password, + } + : null, }) export function sendTemplateMail( diff --git a/idp/src/infra/password.ts b/idp/src/infra/password.ts index 744123480..4df4208b5 100644 --- a/idp/src/infra/password.ts +++ b/idp/src/infra/password.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { scryptSync, randomBytes } from 'node:crypto' +import { randomBytes, scryptSync } from 'node:crypto' export function hashPassword(password: string): string { const salt = randomBytes(16).toString('hex') diff --git a/idp/src/infra/postgres.ts b/idp/src/infra/postgres.ts index d951e569f..37d34361f 100644 --- a/idp/src/infra/postgres.ts +++ b/idp/src/infra/postgres.ts @@ -11,4 +11,4 @@ import { Client } from 'https://deno.land/x/postgres@v0.19.3/mod.ts' import { getConfig } from '@/config/config.ts' export const client = new Client(getConfig().databaseURL) -await client.connect() \ No newline at end of file +await client.connect() diff --git a/idp/src/token/router.ts b/idp/src/token/router.ts index 880877a63..89dd459d1 100644 --- a/idp/src/token/router.ts +++ b/idp/src/token/router.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Router, Request, Response } from 'express' +import { Request, Response, Router } from 'express' import { exchange, TokenExchangeOptions } from './service.ts' const router = Router() diff --git a/idp/src/token/service.ts b/idp/src/token/service.ts index 93bc7c177..4e3389491 100644 --- a/idp/src/token/service.ts +++ b/idp/src/token/service.ts @@ -48,7 +48,9 @@ export async function exchange(options: TokenExchangeOptions): Promise { // https://datatracker.ietf.org/doc/html/rfc6749#section-4.3 let user: User try { - user = await userRepo.findByUsername(options.username!.toLocaleLowerCase()) + user = await userRepo.findByUsername( + options.username!.toLocaleLowerCase(), + ) } catch { throw newInvalidUsernameOrPasswordError() } diff --git a/idp/src/user/model.ts b/idp/src/user/model.ts index d9004232c..28740db6b 100644 --- a/idp/src/user/model.ts +++ b/idp/src/user/model.ts @@ -53,19 +53,19 @@ export type UpdateOptions = { fullName?: string username?: string email?: string - passwordHash?: string|null - refreshTokenValue?: string|null - refreshTokenExpiry?: string|null - resetPasswordToken?: string|null - emailConfirmationToken?: string|null + passwordHash?: string | null + refreshTokenValue?: string | null + refreshTokenExpiry?: string | null + resetPasswordToken?: string | null + emailConfirmationToken?: string | null isEmailConfirmed?: boolean - emailUpdateToken?: string|null - emailUpdateValue?: string|null - picture?: string|null + emailUpdateToken?: string | null + emailUpdateValue?: string | null + picture?: string | null failedAttempts?: number - lockedUntil?: string|null + lockedUntil?: string | null createTime?: string - updateTime?: string|null + updateTime?: string | null } export interface UserRepo { diff --git a/idp/src/user/repo.ts b/idp/src/user/repo.ts index baa4997f5..8e8985800 100644 --- a/idp/src/user/repo.ts +++ b/idp/src/user/repo.ts @@ -7,7 +7,10 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { newInternalServerError, newUserNotFoundError } from '@/infra/error/index.ts' +import { + newInternalServerError, + newUserNotFoundError, +} from '@/infra/error/index.ts' import { client } from '@/infra/postgres.ts' import { InsertOptions, UpdateOptions, User } from './model.ts' diff --git a/idp/src/user/router.ts b/idp/src/user/router.ts index d6e20db45..efde5339b 100644 --- a/idp/src/user/router.ts +++ b/idp/src/user/router.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Router, Response, Request } from 'express' +import { Request, Response, Router } from 'express' import { body, query, validationResult } from 'express-validator' import fs from 'node:fs/promises' import { jwtVerify } from 'jose' @@ -24,26 +24,26 @@ import { import { PassportRequest } from '@/infra/passport-request.ts' import { checkAdmin } from '@/token/service.ts' import { + deletePicture, deleteUser, getUser, + getUserByAdmin, + getUserPicture, + list, + makeAdminUser, + suspendUser, + updateEmailConfirmation, + updateEmailRequest, updateFullName, - updatePicture, updatePassword, + updatePicture, UserDeleteOptions, - UserUpdateFullNameOptions, - UserUpdatePasswordOptions, - deletePicture, - UserUpdateEmailRequestOptions, - UserUpdateEmailConfirmationOptions, - updateEmailRequest, - updateEmailConfirmation, - suspendUser, - makeAdminUser, - getUserByAdmin, - list, - getUserPicture, UserMakeAdminOptions, UserSuspendOptions, + UserUpdateEmailConfirmationOptions, + UserUpdateEmailRequestOptions, + UserUpdateFullNameOptions, + UserUpdatePasswordOptions, } from './service.ts' const router = Router() @@ -58,7 +58,7 @@ router.get( router.get( '/me/picture:extension', - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { if (!req.query.access_token) { throw newMissingQueryParamError('access_token') } @@ -82,7 +82,7 @@ router.post( '/me/update_full_name', passport.authenticate('jwt', { session: false }), body('fullName').isString().notEmpty().trim().escape().isLength({ max: 255 }), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const result = validationResult(req) if (!result.isEmpty()) { throw parseValidationError(result) @@ -97,7 +97,7 @@ router.post( '/me/update_email_request', passport.authenticate('jwt', { session: false }), body('email').isEmail().isLength({ max: 255 }), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const result = validationResult(req) if (!result.isEmpty()) { throw parseValidationError(result) @@ -115,7 +115,7 @@ router.post( '/me/update_email_confirmation', passport.authenticate('jwt', { session: false }), body('token').isString().notEmpty().trim(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const result = validationResult(req) if (!result.isEmpty()) { throw parseValidationError(result) @@ -133,7 +133,7 @@ router.post( passport.authenticate('jwt', { session: false }), body('currentPassword').notEmpty(), body('newPassword').isStrongPassword(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const result = validationResult(req) if (!result.isEmpty()) { throw parseValidationError(result) @@ -151,7 +151,7 @@ router.post( dest: os.tmpdir(), limits: { fileSize: 3000000, fields: 0, files: 1 }, }).single('file'), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const user = await updatePicture( req.user.id, req.file.path, @@ -174,7 +174,7 @@ router.delete( '/me', passport.authenticate('jwt', { session: false }), body('password').isString().notEmpty(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { const result = validationResult(req) if (!result.isEmpty()) { throw parseValidationError(result) @@ -190,7 +190,7 @@ router.get( query('query').isString().optional(), query('page').isInt(), query('size').isInt(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { checkAdmin(req.header('Authorization')) const result = validationResult(req) if (!result.isEmpty()) { @@ -210,7 +210,7 @@ router.post( '/:id/suspend', passport.authenticate('jwt', { session: false }), body('suspend').isBoolean(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { checkAdmin(req.header('Authorization')) const result = validationResult(req) if (!result.isEmpty()) { @@ -225,7 +225,7 @@ router.post( '/:id/make_admin', passport.authenticate('jwt', { session: false }), body('makeAdmin').isBoolean(), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { checkAdmin(req.header('Authorization')) const result = validationResult(req) if (!result.isEmpty()) { @@ -239,7 +239,7 @@ router.post( router.get( '/:id', passport.authenticate('jwt', { session: false }), - async (req: PassportRequest&Request, res: Response) => { + async (req: PassportRequest & Request, res: Response) => { checkAdmin(req.header('Authorization')) res.json(await getUserByAdmin(req.params.id)) }, diff --git a/idp/src/user/service.ts b/idp/src/user/service.ts index bb958fd1c..514544d03 100644 --- a/idp/src/user/service.ts +++ b/idp/src/user/service.ts @@ -9,16 +9,20 @@ // AGPL-3.0-only in the root of this repository. import fs from 'node:fs/promises' import { getConfig } from '@/config/config.ts' -import { base64ToBuffer, base64ToExtension, base64ToMIME } from '@/infra/base64.ts' +import { + base64ToBuffer, + base64ToExtension, + base64ToMIME, +} from '@/infra/base64.ts' import { newCannotDemoteSoleAdminError, newCannotSuspendSoleAdminError, newInternalServerError, newInvalidPasswordError, newPasswordValidationFailedError, + newPictureNotFoundError, newUsernameUnavailableError, newUserNotFoundError, - newPictureNotFoundError, } from '@/infra/error/index.ts' import { ErrorCode, newError } from '@/infra/error/core.ts' import { newHyphenlessUuid } from '@/infra/id.ts' @@ -27,7 +31,7 @@ import { hashPassword, verifyPassword } from '@/infra/password.ts' import search, { USER_SEARCH_INDEX } from '@/infra/search.ts' import { User } from '@/user/model.ts' import userRepo from '@/user/repo.ts' -import { Buffer } from "node:buffer" +import { Buffer } from 'node:buffer' export type UserDTO = { id: string @@ -170,7 +174,7 @@ export async function list({ } else { return { data: (await userRepo.list(page, size)).map((value) => - adminMapEntity(value), + adminMapEntity(value) ), totalElements: await userRepo.getCount(), totalPages: Math.floor(((await userRepo.getCount()) + size - 1) / size), diff --git a/idp/src/version/router.ts b/idp/src/version/router.ts index 00a43e7cd..e39224d02 100644 --- a/idp/src/version/router.ts +++ b/idp/src/version/router.ts @@ -7,7 +7,7 @@ // the Business Source License, use of this software will be governed // by the GNU Affero General Public License v3.0 only, included in the file // AGPL-3.0-only in the root of this repository. -import { Router, Request, Response } from 'express' +import { Request, Response, Router } from 'express' const router = Router()