fix: types and tests? (#2193)

This commit is contained in:
Daniel Salazar
2025-12-17 13:11:51 -08:00
committed by GitHub
parent d76adcf831
commit a6617527db
7 changed files with 59 additions and 70 deletions

33
extensions/api.d.ts vendored
View File

@@ -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<string, ParameterDefinition>;
};
type DriverInterface = {
}
interface DriverInterface {
description: string;
methods: Record<string, MethodDefinition>;
};
}
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
@@ -97,12 +92,12 @@ interface Extension extends RouterMethods {
run<T>(label: string, fn: () => T): T;
run<T>(fn: () => T): T;
},
config: Record<string | number | symbol, any>,
config: Record<string | number | symbol, any>,
on<T extends unknown[]>(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,

View File

@@ -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;
}
},
});
});

1
extensions/app-telemetry/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
import '../api.js';

View File

@@ -12,7 +12,8 @@
"sourceMap": true,
},
"include": [
"app-user-count.ts",
"./**/*.ts",
"./**/*.d.ts"
],
"exclude": [
"**/*.test.ts",

View File

@@ -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)

View File

@@ -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:

View File

@@ -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)
# =========================================================================