From ef93cdd66998014cf998531193306b66c98339b3 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 5 Nov 2022 12:24:23 +0100 Subject: [PATCH 1/6] fix: semver comparaison client side --- packages/dashboard/package.json | 2 + packages/dashboard/src/pages/settings.tsx | 5 +- packages/dashboard/src/utils/typescript.ts | 2 +- packages/system-api/__mocks__/internal-ip.ts | 11 - .../system-api/__mocks__/tcp-port-used.ts | 9 - packages/system-api/package.json | 4 - .../apps/__tests__/apps.helpers.test.ts | 19 - .../apps/__tests__/apps.resolver.test.ts | 19 - .../src/modules/apps/apps.helpers.ts | 11 - .../src/modules/system/system.service.ts | 2 +- pnpm-lock.yaml | 418 ++++++++++++------ 11 files changed, 293 insertions(+), 209 deletions(-) delete mode 100644 packages/system-api/__mocks__/internal-ip.ts delete mode 100644 packages/system-api/__mocks__/tcp-port-used.ts diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 48059b05..b325bb14 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -32,6 +32,7 @@ "remark-breaks": "^3.0.2", "remark-gfm": "^3.0.1", "remark-mdx": "^2.1.1", + "semver": "^7.3.7", "swr": "^1.3.0", "tslib": "^2.4.0", "validator": "^13.7.0", @@ -48,6 +49,7 @@ "@types/react": "18.0.8", "@types/react-dom": "18.0.3", "@types/react-slick": "^0.23.8", + "@types/semver": "^7.3.12", "@types/validator": "^13.7.2", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/packages/dashboard/src/pages/settings.tsx b/packages/dashboard/src/pages/settings.tsx index 886be65a..f13cd6f5 100644 --- a/packages/dashboard/src/pages/settings.tsx +++ b/packages/dashboard/src/pages/settings.tsx @@ -3,6 +3,7 @@ import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, Al import Layout from '../components/Layout'; import { useLogoutMutation, useRestartMutation, useUpdateMutation, useVersionQuery } from '../generated/graphql'; import { useRef, useState } from 'react'; +import semver from 'semver'; const Settings: NextPage = () => { const toast = useToast(); @@ -15,7 +16,9 @@ const Settings: NextPage = () => { const [restart] = useRestartMutation(); const [update] = useUpdateMutation(); const [logout] = useLogoutMutation({ refetchQueries: ['Me'] }); - const isLatest = data?.version.latest === data?.version.current; + + const defaultVersion = '0.0.0'; + const isLatest = semver.gte(data?.version.latest || defaultVersion, data?.version.current || defaultVersion); const handleError = (error: unknown) => { if (error instanceof Error) { diff --git a/packages/dashboard/src/utils/typescript.ts b/packages/dashboard/src/utils/typescript.ts index 507ce217..78106460 100644 --- a/packages/dashboard/src/utils/typescript.ts +++ b/packages/dashboard/src/utils/typescript.ts @@ -1,4 +1,4 @@ -const objectKeys = (obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[]; +const objectKeys = (obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[]; function nonNullable(value: T): value is NonNullable { return value !== null && value !== undefined; diff --git a/packages/system-api/__mocks__/internal-ip.ts b/packages/system-api/__mocks__/internal-ip.ts deleted file mode 100644 index 73ef0632..00000000 --- a/packages/system-api/__mocks__/internal-ip.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { faker } from '@faker-js/faker'; - -const internalIp: { v4: typeof v4Mock } = jest.genMockFromModule('internal-ip'); - -const v4Mock = () => { - return faker.internet.ipv4(); -}; - -internalIp.v4 = v4Mock; - -module.exports = internalIp; diff --git a/packages/system-api/__mocks__/tcp-port-used.ts b/packages/system-api/__mocks__/tcp-port-used.ts deleted file mode 100644 index 898f6f5e..00000000 --- a/packages/system-api/__mocks__/tcp-port-used.ts +++ /dev/null @@ -1,9 +0,0 @@ -import portUsed, { TcpPortUsedOptions } from 'tcp-port-used'; - -const internalIp: { check: typeof portUsed.check } = jest.genMockFromModule('tcp-port-used'); - -internalIp.check = async (_: number | TcpPortUsedOptions, __?: string | undefined) => { - return true; -}; - -module.exports = internalIp; diff --git a/packages/system-api/package.json b/packages/system-api/package.json index 6728789a..7d65922c 100644 --- a/packages/system-api/package.json +++ b/packages/system-api/package.json @@ -37,16 +37,13 @@ "graphql": "^15.3.0", "graphql-type-json": "^0.3.2", "http": "0.0.1-security", - "internal-ip": "^6.0.0", "jsonwebtoken": "^8.5.1", "node-cache": "^5.1.2", "node-cron": "^3.0.1", - "node-port-scanner": "^3.0.1", "pg": "^8.7.3", "redis": "^4.3.1", "reflect-metadata": "^0.1.13", "semver": "^7.3.7", - "tcp-port-used": "^1.0.2", "type-graphql": "^1.1.1", "typeorm": "^0.3.6", "uuid": "^9.0.0", @@ -67,7 +64,6 @@ "@types/node-cron": "^3.0.2", "@types/pg": "^8.6.5", "@types/semver": "^7.3.12", - "@types/tcp-port-used": "^1.0.1", "@types/uuid": "^8.3.4", "@types/validator": "^13.7.2", "@typescript-eslint/eslint-plugin": "^5.18.0", diff --git a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts index 78fe9557..6f13638f 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts @@ -10,16 +10,6 @@ import { createApp } from './apps.factory'; jest.mock('fs-extra'); jest.mock('child_process'); -jest.mock('internal-ip'); - -jest.mock('tcp-port-used', () => ({ - check: (port: number) => { - if (port === 53) { - return true; - } - return false; - }, -})); let db: DataSource | null = null; const TEST_SUITE = 'appshelpers'; @@ -51,15 +41,6 @@ describe('checkAppRequirements', () => { it('Should throw an error if app does not exist', async () => { await expect(checkAppRequirements('not-existing-app')).rejects.toThrow('App not-existing-app not found'); }); - - it('Should return false if a required port is in use', async () => { - const { appInfo, MockFiles } = await createApp({ requiredPort: 53 }); - // @ts-ignore - fs.__createMockFiles(MockFiles); - - const ivValid = await checkAppRequirements(appInfo.id); - expect(ivValid).toBe(false); - }); }); describe('getEnvMap', () => { diff --git a/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts index c0c2da7e..263249a8 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts @@ -14,8 +14,6 @@ import EventDispatcher from '../../../core/config/EventDispatcher'; jest.mock('fs'); jest.mock('child_process'); -jest.mock('internal-ip'); -jest.mock('tcp-port-used'); type TApp = App & { info: AppInfo; @@ -220,23 +218,6 @@ describe('InstallApp', () => { expect(errors?.[0].message).toBe(`Variable ${app1.form_fields?.[0].env_variable} is required`); expect(data?.installApp).toBeUndefined(); }); - - it('Should throw an error if the requirements are not met', async () => { - const { appInfo, MockFiles } = await createApp({ requiredPort: 400 }); - // @ts-ignore - fs.__createMockFiles(MockFiles); - - const user = await createUser(); - - const { data, errors } = await gcall<{ installApp: TApp }>({ - source: installAppMutation, - userId: user.id, - variableValues: { input: { id: appInfo.id, form: { TEST_FIELD: 'hello' }, exposed: false, domain: '' } }, - }); - - expect(errors?.[0].message).toBe(`App ${appInfo.id} requirements not met`); - expect(data?.installApp).toBeUndefined(); - }); }); describe('StartApp', () => { diff --git a/packages/system-api/src/modules/apps/apps.helpers.ts b/packages/system-api/src/modules/apps/apps.helpers.ts index 685cde0e..98f85071 100644 --- a/packages/system-api/src/modules/apps/apps.helpers.ts +++ b/packages/system-api/src/modules/apps/apps.helpers.ts @@ -1,6 +1,4 @@ -import portUsed from 'tcp-port-used'; import { fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers'; -import InternalIp from 'internal-ip'; import crypto from 'crypto'; import { AppInfo, AppStatusEnum } from './apps.types'; import logger from '../../config/logger/logger'; @@ -17,15 +15,6 @@ export const checkAppRequirements = async (appName: string) => { throw new Error(`App ${appName} not found`); } - if (configFile?.requirements?.ports) { - for (const port of configFile.requirements.ports) { - const ip = await InternalIp.v4(); - const used = await portUsed.check(port, ip); - - if (used) valid = false; - } - } - if (configFile?.supported_architectures && !configFile.supported_architectures.includes(getConfig().architecture)) { throw new Error(`App ${appName} is not supported on this architecture`); } diff --git a/packages/system-api/src/modules/system/system.service.ts b/packages/system-api/src/modules/system/system.service.ts index a1c833ee..3a934d15 100644 --- a/packages/system-api/src/modules/system/system.service.ts +++ b/packages/system-api/src/modules/system/system.service.ts @@ -45,7 +45,7 @@ const getVersion = async (): Promise<{ current: string; latest?: string }> => { version = data.name.replace('v', ''); } - await TipiCache.set('latestVersion', version?.replace('v', '') || ''); + await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60); return { current: getConfig().version, latest: version?.replace('v', '') }; } catch (e) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2636c52d..bb37caff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,7 @@ importers: '@types/react': 18.0.8 '@types/react-dom': 18.0.3 '@types/react-slick': ^0.23.8 + '@types/semver': ^7.3.12 '@types/validator': ^13.7.2 '@typescript-eslint/eslint-plugin': ^5.18.0 '@typescript-eslint/parser': ^5.0.0 @@ -60,6 +61,7 @@ importers: remark-breaks: ^3.0.2 remark-gfm: ^3.0.1 remark-mdx: ^2.1.1 + semver: ^7.3.7 swr: ^1.3.0 tailwindcss: ^3.0.23 ts-jest: ^28.0.2 @@ -88,6 +90,7 @@ importers: remark-breaks: 3.0.2 remark-gfm: 3.0.1 remark-mdx: 2.1.1 + semver: 7.3.7 swr: 1.3.0_react@18.1.0 tslib: 2.4.0 validator: 13.7.0 @@ -103,6 +106,7 @@ importers: '@types/react': 18.0.8 '@types/react-dom': 18.0.3 '@types/react-slick': 0.23.8 + '@types/semver': 7.3.12 '@types/validator': 13.7.2 '@typescript-eslint/eslint-plugin': 5.22.0_oztpoyrbzkyaikrhdkppp3gagu '@typescript-eslint/parser': 5.22.0_uhoeudlwl7kc47h4kncsfowede @@ -131,7 +135,6 @@ importers: '@types/node-cron': ^3.0.2 '@types/pg': ^8.6.5 '@types/semver': ^7.3.12 - '@types/tcp-port-used': ^1.0.1 '@types/uuid': ^8.3.4 '@types/validator': ^13.7.2 '@typescript-eslint/eslint-plugin': ^5.18.0 @@ -144,6 +147,7 @@ importers: concurrently: ^7.1.0 cors: ^2.8.5 dotenv: ^16.0.0 + esbuild: ^0.15.13 eslint: ^8.13.0 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.5.0 @@ -155,12 +159,10 @@ importers: graphql-import-node: ^0.0.5 graphql-type-json: ^0.3.2 http: 0.0.1-security - internal-ip: ^6.0.0 jest: ^28.1.0 jsonwebtoken: ^8.5.1 node-cache: ^5.1.2 node-cron: ^3.0.1 - node-port-scanner: ^3.0.1 nodemon: ^2.0.15 pg: ^8.7.3 prettier: 2.6.2 @@ -168,7 +170,6 @@ importers: reflect-metadata: ^0.1.13 rimraf: ^3.0.2 semver: ^7.3.7 - tcp-port-used: ^1.0.2 ts-jest: ^28.0.2 ts-node: ^10.8.2 type-graphql: ^1.1.1 @@ -191,16 +192,13 @@ importers: graphql: 15.8.0 graphql-type-json: 0.3.2_graphql@15.8.0 http: 0.0.1-security - internal-ip: 6.2.0 jsonwebtoken: 8.5.1 node-cache: 5.1.2 node-cron: 3.0.1 - node-port-scanner: 3.0.1 pg: 8.7.3 redis: 4.3.1 reflect-metadata: 0.1.13 semver: 7.3.7 - tcp-port-used: 1.0.2 type-graphql: 1.1.1_v2revtygxcm7xrdg2oz3ssohfu typeorm: 0.3.6_rymjtjxvmmxrsowl5wrmwxcyqa uuid: 9.0.0 @@ -220,12 +218,12 @@ importers: '@types/node-cron': 3.0.2 '@types/pg': 8.6.5 '@types/semver': 7.3.12 - '@types/tcp-port-used': 1.0.1 '@types/uuid': 8.3.4 '@types/validator': 13.7.2 '@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu concurrently: 7.1.0 + esbuild: 0.15.13 eslint: 8.15.0 eslint-config-airbnb-typescript: 17.0.0_c2ouaf3l4ivgkc6ae4nebvztom eslint-config-prettier: 8.5.0_eslint@8.15.0 @@ -236,7 +234,7 @@ importers: nodemon: 2.0.16 prettier: 2.6.2 rimraf: 3.0.2 - ts-jest: 28.0.2_z3fx76c5ksuwr36so7o5uc2kcy + ts-jest: 28.0.2_mlhfkqsuuditdvydbpk2q36p6y ts-node: 10.8.2_uva6s4l7h33czpzezvop6ux5pe typescript: 4.6.4 @@ -2316,6 +2314,24 @@ packages: - typescript dev: true + /@esbuild/android-arm/0.15.13: + resolution: {integrity: sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.15.13: + resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@eslint/eslintrc/1.2.3: resolution: {integrity: sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3961,10 +3977,6 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true - /@types/tcp-port-used/1.0.1: - resolution: {integrity: sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==} - dev: true - /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: false @@ -5628,6 +5640,7 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + dev: true /cross-undici-fetch/0.4.7: resolution: {integrity: sha512-e5KZdjHigxFECfw1B7cjmXLm3yT8eiffSJYUSyIWxy6c+f/MGiJsV1NHegZvG23ZgQ0o8rNaZxbtu5NdF5FmwQ==} @@ -5742,18 +5755,6 @@ packages: supports-color: 5.5.0 dev: true - /debug/4.3.1: - resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -5802,19 +5803,13 @@ packages: /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true /deepmerge/4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} dev: true - /default-gateway/6.0.3: - resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} - engines: {node: '>= 10'} - dependencies: - execa: 5.1.1 - dev: false - /defaults/1.0.3: resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} dependencies: @@ -6077,6 +6072,216 @@ packages: is-symbol: 1.0.4 dev: true + /esbuild-android-64/0.15.13: + resolution: {integrity: sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64/0.15.13: + resolution: {integrity: sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.15.13: + resolution: {integrity: sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.15.13: + resolution: {integrity: sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.15.13: + resolution: {integrity: sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.15.13: + resolution: {integrity: sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.15.13: + resolution: {integrity: sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.15.13: + resolution: {integrity: sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.15.13: + resolution: {integrity: sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.15.13: + resolution: {integrity: sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.15.13: + resolution: {integrity: sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.15.13: + resolution: {integrity: sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.15.13: + resolution: {integrity: sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.15.13: + resolution: {integrity: sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.15.13: + resolution: {integrity: sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64/0.15.13: + resolution: {integrity: sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.15.13: + resolution: {integrity: sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.15.13: + resolution: {integrity: sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.15.13: + resolution: {integrity: sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.15.13: + resolution: {integrity: sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.15.13: + resolution: {integrity: sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.13 + '@esbuild/linux-loong64': 0.15.13 + esbuild-android-64: 0.15.13 + esbuild-android-arm64: 0.15.13 + esbuild-darwin-64: 0.15.13 + esbuild-darwin-arm64: 0.15.13 + esbuild-freebsd-64: 0.15.13 + esbuild-freebsd-arm64: 0.15.13 + esbuild-linux-32: 0.15.13 + esbuild-linux-64: 0.15.13 + esbuild-linux-arm: 0.15.13 + esbuild-linux-arm64: 0.15.13 + esbuild-linux-mips64le: 0.15.13 + esbuild-linux-ppc64le: 0.15.13 + esbuild-linux-riscv64: 0.15.13 + esbuild-linux-s390x: 0.15.13 + esbuild-netbsd-64: 0.15.13 + esbuild-openbsd-64: 0.15.13 + esbuild-sunos-64: 0.15.13 + esbuild-windows-32: 0.15.13 + esbuild-windows-64: 0.15.13 + esbuild-windows-arm64: 0.15.13 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6668,6 +6873,7 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true /exit/0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} @@ -7131,6 +7337,7 @@ packages: /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + dev: true /get-symbol-description/1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} @@ -7527,6 +7734,7 @@ packages: /human-signals/2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + dev: true /husky/8.0.1: resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} @@ -7641,16 +7849,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /internal-ip/6.2.0: - resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} - engines: {node: '>=10'} - dependencies: - default-gateway: 6.0.3 - ipaddr.js: 1.9.1 - is-ip: 3.1.0 - p-event: 4.2.0 - dev: false - /internal-slot/1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} engines: {node: '>= 0.4'} @@ -7665,11 +7863,6 @@ packages: dependencies: loose-envify: 1.4.0 - /ip-regex/4.3.0: - resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} - engines: {node: '>=8'} - dev: false - /ipaddr.js/1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -7805,13 +7998,6 @@ packages: engines: {node: '>=8'} dev: true - /is-ip/3.1.0: - resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} - engines: {node: '>=8'} - dependencies: - ip-regex: 4.3.0 - dev: false - /is-lower-case/2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} dependencies: @@ -7944,10 +8130,6 @@ packages: tslib: 2.4.0 dev: true - /is-url/1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} - dev: false - /is-utf8/0.2.1: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true @@ -7967,17 +8149,9 @@ packages: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} dev: true - /is2/2.0.7: - resolution: {integrity: sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==} - engines: {node: '>=v0.10.0'} - dependencies: - deep-is: 0.1.4 - ip-regex: 4.3.0 - is-url: 1.2.4 - dev: false - /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true /isomorphic-fetch/3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} @@ -9203,6 +9377,7 @@ packages: /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true /merge/2.1.1: resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} @@ -9594,6 +9769,7 @@ packages: /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + dev: true /mimic-response/1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} @@ -9778,10 +9954,6 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true - /node-port-scanner/3.0.1: - resolution: {integrity: sha512-TuFGEWfye+1atB74v0Vm6myEjpq0L5Jo3UaOG9xgtYHxnFZN0fF9CnwCxp7ENWDerGbI1UXAgdRMIPz8TM73Hg==} - dev: false - /node-releases/2.0.4: resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==} dev: true @@ -9865,6 +10037,7 @@ packages: engines: {node: '>=8'} dependencies: path-key: 3.1.1 + dev: true /npmlog/5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} @@ -9975,6 +10148,7 @@ packages: engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: true /optimism/0.16.1: resolution: {integrity: sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==} @@ -10020,18 +10194,6 @@ packages: engines: {node: '>=6'} dev: true - /p-event/4.2.0: - resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} - engines: {node: '>=8'} - dependencies: - p-timeout: 3.2.0 - dev: false - - /p-finally/1.0.0: - resolution: {integrity: sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=} - engines: {node: '>=4'} - dev: false - /p-limit/1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} @@ -10079,13 +10241,6 @@ packages: engines: {node: '>=6'} dev: true - /p-timeout/3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - dependencies: - p-finally: 1.0.0 - dev: false - /p-try/1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} @@ -10209,6 +10364,7 @@ packages: /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + dev: true /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -11150,10 +11306,12 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 + dev: true /shebang-regex/3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + dev: true /side-channel/1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -11412,6 +11570,7 @@ packages: /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + dev: true /strip-indent/3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} @@ -11576,15 +11735,6 @@ packages: yallist: 4.0.0 dev: false - /tcp-port-used/1.0.2: - resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} - dependencies: - debug: 4.3.1 - is2: 2.0.7 - transitivePeerDependencies: - - supports-color - dev: false - /terminal-link/2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -11723,6 +11873,41 @@ packages: tslib: 2.4.0 dev: false + /ts-jest/28.0.2_mlhfkqsuuditdvydbpk2q36p6y: + resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@types/jest': ^27.0.0 + babel-jest: ^28.0.0 + esbuild: '*' + jest: ^28.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@types/jest': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@types/jest': 27.5.0 + bs-logger: 0.2.6 + esbuild: 0.15.13 + fast-json-stable-stringify: 2.1.0 + jest: 28.1.0_qxft4nzwxz7jey57xog52j3doy + jest-util: 28.1.0 + json5: 2.2.1 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.7 + typescript: 4.6.4 + yargs-parser: 20.2.9 + dev: true + /ts-jest/28.0.2_ps5qfvt5fosg52obpfzuxthwve: resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -11757,40 +11942,6 @@ packages: yargs-parser: 20.2.9 dev: true - /ts-jest/28.0.2_z3fx76c5ksuwr36so7o5uc2kcy: - resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@types/jest': ^27.0.0 - babel-jest: ^28.0.0 - esbuild: '*' - jest: ^28.0.0 - typescript: '>=4.3' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/jest': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - dependencies: - '@types/jest': 27.5.0 - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - jest: 28.1.0_qxft4nzwxz7jey57xog52j3doy - jest-util: 28.1.0 - json5: 2.2.1 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.3.7 - typescript: 4.6.4 - yargs-parser: 20.2.9 - dev: true - /ts-log/2.2.4: resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==} dev: true @@ -12438,6 +12589,7 @@ packages: hasBin: true dependencies: isexe: 2.0.0 + dev: true /wide-align/1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} From 35ebb1069a0b07a1c5848310295cf9cff9586b6f Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 5 Nov 2022 20:43:14 +0100 Subject: [PATCH 2/6] chore: fix eslint rules --- packages/system-api/.eslintrc.cjs | 15 ++-- .../system-api/__mocks__/child_process.ts | 2 +- packages/system-api/__mocks__/fs-extra.ts | 18 ++-- packages/system-api/__mocks__/redis.ts | 12 +-- packages/system-api/src/config/datasource.ts | 2 +- .../src/config/logger/apollo.logger.ts | 6 +- .../system-api/src/core/config/TipiConfig.ts | 4 +- .../config/__tests__/EventDispatcher.test.ts | 17 ++-- .../src/core/jobs/__tests__/jobs.test.ts | 2 +- packages/system-api/src/core/jobs/jobs.ts | 2 +- .../src/core/updates/__tests__/v040.test.ts | 4 +- .../src/core/updates/recover-migrations.ts | 28 +++--- packages/system-api/src/core/updates/v040.ts | 70 ++++++++------- .../modules/apps/__tests__/apps.factory.ts | 3 +- .../apps/__tests__/apps.helpers.test.ts | 80 ++++++++++++++++- .../apps/__tests__/apps.resolver.test.ts | 4 +- .../apps/__tests__/apps.service.test.ts | 36 ++++---- .../system-api/src/modules/apps/app.entity.ts | 2 +- .../system-api/src/modules/apps/app.types.ts | 6 ++ .../src/modules/apps/apps.helpers.ts | 37 +++++--- .../src/modules/apps/apps.service.ts | 18 ++-- .../auth/__tests__/auth.resolver.test.ts | 4 +- .../auth/__tests__/auth.service.test.ts | 4 +- .../modules/auth/__tests__/user.factory.ts | 2 +- .../system-api/src/modules/auth/auth.types.ts | 1 - .../modules/fs/__tests__/fs.helpers.test.ts | 88 ++----------------- .../system-api/src/modules/fs/fs.helpers.ts | 15 ---- .../system/__tests__/system.service.test.ts | 2 +- packages/system-api/src/server.ts | 6 +- packages/system-api/src/test/connection.ts | 2 +- 30 files changed, 241 insertions(+), 251 deletions(-) create mode 100644 packages/system-api/src/modules/apps/app.types.ts diff --git a/packages/system-api/.eslintrc.cjs b/packages/system-api/.eslintrc.cjs index 368f2199..ba81e810 100644 --- a/packages/system-api/.eslintrc.cjs +++ b/packages/system-api/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { - env: { node: true, jest: true }, - extends: ['airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'], + plugins: ['@typescript-eslint', 'import', 'react'], + extends: ['airbnb-base', 'airbnb-typescript/base', 'eslint:recommended', 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { project: './tsconfig.json', @@ -8,18 +8,19 @@ module.exports = { ecmaVersion: 'latest', sourceType: 'module', }, - plugins: ['@typescript-eslint', 'import', 'react'], rules: { - 'arrow-body-style': 0, - 'no-restricted-exports': 0, 'max-len': [1, { code: 200 }], 'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }], - indent: 'off', - '@typescript-eslint/indent': 0, 'no-unused-vars': [1, { argsIgnorePattern: '^_' }], '@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }], + 'max-classes-per-file': 0, + 'class-methods-use-this': 0, + 'import/prefer-default-export': 0, + 'no-underscore-dangle': 0, + '@typescript-eslint/ban-ts-comment': 0, }, globals: { NodeJS: true, }, + env: { node: true, jest: true }, }; diff --git a/packages/system-api/__mocks__/child_process.ts b/packages/system-api/__mocks__/child_process.ts index b8d76396..839925f0 100644 --- a/packages/system-api/__mocks__/child_process.ts +++ b/packages/system-api/__mocks__/child_process.ts @@ -1,6 +1,6 @@ const childProcess: { execFile: typeof execFile } = jest.genMockFromModule('child_process'); -const execFile = (_path: string, _args: string[], _thing: any, callback: Function) => { +const execFile = (_path: string, _args: string[], _thing: any, callback: any) => { callback(); }; diff --git a/packages/system-api/__mocks__/fs-extra.ts b/packages/system-api/__mocks__/fs-extra.ts index 476f40b5..d74bbf86 100644 --- a/packages/system-api/__mocks__/fs-extra.ts +++ b/packages/system-api/__mocks__/fs-extra.ts @@ -1,4 +1,5 @@ import path from 'path'; + const fs: { __createMockFiles: typeof createMockFiles; __resetAllMocks: typeof resetAllMocks; @@ -20,7 +21,7 @@ const createMockFiles = (newMockFiles: Record) => { mockFiles = Object.create(null); // Create folder tree - for (const file in newMockFiles) { + Object.keys(newMockFiles).forEach((file) => { const dir = path.dirname(file); if (!mockFiles[dir]) { @@ -29,16 +30,12 @@ const createMockFiles = (newMockFiles: Record) => { mockFiles[dir].push(path.basename(file)); mockFiles[file] = newMockFiles[file]; - } + }); }; -const readFileSync = (p: string) => { - return mockFiles[p]; -}; +const readFileSync = (p: string) => mockFiles[p]; -const existsSync = (p: string) => { - return mockFiles[p] !== undefined; -}; +const existsSync = (p: string) => mockFiles[p] !== undefined; const writeFileSync = (p: string, data: any) => { mockFiles[p] = data; @@ -85,7 +82,7 @@ const copySync = (source: string, destination: string) => { if (mockFiles[source] instanceof Array) { mockFiles[source].forEach((file: string) => { - mockFiles[destination + '/' + file] = mockFiles[source + '/' + file]; + mockFiles[`${destination}/${file}`] = mockFiles[`${source}/${file}`]; }); } }; @@ -120,4 +117,5 @@ fs.createFileSync = createFileSync; fs.__createMockFiles = createMockFiles; fs.__resetAllMocks = resetAllMocks; -module.exports = fs; +export default fs; +// module.exports = fs; diff --git a/packages/system-api/__mocks__/redis.ts b/packages/system-api/__mocks__/redis.ts index efd884fe..8faced36 100644 --- a/packages/system-api/__mocks__/redis.ts +++ b/packages/system-api/__mocks__/redis.ts @@ -9,16 +9,10 @@ module.exports = { values.set(key, value); expirations.set(key, exp); }, - get: (key: string) => { - return values.get(key); - }, + get: (key: string) => values.get(key), quit: jest.fn(), - del: (key: string) => { - return values.delete(key); - }, - ttl: (key: string) => { - return expirations.get(key); - }, + del: (key: string) => values.delete(key), + ttl: (key: string) => expirations.get(key), }; }), }; diff --git a/packages/system-api/src/config/datasource.ts b/packages/system-api/src/config/datasource.ts index 600016d5..0e2f1418 100644 --- a/packages/system-api/src/config/datasource.ts +++ b/packages/system-api/src/config/datasource.ts @@ -22,5 +22,5 @@ export default new DataSource({ logging: !__prod__, synchronize: false, entities: [App, User, Update], - migrations: [process.cwd() + '/dist/config/migrations/*.js'], + migrations: [`${process.cwd()}/dist/config/migrations/*.js`], }); diff --git a/packages/system-api/src/config/logger/apollo.logger.ts b/packages/system-api/src/config/logger/apollo.logger.ts index ae59e61a..670d95ab 100644 --- a/packages/system-api/src/config/logger/apollo.logger.ts +++ b/packages/system-api/src/config/logger/apollo.logger.ts @@ -4,15 +4,13 @@ import { __prod__ } from '../constants/constants'; import logger from './logger'; const ApolloLogs: PluginDefinition = { - requestDidStart: async () => { - return { + requestDidStart: async () => ({ async didEncounterErrors(errors) { if (!__prod__) { logger.error(JSON.stringify(errors.errors)); } }, - }; - }, + }), }; export { ApolloLogs }; diff --git a/packages/system-api/src/core/config/TipiConfig.ts b/packages/system-api/src/core/config/TipiConfig.ts index 38c07ce3..982499da 100644 --- a/packages/system-api/src/core/config/TipiConfig.ts +++ b/packages/system-api/src/core/config/TipiConfig.ts @@ -105,7 +105,7 @@ class Config { this.config = parsed; } - public setConfig(key: T, value: z.infer[T], writeFile: boolean = false) { + public setConfig(key: T, value: z.infer[T], writeFile = false) { const newConf: z.infer = { ...this.getConfig() }; newConf[key] = value; @@ -122,7 +122,7 @@ class Config { } } -export const setConfig = (key: T, value: z.infer[T], writeFile: boolean = false) => { +export const setConfig = (key: T, value: z.infer[T], writeFile = false) => { Config.getInstance().setConfig(key, value, writeFile); }; diff --git a/packages/system-api/src/core/config/__tests__/EventDispatcher.test.ts b/packages/system-api/src/core/config/__tests__/EventDispatcher.test.ts index 7128c6b7..a6fcd572 100644 --- a/packages/system-api/src/core/config/__tests__/EventDispatcher.test.ts +++ b/packages/system-api/src/core/config/__tests__/EventDispatcher.test.ts @@ -5,6 +5,7 @@ const WATCH_FILE = '/runtipi/state/events'; jest.mock('fs-extra'); +// eslint-disable-next-line no-promise-executor-return const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); beforeEach(() => { @@ -29,7 +30,7 @@ describe('EventDispatcher - dispatchEvent', () => { eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']); // @ts-ignore - const queue = eventDispatcher.queue; + const { queue } = eventDispatcher; expect(queue.length).toBe(2); }); @@ -39,12 +40,12 @@ describe('EventDispatcher - dispatchEvent', () => { eventDispatcher.dispatchEvent(EventTypes.UPDATE, ['--help']); // @ts-ignore - const queue = eventDispatcher.queue; + const { queue } = eventDispatcher; await wait(1050); // @ts-ignore - const lock = eventDispatcher.lock; + const { lock } = eventDispatcher; expect(queue.length).toBe(2); expect(lock).toBeDefined(); @@ -59,7 +60,7 @@ describe('EventDispatcher - dispatchEvent', () => { await wait(1050); // @ts-ignore - const queue = eventDispatcher.queue; + const { queue } = eventDispatcher; expect(queue.length).toBe(0); }); @@ -72,7 +73,7 @@ describe('EventDispatcher - dispatchEvent', () => { await wait(1050); // @ts-ignore - const queue = eventDispatcher.queue; + const { queue } = eventDispatcher; expect(queue.length).toBe(0); }); @@ -161,7 +162,7 @@ describe('EventDispatcher - clearEvent', () => { eventDispatcher.clearEvent(event); // @ts-ignore - const queue = eventDispatcher.queue; + const { queue } = eventDispatcher; expect(queue.length).toBe(0); }); @@ -174,7 +175,7 @@ describe('EventDispatcher - pollQueue', () => { // @ts-ignore const id = eventDispatcher.pollQueue(); // @ts-ignore - const interval = eventDispatcher.interval; + const { interval } = eventDispatcher; expect(interval).toBe(123); expect(id).toBe(123); @@ -192,7 +193,7 @@ describe('EventDispatcher - collectLockStatusAndClean', () => { eventDispatcher.collectLockStatusAndClean(); // @ts-ignore - const lock = eventDispatcher.lock; + const { lock } = eventDispatcher; expect(lock).toBeNull(); }); diff --git a/packages/system-api/src/core/jobs/__tests__/jobs.test.ts b/packages/system-api/src/core/jobs/__tests__/jobs.test.ts index 73400ccc..e5510416 100644 --- a/packages/system-api/src/core/jobs/__tests__/jobs.test.ts +++ b/packages/system-api/src/core/jobs/__tests__/jobs.test.ts @@ -1,7 +1,7 @@ import cron from 'node-cron'; import { getConfig } from '../../config/TipiConfig'; import startJobs from '../jobs'; -import { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher'; +import { eventDispatcher, EventTypes } from '../../config/EventDispatcher'; jest.mock('node-cron'); jest.mock('child_process'); diff --git a/packages/system-api/src/core/jobs/jobs.ts b/packages/system-api/src/core/jobs/jobs.ts index 29d99ae8..c4cc8520 100644 --- a/packages/system-api/src/core/jobs/jobs.ts +++ b/packages/system-api/src/core/jobs/jobs.ts @@ -1,6 +1,6 @@ import cron from 'node-cron'; import logger from '../../config/logger/logger'; -import { getConfig } from '../../core/config/TipiConfig'; +import { getConfig } from '../config/TipiConfig'; import { eventDispatcher, EventTypes } from '../config/EventDispatcher'; const startJobs = () => { diff --git a/packages/system-api/src/core/updates/__tests__/v040.test.ts b/packages/system-api/src/core/updates/__tests__/v040.test.ts index 3171c5a4..61262e6e 100644 --- a/packages/system-api/src/core/updates/__tests__/v040.test.ts +++ b/packages/system-api/src/core/updates/__tests__/v040.test.ts @@ -30,9 +30,7 @@ afterAll(async () => { await teardownConnection(TEST_SUITE); }); -const createState = (apps: string[]) => { - return JSON.stringify({ installed: apps.join(' ') }); -}; +const createState = (apps: string[]) => JSON.stringify({ installed: apps.join(' ') }); describe('No state/apps.json', () => { it('Should do nothing and create the update with status SUCCES', async () => { diff --git a/packages/system-api/src/core/updates/recover-migrations.ts b/packages/system-api/src/core/updates/recover-migrations.ts index 0ea1966e..b59adf7e 100644 --- a/packages/system-api/src/core/updates/recover-migrations.ts +++ b/packages/system-api/src/core/updates/recover-migrations.ts @@ -1,9 +1,21 @@ -import { DataSource } from 'typeorm'; +import { BaseEntity, DataSource, DeepPartial } from 'typeorm'; import logger from '../../config/logger/logger'; import App from '../../modules/apps/app.entity'; import User from '../../modules/auth/user.entity'; import Update from '../../modules/system/update.entity'; +const createUser = async (user: DeepPartial): Promise => { + await User.create(user).save(); +}; + +const createApp = async (app: DeepPartial): Promise => { + await App.create(app).save(); +}; + +const createUpdate = async (update: DeepPartial): Promise => { + await Update.create(update).save(); +}; + const recover = async (datasource: DataSource) => { logger.info('Recovering broken database'); @@ -18,20 +30,14 @@ const recover = async (datasource: DataSource) => { logger.info('running migrations'); await datasource.runMigrations(); - // create users - for (const user of users) { - await User.create(user).save(); - } + // recreate users + await Promise.all(users.map(createUser)); // create apps - for (const app of apps) { - await App.create(app).save(); - } + await Promise.all(apps.map(createApp)); // create updates - for (const update of updates) { - await Update.create(update).save(); - } + await Promise.all(updates.map(createUpdate)); logger.info(`Users recovered ${users.length}`); logger.info(`Apps recovered ${apps.length}`); diff --git a/packages/system-api/src/core/updates/v040.ts b/packages/system-api/src/core/updates/v040.ts index a93b2204..186c1080 100644 --- a/packages/system-api/src/core/updates/v040.ts +++ b/packages/system-api/src/core/updates/v040.ts @@ -10,6 +10,41 @@ type AppsState = { installed: string }; const UPDATE_NAME = 'v040'; +const migrateApp = async (appId: string): Promise => { + const app = await App.findOne({ where: { id: appId } }); + + if (!app) { + const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString(); + const envVars = envFile.split('\n'); + const envVarsMap = new Map(); + + envVars.forEach((envVar) => { + const [key, value] = envVar.split('='); + envVarsMap.set(key, value); + }); + + const form: Record = {}; + + const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`); + configFile?.form_fields?.forEach((field) => { + const envVar = field.env_variable; + const envVarValue = envVarsMap.get(envVar); + + if (envVarValue) { + form[field.env_variable] = envVarValue; + } + }); + + await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save(); + } else { + logger.info('App already migrated'); + } +}; + +const migrateUser = async (user: { email: string; password: string }): Promise => { + await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save(); +}; + export const updateV040 = async (): Promise => { try { const update = await Update.findOne({ where: { name: UPDATE_NAME } }); @@ -24,36 +59,7 @@ export const updateV040 = async (): Promise => { const state: AppsState = await readJsonFile('/runtipi/state/apps.json'); const installed: string[] = state.installed.split(' ').filter(Boolean); - for (const appId of installed) { - const app = await App.findOne({ where: { id: appId } }); - - if (!app) { - const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString(); - const envVars = envFile.split('\n'); - const envVarsMap = new Map(); - - envVars.forEach((envVar) => { - const [key, value] = envVar.split('='); - envVarsMap.set(key, value); - }); - - const form: Record = {}; - - const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`); - configFile?.form_fields?.forEach((field) => { - const envVar = field.env_variable; - const envVarValue = envVarsMap.get(envVar); - - if (envVarValue) { - form[field.env_variable] = envVarValue; - } - }); - - await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save(); - } else { - logger.info('App already migrated'); - } - } + await Promise.all(installed.map((appId) => migrateApp(appId))); deleteFolder('/runtipi/state/apps.json'); } @@ -61,9 +67,7 @@ export const updateV040 = async (): Promise => { if (fileExists('/state/users.json')) { const state: { email: string; password: string }[] = await readJsonFile('/runtipi/state/users.json'); - for (const user of state) { - await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save(); - } + await Promise.all(state.map((user) => migrateUser(user))); deleteFolder('/runtipi/state/users.json'); } diff --git a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts index 4a76bd83..9f171b6d 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts @@ -56,7 +56,7 @@ const createApp = async (props: IProps) => { }; } - let MockFiles: any = {}; + const MockFiles: any = {}; MockFiles['/runtipi/.env'] = 'TEST=test'; MockFiles['/runtipi/repos/repo-id'] = ''; MockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo); @@ -71,6 +71,7 @@ const createApp = async (props: IProps) => { status, exposed, domain, + version: 1, }).save(); MockFiles[`/app/storage/app-data/${appInfo.id}`] = ''; diff --git a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts index 6f13638f..aa98a322 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts @@ -4,7 +4,7 @@ import { DataSource } from 'typeorm'; import logger from '../../../config/logger/logger'; import { setupConnection, teardownConnection } from '../../../test/connection'; import App from '../app.entity'; -import { checkAppRequirements, checkEnvFile, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers'; +import { checkAppRequirements, checkEnvFile, ensureAppFolder, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers'; import { AppInfo } from '../apps.types'; import { createApp } from './apps.factory'; @@ -336,15 +336,89 @@ describe('getUpdateInfo', () => { }); it('Should return update info', async () => { - const updateInfo = await getUpdateInfo(app1.id); + const updateInfo = await getUpdateInfo(app1.id, 1); expect(updateInfo?.latest).toBe(app1.tipi_version); expect(updateInfo?.current).toBe(1); }); it('Should return null if app is not installed', async () => { - const updateInfo = await getUpdateInfo(faker.random.word()); + const updateInfo = await getUpdateInfo(faker.random.word(), 1); expect(updateInfo).toBeNull(); }); }); + +describe('Test: ensureAppFolder', () => { + beforeEach(() => { + const mockFiles = { + [`/runtipi/repos/repo-id/apps/test`]: ['test.yml'], + }; + // @ts-ignore + fs.__createMockFiles(mockFiles); + }); + + it('should copy the folder from repo', () => { + // Act + ensureAppFolder('test'); + + // Assert + const files = fs.readdirSync('/runtipi/apps/test'); + expect(files).toEqual(['test.yml']); + }); + + it('should not copy the folder if it already exists', () => { + const mockFiles = { + [`/runtipi/repos/repo-id/apps/test`]: ['test.yml'], + '/runtipi/apps/test': ['docker-compose.yml'], + '/runtipi/apps/test/docker-compose.yml': 'test', + }; + + // @ts-ignore + fs.__createMockFiles(mockFiles); + + // Act + ensureAppFolder('test'); + + // Assert + const files = fs.readdirSync('/runtipi/apps/test'); + expect(files).toEqual(['docker-compose.yml']); + }); + + it('Should overwrite the folder if clean up is true', () => { + const mockFiles = { + [`/runtipi/repos/repo-id/apps/test`]: ['test.yml'], + '/runtipi/apps/test': ['docker-compose.yml'], + '/runtipi/apps/test/docker-compose.yml': 'test', + }; + + // @ts-ignore + fs.__createMockFiles(mockFiles); + + // Act + ensureAppFolder('test', true); + + // Assert + const files = fs.readdirSync('/runtipi/apps/test'); + expect(files).toEqual(['test.yml']); + }); + + it('Should delete folder if it exists but has no docker-compose.yml file', () => { + // Arrange + const randomFileName = `${faker.random.word()}.yml`; + const mockFiles = { + [`/runtipi/repos/repo-id/apps/test`]: [randomFileName], + '/runtipi/apps/test': ['test.yml'], + }; + + // @ts-ignore + fs.__createMockFiles(mockFiles); + + // Act + ensureAppFolder('test'); + + // Assert + const files = fs.readdirSync('/runtipi/apps/test'); + expect(files).toEqual([randomFileName]); + }); +}); diff --git a/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts index 263249a8..0f57182f 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.resolver.test.ts @@ -1,6 +1,7 @@ import { DataSource } from 'typeorm'; -import { setupConnection, teardownConnection } from '../../../test/connection'; import fs from 'fs-extra'; +import { faker } from '@faker-js/faker'; +import { setupConnection, teardownConnection } from '../../../test/connection'; import { gcall } from '../../../test/gcall'; import App from '../app.entity'; import { getAppQuery, InstalledAppsQuery, listAppInfosQuery } from '../../../test/queries'; @@ -9,7 +10,6 @@ import { AppInfo, AppStatusEnum, ListAppsResonse } from '../apps.types'; import { createUser } from '../../auth/__tests__/user.factory'; import User from '../../auth/user.entity'; import { installAppMutation, startAppMutation, stopAppMutation, uninstallAppMutation, updateAppConfigMutation, updateAppMutation } from '../../../test/mutations'; -import { faker } from '@faker-js/faker'; import EventDispatcher from '../../../core/config/EventDispatcher'; jest.mock('fs'); diff --git a/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts index 1da8d02e..05ebcd6a 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts @@ -1,10 +1,10 @@ -import AppsService from '../apps.service'; import fs from 'fs-extra'; +import { DataSource } from 'typeorm'; +import AppsService from '../apps.service'; import { AppInfo, AppStatusEnum, AppSupportedArchitecturesEnum } from '../apps.types'; import App from '../app.entity'; import { createApp } from './apps.factory'; import { setupConnection, teardownConnection } from '../../../test/connection'; -import { DataSource } from 'typeorm'; import { getEnvMap } from '../apps.helpers'; import EventDispatcher, { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher'; import { setConfig } from '../../../core/config/TipiConfig'; @@ -56,9 +56,9 @@ describe('Install app', () => { const app = await App.findOne({ where: { id: app1.id } }); expect(app).toBeDefined(); - expect(app!.id).toBe(app1.id); - expect(app!.config).toStrictEqual({ TEST_FIELD: 'test' }); - expect(app!.status).toBe(AppStatusEnum.RUNNING); + expect(app?.id).toBe(app1.id); + expect(app?.config).toStrictEqual({ TEST_FIELD: 'test' }); + expect(app?.status).toBe(AppStatusEnum.RUNNING); }); it('Should start app if already installed', async () => { @@ -147,7 +147,7 @@ describe('Install app', () => { const app2 = await createApp({ exposable: true }); const app3 = await createApp({ exposable: true }); // @ts-ignore - fs.__createMockFiles(Object.assign({}, app2.MockFiles, app3.MockFiles)); + fs.__createMockFiles({ ...app2.MockFiles, ...app3.MockFiles }); await AppsService.installApp(app2.appInfo.id, { TEST_FIELD: 'test' }, true, 'test.com'); @@ -203,8 +203,8 @@ describe('Uninstall app', () => { // Assert expect(app).toBeDefined(); - expect(app!.id).toBe(app1.id); - expect(app!.status).toBe(AppStatusEnum.RUNNING); + expect(app?.id).toBe(app1.id); + expect(app?.status).toBe(AppStatusEnum.RUNNING); }); it('Should correctly remove app from database', async () => { @@ -244,7 +244,7 @@ describe('Uninstall app', () => { // Act & Assert await expect(AppsService.uninstallApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to uninstall\nstdout: test`); const app = await App.findOne({ where: { id: app1.id } }); - expect(app!.status).toBe(AppStatusEnum.STOPPED); + expect(app?.status).toBe(AppStatusEnum.STOPPED); }); }); @@ -300,7 +300,7 @@ describe('Start app', () => { // Act & Assert await expect(AppsService.startApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to start\nstdout: test`); const app = await App.findOne({ where: { id: app1.id } }); - expect(app!.status).toBe(AppStatusEnum.STOPPED); + expect(app?.status).toBe(AppStatusEnum.STOPPED); }); }); @@ -333,7 +333,7 @@ describe('Stop app', () => { // Act & Assert await expect(AppsService.stopApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to stop\nstdout: test`); const app = await App.findOne({ where: { id: app1.id } }); - expect(app!.status).toBe(AppStatusEnum.RUNNING); + expect(app?.status).toBe(AppStatusEnum.RUNNING); }); }); @@ -378,17 +378,13 @@ describe('Update app config', () => { expect(envMap.get('RANDOM_FIELD')).toBe('test'); }); - it('Should throw if app is exposed and domain is not provided', () => { - return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required'); - }); + it('Should throw if app is exposed and domain is not provided', () => expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required')); - it('Should throw if app is exposed and domain is not valid', () => { - return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid'); - }); + it('Should throw if app is exposed and domain is not valid', () => + expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid')); - it('Should throw if app is exposed and config does not allow it', () => { - return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`); - }); + it('Should throw if app is exposed and config does not allow it', () => + expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`)); it('Should throw if app is exposed and domain is already used', async () => { const app2 = await createApp({ exposable: true, installed: true }); diff --git a/packages/system-api/src/modules/apps/app.entity.ts b/packages/system-api/src/modules/apps/app.entity.ts index a3ff70a2..9f954cfc 100644 --- a/packages/system-api/src/modules/apps/app.entity.ts +++ b/packages/system-api/src/modules/apps/app.entity.ts @@ -70,7 +70,7 @@ class App extends BaseEntity { @Field(() => UpdateInfo, { nullable: true }) updateInfo(): Promise { - return getUpdateInfo(this.id); + return getUpdateInfo(this.id, this.version); } } diff --git a/packages/system-api/src/modules/apps/app.types.ts b/packages/system-api/src/modules/apps/app.types.ts new file mode 100644 index 00000000..5dc0f2a0 --- /dev/null +++ b/packages/system-api/src/modules/apps/app.types.ts @@ -0,0 +1,6 @@ +export interface AppEntityType { + id: string; + config: Record; + exposed: boolean; + domain?: string; +} diff --git a/packages/system-api/src/modules/apps/apps.helpers.ts b/packages/system-api/src/modules/apps/apps.helpers.ts index 98f85071..084304cf 100644 --- a/packages/system-api/src/modules/apps/apps.helpers.ts +++ b/packages/system-api/src/modules/apps/apps.helpers.ts @@ -1,14 +1,12 @@ -import { fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers'; import crypto from 'crypto'; +import fs from 'fs-extra'; +import { deleteFolder, fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers'; import { AppInfo, AppStatusEnum } from './apps.types'; import logger from '../../config/logger/logger'; -import App from './app.entity'; import { getConfig } from '../../core/config/TipiConfig'; -import fs from 'fs-extra'; +import { AppEntityType } from './app.types'; export const checkAppRequirements = async (appName: string) => { - let valid = true; - const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}/config.json`); if (!configFile) { @@ -19,7 +17,7 @@ export const checkAppRequirements = async (appName: string) => { throw new Error(`App ${appName} is not supported on this architecture`); } - return valid; + return true; }; export const getEnvMap = (appName: string): Map => { @@ -55,7 +53,7 @@ const getEntropy = (name: string, length: number) => { return hash.digest('hex').substring(0, length); }; -export const generateEnvFile = (app: App) => { +export const generateEnvFile = (app: AppEntityType) => { const configFile: AppInfo | null = readJsonFile(`/runtipi/apps/${app.id}/config.json`); if (!configFile) { @@ -129,7 +127,8 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null = const configFile: AppInfo = readJsonFile(`/runtipi/apps/${id}/config.json`); configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString(); return configFile; - } else if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) { + } + if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) { const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`); @@ -145,20 +144,32 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null = } }; -export const getUpdateInfo = async (id: string) => { - const app = await App.findOne({ where: { id } }); - +export const getUpdateInfo = async (id: string, version: number) => { const doesFileExist = fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}`); - if (!app || !doesFileExist) { + if (!doesFileExist) { return null; } const repoConfig: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); return { - current: app.version, + current: version, latest: repoConfig.tipi_version, dockerVersion: repoConfig.version, }; }; + +export const ensureAppFolder = (appName: string, cleanup = false) => { + if (cleanup && fileExists(`/runtipi/apps/${appName}`)) { + deleteFolder(`/runtipi/apps/${appName}`); + } + + if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) { + if (fileExists(`/runtipi/apps/${appName}`)) { + deleteFolder(`/runtipi/apps/${appName}`); + } + // Copy from apps repo + fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`); + } +}; diff --git a/packages/system-api/src/modules/apps/apps.service.ts b/packages/system-api/src/modules/apps/apps.service.ts index 75999236..5574ff56 100644 --- a/packages/system-api/src/modules/apps/apps.service.ts +++ b/packages/system-api/src/modules/apps/apps.service.ts @@ -1,10 +1,10 @@ import validator from 'validator'; -import { createFolder, ensureAppFolder, readFile, readJsonFile } from '../fs/fs.helpers'; -import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps } from './apps.helpers'; +import { Not } from 'typeorm'; +import { createFolder, readFile, readJsonFile } from '../fs/fs.helpers'; +import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, ensureAppFolder } from './apps.helpers'; import { AppInfo, AppStatusEnum, ListAppsResonse } from './apps.types'; import App from './app.entity'; import logger from '../../config/logger/logger'; -import { Not } from 'typeorm'; import { getConfig } from '../../core/config/TipiConfig'; import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher'; @@ -18,9 +18,7 @@ const filterApp = (app: AppInfo): boolean => { return app.supported_architectures.includes(arch); }; -const filterApps = (apps: AppInfo[]): AppInfo[] => { - return apps.sort(sortApps).filter(filterApp); -}; +const filterApps = (apps: AppInfo[]): AppInfo[] => apps.sort(sortApps).filter(filterApp); /** * Start all apps which had the status RUNNING in the database @@ -157,11 +155,7 @@ const installApp = async (id: string, form: Record, exposed?: bo const listApps = async (): Promise => { const folders: string[] = await getAvailableApps(); - const apps: AppInfo[] = folders - .map((app) => { - return readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`); - }) - .filter(Boolean); + const apps: AppInfo[] = folders.map((app) => readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`)).filter(Boolean); const filteredApps = filterApps(apps).map((app) => { const description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app.id}/metadata/description.md`); @@ -254,7 +248,7 @@ const stopApp = async (id: string): Promise => { * @returns - the app entity */ const uninstallApp = async (id: string): Promise => { - let app = await App.findOne({ where: { id } }); + const app = await App.findOne({ where: { id } }); if (!app) { throw new Error(`App ${id} not found`); diff --git a/packages/system-api/src/modules/auth/__tests__/auth.resolver.test.ts b/packages/system-api/src/modules/auth/__tests__/auth.resolver.test.ts index c16777f9..9727ebb4 100644 --- a/packages/system-api/src/modules/auth/__tests__/auth.resolver.test.ts +++ b/packages/system-api/src/modules/auth/__tests__/auth.resolver.test.ts @@ -7,7 +7,7 @@ import { setupConnection, teardownConnection } from '../../../test/connection'; import { gcall } from '../../../test/gcall'; import { loginMutation, registerMutation } from '../../../test/mutations'; import { isConfiguredQuery, MeQuery, refreshTokenQuery } from '../../../test/queries'; -import User from '../../auth/user.entity'; +import User from '../user.entity'; import { TokenResponse } from '../auth.types'; import { createUser } from './user.factory'; @@ -214,7 +214,7 @@ describe('Test: refreshToken', () => { const { data } = await gcall<{ refreshToken: TokenResponse }>({ source: refreshTokenQuery, userId: user1.id, - session: session, + session, }); const decoded = jwt.verify(data?.refreshToken?.token || '', getConfig().jwtSecret) as jwt.JwtPayload; diff --git a/packages/system-api/src/modules/auth/__tests__/auth.service.test.ts b/packages/system-api/src/modules/auth/__tests__/auth.service.test.ts index 384270bb..bfb11f68 100644 --- a/packages/system-api/src/modules/auth/__tests__/auth.service.test.ts +++ b/packages/system-api/src/modules/auth/__tests__/auth.service.test.ts @@ -1,11 +1,11 @@ import * as argon2 from 'argon2'; import jwt from 'jsonwebtoken'; +import { faker } from '@faker-js/faker'; +import { DataSource } from 'typeorm'; import AuthService from '../auth.service'; import { createUser } from './user.factory'; import User from '../user.entity'; -import { faker } from '@faker-js/faker'; import { setupConnection, teardownConnection } from '../../../test/connection'; -import { DataSource } from 'typeorm'; import { setConfig } from '../../../core/config/TipiConfig'; import TipiCache from '../../../config/TipiCache'; diff --git a/packages/system-api/src/modules/auth/__tests__/user.factory.ts b/packages/system-api/src/modules/auth/__tests__/user.factory.ts index 25f56ee0..8da7a717 100644 --- a/packages/system-api/src/modules/auth/__tests__/user.factory.ts +++ b/packages/system-api/src/modules/auth/__tests__/user.factory.ts @@ -1,6 +1,6 @@ -import User from '../user.entity'; import * as argon2 from 'argon2'; import { faker } from '@faker-js/faker'; +import User from '../user.entity'; const createUser = async (email?: string) => { const hash = await argon2.hash('password'); diff --git a/packages/system-api/src/modules/auth/auth.types.ts b/packages/system-api/src/modules/auth/auth.types.ts index 36986f2b..fe46d355 100644 --- a/packages/system-api/src/modules/auth/auth.types.ts +++ b/packages/system-api/src/modules/auth/auth.types.ts @@ -1,5 +1,4 @@ import { Field, InputType, ObjectType } from 'type-graphql'; -import User from './user.entity'; @InputType() class UsernamePasswordInput { diff --git a/packages/system-api/src/modules/fs/__tests__/fs.helpers.test.ts b/packages/system-api/src/modules/fs/__tests__/fs.helpers.test.ts index e4ab41fe..83ca17d1 100644 --- a/packages/system-api/src/modules/fs/__tests__/fs.helpers.test.ts +++ b/packages/system-api/src/modules/fs/__tests__/fs.helpers.test.ts @@ -1,7 +1,5 @@ -import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed, ensureAppFolder } from '../fs.helpers'; import fs from 'fs-extra'; -import { getConfig } from '../../../core/config/TipiConfig'; -import { faker } from '@faker-js/faker'; +import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed } from '../fs.helpers'; jest.mock('fs-extra'); @@ -15,7 +13,7 @@ describe('Test: readJsonFile', () => { // Arrange const rawFile = '{"test": "test"}'; const mockFiles = { - ['/runtipi/test-file.json']: rawFile, + '/runtipi/test-file.json': rawFile, }; // @ts-ignore fs.__createMockFiles(mockFiles); @@ -52,7 +50,7 @@ describe('Test: readFile', () => { it('should return the file', () => { const rawFile = 'test'; const mockFiles = { - ['/runtipi/test-file.txt']: rawFile, + '/runtipi/test-file.txt': rawFile, }; // @ts-ignore @@ -69,7 +67,7 @@ describe('Test: readFile', () => { describe('Test: readdirSync', () => { it('should return the files', () => { const mockFiles = { - ['/runtipi/test/test-file.txt']: 'test', + '/runtipi/test/test-file.txt': 'test', }; // @ts-ignore @@ -86,7 +84,7 @@ describe('Test: readdirSync', () => { describe('Test: fileExists', () => { it('should return true if the file exists', () => { const mockFiles = { - ['/runtipi/test-file.txt']: 'test', + '/runtipi/test-file.txt': 'test', }; // @ts-ignore @@ -133,7 +131,7 @@ describe('Test: deleteFolder', () => { describe('Test: getSeed', () => { it('should return the seed', () => { const mockFiles = { - ['/runtipi/state/seed']: 'test', + '/runtipi/state/seed': 'test', }; // @ts-ignore @@ -142,77 +140,3 @@ describe('Test: getSeed', () => { expect(getSeed()).toEqual('test'); }); }); - -describe('Test: ensureAppFolder', () => { - beforeEach(() => { - const mockFiles = { - [`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'], - }; - // @ts-ignore - fs.__createMockFiles(mockFiles); - }); - - it('should copy the folder from repo', () => { - // Act - ensureAppFolder('test'); - - // Assert - const files = fs.readdirSync('/runtipi/apps/test'); - expect(files).toEqual(['test.yml']); - }); - - it('should not copy the folder if it already exists', () => { - const mockFiles = { - [`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'], - ['/runtipi/apps/test']: ['docker-compose.yml'], - ['/runtipi/apps/test/docker-compose.yml']: 'test', - }; - - // @ts-ignore - fs.__createMockFiles(mockFiles); - - // Act - ensureAppFolder('test'); - - // Assert - const files = fs.readdirSync('/runtipi/apps/test'); - expect(files).toEqual(['docker-compose.yml']); - }); - - it('Should overwrite the folder if clean up is true', () => { - const mockFiles = { - [`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'], - ['/runtipi/apps/test']: ['docker-compose.yml'], - ['/runtipi/apps/test/docker-compose.yml']: 'test', - }; - - // @ts-ignore - fs.__createMockFiles(mockFiles); - - // Act - ensureAppFolder('test', true); - - // Assert - const files = fs.readdirSync('/runtipi/apps/test'); - expect(files).toEqual(['test.yml']); - }); - - it('Should delete folder if it exists but has no docker-compose.yml file', () => { - // Arrange - const randomFileName = `${faker.random.word()}.yml`; - const mockFiles = { - [`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: [randomFileName], - ['/runtipi/apps/test']: ['test.yml'], - }; - - // @ts-ignore - fs.__createMockFiles(mockFiles); - - // Act - ensureAppFolder('test'); - - // Assert - const files = fs.readdirSync('/runtipi/apps/test'); - expect(files).toEqual([randomFileName]); - }); -}); diff --git a/packages/system-api/src/modules/fs/fs.helpers.ts b/packages/system-api/src/modules/fs/fs.helpers.ts index aedf8bc6..34be54a8 100644 --- a/packages/system-api/src/modules/fs/fs.helpers.ts +++ b/packages/system-api/src/modules/fs/fs.helpers.ts @@ -1,5 +1,4 @@ import fs from 'fs-extra'; -import { getConfig } from '../../core/config/TipiConfig'; export const readJsonFile = (path: string): any => { try { @@ -36,17 +35,3 @@ export const getSeed = () => { const seed = readFile('/runtipi/state/seed'); return seed.toString(); }; - -export const ensureAppFolder = (appName: string, cleanup = false) => { - if (cleanup && fileExists(`/runtipi/apps/${appName}`)) { - deleteFolder(`/runtipi/apps/${appName}`); - } - - if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) { - if (fileExists(`/runtipi/apps/${appName}`)) { - deleteFolder(`/runtipi/apps/${appName}`); - } - // Copy from apps repo - fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`); - } -}; diff --git a/packages/system-api/src/modules/system/__tests__/system.service.test.ts b/packages/system-api/src/modules/system/__tests__/system.service.test.ts index ead6b4c6..2dc60ae3 100644 --- a/packages/system-api/src/modules/system/__tests__/system.service.test.ts +++ b/packages/system-api/src/modules/system/__tests__/system.service.test.ts @@ -1,8 +1,8 @@ import fs from 'fs-extra'; import semver from 'semver'; import axios from 'axios'; -import SystemService from '../system.service'; import { faker } from '@faker-js/faker'; +import SystemService from '../system.service'; import TipiCache from '../../../config/TipiCache'; import { setConfig } from '../../../core/config/TipiConfig'; import logger from '../../../config/logger/logger'; diff --git a/packages/system-api/src/server.ts b/packages/system-api/src/server.ts index c6e29373..87f8e0ba 100644 --- a/packages/system-api/src/server.ts +++ b/packages/system-api/src/server.ts @@ -2,9 +2,11 @@ import 'reflect-metadata'; import express from 'express'; import { ApolloServerPluginLandingPageGraphQLPlayground as Playground } from 'apollo-server-core'; import { ApolloServer } from 'apollo-server-express'; +import { createServer } from 'http'; +import { ZodError } from 'zod'; +import cors from 'cors'; import { createSchema } from './schema'; import { ApolloLogs } from './config/logger/apollo.logger'; -import { createServer } from 'http'; import logger from './config/logger/logger'; import getSessionMiddleware from './core/middlewares/sessionMiddleware'; import { MyContext } from './types'; @@ -15,10 +17,8 @@ import { runUpdates } from './core/updates/run'; import recover from './core/updates/recover-migrations'; import startJobs from './core/jobs/jobs'; import { applyJsonConfig, getConfig, setConfig } from './core/config/TipiConfig'; -import { ZodError } from 'zod'; import systemController from './modules/system/system.controller'; import { eventDispatcher, EventTypes } from './core/config/EventDispatcher'; -import cors from 'cors'; const applyCustomConfig = () => { try { diff --git a/packages/system-api/src/test/connection.ts b/packages/system-api/src/test/connection.ts index 4dfb59ab..a1348f29 100755 --- a/packages/system-api/src/test/connection.ts +++ b/packages/system-api/src/test/connection.ts @@ -1,7 +1,7 @@ import { DataSource } from 'typeorm'; +import pg from 'pg'; import App from '../modules/apps/app.entity'; import User from '../modules/auth/user.entity'; -import pg from 'pg'; import Update from '../modules/system/update.entity'; const HOST = 'localhost'; From b201f99d49bfb482f141574b75e234b934c947b6 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 8 Nov 2022 00:23:14 +0100 Subject: [PATCH 3/6] fix: linting warnings --- .../system-api/__mocks__/child_process.ts | 2 +- packages/system-api/__mocks__/fs-extra.ts | 2 +- .../migrations/1657299198975-Initial.ts | 1 + .../system-api/src/core/config/TipiConfig.ts | 2 +- .../__tests__/sessionMiddleware.test.ts | 13 +++--- .../src/core/updates/__tests__/v040.test.ts | 2 +- packages/system-api/src/declarations.d.ts | 2 +- .../modules/apps/__tests__/apps.factory.ts | 2 +- .../apps/__tests__/apps.helpers.test.ts | 40 +++++++++++++------ .../apps/__tests__/apps.service.test.ts | 2 +- .../src/modules/apps/apps.helpers.ts | 31 +++++++++----- .../system-api/src/modules/fs/fs.helpers.ts | 4 +- .../system/__tests__/system.service.test.ts | 10 +++-- packages/system-api/src/test/gcall.ts | 6 +-- 14 files changed, 76 insertions(+), 43 deletions(-) diff --git a/packages/system-api/__mocks__/child_process.ts b/packages/system-api/__mocks__/child_process.ts index 839925f0..949adc12 100644 --- a/packages/system-api/__mocks__/child_process.ts +++ b/packages/system-api/__mocks__/child_process.ts @@ -1,6 +1,6 @@ const childProcess: { execFile: typeof execFile } = jest.genMockFromModule('child_process'); -const execFile = (_path: string, _args: string[], _thing: any, callback: any) => { +const execFile = (_path: string, _args: string[], _thing: unknown, callback: () => void) => { callback(); }; diff --git a/packages/system-api/__mocks__/fs-extra.ts b/packages/system-api/__mocks__/fs-extra.ts index d74bbf86..b182ea84 100644 --- a/packages/system-api/__mocks__/fs-extra.ts +++ b/packages/system-api/__mocks__/fs-extra.ts @@ -37,7 +37,7 @@ const readFileSync = (p: string) => mockFiles[p]; const existsSync = (p: string) => mockFiles[p] !== undefined; -const writeFileSync = (p: string, data: any) => { +const writeFileSync = (p: string, data: string | string[]) => { mockFiles[p] = data; }; diff --git a/packages/system-api/src/config/migrations/1657299198975-Initial.ts b/packages/system-api/src/config/migrations/1657299198975-Initial.ts index 8251e6f7..1a9317a0 100644 --- a/packages/system-api/src/config/migrations/1657299198975-Initial.ts +++ b/packages/system-api/src/config/migrations/1657299198975-Initial.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import { MigrationInterface, QueryRunner } from 'typeorm'; export class Initial1657299198975 implements MigrationInterface { diff --git a/packages/system-api/src/core/config/TipiConfig.ts b/packages/system-api/src/core/config/TipiConfig.ts index 982499da..d40facf7 100644 --- a/packages/system-api/src/core/config/TipiConfig.ts +++ b/packages/system-api/src/core/config/TipiConfig.ts @@ -112,7 +112,7 @@ class Config { this.config = configSchema.parse(newConf); if (writeFile) { - const currentJsonConf = readJsonFile('/runtipi/state/settings.json') || {}; + const currentJsonConf = readJsonFile>>('/runtipi/state/settings.json') || {}; currentJsonConf[key] = value; const partialConfig = configSchema.partial(); const parsed = partialConfig.parse(currentJsonConf); diff --git a/packages/system-api/src/core/middlewares/__tests__/sessionMiddleware.test.ts b/packages/system-api/src/core/middlewares/__tests__/sessionMiddleware.test.ts index 0b6d3dd6..ebafc265 100644 --- a/packages/system-api/src/core/middlewares/__tests__/sessionMiddleware.test.ts +++ b/packages/system-api/src/core/middlewares/__tests__/sessionMiddleware.test.ts @@ -1,5 +1,6 @@ import { faker } from '@faker-js/faker'; import jwt from 'jsonwebtoken'; +import { Request, Response } from 'express'; import TipiCache from '../../../config/TipiCache'; import { getConfig } from '../../config/TipiConfig'; import getSessionMiddleware from '../sessionMiddleware'; @@ -15,9 +16,9 @@ describe('SessionMiddleware', () => { headers: { authorization: `Bearer ${token}`, }, - } as any; + } as Request; const next = jest.fn(); - const res = {} as any; + const res = {} as Response; // Act await getSessionMiddleware(req, res, next); @@ -41,9 +42,9 @@ describe('SessionMiddleware', () => { headers: { authorization: `Bearer ${token}`, }, - } as any; + } as Request; const next = jest.fn(); - const res = {} as any; + const res = {} as Response; // Act await getSessionMiddleware(req, res, next); @@ -59,9 +60,9 @@ describe('SessionMiddleware', () => { // Arrange const req = { headers: {}, - } as any; + } as Request; const next = jest.fn(); - const res = {} as any; + const res = {} as Response; // Act await getSessionMiddleware(req, res, next); diff --git a/packages/system-api/src/core/updates/__tests__/v040.test.ts b/packages/system-api/src/core/updates/__tests__/v040.test.ts index 61262e6e..a515616e 100644 --- a/packages/system-api/src/core/updates/__tests__/v040.test.ts +++ b/packages/system-api/src/core/updates/__tests__/v040.test.ts @@ -39,7 +39,7 @@ describe('No state/apps.json', () => { const update = await Update.findOne({ where: { name: 'v040' } }); expect(update).toBeDefined(); - expect(update!.status).toBe(UpdateStatusEnum.SUCCESS); + expect(update?.status).toBe(UpdateStatusEnum.SUCCESS); const apps = await App.find(); diff --git a/packages/system-api/src/declarations.d.ts b/packages/system-api/src/declarations.d.ts index 2decf771..990c4609 100644 --- a/packages/system-api/src/declarations.d.ts +++ b/packages/system-api/src/declarations.d.ts @@ -4,6 +4,6 @@ declare namespace Express { userId?: number; id?: string; }; - [key: string]: any; + [key: string]: unknown; } } diff --git a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts index 9f171b6d..0718c85c 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts @@ -56,7 +56,7 @@ const createApp = async (props: IProps) => { }; } - const MockFiles: any = {}; + const MockFiles: Record = {}; MockFiles['/runtipi/.env'] = 'TEST=test'; MockFiles['/runtipi/repos/repo-id'] = ''; MockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo); diff --git a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts index aa98a322..746a4626 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.helpers.test.ts @@ -81,9 +81,13 @@ describe('checkEnvFile', () => { try { checkEnvFile(app1.id); expect(true).toBe(false); - } catch (e: any) { - expect(e).toBeDefined(); - expect(e.message).toBe('New info needed. App config needs to be updated'); + } catch (e: unknown) { + if (e instanceof Error) { + expect(e).toBeDefined(); + expect(e.message).toBe('New info needed. App config needs to be updated'); + } else { + fail('Should throw an error'); + } } }); }); @@ -142,9 +146,13 @@ describe('Test: generateEnvFile', () => { try { generateEnvFile(Object.assign(appEntity1, { config: { TEST_FIELD: undefined } })); expect(true).toBe(false); - } catch (e: any) { - expect(e).toBeDefined(); - expect(e.message).toBe('Variable TEST_FIELD is required'); + } catch (e: unknown) { + if (e instanceof Error) { + expect(e).toBeDefined(); + expect(e.message).toBe('Variable TEST_FIELD is required'); + } else { + fail('Should throw an error'); + } } }); @@ -152,9 +160,13 @@ describe('Test: generateEnvFile', () => { try { generateEnvFile(Object.assign(appEntity1, { id: 'not-existing-app' })); expect(true).toBe(false); - } catch (e: any) { - expect(e).toBeDefined(); - expect(e.message).toBe('App not-existing-app not found'); + } catch (e: unknown) { + if (e instanceof Error) { + expect(e).toBeDefined(); + expect(e.message).toBe('App not-existing-app not found'); + } else { + fail('Should throw an error'); + } } }); @@ -310,9 +322,13 @@ describe('Test: getAppInfo', () => { try { await getAppInfo(appInfo.id, appEntity.status); expect(true).toBe(false); - } catch (e: any) { - expect(e.message).toBe(`Error loading app: ${appInfo.id}`); - expect(log).toBeCalledWith(`Error loading app: ${appInfo.id}`); + } catch (e: unknown) { + if (e instanceof Error) { + expect(e.message).toBe(`Error loading app: ${appInfo.id}`); + expect(log).toBeCalledWith(`Error loading app: ${appInfo.id}`); + } else { + expect(true).toBe(false); + } } spy.mockRestore(); diff --git a/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts b/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts index 05ebcd6a..a7f7ee77 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.service.test.ts @@ -588,6 +588,6 @@ describe('Update app', () => { await expect(AppsService.updateApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to update\nstdout: error`); const app = await App.findOne({ where: { id: app1.id } }); - expect(app!.status).toBe(AppStatusEnum.STOPPED); + expect(app?.status).toBe(AppStatusEnum.STOPPED); }); }); diff --git a/packages/system-api/src/modules/apps/apps.helpers.ts b/packages/system-api/src/modules/apps/apps.helpers.ts index 084304cf..5795192a 100644 --- a/packages/system-api/src/modules/apps/apps.helpers.ts +++ b/packages/system-api/src/modules/apps/apps.helpers.ts @@ -107,9 +107,9 @@ export const getAvailableApps = async (): Promise => { appsDir.forEach((app) => { if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`)) { - const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`); + const configFile = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`); - if (configFile.available) { + if (configFile?.available) { apps.push(app); } } @@ -124,15 +124,22 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null = const installed = typeof status !== 'undefined' && status !== AppStatusEnum.MISSING; if (installed && fileExists(`/runtipi/apps/${id}/config.json`)) { - const configFile: AppInfo = readJsonFile(`/runtipi/apps/${id}/config.json`); - configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString(); + const configFile = readJsonFile(`/runtipi/apps/${id}/config.json`); + + if (configFile) { + configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString(); + } + return configFile; } if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) { - const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); - configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`); + const configFile = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); - if (configFile.available) { + if (configFile) { + configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`); + } + + if (configFile?.available) { return configFile; } } @@ -151,12 +158,16 @@ export const getUpdateInfo = async (id: string, version: number) => { return null; } - const repoConfig: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); + const repoConfig = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); + + if (!repoConfig?.tipi_version || !repoConfig?.version) { + return null; + } return { current: version, - latest: repoConfig.tipi_version, - dockerVersion: repoConfig.version, + latest: repoConfig?.tipi_version, + dockerVersion: repoConfig?.version, }; }; diff --git a/packages/system-api/src/modules/fs/fs.helpers.ts b/packages/system-api/src/modules/fs/fs.helpers.ts index 34be54a8..3f59e94a 100644 --- a/packages/system-api/src/modules/fs/fs.helpers.ts +++ b/packages/system-api/src/modules/fs/fs.helpers.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; -export const readJsonFile = (path: string): any => { +export const readJsonFile = (path: string): T | null => { try { const rawFile = fs.readFileSync(path).toString(); @@ -22,7 +22,7 @@ export const readdirSync = (path: string): string[] => fs.readdirSync(path); export const fileExists = (path: string): boolean => fs.existsSync(path); -export const writeFile = (path: string, data: any) => fs.writeFileSync(path, data); +export const writeFile = (path: string, data: string) => fs.writeFileSync(path, data); export const createFolder = (path: string) => { if (!fileExists(path)) { diff --git a/packages/system-api/src/modules/system/__tests__/system.service.test.ts b/packages/system-api/src/modules/system/__tests__/system.service.test.ts index 2dc60ae3..5ecd2889 100644 --- a/packages/system-api/src/modules/system/__tests__/system.service.test.ts +++ b/packages/system-api/src/modules/system/__tests__/system.service.test.ts @@ -21,9 +21,13 @@ describe('Test: systemInfo', () => { it('Should throw if system-info.json does not exist', () => { try { SystemService.systemInfo(); - } catch (e: any) { - expect(e).toBeDefined(); - expect(e.message).toBe('Error parsing system info'); + } catch (e: unknown) { + if (e instanceof Error) { + expect(e).toBeDefined(); + expect(e.message).toBe('Error parsing system info'); + } else { + fail('Should throw an error'); + } } }); diff --git a/packages/system-api/src/test/gcall.ts b/packages/system-api/src/test/gcall.ts index 8fe9dcf9..97cd7ec7 100644 --- a/packages/system-api/src/test/gcall.ts +++ b/packages/system-api/src/test/gcall.ts @@ -5,7 +5,7 @@ import { createSchema } from '../schema'; interface Options { source: string; variableValues?: Maybe<{ - [key: string]: any; + [key: string]: unknown; }>; userId?: number; session?: string; @@ -13,7 +13,7 @@ interface Options { let schema: GraphQLSchema | null = null; -export const gcall = async ({ source, variableValues, userId, session }: Options): Promise> => { +export const gcall = async ({ source, variableValues, userId, session }: Options): Promise> => { if (!schema) { schema = await createSchema(); } @@ -23,5 +23,5 @@ export const gcall = async ({ source, variableValues, userId, session }: Opti source, variableValues, contextValue: { req: { session: { userId, id: session } } }, - }) as any; + }) as unknown as ExecutionResult; }; From c8dce109a23abf37ea49ae85217e6e09254d9b97 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 8 Nov 2022 00:27:05 +0100 Subject: [PATCH 4/6] refactor: allow all origins --- packages/dashboard/src/generated/graphql.tsx | 515 ++++++++---------- .../modules/apps/__tests__/apps.factory.ts | 1 - .../src/modules/apps/apps.helpers.ts | 4 +- packages/system-api/src/server.ts | 11 +- pnpm-lock.yaml | 301 ++-------- 5 files changed, 283 insertions(+), 549 deletions(-) diff --git a/packages/dashboard/src/generated/graphql.tsx b/packages/dashboard/src/generated/graphql.tsx index bf85be6b..d2e6df60 100644 --- a/packages/dashboard/src/generated/graphql.tsx +++ b/packages/dashboard/src/generated/graphql.tsx @@ -67,6 +67,7 @@ export type AppInfo = { requirements?: Maybe; short_desc: Scalars['String']; source: Scalars['String']; + supported_architectures?: Maybe>; tipi_version: Scalars['Float']; url_suffix?: Maybe; version?: Maybe; @@ -90,6 +91,12 @@ export enum AppStatusEnum { Updating = 'UPDATING', } +export enum AppSupportedArchitecturesEnum { + Amd64 = 'AMD64', + Arm = 'ARM', + Arm64 = 'ARM64', +} + export type Cpu = { __typename?: 'Cpu'; load: Scalars['Float']; @@ -146,34 +153,42 @@ export type Mutation = { updateAppConfig: App; }; + export type MutationInstallAppArgs = { input: AppInputType; }; + export type MutationLoginArgs = { input: UsernamePasswordInput; }; + export type MutationRegisterArgs = { input: UsernamePasswordInput; }; + export type MutationStartAppArgs = { id: Scalars['String']; }; + export type MutationStopAppArgs = { id: Scalars['String']; }; + export type MutationUninstallAppArgs = { id: Scalars['String']; }; + export type MutationUpdateAppArgs = { id: Scalars['String']; }; + export type MutationUpdateAppConfigArgs = { input: AppInputType; }; @@ -190,6 +205,7 @@ export type Query = { version: VersionResponse; }; + export type QueryGetAppArgs = { id: Scalars['String']; }; @@ -236,182 +252,125 @@ export type InstallAppMutationVariables = Exact<{ input: AppInputType; }>; -export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename: 'App'; id: string; status: AppStatusEnum } }; + +export type InstallAppMutation = { __typename?: 'Mutation', installApp: { __typename: 'App', id: string, status: AppStatusEnum } }; export type LoginMutationVariables = Exact<{ input: UsernamePasswordInput; }>; -export type LoginMutation = { __typename?: 'Mutation'; login: { __typename?: 'TokenResponse'; token: string } }; -export type LogoutMutationVariables = Exact<{ [key: string]: never }>; +export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'TokenResponse', token: string } }; -export type LogoutMutation = { __typename?: 'Mutation'; logout: boolean }; +export type LogoutMutationVariables = Exact<{ [key: string]: never; }>; + + +export type LogoutMutation = { __typename?: 'Mutation', logout: boolean }; export type RegisterMutationVariables = Exact<{ input: UsernamePasswordInput; }>; -export type RegisterMutation = { __typename?: 'Mutation'; register: { __typename?: 'TokenResponse'; token: string } }; -export type RestartMutationVariables = Exact<{ [key: string]: never }>; +export type RegisterMutation = { __typename?: 'Mutation', register: { __typename?: 'TokenResponse', token: string } }; -export type RestartMutation = { __typename?: 'Mutation'; restart: boolean }; +export type RestartMutationVariables = Exact<{ [key: string]: never; }>; + + +export type RestartMutation = { __typename?: 'Mutation', restart: boolean }; export type StartAppMutationVariables = Exact<{ id: Scalars['String']; }>; -export type StartAppMutation = { __typename?: 'Mutation'; startApp: { __typename: 'App'; id: string; status: AppStatusEnum } }; + +export type StartAppMutation = { __typename?: 'Mutation', startApp: { __typename: 'App', id: string, status: AppStatusEnum } }; export type StopAppMutationVariables = Exact<{ id: Scalars['String']; }>; -export type StopAppMutation = { __typename?: 'Mutation'; stopApp: { __typename: 'App'; id: string; status: AppStatusEnum } }; + +export type StopAppMutation = { __typename?: 'Mutation', stopApp: { __typename: 'App', id: string, status: AppStatusEnum } }; export type UninstallAppMutationVariables = Exact<{ id: Scalars['String']; }>; -export type UninstallAppMutation = { __typename?: 'Mutation'; uninstallApp: { __typename: 'App'; id: string; status: AppStatusEnum } }; -export type UpdateMutationVariables = Exact<{ [key: string]: never }>; +export type UninstallAppMutation = { __typename?: 'Mutation', uninstallApp: { __typename: 'App', id: string, status: AppStatusEnum } }; -export type UpdateMutation = { __typename?: 'Mutation'; update: boolean }; +export type UpdateMutationVariables = Exact<{ [key: string]: never; }>; + + +export type UpdateMutation = { __typename?: 'Mutation', update: boolean }; export type UpdateAppMutationVariables = Exact<{ id: Scalars['String']; }>; -export type UpdateAppMutation = { __typename?: 'Mutation'; updateApp: { __typename: 'App'; id: string; status: AppStatusEnum } }; + +export type UpdateAppMutation = { __typename?: 'Mutation', updateApp: { __typename: 'App', id: string, status: AppStatusEnum } }; export type UpdateAppConfigMutationVariables = Exact<{ input: AppInputType; }>; -export type UpdateAppConfigMutation = { __typename?: 'Mutation'; updateAppConfig: { __typename: 'App'; id: string; status: AppStatusEnum } }; + +export type UpdateAppConfigMutation = { __typename?: 'Mutation', updateAppConfig: { __typename: 'App', id: string, status: AppStatusEnum } }; export type GetAppQueryVariables = Exact<{ appId: Scalars['String']; }>; -export type GetAppQuery = { - __typename?: 'Query'; - getApp: { - __typename?: 'App'; - id: string; - status: AppStatusEnum; - config: any; - version?: number | null; - exposed: boolean; - domain?: string | null; - updateInfo?: { __typename?: 'UpdateInfo'; current: number; latest: number; dockerVersion?: string | null } | null; - info?: { - __typename?: 'AppInfo'; - id: string; - port: number; - name: string; - description: string; - available: boolean; - version?: string | null; - tipi_version: number; - short_desc: string; - author: string; - source: string; - categories: Array; - url_suffix?: string | null; - https?: boolean | null; - exposable?: boolean | null; - form_fields: Array<{ - __typename?: 'FormField'; - type: FieldTypesEnum; - label: string; - max?: number | null; - min?: number | null; - hint?: string | null; - required?: boolean | null; - env_variable: string; - }>; - } | null; - }; -}; -export type InstalledAppsQueryVariables = Exact<{ [key: string]: never }>; +export type GetAppQuery = { __typename?: 'Query', getApp: { __typename?: 'App', id: string, status: AppStatusEnum, config: any, version?: number | null, exposed: boolean, domain?: string | null, updateInfo?: { __typename?: 'UpdateInfo', current: number, latest: number, dockerVersion?: string | null } | null, info?: { __typename?: 'AppInfo', id: string, port: number, name: string, description: string, available: boolean, version?: string | null, tipi_version: number, short_desc: string, author: string, source: string, categories: Array, url_suffix?: string | null, https?: boolean | null, exposable?: boolean | null, form_fields: Array<{ __typename?: 'FormField', type: FieldTypesEnum, label: string, max?: number | null, min?: number | null, hint?: string | null, required?: boolean | null, env_variable: string }> } | null } }; -export type InstalledAppsQuery = { - __typename?: 'Query'; - installedApps: Array<{ - __typename?: 'App'; - id: string; - status: AppStatusEnum; - config: any; - version?: number | null; - updateInfo?: { __typename?: 'UpdateInfo'; current: number; latest: number; dockerVersion?: string | null } | null; - info?: { __typename?: 'AppInfo'; id: string; name: string; description: string; tipi_version: number; short_desc: string; https?: boolean | null } | null; - }>; -}; +export type InstalledAppsQueryVariables = Exact<{ [key: string]: never; }>; -export type ConfiguredQueryVariables = Exact<{ [key: string]: never }>; -export type ConfiguredQuery = { __typename?: 'Query'; isConfigured: boolean }; +export type InstalledAppsQuery = { __typename?: 'Query', installedApps: Array<{ __typename?: 'App', id: string, status: AppStatusEnum, config: any, version?: number | null, updateInfo?: { __typename?: 'UpdateInfo', current: number, latest: number, dockerVersion?: string | null } | null, info?: { __typename?: 'AppInfo', id: string, name: string, description: string, tipi_version: number, short_desc: string, https?: boolean | null } | null }> }; -export type ListAppsQueryVariables = Exact<{ [key: string]: never }>; +export type ConfiguredQueryVariables = Exact<{ [key: string]: never; }>; -export type ListAppsQuery = { - __typename?: 'Query'; - listAppsInfo: { - __typename?: 'ListAppsResonse'; - total: number; - apps: Array<{ - __typename?: 'AppInfo'; - id: string; - available: boolean; - tipi_version: number; - port: number; - name: string; - version?: string | null; - short_desc: string; - author: string; - categories: Array; - https?: boolean | null; - }>; - }; -}; -export type MeQueryVariables = Exact<{ [key: string]: never }>; +export type ConfiguredQuery = { __typename?: 'Query', isConfigured: boolean }; -export type MeQuery = { __typename?: 'Query'; me?: { __typename?: 'User'; id: string } | null }; +export type ListAppsQueryVariables = Exact<{ [key: string]: never; }>; -export type RefreshTokenQueryVariables = Exact<{ [key: string]: never }>; -export type RefreshTokenQuery = { __typename?: 'Query'; refreshToken?: { __typename?: 'TokenResponse'; token: string } | null }; +export type ListAppsQuery = { __typename?: 'Query', listAppsInfo: { __typename?: 'ListAppsResonse', total: number, apps: Array<{ __typename?: 'AppInfo', id: string, available: boolean, tipi_version: number, port: number, name: string, version?: string | null, short_desc: string, author: string, categories: Array, https?: boolean | null }> } }; -export type SystemInfoQueryVariables = Exact<{ [key: string]: never }>; +export type MeQueryVariables = Exact<{ [key: string]: never; }>; -export type SystemInfoQuery = { - __typename?: 'Query'; - systemInfo?: { - __typename?: 'SystemInfoResponse'; - cpu: { __typename?: 'Cpu'; load: number }; - disk: { __typename?: 'DiskMemory'; available: number; used: number; total: number }; - memory: { __typename?: 'DiskMemory'; available: number; used: number; total: number }; - } | null; -}; -export type VersionQueryVariables = Exact<{ [key: string]: never }>; +export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + +export type RefreshTokenQueryVariables = Exact<{ [key: string]: never; }>; + + +export type RefreshTokenQuery = { __typename?: 'Query', refreshToken?: { __typename?: 'TokenResponse', token: string } | null }; + +export type SystemInfoQueryVariables = Exact<{ [key: string]: never; }>; + + +export type SystemInfoQuery = { __typename?: 'Query', systemInfo?: { __typename?: 'SystemInfoResponse', cpu: { __typename?: 'Cpu', load: number }, disk: { __typename?: 'DiskMemory', available: number, used: number, total: number }, memory: { __typename?: 'DiskMemory', available: number, used: number, total: number } } | null }; + +export type VersionQueryVariables = Exact<{ [key: string]: never; }>; + + +export type VersionQuery = { __typename?: 'Query', version: { __typename?: 'VersionResponse', current: string, latest?: string | null } }; -export type VersionQuery = { __typename?: 'Query'; version: { __typename?: 'VersionResponse'; current: string; latest?: string | null } }; export const InstallAppDocument = gql` - mutation InstallApp($input: AppInputType!) { - installApp(input: $input) { - id - status - __typename - } + mutation InstallApp($input: AppInputType!) { + installApp(input: $input) { + id + status + __typename } -`; +} + `; export type InstallAppMutationFn = Apollo.MutationFunction; /** @@ -439,12 +398,12 @@ export type InstallAppMutationHookResult = ReturnType; export type InstallAppMutationOptions = Apollo.BaseMutationOptions; export const LoginDocument = gql` - mutation Login($input: UsernamePasswordInput!) { - login(input: $input) { - token - } + mutation Login($input: UsernamePasswordInput!) { + login(input: $input) { + token } -`; +} + `; export type LoginMutationFn = Apollo.MutationFunction; /** @@ -472,10 +431,10 @@ export type LoginMutationHookResult = ReturnType; export type LoginMutationResult = Apollo.MutationResult; export type LoginMutationOptions = Apollo.BaseMutationOptions; export const LogoutDocument = gql` - mutation Logout { - logout - } -`; + mutation Logout { + logout +} + `; export type LogoutMutationFn = Apollo.MutationFunction; /** @@ -502,12 +461,12 @@ export type LogoutMutationHookResult = ReturnType; export type LogoutMutationResult = Apollo.MutationResult; export type LogoutMutationOptions = Apollo.BaseMutationOptions; export const RegisterDocument = gql` - mutation Register($input: UsernamePasswordInput!) { - register(input: $input) { - token - } + mutation Register($input: UsernamePasswordInput!) { + register(input: $input) { + token } -`; +} + `; export type RegisterMutationFn = Apollo.MutationFunction; /** @@ -535,10 +494,10 @@ export type RegisterMutationHookResult = ReturnType; export type RegisterMutationResult = Apollo.MutationResult; export type RegisterMutationOptions = Apollo.BaseMutationOptions; export const RestartDocument = gql` - mutation Restart { - restart - } -`; + mutation Restart { + restart +} + `; export type RestartMutationFn = Apollo.MutationFunction; /** @@ -565,14 +524,14 @@ export type RestartMutationHookResult = ReturnType; export type RestartMutationResult = Apollo.MutationResult; export type RestartMutationOptions = Apollo.BaseMutationOptions; export const StartAppDocument = gql` - mutation StartApp($id: String!) { - startApp(id: $id) { - id - status - __typename - } + mutation StartApp($id: String!) { + startApp(id: $id) { + id + status + __typename } -`; +} + `; export type StartAppMutationFn = Apollo.MutationFunction; /** @@ -600,14 +559,14 @@ export type StartAppMutationHookResult = ReturnType; export type StartAppMutationResult = Apollo.MutationResult; export type StartAppMutationOptions = Apollo.BaseMutationOptions; export const StopAppDocument = gql` - mutation StopApp($id: String!) { - stopApp(id: $id) { - id - status - __typename - } + mutation StopApp($id: String!) { + stopApp(id: $id) { + id + status + __typename } -`; +} + `; export type StopAppMutationFn = Apollo.MutationFunction; /** @@ -635,14 +594,14 @@ export type StopAppMutationHookResult = ReturnType; export type StopAppMutationResult = Apollo.MutationResult; export type StopAppMutationOptions = Apollo.BaseMutationOptions; export const UninstallAppDocument = gql` - mutation UninstallApp($id: String!) { - uninstallApp(id: $id) { - id - status - __typename - } + mutation UninstallApp($id: String!) { + uninstallApp(id: $id) { + id + status + __typename } -`; +} + `; export type UninstallAppMutationFn = Apollo.MutationFunction; /** @@ -670,10 +629,10 @@ export type UninstallAppMutationHookResult = ReturnType; export type UninstallAppMutationOptions = Apollo.BaseMutationOptions; export const UpdateDocument = gql` - mutation Update { - update - } -`; + mutation Update { + update +} + `; export type UpdateMutationFn = Apollo.MutationFunction; /** @@ -700,14 +659,14 @@ export type UpdateMutationHookResult = ReturnType; export type UpdateMutationResult = Apollo.MutationResult; export type UpdateMutationOptions = Apollo.BaseMutationOptions; export const UpdateAppDocument = gql` - mutation UpdateApp($id: String!) { - updateApp(id: $id) { - id - status - __typename - } + mutation UpdateApp($id: String!) { + updateApp(id: $id) { + id + status + __typename } -`; +} + `; export type UpdateAppMutationFn = Apollo.MutationFunction; /** @@ -735,14 +694,14 @@ export type UpdateAppMutationHookResult = ReturnType; export type UpdateAppMutationOptions = Apollo.BaseMutationOptions; export const UpdateAppConfigDocument = gql` - mutation UpdateAppConfig($input: AppInputType!) { - updateAppConfig(input: $input) { - id - status - __typename - } + mutation UpdateAppConfig($input: AppInputType!) { + updateAppConfig(input: $input) { + id + status + __typename } -`; +} + `; export type UpdateAppConfigMutationFn = Apollo.MutationFunction; /** @@ -770,47 +729,47 @@ export type UpdateAppConfigMutationHookResult = ReturnType; export type UpdateAppConfigMutationOptions = Apollo.BaseMutationOptions; export const GetAppDocument = gql` - query GetApp($appId: String!) { - getApp(id: $appId) { + query GetApp($appId: String!) { + getApp(id: $appId) { + id + status + config + version + exposed + domain + updateInfo { + current + latest + dockerVersion + } + info { id - status - config + port + name + description + available version - exposed - domain - updateInfo { - current - latest - dockerVersion - } - info { - id - port - name - description - available - version - tipi_version - short_desc - author - source - categories - url_suffix - https - exposable - form_fields { - type - label - max - min - hint - required - env_variable - } + tipi_version + short_desc + author + source + categories + url_suffix + https + exposable + form_fields { + type + label + max + min + hint + required + env_variable } } } -`; +} + `; /** * __useGetAppQuery__ @@ -840,28 +799,28 @@ export type GetAppQueryHookResult = ReturnType; export type GetAppLazyQueryHookResult = ReturnType; export type GetAppQueryResult = Apollo.QueryResult; export const InstalledAppsDocument = gql` - query InstalledApps { - installedApps { + query InstalledApps { + installedApps { + id + status + config + version + updateInfo { + current + latest + dockerVersion + } + info { id - status - config - version - updateInfo { - current - latest - dockerVersion - } - info { - id - name - description - tipi_version - short_desc - https - } + name + description + tipi_version + short_desc + https } } -`; +} + `; /** * __useInstalledAppsQuery__ @@ -890,10 +849,10 @@ export type InstalledAppsQueryHookResult = ReturnType; export type InstalledAppsQueryResult = Apollo.QueryResult; export const ConfiguredDocument = gql` - query Configured { - isConfigured - } -`; + query Configured { + isConfigured +} + `; /** * __useConfiguredQuery__ @@ -922,24 +881,24 @@ export type ConfiguredQueryHookResult = ReturnType; export type ConfiguredLazyQueryHookResult = ReturnType; export type ConfiguredQueryResult = Apollo.QueryResult; export const ListAppsDocument = gql` - query ListApps { - listAppsInfo { - apps { - id - available - tipi_version - port - name - version - short_desc - author - categories - https - } - total + query ListApps { + listAppsInfo { + apps { + id + available + tipi_version + port + name + version + short_desc + author + categories + https } + total } -`; +} + `; /** * __useListAppsQuery__ @@ -968,12 +927,12 @@ export type ListAppsQueryHookResult = ReturnType; export type ListAppsLazyQueryHookResult = ReturnType; export type ListAppsQueryResult = Apollo.QueryResult; export const MeDocument = gql` - query Me { - me { - id - } + query Me { + me { + id } -`; +} + `; /** * __useMeQuery__ @@ -1002,12 +961,12 @@ export type MeQueryHookResult = ReturnType; export type MeLazyQueryHookResult = ReturnType; export type MeQueryResult = Apollo.QueryResult; export const RefreshTokenDocument = gql` - query RefreshToken { - refreshToken { - token - } + query RefreshToken { + refreshToken { + token } -`; +} + `; /** * __useRefreshTokenQuery__ @@ -1036,24 +995,24 @@ export type RefreshTokenQueryHookResult = ReturnType; export type RefreshTokenQueryResult = Apollo.QueryResult; export const SystemInfoDocument = gql` - query SystemInfo { - systemInfo { - cpu { - load - } - disk { - available - used - total - } - memory { - available - used - total - } + query SystemInfo { + systemInfo { + cpu { + load + } + disk { + available + used + total + } + memory { + available + used + total } } -`; +} + `; /** * __useSystemInfoQuery__ @@ -1082,13 +1041,13 @@ export type SystemInfoQueryHookResult = ReturnType; export type SystemInfoLazyQueryHookResult = ReturnType; export type SystemInfoQueryResult = Apollo.QueryResult; export const VersionDocument = gql` - query Version { - version { - current - latest - } + query Version { + version { + current + latest } -`; +} + `; /** * __useVersionQuery__ @@ -1115,4 +1074,4 @@ export function useVersionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type VersionLazyQueryHookResult = ReturnType; -export type VersionQueryResult = Apollo.QueryResult; +export type VersionQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts index 0718c85c..a356b8e2 100644 --- a/packages/system-api/src/modules/apps/__tests__/apps.factory.ts +++ b/packages/system-api/src/modules/apps/__tests__/apps.factory.ts @@ -30,7 +30,6 @@ const createApp = async (props: IProps) => { env_variable: 'TEST_FIELD', }, ], - name: faker.random.word(), description: faker.random.words(), tipi_version: faker.datatype.number({ min: 1, max: 10 }), diff --git a/packages/system-api/src/modules/apps/apps.helpers.ts b/packages/system-api/src/modules/apps/apps.helpers.ts index 5795192a..0879e033 100644 --- a/packages/system-api/src/modules/apps/apps.helpers.ts +++ b/packages/system-api/src/modules/apps/apps.helpers.ts @@ -160,12 +160,12 @@ export const getUpdateInfo = async (id: string, version: number) => { const repoConfig = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`); - if (!repoConfig?.tipi_version || !repoConfig?.version) { + if (!repoConfig?.tipi_version) { return null; } return { - current: version, + current: version || 0, latest: repoConfig?.tipi_version, dockerVersion: repoConfig?.version, }; diff --git a/packages/system-api/src/server.ts b/packages/system-api/src/server.ts index 87f8e0ba..ad2b01fa 100644 --- a/packages/system-api/src/server.ts +++ b/packages/system-api/src/server.ts @@ -4,7 +4,7 @@ import { ApolloServerPluginLandingPageGraphQLPlayground as Playground } from 'ap import { ApolloServer } from 'apollo-server-express'; import { createServer } from 'http'; import { ZodError } from 'zod'; -import cors from 'cors'; +import cors, { CorsOptions } from 'cors'; import { createSchema } from './schema'; import { ApolloLogs } from './config/logger/apollo.logger'; import logger from './config/logger/logger'; @@ -33,6 +33,13 @@ const applyCustomConfig = () => { } }; +const corsOptions: CorsOptions = { + credentials: false, + origin: (_, callback) => { + callback(null, true); + }, +}; + const main = async () => { try { eventDispatcher.clear(); @@ -41,8 +48,8 @@ const main = async () => { const app = express(); const port = 3001; + app.use(cors(corsOptions)); app.use(express.static(`${getConfig().rootFolder}/repos/${getConfig().appsRepoId}`)); - app.use(cors()); app.use('/status', systemController.status); app.use(getSessionMiddleware); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb37caff..fc89c2c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,7 +147,6 @@ importers: concurrently: ^7.1.0 cors: ^2.8.5 dotenv: ^16.0.0 - esbuild: ^0.15.13 eslint: ^8.13.0 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.5.0 @@ -223,7 +222,6 @@ importers: '@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu concurrently: 7.1.0 - esbuild: 0.15.13 eslint: 8.15.0 eslint-config-airbnb-typescript: 17.0.0_c2ouaf3l4ivgkc6ae4nebvztom eslint-config-prettier: 8.5.0_eslint@8.15.0 @@ -234,7 +232,7 @@ importers: nodemon: 2.0.16 prettier: 2.6.2 rimraf: 3.0.2 - ts-jest: 28.0.2_mlhfkqsuuditdvydbpk2q36p6y + ts-jest: 28.0.2_z3fx76c5ksuwr36so7o5uc2kcy ts-node: 10.8.2_uva6s4l7h33czpzezvop6ux5pe typescript: 4.6.4 @@ -2314,24 +2312,6 @@ packages: - typescript dev: true - /@esbuild/android-arm/0.15.13: - resolution: {integrity: sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64/0.15.13: - resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@eslint/eslintrc/1.2.3: resolution: {integrity: sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6072,216 +6052,6 @@ packages: is-symbol: 1.0.4 dev: true - /esbuild-android-64/0.15.13: - resolution: {integrity: sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.15.13: - resolution: {integrity: sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.15.13: - resolution: {integrity: sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.15.13: - resolution: {integrity: sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.15.13: - resolution: {integrity: sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.15.13: - resolution: {integrity: sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.15.13: - resolution: {integrity: sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.15.13: - resolution: {integrity: sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.15.13: - resolution: {integrity: sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.15.13: - resolution: {integrity: sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.15.13: - resolution: {integrity: sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.15.13: - resolution: {integrity: sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.15.13: - resolution: {integrity: sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.15.13: - resolution: {integrity: sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.15.13: - resolution: {integrity: sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.15.13: - resolution: {integrity: sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.15.13: - resolution: {integrity: sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.15.13: - resolution: {integrity: sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.15.13: - resolution: {integrity: sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.15.13: - resolution: {integrity: sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild/0.15.13: - resolution: {integrity: sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.15.13 - '@esbuild/linux-loong64': 0.15.13 - esbuild-android-64: 0.15.13 - esbuild-android-arm64: 0.15.13 - esbuild-darwin-64: 0.15.13 - esbuild-darwin-arm64: 0.15.13 - esbuild-freebsd-64: 0.15.13 - esbuild-freebsd-arm64: 0.15.13 - esbuild-linux-32: 0.15.13 - esbuild-linux-64: 0.15.13 - esbuild-linux-arm: 0.15.13 - esbuild-linux-arm64: 0.15.13 - esbuild-linux-mips64le: 0.15.13 - esbuild-linux-ppc64le: 0.15.13 - esbuild-linux-riscv64: 0.15.13 - esbuild-linux-s390x: 0.15.13 - esbuild-netbsd-64: 0.15.13 - esbuild-openbsd-64: 0.15.13 - esbuild-sunos-64: 0.15.13 - esbuild-windows-32: 0.15.13 - esbuild-windows-64: 0.15.13 - esbuild-windows-arm64: 0.15.13 - dev: true - /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -11873,41 +11643,6 @@ packages: tslib: 2.4.0 dev: false - /ts-jest/28.0.2_mlhfkqsuuditdvydbpk2q36p6y: - resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@types/jest': ^27.0.0 - babel-jest: ^28.0.0 - esbuild: '*' - jest: ^28.0.0 - typescript: '>=4.3' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/jest': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - dependencies: - '@types/jest': 27.5.0 - bs-logger: 0.2.6 - esbuild: 0.15.13 - fast-json-stable-stringify: 2.1.0 - jest: 28.1.0_qxft4nzwxz7jey57xog52j3doy - jest-util: 28.1.0 - json5: 2.2.1 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.3.7 - typescript: 4.6.4 - yargs-parser: 20.2.9 - dev: true - /ts-jest/28.0.2_ps5qfvt5fosg52obpfzuxthwve: resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -11942,6 +11677,40 @@ packages: yargs-parser: 20.2.9 dev: true + /ts-jest/28.0.2_z3fx76c5ksuwr36so7o5uc2kcy: + resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@types/jest': ^27.0.0 + babel-jest: ^28.0.0 + esbuild: '*' + jest: ^28.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@types/jest': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@types/jest': 27.5.0 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 28.1.0_qxft4nzwxz7jey57xog52j3doy + jest-util: 28.1.0 + json5: 2.2.1 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.7 + typescript: 4.6.4 + yargs-parser: 20.2.9 + dev: true + /ts-log/2.2.4: resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==} dev: true From 0bc2db058f8ca8fe2608257f6c19ab91240e852c Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 8 Nov 2022 19:09:00 +0100 Subject: [PATCH 5/6] feat: open apps from same domain in the dashboard --- docker-compose.dev.yml | 8 ----- docker-compose.rc.yml | 3 -- docker-compose.yml | 3 -- .../src/hooks/useCachedRessources.ts | 29 +++++-------------- .../modules/Apps/containers/AppDetails.tsx | 7 ++--- packages/dashboard/src/pages/api/getenv.tsx | 7 ----- packages/dashboard/src/state/systemStore.ts | 8 ----- .../core/config/__tests__/TipiConfig.test.ts | 2 +- scripts/start-dev.sh | 2 +- 9 files changed, 13 insertions(+), 56 deletions(-) delete mode 100644 packages/dashboard/src/pages/api/getenv.tsx diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9e8e7dc2..2bb1b058 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -57,8 +57,6 @@ services: tipi-db: condition: service_healthy container_name: api - ports: - - 3001:3001 volumes: - ${PWD}/repos:/runtipi/repos:ro - ${PWD}/apps:/runtipi/apps @@ -103,14 +101,8 @@ services: depends_on: api: condition: service_started - ports: - - 3000:3000 networks: - tipi_main_network - environment: - INTERNAL_IP: ${INTERNAL_IP} - DOMAIN: ${DOMAIN} - NGINX_PORT: ${NGINX_PORT-80} volumes: - ${PWD}/packages/dashboard/src:/dashboard/src # - /dashboard/node_modules diff --git a/docker-compose.rc.yml b/docker-compose.rc.yml index c0ff8acb..8bfd635f 100644 --- a/docker-compose.rc.yml +++ b/docker-compose.rc.yml @@ -103,10 +103,7 @@ services: api: condition: service_started environment: - INTERNAL_IP: ${INTERNAL_IP} NODE_ENV: production - DOMAIN: ${DOMAIN} - NGINX_PORT: ${NGINX_PORT-80} labels: traefik.enable: true traefik.http.routers.dashboard-redirect.rule: PathPrefix("/") diff --git a/docker-compose.yml b/docker-compose.yml index 207db198..732280de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -104,10 +104,7 @@ services: api: condition: service_started environment: - INTERNAL_IP: ${INTERNAL_IP} NODE_ENV: production - DOMAIN: ${DOMAIN} - NGINX_PORT: ${NGINX_PORT-80} labels: traefik.enable: true traefik.http.routers.dashboard-redirect.rule: PathPrefix("/") diff --git a/packages/dashboard/src/hooks/useCachedRessources.ts b/packages/dashboard/src/hooks/useCachedRessources.ts index 0be1f1e8..9ee818e9 100644 --- a/packages/dashboard/src/hooks/useCachedRessources.ts +++ b/packages/dashboard/src/hooks/useCachedRessources.ts @@ -2,19 +2,14 @@ import { useEffect, useState } from 'react'; import { ApolloClient } from '@apollo/client'; import { createApolloClient } from '../core/apollo/client'; import { useSystemStore } from '../state/systemStore'; -import useSWR, { Fetcher } from 'swr'; -import { getUrl } from '../core/helpers/url-helpers'; interface IReturnProps { client?: ApolloClient; isLoadingComplete?: boolean; } -const fetcher: Fetcher<{ ip: string; domain: string; port: string }, string> = (...args) => fetch(...args).then((res) => res.json()); - export default function useCachedResources(): IReturnProps { - const { data } = useSWR(getUrl('api/getenv'), fetcher); - const { baseUrl, setBaseUrl, setInternalIp, setDomain } = useSystemStore(); + const { baseUrl, setBaseUrl } = useSystemStore(); const [isLoadingComplete, setLoadingComplete] = useState(false); const [client, setClient] = useState>(); @@ -32,23 +27,15 @@ export default function useCachedResources(): IReturnProps { } useEffect(() => { - const { ip, domain, port } = data || {}; + const hostname = window.location.hostname; + const port = window.location.port; - if (ip && !baseUrl) { - setInternalIp(ip); - setDomain(domain); - - if (!domain || domain === 'tipi.localhost') { - if (port === '80') { - setBaseUrl(`http://${ip}/api`); - } else { - setBaseUrl(`http://${ip}:${port}/api`); - } - } else { - setBaseUrl(`https://${domain}/api`); - } + if (!port) { + setBaseUrl(`http://${hostname}/api`); + } else { + setBaseUrl(`http://${hostname}:${port}/api`); } - }, [baseUrl, setBaseUrl, setInternalIp, setDomain, data]); + }, [setBaseUrl]); useEffect(() => { if (baseUrl) { diff --git a/packages/dashboard/src/modules/Apps/containers/AppDetails.tsx b/packages/dashboard/src/modules/Apps/containers/AppDetails.tsx index b5699edc..9b55c8d7 100644 --- a/packages/dashboard/src/modules/Apps/containers/AppDetails.tsx +++ b/packages/dashboard/src/modules/Apps/containers/AppDetails.tsx @@ -1,7 +1,6 @@ import { SlideFade, Flex, Divider, useDisclosure, useToast } from '@chakra-ui/react'; import React from 'react'; import { FiExternalLink } from 'react-icons/fi'; -import { useSystemStore } from '../../../state/systemStore'; import AppActions from '../components/AppActions'; import InstallModal from '../components/InstallModal'; import StopModal from '../components/StopModal'; @@ -48,8 +47,6 @@ const AppDetails: React.FC = ({ app, info }) => { const updateAvailable = Number(app?.updateInfo?.current || 0) < Number(app?.updateInfo?.latest); - const { internalIp } = useSystemStore(); - const handleError = (error: unknown) => { if (error instanceof Error) { toast({ @@ -139,7 +136,9 @@ const AppDetails: React.FC = ({ app, info }) => { const protocol = https ? 'https' : 'http'; if (typeof window !== 'undefined') { - window.open(`${protocol}://${internalIp}:${info.port}${info.url_suffix || ''}`, '_blank', 'noreferrer'); + // Current domain + const domain = window.location.hostname; + window.open(`${protocol}://${domain}:${info.port}${info.url_suffix || ''}`, '_blank', 'noreferrer'); } }; diff --git a/packages/dashboard/src/pages/api/getenv.tsx b/packages/dashboard/src/pages/api/getenv.tsx deleted file mode 100644 index 42740282..00000000 --- a/packages/dashboard/src/pages/api/getenv.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function getEnv(_: any, res: any) { - const { INTERNAL_IP } = process.env; - const { NGINX_PORT } = process.env; - const { DOMAIN } = process.env; - - res.status(200).json({ ip: INTERNAL_IP, domain: DOMAIN, port: NGINX_PORT }); -} diff --git a/packages/dashboard/src/state/systemStore.ts b/packages/dashboard/src/state/systemStore.ts index fb2a8453..e2f20f35 100644 --- a/packages/dashboard/src/state/systemStore.ts +++ b/packages/dashboard/src/state/systemStore.ts @@ -8,22 +8,14 @@ export enum SystemStatus { type Store = { baseUrl: string; - internalIp: string; - domain: string; status: SystemStatus; - setDomain: (domain?: string) => void; setBaseUrl: (url: string) => void; - setInternalIp: (ip: string) => void; setStatus: (status: SystemStatus) => void; }; export const useSystemStore = create((set) => ({ baseUrl: '', - internalIp: '', - domain: '', status: SystemStatus.RUNNING, - setDomain: (domain?: string) => set((state) => ({ ...state, domain: domain || '' })), setBaseUrl: (url: string) => set((state) => ({ ...state, baseUrl: url })), - setInternalIp: (ip: string) => set((state) => ({ ...state, internalIp: ip })), setStatus: (status: SystemStatus) => set((state) => ({ ...state, status })), })); diff --git a/packages/system-api/src/core/config/__tests__/TipiConfig.test.ts b/packages/system-api/src/core/config/__tests__/TipiConfig.test.ts index b47cf670..9fecf63f 100644 --- a/packages/system-api/src/core/config/__tests__/TipiConfig.test.ts +++ b/packages/system-api/src/core/config/__tests__/TipiConfig.test.ts @@ -48,7 +48,7 @@ describe('Test: setConfig', () => { expect(config).toBeDefined(); expect(config.appsRepoUrl).toBe(randomWord); - const settingsJson = readJsonFile('/runtipi/state/settings.json'); + const settingsJson = readJsonFile('/runtipi/state/settings.json'); expect(settingsJson).toBeDefined(); expect(settingsJson.appsRepoUrl).toBe(randomWord); diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh index 3cf9c63e..ccbb35b5 100755 --- a/scripts/start-dev.sh +++ b/scripts/start-dev.sh @@ -14,7 +14,7 @@ source "${BASH_SOURCE%/*}/common.sh" ROOT_FOLDER="${PWD}" STATE_FOLDER="${ROOT_FOLDER}/state" SED_ROOT_FOLDER="$(echo "$ROOT_FOLDER" | sed 's/\//\\\//g')" -NGINX_PORT=80 +NGINX_PORT=3000 NGINX_PORT_SSL=443 DOMAIN=tipi.localhost DNS_IP="9.9.9.9" # Default to Quad9 DNS From c505d418eafcaa984bcdccb089bfeef9059f4ee0 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 8 Nov 2022 19:25:08 +0100 Subject: [PATCH 6/6] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9bfdccf..c44ca286 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "runtipi", - "version": "0.7.2", + "version": "0.7.3", "description": "A homeserver for everyone", "scripts": { "prepare": "husky install",