mirror of
https://github.com/HeyPuter/puter.git
synced 2025-01-23 22:40:20 +08:00
refactor: migrate entity storage driver invocation
This commit is contained in:
parent
193833f4f9
commit
2fcc257ae1
@ -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,
|
||||
|
@ -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'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
@ -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' }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
@ -72,6 +72,70 @@ class EntityStoreService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
/**
|
||||
* Retrieves an entity by its unique identifier.
|
||||
@ -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 = {
|
||||
|
@ -200,10 +200,22 @@ 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] ) {
|
||||
driver = this.interface_to_test_service[iface];
|
||||
|
@ -291,4 +291,7 @@ module.exports = {
|
||||
...ENTITY_STORAGE_INTERFACE,
|
||||
description: 'Read notifications on Puter.',
|
||||
},
|
||||
'crud-q': {
|
||||
...ENTITY_STORAGE_INTERFACE,
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user