Nicolas Meienberger vor 3 Jahren
Ursprung
Commit
6715880c01

+ 1 - 1
packages/common/src/types/app.types.ts

@@ -53,7 +53,7 @@ export interface AppConfig {
   description: string;
   version: string;
   image: string;
-  form_fields: Record<string, FormField>;
+  form_fields: FormField[];
   short_desc: string;
   author: string;
   source: string;

+ 2 - 0
packages/system-api/.eslintrc.cjs

@@ -14,5 +14,7 @@ module.exports = {
     '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,
   },
 };

+ 3 - 1
packages/system-api/package.json

@@ -29,6 +29,7 @@
     "cors": "^2.8.5",
     "dotenv": "^16.0.0",
     "express": "^4.17.3",
+    "graphql": "^16.5.0",
     "helmet": "^5.0.2",
     "internal-ip": "^6.0.0",
     "jsonwebtoken": "^8.5.1",
@@ -41,7 +42,8 @@
     "passport-http-bearer": "^1.0.1",
     "public-ip": "^5.0.0",
     "systeminformation": "^5.11.9",
-    "tcp-port-used": "^1.0.2"
+    "tcp-port-used": "^1.0.2",
+    "type-graphql": "^1.1.1"
   },
   "devDependencies": {
     "@types/compression": "^1.7.2",

+ 1 - 1
packages/system-api/src/config/apps.ts

@@ -18,4 +18,4 @@ export const appNames = [
   'homarr',
   'code-server',
   'calibre-web',
-];
+] as const;

+ 11 - 0
packages/system-api/src/modules/apps/apps.resolver.ts

@@ -0,0 +1,11 @@
+import { Query, Resolver } from 'type-graphql';
+import AppsService from './apps.service';
+import { ListAppsResonse } from './apps.types';
+
+@Resolver()
+export default class AppsResolver {
+  @Query(() => ListAppsResonse)
+  getChannels(): Promise<ListAppsResonse> {
+    return AppsService.listApps();
+  }
+}

+ 3 - 2
packages/system-api/src/modules/apps/apps.service.ts

@@ -2,6 +2,7 @@ import si from 'systeminformation';
 import { AppConfig, AppStatusEnum } from '@runtipi/common';
 import { createFolder, fileExists, readFile, readJsonFile } from '../fs/fs.helpers';
 import { checkAppExists, checkAppRequirements, checkEnvFile, ensureAppState, generateEnvFile, getAvailableApps, getInitalFormValues, getStateFile, runAppScript } from './apps.helpers';
+import { ListAppsResonse } from './apps.types';
 
 const startApp = async (appName: string): Promise<void> => {
   checkAppExists(appName);
@@ -43,7 +44,7 @@ const installApp = async (id: string, form: Record<string, string>): Promise<voi
   return Promise.resolve();
 };
 
