diff --git a/extensions/api.d.ts b/extensions/api.d.ts index caa263b9..6eec9715 100644 --- a/extensions/api.d.ts +++ b/extensions/api.d.ts @@ -1,4 +1,6 @@ +import APIError from '@heyputer/backend/src/api/APIError.js'; import type { WebServerService } from '@heyputer/backend/src/modules/web/WebServerService.js'; +import query from '@heyputer/backend/src/om/query/query'; import type { Actor } from '@heyputer/backend/src/services/auth/Actor.js'; import type { BaseDatabaseAccessService } from '@heyputer/backend/src/services/database/BaseDatabaseAccessService.d.ts'; import type { MeteringService } from '@heyputer/backend/src/services/MeteringService/MeteringService.ts'; @@ -7,14 +9,11 @@ import type { DBKVStore } from '@heyputer/backend/src/services/repositories/DBKV import type { SUService } from '@heyputer/backend/src/services/SUService.js'; import type { IUser } from '@heyputer/backend/src/services/User.js'; import type { UserService } from '@heyputer/backend/src/services/UserService.d.ts'; +import { Context } from '@heyputer/backend/src/util/context.js'; import type { RequestHandler } from 'express'; import type FSNodeContext from '../src/backend/src/filesystem/FSNodeContext.js'; import type helpers from '../src/backend/src/helpers.js'; import type * as ExtensionControllerExports from './ExtensionController/src/ExtensionController.ts'; -import { Context } from '@heyputer/backend/src/util/context.js'; -import config from '../volatile/config/config.json' -import APIError from '@heyputer/backend/src/api/APIError.js'; -import query from '@heyputer/backend/src/om/query/query'; declare global { namespace Express { @@ -40,22 +39,18 @@ interface EndpointOptions { } // Driver interface types -type ParameterDefinition = { +interface ParameterDefinition { type: 'string' | 'number' | 'boolean' | 'object' | 'array'; optional: boolean; -}; -type MethodDefinition = { +} +interface MethodDefinition { description: string; parameters: Record; -}; -type DriverInterface = { +} +interface DriverInterface { description: string; methods: Record; -}; - - - - +} type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'; @@ -97,12 +92,12 @@ interface Extension extends RouterMethods { run(label: string, fn: () => T): T; run(fn: () => T): T; }, - config: Record, + config: Record, on(event: string, listener: (...args: T) => void): void, // TODO DS: type events better - on(event: 'create.drivers', listener: (event: {createDriver: (interface: string, service: string, executors: any)=>any}) => void), - on(event: 'create.permissions', listener: (event: {grant_to_everyone: (permission: string) => void, grant_to_users: (permission: string) => void})=>void) - on(event: 'create.interfaces', listener: (event: {createInterface: (interface: string, interfaces: DriverInterface) => void}) => void) - import(module: 'data'): { db: BaseDatabaseAccessService, kv: DBKVStore & {get: (string) => void, set: (string, string) => void}, cache: unknown }// TODO DS: type cache better + on(event: 'create.drivers', listener: (event: { createDriver: (interface: string, service: string, executors: any) => any }) => void), + on(event: 'create.permissions', listener: (event: { grant_to_everyone: (permission: string) => void, grant_to_users: (permission: string) => void }) => void) + on(event: 'create.interfaces', listener: (event: { createInterface: (interface: string, interfaces: DriverInterface) => void }) => void) + import(module: 'data'): { db: BaseDatabaseAccessService, kv: DBKVStore & { get: (string) => void, set: (string, string) => void }, cache: unknown }// TODO DS: type cache better import(module: 'core'): CoreRuntimeModule, import(module: 'fs'): FilesystemModule, import(module: 'query'): typeof query, diff --git a/extensions/app-telemetry/app-user-count.ts b/extensions/app-telemetry/app-user-count.ts index 912bc5ba..fa04e342 100644 --- a/extensions/app-telemetry/app-user-count.ts +++ b/extensions/app-telemetry/app-user-count.ts @@ -1,9 +1,7 @@ -const { Eq } = extension.import('query') -const { kv } = extension.import('data'); -const span = extension.span; +const { Eq } = extension.import('query'); const { db } = extension.import('data'); -const { Context, APIError } = extension.import('core'); -const app_es: any = extension.import('service:es:app'); +const { APIError } = extension.import('core'); +const app_es = extension.import('service:es:app') as any; extension.on('create.interfaces', (event) => { event.createInterface('app-telemetry', { @@ -22,8 +20,8 @@ extension.on('create.interfaces', (event) => { }, offset: { type: 'number', - optional: true - } + optional: true, + }, }, }, user_count: { @@ -32,47 +30,45 @@ extension.on('create.interfaces', (event) => { app_uuid: { type: 'string', optional: false, - } + }, }, - } + }, }, }); }); extension.on('create.drivers', event => { event.createDriver('app-telemetry', 'app-telemetry', { - async get_users({ app_uuid, limit = 100, offset = 0 }: {app_uuid: string, limit: number, offset: number}) { + async get_users ({ app_uuid, limit = 100, offset = 0 }: { app_uuid: string, limit: number, offset: number }) { // first lets make sure executor owns this app const [result] = (await app_es.select({ predicate: new Eq({ key: 'uid', value: app_uuid }) })); - if (!result) { + if ( ! result ) { throw APIError.create('permission_denied'); } // Fetch and return users - const users: Array<{username: string, uuid: string}> = await db.read( - `SELECT user.username, user.uuid FROM user_to_app_permissions + const users: Array<{ username: string, uuid: string }> = await db.read(`SELECT user.username, user.uuid FROM user_to_app_permissions INNER JOIN user ON user_to_app_permissions.user_id = user.id WHERE permission = 'flag:app-is-authenticated' AND app_id=? ORDER BY (dt IS NOT NULL), dt, user_id LIMIT ? OFFSET ?`, - [result.private_meta.mysql_id, limit, offset], - ); - return users.map(e=>{return {user: e.username, user_uuid: e.uuid}}); + [result.private_meta.mysql_id, limit, offset]); + return users.map(e => { + return { user: e.username, user_uuid: e.uuid }; + }); }, - async user_count({ app_uuid }: {app_uuid: string}) { + async user_count ({ app_uuid }: { app_uuid: string }) { // first lets make sure executor owns this app const [result] = (await app_es.select({ predicate: new Eq({ key: 'uid', value: app_uuid }) })); - if (!result) { + if ( ! result ) { throw APIError.create('permission_denied'); } // Fetch and return authenticated user count - const [data] = await db.read( - `SELECT count(*) FROM user_to_app_permissions + const [data] = await db.read(`SELECT count(*) FROM user_to_app_permissions WHERE permission = 'flag:app-is-authenticated' AND app_id=?;`, - [result.private_meta.mysql_id], - ); + [result.private_meta.mysql_id]); const count = data['count(*)']; return count; - } + }, }); }); diff --git a/extensions/app-telemetry/index.d.ts b/extensions/app-telemetry/index.d.ts new file mode 100644 index 00000000..2fc1c14d --- /dev/null +++ b/extensions/app-telemetry/index.d.ts @@ -0,0 +1 @@ +import '../api.js'; \ No newline at end of file diff --git a/extensions/app-telemetry/tsconfig.json b/extensions/app-telemetry/tsconfig.json index 2616e0df..6b590838 100644 --- a/extensions/app-telemetry/tsconfig.json +++ b/extensions/app-telemetry/tsconfig.json @@ -12,7 +12,8 @@ "sourceMap": true, }, "include": [ - "app-user-count.ts", + "./**/*.ts", + "./**/*.d.ts" ], "exclude": [ "**/*.test.ts", diff --git a/tests/ci/api-test.py b/tests/ci/api-test.py index 695a87d3..e896c356 100755 --- a/tests/ci/api-test.py +++ b/tests/ci/api-test.py @@ -54,16 +54,19 @@ def run(): # ========================================================================= cxc_toolkit.exec.run_command("npm install") common.init_backend_config() - admin_password = common.get_admin_password() update_server_config() # ========================================================================= # config client # ========================================================================= - cxc_toolkit.exec.run_background("npm start") - # wait 10s for the server to start + cxc_toolkit.exec.run_background( + "npm start", work_dir=common.PUTER_ROOT, log_path="/tmp/backend.log" + ) + admin_password = common.get_admin_password() + # wait 10 more sec for the server to start time.sleep(10) + token = common.get_token(admin_password) common.init_client_config(token) diff --git a/tests/ci/common.py b/tests/ci/common.py index 32812942..e6f7dc90 100644 --- a/tests/ci/common.py +++ b/tests/ci/common.py @@ -30,25 +30,20 @@ def get_admin_password() -> str: """ Get the admin password from the backend server, throw an error if not found. """ - LOG_PATH = "/tmp/backend.log" - backend_process = cxc_toolkit.exec.run_background("npm start", log_path=LOG_PATH) - - # NB: run_command + kill_on_output may wait indefinitely, use run_background + hard limit instead - time.sleep(10) - - backend_process.terminate() - - # read the log file - with open(LOG_PATH, "r") as f: - lines = f.readlines() - for line in lines: - if "password for admin" in line: - print(f"found password line: ---{line}---") - admin_password = line.split("password for admin is:")[1].strip() - print(f"Extracted admin password: {admin_password}") - return admin_password - - raise RuntimeError(f"no admin password found, check {LOG_PATH} for details") + for attempt in range(60): # wait up to 60 seconds (1 minute) + time.sleep(1) + + # read the log file + with open("/tmp/backend.log", "r") as f: + lines = f.readlines() + for line in lines: + if "password for admin" in line: + print(f"found password line: ---{line}---") + admin_password = line.split("password for admin is:")[1].strip() + print(f"Extracted admin password: {admin_password}") + return admin_password + + raise RuntimeError(f"no admin password found after 60 seconds, check {LOG_PATH} for details") def get_token(admin_password: str) -> str: diff --git a/tests/ci/vitest.py b/tests/ci/vitest.py index e2155389..72758435 100755 --- a/tests/ci/vitest.py +++ b/tests/ci/vitest.py @@ -15,23 +15,21 @@ def run(): # clean port 4100 for backend server cxc_toolkit.exec.run_command("fuser -k 4100/tcp", ignore_failure=True) - # clean port 50052 for fs-tree-manager server - cxc_toolkit.exec.run_command("fuser -k 50052/tcp", ignore_failure=True) # ========================================================================= # config server # ========================================================================= cxc_toolkit.exec.run_command("npm install") common.init_backend_config() - admin_password = common.get_admin_password() # ========================================================================= # start backend server # ========================================================================= cxc_toolkit.exec.run_background( - "npm start", work_dir=common.PUTER_ROOT, log_path="/tmp/backend.log" + "npm start", work_dir=common.PUTER_ROOT, log_path="/tmp/backend.log" ) - # wait 10s for the server to start + admin_password = common.get_admin_password() + # wait 10 more sec for the server to start time.sleep(10) # =========================================================================