From 2fcc257ae1cd9ee4826f3826eec4195f7431fe74 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Tue, 10 Dec 2024 11:40:49 -0500 Subject: [PATCH] refactor: migrate entity storage driver invocation --- src/backend/exports.js | 2 - src/backend/src/data/hardcoded-permissions.js | 12 +- .../src/drivers/EntityStoreImplementation.js | 171 ------------------ .../modules/selfhosted/SelfhostedService.js | 5 - .../src/services/EntityStoreService.js | 130 ++++++++++++- .../src/services/drivers/DriverService.js | 12 ++ .../src/services/drivers/interfaces.js | 3 + 7 files changed, 149 insertions(+), 186 deletions(-) delete mode 100644 src/backend/src/drivers/EntityStoreImplementation.js diff --git a/src/backend/exports.js b/src/backend/exports.js index c72b21d6..dc3946ef 100644 --- a/src/backend/exports.js +++ b/src/backend/exports.js @@ -21,7 +21,6 @@ const { Kernel } = require("./src/Kernel.js"); const DatabaseModule = require("./src/DatabaseModule.js"); const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js"); const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js"); -const PuterDriversModule = require("./src/PuterDriversModule.js"); const { testlaunch } = require("./src/index.js"); const BaseService = require("./src/services/BaseService.js"); const { Context } = require("./src/util/context.js"); @@ -59,7 +58,6 @@ module.exports = { CoreModule, WebModule, DatabaseModule, - PuterDriversModule, LocalDiskStorageModule, SelfHostedModule, TestDriversModule, diff --git a/src/backend/src/data/hardcoded-permissions.js b/src/backend/src/data/hardcoded-permissions.js index abaa2940..d823a2c2 100644 --- a/src/backend/src/data/hardcoded-permissions.js +++ b/src/backend/src/data/hardcoded-permissions.js @@ -84,17 +84,17 @@ const hardcoded_user_group_permissions = { 'service:hello-world:ii:hello-world': policy_perm('temp.es'), 'service:puter-kvstore:ii:puter-kvstore': policy_perm('temp.kv'), 'driver:puter-kvstore': policy_perm('temp.kv'), - 'driver:puter-notifications': policy_perm('temp.es'), - 'driver:puter-apps': policy_perm('temp.es'), - 'driver:puter-subdomains': policy_perm('temp.es'), + 'service:puter-notifications:ii:crud-q': policy_perm('temp.es'), + 'service:puter-apps:ii:crud-q': policy_perm('temp.es'), + 'service:puter-subdomains:ii:crud-q': policy_perm('temp.es'), }, '78b1b1dd-c959-44d2-b02c-8735671f9997': { 'service:hello-world:ii:hello-world': policy_perm('user.es'), 'service:puter-kvstore:ii:puter-kvstore': policy_perm('user.kv'), 'driver:puter-kvstore': policy_perm('user.kv'), - 'driver:puter-notifications': policy_perm('user.es'), - 'driver:puter-apps': policy_perm('user.es'), - 'driver:puter-subdomains': policy_perm('user.es'), + 'service:puter-notifications:ii:crud-q': policy_perm('user.es'), + 'service:puter-apps:ii:crud-q': policy_perm('user.es'), + 'service:puter-subdomains:ii:crud-q': policy_perm('user.es'), }, }, }; diff --git a/src/backend/src/drivers/EntityStoreImplementation.js b/src/backend/src/drivers/EntityStoreImplementation.js deleted file mode 100644 index a84ae8bd..00000000 --- a/src/backend/src/drivers/EntityStoreImplementation.js +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2024 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 APIError = require("../api/APIError"); -const { Driver } = require("../definitions/Driver"); -const { Entity } = require("../om/entitystorage/Entity"); -const { Or, And, Eq } = require("../om/query/query"); - -const _fetch_based_on_complex_id = async (self, id) => { - // Ensure `id` is an object and get its keys - if ( ! id || typeof id !== 'object' || Array.isArray(id) ) { - throw APIError.create('invalid_id', null, { id }); - } - - const id_keys = Object.keys(id); - // sort keys alphabetically - id_keys.sort(); - - // Ensure key set is valid based on redundant keys listing - const svc_es = self.services.get(self.service); - const redundant_identifiers = svc_es.om.redundant_identifiers ?? []; - - let match_found = false; - for ( let key of redundant_identifiers ) { - // Either a single key or a list - key = Array.isArray(key) ? key : [key]; - - // All keys in the list must be present in the id - for ( let i=0 ; i < key.length ; i++ ) { - if ( ! id_keys.includes(key[i]) ) { - break; - } - if ( i === key.length - 1 ) { - match_found = true; - break; - } - } - } - - if ( ! match_found ) { - throw APIError.create('invalid_id', null, { id }); - } - - // Construct a query predicate based on the keys - const key_eqs = []; - for ( const key of id_keys ) { - key_eqs.push(new Eq({ - key, - value: id[key], - })); - } - let predicate = new And({ children: key_eqs }); - - // Perform a select - const entity = await svc_es.read({ predicate }); - if ( ! entity ) { - return null; - } - - // Ensure there is only one result - return entity; -} - -const _fetch_based_on_either_id = async (self, uid, id) => { - if ( uid ) { - const svc_es = self.services.get(self.service); - return await svc_es.read(uid); - } - - return await _fetch_based_on_complex_id(self, id); -} - -class EntityStoreImplementation extends Driver { - constructor ({ service }) { - super(); - this.service = service; - } - get_usage_extra () { - return { - ['driver.interface']: 'puter-es', - ['driver.implementation']: 'puter-es:' + this.service, - }; - } - static METHODS = { - create: async function ({ object, options }) { - const svc_es = this.services.get(this.service); - if ( object.hasOwnProperty(svc_es.om.primary_identifier) ) { - throw APIError.create('field_not_allowed_for_create', null, { key: svc_es.om.primary_identifier }); - } - const entity = await Entity.create({ om: svc_es.om }, object); - return await svc_es.create(entity, options); - }, - update: async function ({ object, id, options }) { - const svc_es = this.services.get(this.service); - // if ( ! object.hasOwnProperty(svc_es.om.primary_identifier) ) { - // throw APIError.create('field_required_for_update', null, { key: svc_es.om.primary_identifier }); - // } - const entity = await Entity.create({ om: svc_es.om }, object); - return await svc_es.update(entity, id, options); - }, - upsert: async function ({ object, id, options }) { - const svc_es = this.services.get(this.service); - const entity = await Entity.create({ om: svc_es.om }, object); - return await svc_es.upsert(entity, id, options); - }, - read: async function ({ uid, id }) { - if ( ! uid && ! id ) { - throw APIError.create('xor_field_missing', null, { - names: ['uid', 'id'], - }); - } - - const entity = await _fetch_based_on_either_id(this, uid, id); - if ( ! entity ) { - throw APIError.create('entity_not_found', null, { - identifier: uid - }); - } - return await entity.get_client_safe(); - }, - select: async function (options) { - const svc_es = this.services.get(this.service); - const entities = await svc_es.select(options); - const client_safe_entities = []; - for ( const entity of entities ) { - client_safe_entities.push(await entity.get_client_safe()); - } - return client_safe_entities; - }, - delete: async function ({ uid, id }) { - if ( ! uid && ! id ) { - throw APIError.create('xor_field_missing', null, { - names: ['uid', 'id'], - }); - } - - if ( id && ! uid ) { - const entity = await _fetch_based_on_complex_id(this, id); - if ( ! entity ) { - throw APIError.create('entity_not_found', null, { - identifier: id - }); - } - const svc_es = this.services.get(this.service); - uid = await entity.get(svc_es.om.primary_identifier); - } - - const svc_es = this.services.get(this.service); - return await svc_es.delete(uid); - }, - }; -} - -module.exports = { - EntityStoreImplementation, -}; diff --git a/src/backend/src/modules/selfhosted/SelfhostedService.js b/src/backend/src/modules/selfhosted/SelfhostedService.js index 7a0109eb..e6073b55 100644 --- a/src/backend/src/modules/selfhosted/SelfhostedService.js +++ b/src/backend/src/modules/selfhosted/SelfhostedService.js @@ -17,8 +17,6 @@ * along with this program. If not, see . */ const { DBKVStore } = require("../../drivers/DBKVStore"); -const { EntityStoreImplementation } = require("../../drivers/EntityStoreImplementation"); -const { HelloWorld } = require("../../drivers/HelloWorld"); const BaseService = require("../../services/BaseService"); class SelfhostedService extends BaseService { @@ -30,9 +28,6 @@ class SelfhostedService extends BaseService { const svc_driver = this.services.get('driver'); svc_driver.register_driver('puter-kvstore', new DBKVStore()); - svc_driver.register_driver('puter-apps', new EntityStoreImplementation({ service: 'es:app' })); - svc_driver.register_driver('puter-subdomains', new EntityStoreImplementation({ service: 'es:subdomain' })); - svc_driver.register_driver('puter-notifications', new EntityStoreImplementation({ service: 'es:notification' })); } } diff --git a/src/backend/src/services/EntityStoreService.js b/src/backend/src/services/EntityStoreService.js index bb2720c4..09ce7431 100644 --- a/src/backend/src/services/EntityStoreService.js +++ b/src/backend/src/services/EntityStoreService.js @@ -18,12 +18,12 @@ * along with this program. If not, see . */ const APIError = require("../api/APIError"); +const { Entity } = require("../om/entitystorage/Entity"); const { IdentifierUtil } = require("../om/IdentifierUtil"); -const { Null } = require("../om/query/query"); +const { Null, And, Eq } = require("../om/query/query"); const { Context } = require("../util/context"); const BaseService = require("./BaseService"); - /** * EntityStoreService - A service class that manages entity-related operations in the backend of Puter. * This class extends BaseService to provide methods for creating, reading, updating, selecting, @@ -71,6 +71,70 @@ class EntityStoreService extends BaseService { entity_name: args.entity, }); } + + static IMPLEMENTS = { + ['crud-q']: { + async create ({ object, options }) { + if ( object.hasOwnProperty(this.om.primary_identifier) ) { + throw APIError.create('field_not_allowed_for_create', null, { + key: this.om.primary_identifier + }); + } + const entity = await Entity.create({ om: this.om }, object); + return await this.create(entity, options); + }, + async update ({ object, id, options }) { + const entity = await Entity.create({ om: this.om }, object); + return await this.update(entity, id, options); + }, + async upsert ({ object, id, options }) { + const entity = await Entity.create({ om: this.om }, object); + return await this.upsert(entity, id, options); + }, + async read ({ uid, id }) { + if ( ! uid && ! id ) { + throw APIError.create('xor_field_missing', null, { + names: ['uid', 'id'], + }); + } + + const entity = await this.fetch_based_on_either_id_(uid, id); + if ( ! entity ) { + throw APIError.create('entity_not_found', null, { + identifier: uid + }); + } + return await entity.get_client_safe(); + }, + async select (options) { + const entities = await this.select(options); + const client_safe_entities = []; + for ( const entity of entities ) { + client_safe_entities.push(await entity.get_client_safe()); + } + return client_safe_entities; + }, + async delete ({ uid, id }) { + if ( ! uid && ! id ) { + throw APIError.create('xor_field_missing', null, { + names: ['uid', 'id'], + }); + } + + if ( id && ! uid ) { + const entity = await this.fetch_based_on_complex_id_(id); + if ( ! entity ) { + throw APIError.create('entity_not_found', null, { + identifier: id + }); + } + uid = await entity.get(this.om.primary_identifier); + } + + return await this.delete(uid); + } + } + }; // TODO: can replace these with MethodProxyFeature /** @@ -214,6 +278,68 @@ class EntityStoreService extends BaseService { } return await this.upstream.delete(uid, { old_entity }); } + + async fetch_based_on_complex_id_ (id) { + // Ensure `id` is an object and get its keys + if ( ! id || typeof id !== 'object' || Array.isArray(id) ) { + throw APIError.create('invalid_id', null, { id }); + } + + const id_keys = Object.keys(id); + // sort keys alphabetically + id_keys.sort(); + + // Ensure key set is valid based on redundant keys listing + const redundant_identifiers = this.om.redundant_identifiers ?? []; + + let match_found = false; + for ( let key of redundant_identifiers ) { + // Either a single key or a list + key = Array.isArray(key) ? key : [key]; + + // All keys in the list must be present in the id + for ( let i=0 ; i < key.length ; i++ ) { + if ( ! id_keys.includes(key[i]) ) { + break; + } + if ( i === key.length - 1 ) { + match_found = true; + break; + } + } + } + + if ( ! match_found ) { + throw APIError.create('invalid_id', null, { id }); + } + + // Construct a query predicate based on the keys + const key_eqs = []; + for ( const key of id_keys ) { + key_eqs.push(new Eq({ + key, + value: id[key], + })); + } + let predicate = new And({ children: key_eqs }); + + // Perform a select + const entity = await this.read({ predicate }); + if ( ! entity ) { + return null; + } + + // Ensure there is only one result + return entity; + } + + async fetch_based_on_either_id_ (uid, id) { + if ( uid ) { + return await this.read(uid); + } + + return await this.fetch_based_on_complex_id_(id); + } } module.exports = { diff --git a/src/backend/src/services/drivers/DriverService.js b/src/backend/src/services/drivers/DriverService.js index 9b8a2ec8..46b50134 100644 --- a/src/backend/src/services/drivers/DriverService.js +++ b/src/backend/src/services/drivers/DriverService.js @@ -200,9 +200,21 @@ class DriverService extends BaseService { ['puter-tts']: 'aws-polly', ['puter-chat-completion']: 'openai-completion', ['puter-image-generation']: 'openai-image-generation', + 'puter-apps': 'es:app', + 'puter-subdomains': 'es:subdomain', + 'puter-notifications': 'es:notification', } driver = driver ?? iface_to_driver[iface] ?? iface; + + // For these ones, the interface specified actually specifies the + // specificc driver to use. + const iface_to_iface = { + 'puter-apps': 'crud-q', + 'puter-subdomains': 'crud-q', + 'puter-notifications': 'crud-q', + } + iface = iface_to_iface[iface] ?? iface; let skip_usage = false; if ( test_mode && this.interface_to_test_service[iface] ) { diff --git a/src/backend/src/services/drivers/interfaces.js b/src/backend/src/services/drivers/interfaces.js index b10bf1d8..1c180f4f 100644 --- a/src/backend/src/services/drivers/interfaces.js +++ b/src/backend/src/services/drivers/interfaces.js @@ -291,4 +291,7 @@ module.exports = { ...ENTITY_STORAGE_INTERFACE, description: 'Read notifications on Puter.', }, + 'crud-q': { + ...ENTITY_STORAGE_INTERFACE, + }, };