diff --git a/mods/mods_available/kdmod/CustomPuterService.js b/mods/mods_available/kdmod/CustomPuterService.js index 51f5685f14..45266315e0 100644 --- a/mods/mods_available/kdmod/CustomPuterService.js +++ b/mods/mods_available/kdmod/CustomPuterService.js @@ -20,9 +20,6 @@ const path = require('path'); class CustomPuterService extends use.Service { async _init () { - const svc_commands = this.services.get('commands'); - this._register_commands(svc_commands); - const svc_puterHomepage = this.services.get('puter-homepage'); svc_puterHomepage.register_script('/custom-gui/main.js'); } @@ -36,17 +33,6 @@ class CustomPuterService extends use.Service { express.static(path.join(__dirname, 'gui')), ); } - _register_commands (commands) { - commands.registerCommands('o', [ - { - id: 'k', - description: '', - handler: async (_, log) => { - log.log('kdmod is enabled'); - }, - }, - ]); - } } module.exports = { CustomPuterService }; diff --git a/mods/mods_available/kdmod/ShareTestService.js b/mods/mods_available/kdmod/ShareTestService.js index 8eb434245a..c4854e8f51 100644 --- a/mods/mods_available/kdmod/ShareTestService.js +++ b/mods/mods_available/kdmod/ShareTestService.js @@ -42,8 +42,6 @@ class ShareTestService extends use.Service { }; async _init () { - const svc_commands = this.services.get('commands'); - this._register_commands(svc_commands); this.scenarios = require('./data/sharetest_scenarios'); @@ -51,29 +49,6 @@ class ShareTestService extends use.Service { this.db = svc_db.get(svc_db.DB_WRITE, 'share-test'); } - _register_commands (commands) { - commands.registerCommands('share-test', [ - { - id: 'start', - description: '', - handler: async (_, log) => { - const results = await this.runit(); - - for ( const result of results ) { - log.log(`=== ${result.title} ===`); - if ( ! result.report ) { - log.log('\x1B[32;1mSUCCESS\x1B[0m'); - continue; - } - log.log('\x1B[31;1mSTOPPED\x1B[0m at ' + - `${result.report.step}: ${ - result.report.report.message}`); - } - }, - }, - ]); - } - async runit () { await this.teardown_(); await this.setup_(); @@ -127,17 +102,19 @@ class ShareTestService extends use.Service { } async create_test_user_ (username) { - await this.db.write(` + await this.db.write( + ` INSERT INTO user (uuid, username, email, free_storage, password) VALUES (?, ?, ?, ?, ?) `, - [ - this.modules.uuidv4(), - username, - `${username}@example.com`, - 1024 * 1024 * 500, // 500 MiB - this.modules.uuidv4(), - ]); + [ + this.modules.uuidv4(), + username, + `${username}@example.com`, + 1024 * 1024 * 500, // 500 MiB + this.modules.uuidv4(), + ], + ); const user = await get_user({ username }); const svc_user = this.services.get('user'); await svc_user.generate_default_fsentries({ user }); @@ -152,16 +129,18 @@ class ShareTestService extends use.Service { } // API for scenarios - async ['__scenario:create-example-file'] ( + async '__scenario:create-example-file' ( { actor, user }, { name, contents }, ) { const svc_fs = this.services.get('filesystem'); const parent = await svc_fs.node(new NodePathSelector(`/${user.username}/Desktop`)); - console.log('test -> create-example-file', - user, - name, - contents); + console.log( + 'test -> create-example-file', + user, + name, + contents, + ); const buffer = Buffer.from(contents); const file = { size: buffer.length, @@ -178,7 +157,7 @@ class ShareTestService extends use.Service { file, }); } - async ['__scenario:assert-no-access'] ( + async '__scenario:assert-no-access' ( { actor, user }, { path }, ) { @@ -197,14 +176,14 @@ class ShareTestService extends use.Service { return { message: 'expected error, got none' }; } } - async ['__scenario:grant'] ( + async '__scenario:grant' ( { actor, user }, { to, permission }, ) { const svc_permission = this.services.get('permission'); await svc_permission.grant_user_user_permission(actor, to, permission, {}, {}); } - async ['__scenario:assert-access'] ( + async '__scenario:assert-access' ( { actor, user }, { path, level }, ) { diff --git a/src/backend/src/modules/core/AlarmService.js b/src/backend/src/modules/core/AlarmService.js index dce7f5b312..16e9053f21 100644 --- a/src/backend/src/modules/core/AlarmService.js +++ b/src/backend/src/modules/core/AlarmService.js @@ -59,13 +59,6 @@ class AlarmService extends BaseService { } - /** - * AlarmService registers its commands at the consolidation phase because - * the '_init' method of CommandService may not have been called yet. - */ - '__on_boot.consolidation' () { - } - adapt_id_ (id) { let shorten = true; diff --git a/src/backend/src/modules/core/PagerService.js b/src/backend/src/modules/core/PagerService.js index e40be7308a..8d425e64c8 100644 --- a/src/backend/src/modules/core/PagerService.js +++ b/src/backend/src/modules/core/PagerService.js @@ -40,13 +40,6 @@ class PagerService extends BaseService { } - /** - * PagerService registers its commands at the consolidation phase because - * the '_init' method of CommandService may not have been called yet. - */ - '__on_boot.consolidation' () { - } - /** * Initializes the PagerService instance by setting the configuration and * initializing an empty alert handler array. diff --git a/src/backend/src/modules/test-core/TestCoreModule.js b/src/backend/src/modules/test-core/TestCoreModule.js index 5711fced3b..4cf10b92c3 100644 --- a/src/backend/src/modules/test-core/TestCoreModule.js +++ b/src/backend/src/modules/test-core/TestCoreModule.js @@ -5,7 +5,6 @@ import { AuthService } from '../../services/auth/AuthService.js'; import { GroupService } from '../../services/auth/GroupService.js'; import { PermissionService } from '../../services/auth/PermissionService.js'; import { TokenService } from '../../services/auth/TokenService.js'; -import { CommandService } from '../../services/CommandService.js'; import { SqliteDatabaseAccessService } from '../../services/database/SqliteDatabaseAccessService.js'; import { DetailProviderService } from '../../services/DetailProviderService.js'; import { DynamoKVStoreWrapper } from '../../services/DynamoKVStore/DynamoKVStoreWrapper.js'; diff --git a/src/backend/src/services/CommandService.js b/src/backend/src/services/CommandService.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/src/services/CommandService.test.ts b/src/backend/src/services/CommandService.test.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/src/services/ai/chat/providers/OpenRouterProvider/OpenRouterProvider.ts b/src/backend/src/services/ai/chat/providers/OpenRouterProvider/OpenRouterProvider.ts index 668bae3561..57685c822b 100644 --- a/src/backend/src/services/ai/chat/providers/OpenRouterProvider/OpenRouterProvider.ts +++ b/src/backend/src/services/ai/chat/providers/OpenRouterProvider/OpenRouterProvider.ts @@ -158,48 +158,47 @@ export class OpenRouterProvider implements IChatProvider { } async models () { - return [] as IChatModel[]; - // let models = kv.get('openrouterChat:models'); - // if ( ! models ) { - // try { - // const resp = await axios.request({ - // method: 'GET', - // url: `${this.#apiBaseUrl}/models`, - // }); - - // models = resp.data.data; - // kv.set('openrouterChat:models', models); - // } catch (e) { - // console.log(e); - // } - // } - // const coerced_models: IChatModel[] = []; - // for ( const model of models ) { - // if ( (model.id as string).includes('openrouter/auto') ) { - // continue; - // } - // const overridenModel = OPEN_ROUTER_MODEL_OVERRIDES.find(m => m.id === `openrouter:${model.id}`); - // const microcentCosts = Object.fromEntries(Object.entries(model.pricing).map(([k, v]) => [k, Math.round((v as number < 0 ? 1 : v as number) * 1_000_000 * 100)])) ; - // if ( ! microcentCosts.request ) { - // microcentCosts.request = 0; - // } - // coerced_models.push({ - // id: `openrouter:${model.id}`, - // name: `${model.name} (OpenRouter)`, - // aliases: [model.id, model.name, `openrouter/${model.id}`, model.id.split('/').slice(1).join('/')], - // context: model.context_length, - // max_tokens: model.top_provider.max_completion_tokens, - // costs_currency: 'usd-cents', - // input_cost_key: 'prompt', - // output_cost_key: 'completion', - // costs: { - // tokens: 1_000_000, - // ...microcentCosts, - // }, - // ...overridenModel, - // }); - // } - // return coerced_models; + let models = kv.get('openrouterChat:models'); + if ( ! models ) { + try { + const resp = await axios.request({ + method: 'GET', + url: `${this.#apiBaseUrl}/models`, + }); + + models = resp.data.data; + kv.set('openrouterChat:models', models); + } catch (e) { + console.log(e); + } + } + const coerced_models: IChatModel[] = []; + for ( const model of models ) { + if ( (model.id as string).includes('openrouter/auto') ) { + continue; + } + const overridenModel = OPEN_ROUTER_MODEL_OVERRIDES.find(m => m.id === `openrouter:${model.id}`); + const microcentCosts = Object.fromEntries(Object.entries(model.pricing).map(([k, v]) => [k, Math.round((v as number < 0 ? 1 : v as number) * 1_000_000 * 100)])) ; + if ( ! microcentCosts.request ) { + microcentCosts.request = 0; + } + coerced_models.push({ + id: `openrouter:${model.id}`, + name: `${model.name} (OpenRouter)`, + aliases: [model.id, model.name, `openrouter/${model.id}`, model.id.split('/').slice(1).join('/')], + context: model.context_length, + max_tokens: model.top_provider.max_completion_tokens, + costs_currency: 'usd-cents', + input_cost_key: 'prompt', + output_cost_key: 'completion', + costs: { + tokens: 1_000_000, + ...microcentCosts, + }, + ...overridenModel, + }); + } + return coerced_models; } checkModeration (_text: string): ReturnType { throw new Error('Method not implemented.'); diff --git a/src/backend/src/services/periodic/FSEntryMigrateService.js b/src/backend/src/services/periodic/FSEntryMigrateService.js deleted file mode 100644 index 2f636ab51e..0000000000 --- a/src/backend/src/services/periodic/FSEntryMigrateService.js +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2024-present Puter Technologies Inc. - * - * This file is part of Puter. - * - * Puter is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -const seedrandom = require('seedrandom'); -const { id2path, get_user } = require('../../helpers'); -const { generate_random_code } = require('../../util/identifier'); -const { DB_MODE_WRITE } = require('../MysqlAccessService'); -const { DB_MODE_READ } = require('../MysqlAccessService'); - -/** -* Base Job class for handling migration tasks in the FSEntryMigrateService. -* Provides common functionality for managing job state (green/yellow/red), -* progress tracking, and graceful stopping of migration jobs. -* Contains methods for state management, progress visualization, -* and controlled execution flow. -*/ -class Job { - static STATE_GREEN = {}; - static STATE_YELLOW = {}; - static STATE_RED = {}; - constructor ({ dbrr, dbrw, log }) { - this.dbrr = dbrr; - this.dbrw = dbrw; - this.log = log; - this.state = this.constructor.STATE_RED; - } - /** - * Checks if the job should stop based on its current state - * @returns {boolean} True if the job should stop, false if it can continue - * @private - */ - maybe_stop_ () { - if ( this.state !== this.constructor.STATE_GREEN ) { - this.log.info('Stopping job'); - this.state = this.constructor.STATE_RED; - return true; - } - return false; - } - /** - * Sets the job state to YELLOW, which means it will stop as soon as possible - * (generally after the current batch of work being processed) - */ - stop () { - this.state = this.constructor.STATE_YELLOW; - } - set_progress (progress) { - // Progress bar string to display migration progress in the console - let bar = ''; - // Width of the progress bar display in characters - const WIDTH = 30; - const N = Math.floor(WIDTH * progress); - for ( let i = 0 ; i < WIDTH ; i++ ) { - if ( i < N ) { - bar += '='; - } else { - bar += ' '; - } - } - this.log.info(`${this.constructor.name} :: [${bar}] ${progress.toFixed(2)}%`); - } -} - -/** -* @class Mig_StorePath -* @extends Job -* @description Handles the migration of file system entries to include path information. -* This class processes fsentries that don't have path data set, calculating and storing -* their full paths in batches. It includes rate limiting and progress tracking to prevent -* server overload during migration. -*/ -class Mig_StorePath extends Job { - /** - * Handles migration of file system entries to update storage paths - * @param {Object} args - Command line arguments for the migration - * @param {string[]} args.verbose - If --verbose is included, logs detailed path info - * @returns {Promise} Resolves when migration is complete - * - * Migrates fsentry records that have null paths by: - * - Processing entries in batches of 50 - * - Converting UUIDs to full paths - * - Updating the path column in the database - * - Includes throttling between batches to reduce server load - */ - async start (args) { - this.state = this.constructor.STATE_GREEN; - const { dbrr, dbrw, log } = this; - - for ( ;; ) { - const t_0 = performance.now(); - const [fsentries] = await dbrr.promise().execute( - 'SELECT id, uuid FROM fsentries WHERE path IS NULL ORDER BY accessed DESC LIMIT 50', - ); - - if ( fsentries.length === 0 ) { - log.info('No more fsentries to migrate'); - this.state = this.constructor.STATE_RED; - return; - } - log.info(`Running migration on ${fsentries.length} fsentries`); - - for ( let i = 0 ; i < fsentries.length ; i++ ) { - const fsentry = fsentries[i]; - let path; - try { - path = await id2path(fsentry.uuid); - } catch (e) { - // This happens when an fsentry has a missing parent - log.error(e); - continue; - } - if ( args.includes('--verbose') ) { - log.info(`id=${fsentry.id} uuid=${fsentry.uuid} path=${path}`); - } - await dbrw.promise().execute( - 'UPDATE fsentries SET path=? WHERE id=?', - [path, fsentry.id], - ); - } - - const t_1 = performance.now(); - - // Give the server a break for twice the time it took to execute the query, - // or 100ms at least. - const time_to_wait = Math.max(100, 2 * (t_1 - t_0)); - - if ( this.maybe_stop_() ) return; - - log.info(`Waiting for ${time_to_wait.toFixed(2)}ms`); - await new Promise(rslv => setTimeout(rslv, time_to_wait)); - - if ( this.maybe_stop_() ) return; - } - } -} - -/** -* @class Mig_IndexAccessed -* @extends Job -* @description Migration job that updates the 'accessed' timestamp for file system entries. -* Sets the 'accessed' field to match the 'created' timestamp for entries where 'accessed' is NULL. -* Processes entries in batches of 10000 to avoid overloading the database, with built-in delays -* between batches for server load management. -*/ -class Mig_IndexAccessed extends Job { - /** - * Migrates fsentries to include 'accessed' timestamps by setting null values to their 'created' time - * @param {Array} args - Command line arguments passed to the migration - * @returns {Promise} - * - * Processes fsentries in batches of 10000, updating any null 'accessed' fields - * to match their 'created' timestamp. Includes built-in delays between batches - * to reduce server load. Continues until no more records need updating. - */ - async start (args) { - this.state = this.constructor.STATE_GREEN; - const { dbrr, dbrw, log } = this; - - for ( ;; ) { - log.info('Running update statement'); - const t_0 = performance.now(); - const [results] = await dbrr.promise().execute( - 'UPDATE fsentries SET accessed = COALESCE(accessed, created) WHERE accessed IS NULL LIMIT 10000', - ); - log.info(`Updated ${results.affectedRows} rows`); - - if ( results.affectedRows === 0 ) { - log.info('No more fsentries to migrate'); - this.state = this.constructor.STATE_RED; - return; - } - - const t_1 = performance.now(); - - // Give the server a break for twice the time it took to execute the query, - // or 100ms at least. - const time_to_wait = Math.max(100, 2 * (t_1 - t_0)); - - if ( this.maybe_stop_() ) return; - - log.info(`Waiting for ${time_to_wait.toFixed(2)}ms`); - await new Promise(rslv => setTimeout(rslv, time_to_wait)); - - if ( this.maybe_stop_() ) return; - } - } -} - -/** -* @class Mig_FixTrash -* @extends Job -* @description Migration job that ensures each user has a Trash directory in their root folder. -* Creates missing Trash directories with proper UUIDs, updates user records with trash_uuid, -* and sets appropriate timestamps and permissions. The Trash directory is marked as immutable -* and is created with standardized path '/Trash'. -*/ -class Mig_FixTrash extends Job { - /** - * Handles migration to fix missing Trash directories for users - * Creates a new Trash directory and updates necessary records if one doesn't exist - * - * @param {Array} args - Command line arguments passed to the migration - * @returns {Promise} Resolves when migration is complete - * - * @description - * - Identifies users without a Trash directory - * - Creates new Trash directory with UUID for each user - * - Updates user table with new trash_uuid - * - Includes throttling between operations to reduce server load - */ - async start (args) { - const { v4: uuidv4 } = require('uuid'); - - this.state = this.constructor.STATE_GREEN; - const { dbrr, dbrw, log } = this; - - const SQL_NOTRASH_USERS = ` - SELECT parent.name, parent.uuid FROM fsentries AS parent - WHERE parent_uid IS NULL - AND NOT EXISTS ( - SELECT 1 FROM fsentries AS child - WHERE child.parent_uid = parent.uuid - AND child.name = 'Trash' - ) - `; - - let [user_dirs] = await dbrr.promise().execute(SQL_NOTRASH_USERS); - - for ( const { name, uuid } of user_dirs ) { - const username = name; - const user_dir_uuid = uuid; - - const t_0 = performance.now(); - const user = await get_user({ username }); - const trash_uuid = uuidv4(); - const trash_ts = Date.now() / 1000; - log.info(`Fixing trash for user ${user.username} ${user.id} ${user_dir_uuid} ${trash_uuid} ${trash_ts}`); - - const insert_res = await dbrw.promise().execute(` - INSERT INTO fsentries - (uuid, parent_uid, user_id, name, path, is_dir, created, modified, immutable) - VALUES - ( ?, ?, ?, ?, ?, true, ?, ?, true) - `, [trash_uuid, user_dir_uuid, user.id, 'Trash', '/Trash', trash_ts, trash_ts]); - log.info(`Inserted ${insert_res[0].affectedRows} rows in fsentries`); - // Update uuid cached in the user table - const update_res = await dbrw.promise().execute(` - UPDATE user SET trash_uuid=? WHERE username=? - `, [trash_uuid, user.username]); - log.info(`Updated ${update_res[0].affectedRows} rows in user`); - const t_1 = performance.now(); - - const time_to_wait = Math.max(100, 2 * (t_1 - t_0)); - - if ( this.maybe_stop_() ) return; - - log.info(`Waiting for ${time_to_wait.toFixed(2)}ms`); - await new Promise(rslv => setTimeout(rslv, time_to_wait)); - - if ( this.maybe_stop_() ) return; - } - } -} - -/** -* Class for managing referral code migrations in the user database. -* Generates and assigns unique referral codes to users who don't have them. -* Uses deterministic random generation with seeding to ensure consistent codes -* while avoiding collisions with existing codes. Processes users in batches -* and provides progress tracking. -*/ -class Mig_AddReferralCodes extends Job { - /** - * Adds referral codes to users who don't have them yet. - * Generates unique 8-character random codes using a seeded RNG. - * If a generated code conflicts with existing ones, it iterates with - * a new seed until finding an unused code. - * Updates users in batches, showing progress every 500 users. - * Can be stopped gracefully via stop() method. - * @returns {Promise} - */ - async start (args) { - this.state = this.constructor.STATE_GREEN; - const { dbrr, dbrw, log } = this; - - let existing_codes = new Set(); - // Set to store existing referral codes to avoid duplicates during migration - const SQL_EXISTING_CODES = 'SELECT referral_code FROM user'; - let [codes] = await dbrr.promise().execute(SQL_EXISTING_CODES); - for ( const { referal_code } of codes ) { - existing_codes.add(referal_code); - } - - // SQL query to fetch all user IDs and their referral codes from the user table - const SQL_USER_IDS = 'SELECT id, referral_code FROM user'; - - let [users] = await dbrr.promise().execute(SQL_USER_IDS); - - let i = 0; - - for ( const user of users ) { - if ( user.referal_code ) continue; - // create seed for deterministic random value - let iteration = 0; - let rng = seedrandom(`gen1-${user.id}`); - let referal_code = generate_random_code(8, { rng }); - - while ( existing_codes.has(referal_code) ) { - rng = seedrandom(`gen1-${user.id}-${++iteration}`); - referal_code = generate_random_code(8, { rng }); - } - - const update_res = await dbrw.promise().execute(` - UPDATE user SET referral_code=? WHERE id=? - `, [referal_code, user.id]); - - i++; - if ( i % 500 == 0 ) this.set_progress(i / users.length); - - if ( this.maybe_stop_() ) return; - } - } -} - -/** -* @class Mig_AuditInitialStorage -* @extends Job -* @description Migration class responsible for adding audit logs for users' initial storage capacity. -* This migration is designed to retroactively create audit records for each user's storage capacity -* from before the implementation of the auditing system. Inherits from the base Job class to -* handle migration state management and progress tracking. -*/ -class Mig_AuditInitialStorage extends Job { - /** - * Handles migration for auditing initial storage capacity for users - * before auditing was implemented. Creates audit log entries for each - * user's storage capacity from before the auditing system existed. - * - * @param {Array} args - Command line arguments passed to the migration - * @returns {Promise} - */ - async start (args) { - this.state = this.constructor.STATE_GREEN; - const { dbrr, dbrw, log } = this; - - // TODO: this migration will add an audit log for each user's - // storage capacity before auditing was implemented. - } -} - -/** -* @class FSEntryMigrateService -* @description Service responsible for managing and executing database migrations for filesystem entries. -* Provides functionality to run various migrations including path storage updates, access time indexing, -* trash folder fixes, and referral code generation. Exposes commands to start and stop migrations through -* a command interface. Each migration is implemented as a separate Job class that can be controlled -* independently. -*/ -class FSEntryMigrateService { - constructor (_) { - // const mysql = services.get('mysql'); - // const dbrr = mysql.get(DB_MODE_READ, 'fsentry-migrate'); - // const dbrw = mysql.get(DB_MODE_WRITE, 'fsentry-migrate'); - // const log = services.get('log-service').create('fsentry-migrate'); - - // const migrations = { - // 'store-path': new Mig_StorePath({ dbrr, dbrw, log }), - // 'index-accessed': new Mig_IndexAccessed({ dbrr, dbrw, log }), - // 'fix-trash': new Mig_FixTrash({ dbrr, dbrw, log }), - // 'gen-referral-codes': new Mig_AddReferralCodes({ dbrr, dbrw, log }), - // }; - - } -} - -module.exports = { FSEntryMigrateService };