diff --git a/src/backend/exports.js b/src/backend/exports.js
index c72b21d66..dc3946efd 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 abaa2940b..d823a2c26 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 a84ae8bd3..000000000
--- 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 7a0109eb3..e6073b55b 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 bb2720c45..09ce7431f 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 9b8a2ec8b..46b501340 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 b10bf1d84..1c180f4f5 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,
+ },
};