-const listApps = async (): Promise<AppConfig[]> => {
+const listApps = async (): Promise<ListAppsResonse> => {
   const apps: AppConfig[] = getAvailableApps()
     .map((app) => {
       try {
@@ -65,7 +66,7 @@ const listApps = async (): Promise<AppConfig[]> => {
     app.description = readFile(`/apps/${app.id}/metadata/description.md`);
   });
 
-  return apps;
+  return { apps };
 };
 
 const getAppInfo = async (id: string): Promise<AppConfig> => {

+ 82 - 0
packages/system-api/src/modules/apps/apps.types.ts

@@ -0,0 +1,82 @@
+import { AppCategoriesEnum, AppStatusEnum, FieldTypes } from '@runtipi/common';
+import { Field, ObjectType } from 'type-graphql';
+
+@ObjectType()
+class FormField {
+  @Field()
+  type!: FieldTypes;
+
+  @Field()
+  label!: string;
+
+  @Field({ nullable: true })
+  max?: number;
+
+  @Field({ nullable: true })
+  min?: number;
+
+  @Field({ nullable: true })
+  hint?: string;
+
+  @Field({ nullable: true })
+  required?: boolean;
+
+  @Field()
+  env_variable!: string;
+}
+
+@ObjectType()
+class App {
+  @Field(() => String)
+  id!: string;
+
+  @Field(() => Boolean)
+  available!: boolean;
+
+  @Field(() => Number)
+  port!: number;
+
+  @Field(() => String)
+  name!: string;
+
+  @Field(() => String)
+  description!: string;
+
+  @Field(() => String, { nullable: true })
+  version?: string;
+
+  @Field(() => String)
+  image!: string;
+
+  @Field(() => String)
+  short_desc!: string;
+
+  @Field(() => String)
+  author!: string;
+
+  @Field(() => String)
+  source!: string;
+
+  @Field(() => Boolean)
+  installed!: boolean;
+
+  @Field(() => [AppCategoriesEnum])
+  categories!: AppCategoriesEnum[];
+
+  @Field(() => AppStatusEnum)
+  status!: AppStatusEnum;
+
+  @Field(() => String, { nullable: true })
+  url_suffix?: string;
+
+  @Field(() => [FormField])
+  form_fields?: FormField[];
+}
+
+@ObjectType()
+class ListAppsResonse {
+  @Field(() => [App])
+  apps!: App[];
+}
+
+export { ListAppsResonse, App };

+ 11 - 0
packages/system-api/src/schema.ts

@@ -0,0 +1,11 @@
+import { GraphQLSchema } from 'graphql';
+import { buildSchema } from 'type-graphql';
+import AppsResolver from './modules/apps/apps.resolver.ts';
+
+const createSchema = (): Promise<GraphQLSchema> =>
+  buildSchema({
+    resolvers: [AppsResolver],
+    validate: true,
+  });
+
+export { createSchema };

+ 2 - 1
packages/system-api/tsconfig.json

@@ -13,7 +13,8 @@
     "resolveJsonModule": true,
     "isolatedModules": false,
     "jsx": "preserve",
-    "incremental": true
+    "incremental": true,
+    "experimentalDecorators": true
   },
   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "jest.config.cjs"],
   "exclude": ["node_modules"]

+ 83 - 51
pnpm-lock.yaml

@@ -153,6 +153,7 @@ importers:
       eslint-plugin-import: ^2.26.0
       eslint-plugin-prettier: ^4.0.0
       express: ^4.17.3
+      graphql: ^16.5.0
       helmet: ^5.0.2
       internal-ip: ^6.0.0
       jest: ^28.1.0
@@ -171,6 +172,7 @@ importers:
       systeminformation: ^5.11.9
       tcp-port-used: ^1.0.2
       ts-jest: ^28.0.2
+      type-graphql: ^1.1.1
       typescript: 4.6.4
     dependencies:
       '@runtipi/common': file:packages/common
@@ -181,6 +183,7 @@ importers:
       cors: 2.8.5
       dotenv: 16.0.0
       express: 4.18.1
+      graphql: 16.5.0
       helmet: 5.0.2
       internal-ip: 6.2.0
       jsonwebtoken: 8.5.1
@@ -194,6 +197,7 @@ importers:
       public-ip: 5.0.0
       systeminformation: 5.11.14
       tcp-port-used: 1.0.2
+      type-graphql: 1.1.1_graphql@16.5.0
     devDependencies:
       '@types/compression': 1.7.2
       '@types/cookie-parser': 1.4.3
@@ -213,7 +217,7 @@ importers:
       eslint: 8.15.0
       eslint-config-airbnb-typescript: 17.0.0_c2ouaf3l4ivgkc6ae4nebvztom
       eslint-config-prettier: 8.5.0_eslint@8.15.0
-      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
+      eslint-plugin-import: 2.26.0_eslint@8.15.0
       eslint-plugin-prettier: 4.0.0_iqftbjqlxzn3ny5nablrkczhqi
       jest: 28.1.0
       nodemon: 2.0.16
@@ -2123,6 +2127,13 @@ packages:
       '@types/serve-static': 1.13.10
     dev: true
 
+  /@types/glob/7.2.0:
+    resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
+    dependencies:
+      '@types/minimatch': 3.0.5
+      '@types/node': 17.0.31
+    dev: false
+
   /@types/graceful-fs/4.1.5:
     resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
     dependencies:
@@ -2248,6 +2259,10 @@ packages:
     resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
     dev: true
 
+  /@types/minimatch/3.0.5:
+    resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
+    dev: false
+
   /@types/mock-fs/4.13.1:
     resolution: {integrity: sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==}
     dependencies:
@@ -2327,6 +2342,10 @@ packages:
   /@types/scheduler/0.16.2:
     resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
 
+  /@types/semver/7.3.10:
+    resolution: {integrity: sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw==}
+    dev: false
+
   /@types/serve-static/1.13.10:
     resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
     dependencies:
@@ -2974,8 +2993,6 @@ packages:
       raw-body: 2.5.1
       type-is: 1.6.18
       unpipe: 1.0.0
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /boxen/5.1.2:
@@ -3287,8 +3304,6 @@ packages:
       on-headers: 1.0.2
       safe-buffer: 5.1.2
       vary: 1.1.2
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /compute-scroll-into-view/1.0.14:
@@ -3441,35 +3456,13 @@ packages:
 
   /debug/2.6.9:
     resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
     dependencies:
       ms: 2.0.0
 
   /debug/3.2.7:
     resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
-    dependencies:
-      ms: 2.1.3
-    dev: true
-
-  /debug/3.2.7_supports-color@5.5.0:
-    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
     dependencies:
       ms: 2.1.3
-      supports-color: 5.5.0
     dev: true
 
   /debug/4.3.1:
@@ -4018,7 +4011,7 @@ packages:
     dependencies:
       confusing-browser-globals: 1.0.11
       eslint: 8.15.0
-      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
+      eslint-plugin-import: 2.26.0_eslint@8.15.0
       object.assign: 4.1.2
       object.entries: 1.1.5
       semver: 6.3.0
@@ -4051,7 +4044,7 @@ packages:
       '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
       eslint: 8.15.0
       eslint-config-airbnb-base: 15.0.0_gwd37gqv3vjv3xlpl7ju3ag2qu
-      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
+      eslint-plugin-import: 2.26.0_eslint@8.15.0
     dev: true
 
   /eslint-config-airbnb-typescript/17.0.0_r46exuh3jlhq2wmrnqx2ufqspa:
@@ -4119,8 +4112,6 @@ packages:
     dependencies:
       debug: 3.2.7
       resolve: 1.22.0
-    transitivePeerDependencies:
-      - supports-color
     dev: true
 
   /eslint-import-resolver-typescript/2.4.0_l3k33lf43msdtqtpwrwceacqke:
@@ -4141,6 +4132,14 @@ packages:
       - supports-color
     dev: true
 
+  /eslint-module-utils/2.7.3:
+    resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
+    engines: {node: '>=4'}
+    dependencies:
+      debug: 3.2.7
+      find-up: 2.1.0
+    dev: true
+
   /eslint-module-utils/2.7.3_sysdrzuw2ki4kxpuwc4tznw2ha:
     resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
     engines: {node: '>=4'}
@@ -4225,24 +4224,19 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-import/2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy:
+  /eslint-plugin-import/2.26.0_eslint@8.15.0:
     resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
     engines: {node: '>=4'}
     peerDependencies:
-      '@typescript-eslint/parser': '*'
       eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
-    peerDependenciesMeta:
-      '@typescript-eslint/parser':
-        optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
       array-includes: 3.1.5
       array.prototype.flat: 1.3.0
       debug: 2.6.9
       doctrine: 2.1.0
       eslint: 8.15.0
       eslint-import-resolver-node: 0.3.6
-      eslint-module-utils: 2.7.3_wex3ustmkv4ospy3s77r6ihlwq
+      eslint-module-utils: 2.7.3
       has: 1.0.3
       is-core-module: 2.9.0
       is-glob: 4.0.3
@@ -4250,10 +4244,6 @@ packages:
       object.values: 1.1.5
       resolve: 1.22.0
       tsconfig-paths: 3.14.1
-    transitivePeerDependencies:
-      - eslint-import-resolver-typescript
-      - eslint-import-resolver-webpack
-      - supports-color
     dev: true
 
   /eslint-plugin-import/2.26.0_hhyjdrupy4c2vgtpytri6cjwoy:
@@ -4616,8 +4606,6 @@ packages:
       type-is: 1.6.18
       utils-merge: 1.0.1
       vary: 1.1.2
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /extend/3.0.2:
@@ -4700,8 +4688,6 @@ packages:
       parseurl: 1.3.3
       statuses: 2.0.1
       unpipe: 1.0.0
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /find-root/1.1.0:
@@ -5018,6 +5004,29 @@ packages:
     resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
     dev: true
 
+  /graphql-query-complexity/0.7.2_graphql@16.5.0:
+    resolution: {integrity: sha512-+VgmrfxGEjHI3zuojWOR8bsz7Ycz/BZjNjxnlUieTz5DsB92WoIrYCSZdWG7UWZ3rfcA1Gb2Nf+wB80GsaZWuQ==}
+    peerDependencies:
+      graphql: ^0.13.0 || ^14.0.0 || ^15.0.0
+    dependencies:
+      graphql: 16.5.0
+      lodash.get: 4.4.2
+    dev: false
+
+  /graphql-subscriptions/1.2.1_graphql@16.5.0:
+    resolution: {integrity: sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==}
+    peerDependencies:
+      graphql: ^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
+    dependencies:
+      graphql: 16.5.0
+      iterall: 1.3.0
+    dev: false
+
+  /graphql/16.5.0:
+    resolution: {integrity: sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==}
+    engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+    dev: false
+
   /has-bigints/1.0.2:
     resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
     dev: true
@@ -5521,6 +5530,10 @@ packages:
       istanbul-lib-report: 3.0.0
     dev: true
 
+  /iterall/1.3.0:
+    resolution: {integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==}
+    dev: false
+
   /jest-changed-files/28.0.2:
     resolution: {integrity: sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA==}
     engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
@@ -6212,6 +6225,10 @@ packages:
       p-locate: 4.1.0
     dev: true
 
+  /lodash.get/4.4.2:
+    resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
+    dev: false
+
   /lodash.includes/4.3.0:
     resolution: {integrity: sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=}
     dev: false
@@ -7031,7 +7048,7 @@ packages:
     requiresBuild: true
     dependencies:
       chokidar: 3.5.3
-      debug: 3.2.7_supports-color@5.5.0
+      debug: 3.2.7
       ignore-by-default: 1.0.1
       minimatch: 3.1.2
       pstree.remy: 1.1.8
@@ -8153,8 +8170,6 @@ packages:
       on-finished: 2.4.1
       range-parser: 1.2.1
       statuses: 2.0.1
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /serve-static/1.15.0:
@@ -8165,8 +8180,6 @@ packages:
       escape-html: 1.0.3
       parseurl: 1.3.3
       send: 0.18.0
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /set-blocking/2.0.0:
@@ -8670,6 +8683,25 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /type-graphql/1.1.1_graphql@16.5.0:
+    resolution: {integrity: sha512-iOOWVn0ehCYMukmnXStbkRwFE9dcjt7/oDcBS1JyQZo9CbhlIll4lHHps54HMEk4A4c8bUPd+DjK8w1/ZrxB4A==}
+    engines: {node: '>= 10.3'}
+    requiresBuild: true
+    peerDependencies:
+      class-validator: '>=0.12.0'
+      graphql: ^15.3.0
+    dependencies:
+      '@types/glob': 7.2.0
+      '@types/node': 17.0.31
+      '@types/semver': 7.3.10
+      glob: 7.2.0
+      graphql: 16.5.0
+      graphql-query-complexity: 0.7.2_graphql@16.5.0
+      graphql-subscriptions: 1.2.1_graphql@16.5.0
+      semver: 7.3.7
+      tslib: 2.4.0
+    dev: false
+
   /type-is/1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}