refactor: migrate entity storage driver invocation

This commit is contained in:
KernelDeimos 2024-12-10 11:40:49 -05:00
parent 193833f4f9
commit 2fcc257ae1
7 changed files with 149 additions and 186 deletions

View File

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

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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,
};

View File

@ -17,8 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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' }));
}
}

View File

@ -18,12 +18,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 = {

View File

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

View File

@ -291,4 +291,7 @@ module.exports = {
...ENTITY_STORAGE_INTERFACE,
description: 'Read notifications on Puter.',
},
'crud-q': {
...ENTITY_STORAGE_INTERFACE,
},
};