@rebasepro/server-mongodb 0.0.1-canary.09e5ec5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/dist/ensure-collections-CNrcwVgY.js +74 -0
- package/dist/ensure-collections-CNrcwVgY.js.map +1 -0
- package/dist/ensure-history-collection-DBIiwmCm.js +15 -0
- package/dist/ensure-history-collection-DBIiwmCm.js.map +1 -0
- package/dist/index.es.js +1734 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2043 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-core/src/api/ast-schema-editor.d.ts +22 -0
- package/dist/server-core/src/api/ast-schema-editor.d.ts.map +1 -0
- package/dist/server-core/src/api/errors.d.ts +36 -0
- package/dist/server-core/src/api/errors.d.ts.map +1 -0
- package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +36 -0
- package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts.map +1 -0
- package/dist/server-core/src/api/graphql/index.d.ts +2 -0
- package/dist/server-core/src/api/graphql/index.d.ts.map +1 -0
- package/dist/server-core/src/api/index.d.ts +10 -0
- package/dist/server-core/src/api/index.d.ts.map +1 -0
- package/dist/server-core/src/api/openapi-generator.d.ts +17 -0
- package/dist/server-core/src/api/openapi-generator.d.ts.map +1 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +65 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts.map +1 -0
- package/dist/server-core/src/api/rest/index.d.ts +2 -0
- package/dist/server-core/src/api/rest/index.d.ts.map +1 -0
- package/dist/server-core/src/api/rest/query-parser.d.ts +10 -0
- package/dist/server-core/src/api/rest/query-parser.d.ts.map +1 -0
- package/dist/server-core/src/api/schema-editor-routes.d.ts +4 -0
- package/dist/server-core/src/api/schema-editor-routes.d.ts.map +1 -0
- package/dist/server-core/src/api/server.d.ts +41 -0
- package/dist/server-core/src/api/server.d.ts.map +1 -0
- package/dist/server-core/src/api/types.d.ts +91 -0
- package/dist/server-core/src/api/types.d.ts.map +1 -0
- package/dist/server-core/src/auth/admin-routes.d.ts +17 -0
- package/dist/server-core/src/auth/admin-routes.d.ts.map +1 -0
- package/dist/server-core/src/auth/apple-oauth.d.ts +31 -0
- package/dist/server-core/src/auth/apple-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/github-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/google-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/index.d.ts +24 -0
- package/dist/server-core/src/auth/index.d.ts.map +1 -0
- package/dist/server-core/src/auth/interfaces.d.ts +310 -0
- package/dist/server-core/src/auth/interfaces.d.ts.map +1 -0
- package/dist/server-core/src/auth/jwt.d.ts +44 -0
- package/dist/server-core/src/auth/jwt.d.ts.map +1 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +19 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +17 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/middleware.d.ts +82 -0
- package/dist/server-core/src/auth/middleware.d.ts.map +1 -0
- package/dist/server-core/src/auth/password.d.ts +23 -0
- package/dist/server-core/src/auth/password.d.ts.map +1 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +32 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts.map +1 -0
- package/dist/server-core/src/auth/routes.d.ts +28 -0
- package/dist/server-core/src/auth/routes.d.ts.map +1 -0
- package/dist/server-core/src/auth/slack-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/slack-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts.map +1 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +19 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts.map +1 -0
- package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +14 -0
- package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts.map +1 -0
- package/dist/server-core/src/collections/loader.d.ts +6 -0
- package/dist/server-core/src/collections/loader.d.ts.map +1 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +18 -0
- package/dist/server-core/src/cron/cron-loader.d.ts.map +1 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +15 -0
- package/dist/server-core/src/cron/cron-routes.d.ts.map +1 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +62 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts.map +1 -0
- package/dist/server-core/src/cron/cron-store.d.ts +33 -0
- package/dist/server-core/src/cron/cron-store.d.ts.map +1 -0
- package/dist/server-core/src/cron/index.d.ts +7 -0
- package/dist/server-core/src/cron/index.d.ts.map +1 -0
- package/dist/server-core/src/db/interfaces.d.ts +19 -0
- package/dist/server-core/src/db/interfaces.d.ts.map +1 -0
- package/dist/server-core/src/email/index.d.ts +7 -0
- package/dist/server-core/src/email/index.d.ts.map +1 -0
- package/dist/server-core/src/email/smtp-email-service.d.ts +26 -0
- package/dist/server-core/src/email/smtp-email-service.d.ts.map +1 -0
- package/dist/server-core/src/email/templates.d.ts +43 -0
- package/dist/server-core/src/email/templates.d.ts.map +1 -0
- package/dist/server-core/src/email/types.d.ts +108 -0
- package/dist/server-core/src/email/types.d.ts.map +1 -0
- package/dist/server-core/src/functions/function-loader.d.ts +18 -0
- package/dist/server-core/src/functions/function-loader.d.ts.map +1 -0
- package/dist/server-core/src/functions/function-routes.d.ts +11 -0
- package/dist/server-core/src/functions/function-routes.d.ts.map +1 -0
- package/dist/server-core/src/functions/index.d.ts +4 -0
- package/dist/server-core/src/functions/index.d.ts.map +1 -0
- package/dist/server-core/src/history/history-routes.d.ts +24 -0
- package/dist/server-core/src/history/history-routes.d.ts.map +1 -0
- package/dist/server-core/src/history/index.d.ts +2 -0
- package/dist/server-core/src/history/index.d.ts.map +1 -0
- package/dist/server-core/src/index.d.ts +30 -0
- package/dist/server-core/src/index.d.ts.map +1 -0
- package/dist/server-core/src/init.d.ts +160 -0
- package/dist/server-core/src/init.d.ts.map +1 -0
- package/dist/server-core/src/serve-spa.d.ts +31 -0
- package/dist/server-core/src/serve-spa.d.ts.map +1 -0
- package/dist/server-core/src/services/driver-registry.d.ts +79 -0
- package/dist/server-core/src/services/driver-registry.d.ts.map +1 -0
- package/dist/server-core/src/singleton.d.ts +36 -0
- package/dist/server-core/src/singleton.d.ts.map +1 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +47 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts.map +1 -0
- package/dist/server-core/src/storage/S3StorageController.d.ts +37 -0
- package/dist/server-core/src/storage/S3StorageController.d.ts.map +1 -0
- package/dist/server-core/src/storage/index.d.ts +26 -0
- package/dist/server-core/src/storage/index.d.ts.map +1 -0
- package/dist/server-core/src/storage/routes.d.ts +39 -0
- package/dist/server-core/src/storage/routes.d.ts.map +1 -0
- package/dist/server-core/src/storage/storage-registry.d.ts +79 -0
- package/dist/server-core/src/storage/storage-registry.d.ts.map +1 -0
- package/dist/server-core/src/storage/types.d.ts +104 -0
- package/dist/server-core/src/storage/types.d.ts.map +1 -0
- package/dist/server-core/src/types/index.d.ts +12 -0
- package/dist/server-core/src/types/index.d.ts.map +1 -0
- package/dist/server-core/src/utils/dev-port.d.ts +36 -0
- package/dist/server-core/src/utils/dev-port.d.ts.map +1 -0
- package/dist/server-core/src/utils/logger.d.ts +32 -0
- package/dist/server-core/src/utils/logger.d.ts.map +1 -0
- package/dist/server-core/src/utils/logging.d.ts +10 -0
- package/dist/server-core/src/utils/logging.d.ts.map +1 -0
- package/dist/server-core/src/utils/request-logger.d.ts +20 -0
- package/dist/server-core/src/utils/request-logger.d.ts.map +1 -0
- package/dist/server-core/src/utils/sql.d.ts +28 -0
- package/dist/server-core/src/utils/sql.d.ts.map +1 -0
- package/dist/server-mongodb/src/MongoBootstrapper.d.ts +18 -0
- package/dist/server-mongodb/src/MongoBootstrapper.d.ts.map +1 -0
- package/dist/server-mongodb/src/auth/ensure-collections.d.ts +3 -0
- package/dist/server-mongodb/src/auth/ensure-collections.d.ts.map +1 -0
- package/dist/server-mongodb/src/auth/services.d.ts +135 -0
- package/dist/server-mongodb/src/auth/services.d.ts.map +1 -0
- package/dist/server-mongodb/src/connection.d.ts +35 -0
- package/dist/server-mongodb/src/connection.d.ts.map +1 -0
- package/dist/server-mongodb/src/db/MongoConditionBuilder.d.ts +64 -0
- package/dist/server-mongodb/src/db/MongoConditionBuilder.d.ts.map +1 -0
- package/dist/server-mongodb/src/db/MongoEntityService.d.ts +98 -0
- package/dist/server-mongodb/src/db/MongoEntityService.d.ts.map +1 -0
- package/dist/server-mongodb/src/factory.d.ts +142 -0
- package/dist/server-mongodb/src/factory.d.ts.map +1 -0
- package/dist/server-mongodb/src/history/ensure-history-collection.d.ts +3 -0
- package/dist/server-mongodb/src/history/ensure-history-collection.d.ts.map +1 -0
- package/dist/server-mongodb/src/index.d.ts +18 -0
- package/dist/server-mongodb/src/index.d.ts.map +1 -0
- package/dist/server-mongodb/src/services/MongoDriver.d.ts +83 -0
- package/dist/server-mongodb/src/services/MongoDriver.d.ts.map +1 -0
- package/dist/server-mongodb/src/services/MongoHistoryService.d.ts +37 -0
- package/dist/server-mongodb/src/services/MongoHistoryService.d.ts.map +1 -0
- package/dist/server-mongodb/src/services/MongoRealtimeService.d.ts +86 -0
- package/dist/server-mongodb/src/services/MongoRealtimeService.d.ts.map +1 -0
- package/dist/server-mongodb/src/useMongoDriver.d.ts +18 -0
- package/dist/server-mongodb/src/useMongoDriver.d.ts.map +1 -0
- package/dist/server-mongodb/src/utils.d.ts +10 -0
- package/dist/server-mongodb/src/utils.d.ts.map +1 -0
- package/dist/server-mongodb/src/websocket.d.ts +7 -0
- package/dist/server-mongodb/src/websocket.d.ts.map +1 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +8 -0
- package/dist/types/src/controllers/analytics_controller.d.ts.map +1 -0
- package/dist/types/src/controllers/auth.d.ts +120 -0
- package/dist/types/src/controllers/auth.d.ts.map +1 -0
- package/dist/types/src/controllers/client.d.ts +171 -0
- package/dist/types/src/controllers/client.d.ts.map +1 -0
- package/dist/types/src/controllers/collection_registry.d.ts +46 -0
- package/dist/types/src/controllers/collection_registry.d.ts.map +1 -0
- package/dist/types/src/controllers/customization_controller.d.ts +61 -0
- package/dist/types/src/controllers/customization_controller.d.ts.map +1 -0
- package/dist/types/src/controllers/data.d.ts +169 -0
- package/dist/types/src/controllers/data.d.ts.map +1 -0
- package/dist/types/src/controllers/data_driver.d.ts +161 -0
- package/dist/types/src/controllers/data_driver.d.ts.map +1 -0
- package/dist/types/src/controllers/database_admin.d.ts +12 -0
- package/dist/types/src/controllers/database_admin.d.ts.map +1 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +37 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts.map +1 -0
- package/dist/types/src/controllers/effective_role.d.ts +5 -0
- package/dist/types/src/controllers/effective_role.d.ts.map +1 -0
- package/dist/types/src/controllers/email.d.ts +35 -0
- package/dist/types/src/controllers/email.d.ts.map +1 -0
- package/dist/types/src/controllers/index.d.ts +19 -0
- package/dist/types/src/controllers/index.d.ts.map +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +21 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts.map +1 -0
- package/dist/types/src/controllers/navigation.d.ts +214 -0
- package/dist/types/src/controllers/navigation.d.ts.map +1 -0
- package/dist/types/src/controllers/registry.d.ts +55 -0
- package/dist/types/src/controllers/registry.d.ts.map +1 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +68 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts.map +1 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +91 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts.map +1 -0
- package/dist/types/src/controllers/snackbar.d.ts +25 -0
- package/dist/types/src/controllers/snackbar.d.ts.map +1 -0
- package/dist/types/src/controllers/storage.d.ts +172 -0
- package/dist/types/src/controllers/storage.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/rebase_context.d.ts +106 -0
- package/dist/types/src/rebase_context.d.ts.map +1 -0
- package/dist/types/src/types/backend.d.ts +537 -0
- package/dist/types/src/types/backend.d.ts.map +1 -0
- package/dist/types/src/types/builders.d.ts +16 -0
- package/dist/types/src/types/builders.d.ts.map +1 -0
- package/dist/types/src/types/chips.d.ts +6 -0
- package/dist/types/src/types/chips.d.ts.map +1 -0
- package/dist/types/src/types/collections.d.ts +857 -0
- package/dist/types/src/types/collections.d.ts.map +1 -0
- package/dist/types/src/types/cron.d.ts +103 -0
- package/dist/types/src/types/cron.d.ts.map +1 -0
- package/dist/types/src/types/data_source.d.ts +65 -0
- package/dist/types/src/types/data_source.d.ts.map +1 -0
- package/dist/types/src/types/entities.d.ts +146 -0
- package/dist/types/src/types/entities.d.ts.map +1 -0
- package/dist/types/src/types/entity_actions.d.ts +99 -0
- package/dist/types/src/types/entity_actions.d.ts.map +1 -0
- package/dist/types/src/types/entity_callbacks.d.ts +174 -0
- package/dist/types/src/types/entity_callbacks.d.ts.map +1 -0
- package/dist/types/src/types/entity_link_builder.d.ts +8 -0
- package/dist/types/src/types/entity_link_builder.d.ts.map +1 -0
- package/dist/types/src/types/entity_overrides.d.ts +11 -0
- package/dist/types/src/types/entity_overrides.d.ts.map +1 -0
- package/dist/types/src/types/entity_views.d.ts +62 -0
- package/dist/types/src/types/entity_views.d.ts.map +1 -0
- package/dist/types/src/types/export_import.d.ts +22 -0
- package/dist/types/src/types/export_import.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +24 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/locales.d.ts +5 -0
- package/dist/types/src/types/locales.d.ts.map +1 -0
- package/dist/types/src/types/modify_collections.d.ts +6 -0
- package/dist/types/src/types/modify_collections.d.ts.map +1 -0
- package/dist/types/src/types/plugins.d.ts +280 -0
- package/dist/types/src/types/plugins.d.ts.map +1 -0
- package/dist/types/src/types/properties.d.ts +1177 -0
- package/dist/types/src/types/properties.d.ts.map +1 -0
- package/dist/types/src/types/property_config.d.ts +71 -0
- package/dist/types/src/types/property_config.d.ts.map +1 -0
- package/dist/types/src/types/relations.d.ts +337 -0
- package/dist/types/src/types/relations.d.ts.map +1 -0
- package/dist/types/src/types/slots.d.ts +253 -0
- package/dist/types/src/types/slots.d.ts.map +1 -0
- package/dist/types/src/types/translations.d.ts +871 -0
- package/dist/types/src/types/translations.d.ts.map +1 -0
- package/dist/types/src/types/user_management_delegate.d.ts +122 -0
- package/dist/types/src/types/user_management_delegate.d.ts.map +1 -0
- package/dist/types/src/types/websockets.d.ts +79 -0
- package/dist/types/src/types/websockets.d.ts.map +1 -0
- package/dist/types/src/users/index.d.ts +3 -0
- package/dist/types/src/users/index.d.ts.map +1 -0
- package/dist/types/src/users/roles.d.ts +23 -0
- package/dist/types/src/users/roles.d.ts.map +1 -0
- package/dist/types/src/users/user.d.ts +47 -0
- package/dist/types/src/users/user.d.ts.map +1 -0
- package/dist/websocket-BZlPuJrt.js +220 -0
- package/dist/websocket-BZlPuJrt.js.map +1 -0
- package/package.json +79 -0
- package/src/MongoBootstrapper.ts +177 -0
- package/src/auth/ensure-collections.ts +94 -0
- package/src/auth/services.ts +638 -0
- package/src/connection.ts +60 -0
- package/src/db/MongoConditionBuilder.ts +181 -0
- package/src/db/MongoEntityService.ts +350 -0
- package/src/factory.ts +289 -0
- package/src/history/ensure-history-collection.ts +19 -0
- package/src/index.ts +25 -0
- package/src/services/MongoDriver.ts +297 -0
- package/src/services/MongoDriver.ts.backup +266 -0
- package/src/services/MongoHistoryService.ts +154 -0
- package/src/services/MongoRealtimeService.ts +394 -0
- package/src/useMongoDriver.ts +519 -0
- package/src/utils.ts +28 -0
- package/src/websocket.ts +257 -0
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,1734 @@
|
|
|
1
|
+
import { MongoClient, ObjectId } from "mongodb";
|
|
2
|
+
class MongoDBConnection {
|
|
3
|
+
constructor(db, client) {
|
|
4
|
+
this.db = db;
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
type = "mongodb";
|
|
8
|
+
get isConnected() {
|
|
9
|
+
try {
|
|
10
|
+
const clientInternal = this.client;
|
|
11
|
+
return clientInternal.topology?.isConnected?.() ?? false;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async close() {
|
|
17
|
+
await this.client.close();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function createMongoDBConnection(connectionString, databaseName) {
|
|
21
|
+
const client = new MongoClient(connectionString);
|
|
22
|
+
await client.connect();
|
|
23
|
+
const db = client.db(databaseName);
|
|
24
|
+
return new MongoDBConnection(db, client);
|
|
25
|
+
}
|
|
26
|
+
const REBASE_TO_MONGO_OP = {
|
|
27
|
+
"<": "$lt",
|
|
28
|
+
"<=": "$lte",
|
|
29
|
+
"==": "$eq",
|
|
30
|
+
"!=": "$ne",
|
|
31
|
+
">=": "$gte",
|
|
32
|
+
">": "$gt",
|
|
33
|
+
"array-contains": "$elemMatch",
|
|
34
|
+
"array-contains-any": "$in",
|
|
35
|
+
"in": "$in",
|
|
36
|
+
"not-in": "$nin"
|
|
37
|
+
};
|
|
38
|
+
class MongoConditionBuilder {
|
|
39
|
+
/**
|
|
40
|
+
* Build MongoDB filter conditions from Rebase FilterValues
|
|
41
|
+
*
|
|
42
|
+
* @param filter - Rebase filter values
|
|
43
|
+
* @returns Array of MongoDB filter objects
|
|
44
|
+
*/
|
|
45
|
+
static buildFilterConditions(filter) {
|
|
46
|
+
if (!filter) return [];
|
|
47
|
+
const conditions = [];
|
|
48
|
+
for (const [field, filterParam] of Object.entries(filter)) {
|
|
49
|
+
if (!filterParam) continue;
|
|
50
|
+
const [op, value] = filterParam;
|
|
51
|
+
const mongoOp = REBASE_TO_MONGO_OP[op];
|
|
52
|
+
if (!mongoOp) {
|
|
53
|
+
console.warn(`Unsupported filter operator: ${op}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (op === "array-contains") {
|
|
57
|
+
conditions.push({
|
|
58
|
+
[field]: { $elemMatch: { $eq: value } }
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
conditions.push({
|
|
62
|
+
[field]: { [mongoOp]: value }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return conditions;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build search conditions for text search
|
|
70
|
+
*
|
|
71
|
+
* @param searchString - Text to search for
|
|
72
|
+
* @param properties - Properties to search in
|
|
73
|
+
* @returns Array of MongoDB filter objects for text search
|
|
74
|
+
*/
|
|
75
|
+
static buildSearchConditions(searchString, properties) {
|
|
76
|
+
if (!searchString) return [];
|
|
77
|
+
const orConditions = [];
|
|
78
|
+
const searchRegex = new RegExp(searchString, "i");
|
|
79
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
80
|
+
if (prop?.dataType === "string" || typeof prop === "string") {
|
|
81
|
+
orConditions.push({
|
|
82
|
+
[key]: { $regex: searchRegex }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (orConditions.length === 0) {
|
|
87
|
+
return [{ $text: { $search: searchString } }];
|
|
88
|
+
}
|
|
89
|
+
return orConditions;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Combine multiple conditions with AND operator
|
|
93
|
+
*
|
|
94
|
+
* @param conditions - Array of filter conditions
|
|
95
|
+
* @returns Combined filter or undefined if empty
|
|
96
|
+
*/
|
|
97
|
+
static combineConditionsWithAnd(conditions) {
|
|
98
|
+
if (conditions.length === 0) return void 0;
|
|
99
|
+
if (conditions.length === 1) return conditions[0];
|
|
100
|
+
return { $and: conditions };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Combine multiple conditions with OR operator
|
|
104
|
+
*
|
|
105
|
+
* @param conditions - Array of filter conditions
|
|
106
|
+
* @returns Combined filter or undefined if empty
|
|
107
|
+
*/
|
|
108
|
+
static combineConditionsWithOr(conditions) {
|
|
109
|
+
if (conditions.length === 0) return void 0;
|
|
110
|
+
if (conditions.length === 1) return conditions[0];
|
|
111
|
+
return { $or: conditions };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a complete MongoDB query from Rebase options
|
|
115
|
+
*
|
|
116
|
+
* @param options - Rebase fetch options
|
|
117
|
+
* @returns MongoDB filter object
|
|
118
|
+
*/
|
|
119
|
+
static buildQuery(options) {
|
|
120
|
+
const conditions = [];
|
|
121
|
+
if (options.filter) {
|
|
122
|
+
const filterConditions = this.buildFilterConditions(options.filter);
|
|
123
|
+
conditions.push(...filterConditions);
|
|
124
|
+
}
|
|
125
|
+
if (options.searchString && options.properties) {
|
|
126
|
+
const searchConditions = this.buildSearchConditions(
|
|
127
|
+
options.searchString,
|
|
128
|
+
options.properties
|
|
129
|
+
);
|
|
130
|
+
if (searchConditions.length > 0) {
|
|
131
|
+
const searchFilter = this.combineConditionsWithOr(searchConditions);
|
|
132
|
+
if (searchFilter) {
|
|
133
|
+
conditions.push(searchFilter);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return this.combineConditionsWithAnd(conditions) ?? {};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build MongoDB sort options from Rebase options
|
|
141
|
+
*
|
|
142
|
+
* @param orderBy - Field to order by
|
|
143
|
+
* @param order - Sort direction
|
|
144
|
+
* @returns MongoDB sort object
|
|
145
|
+
*/
|
|
146
|
+
static buildSort(orderBy, order) {
|
|
147
|
+
if (!orderBy) return void 0;
|
|
148
|
+
return { [orderBy]: order === "desc" ? -1 : 1 };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
class MongoEntityService {
|
|
152
|
+
constructor(db) {
|
|
153
|
+
this.db = db;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get a MongoDB collection by its path
|
|
157
|
+
*/
|
|
158
|
+
getCollection(collectionPath) {
|
|
159
|
+
const collectionName = collectionPath.replace(/\//g, "_");
|
|
160
|
+
return this.db.collection(collectionName);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Convert a string ID to ObjectId if it's a valid ObjectId string
|
|
164
|
+
*/
|
|
165
|
+
toObjectId(id) {
|
|
166
|
+
if (typeof id === "string" && ObjectId.isValid(id) && id.length === 24) {
|
|
167
|
+
return new ObjectId(id);
|
|
168
|
+
}
|
|
169
|
+
return id;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Convert a MongoDB document to a Rebase Entity
|
|
173
|
+
*/
|
|
174
|
+
documentToEntity(doc, path) {
|
|
175
|
+
const { _id, ...values } = doc;
|
|
176
|
+
return {
|
|
177
|
+
id: _id.toString(),
|
|
178
|
+
path,
|
|
179
|
+
values: this.convertFromMongoValues(values)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Convert values from MongoDB format to Rebase format
|
|
184
|
+
*/
|
|
185
|
+
convertFromMongoValues(values) {
|
|
186
|
+
const result = {};
|
|
187
|
+
for (const [key, value] of Object.entries(values)) {
|
|
188
|
+
result[key] = this.convertFromMongoValue(value);
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Convert a single value from MongoDB format
|
|
194
|
+
*/
|
|
195
|
+
convertFromMongoValue(value) {
|
|
196
|
+
if (value === null || value === void 0) return value;
|
|
197
|
+
if (value instanceof ObjectId) {
|
|
198
|
+
return value.toString();
|
|
199
|
+
}
|
|
200
|
+
if (value instanceof Date) {
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(value)) {
|
|
204
|
+
return value.map((v) => this.convertFromMongoValue(v));
|
|
205
|
+
}
|
|
206
|
+
if (typeof value === "object" && "path" in value && "id" in value) {
|
|
207
|
+
return {
|
|
208
|
+
path: value.path,
|
|
209
|
+
id: value.id instanceof ObjectId ? value.id.toString() : value.id
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (typeof value === "object") {
|
|
213
|
+
return this.convertFromMongoValues(value);
|
|
214
|
+
}
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Convert values to MongoDB format for storage
|
|
219
|
+
*/
|
|
220
|
+
convertToMongoValues(values) {
|
|
221
|
+
const result = {};
|
|
222
|
+
for (const [key, value] of Object.entries(values)) {
|
|
223
|
+
result[key] = this.convertToMongoValue(value);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Convert a single value to MongoDB format
|
|
229
|
+
*/
|
|
230
|
+
convertToMongoValue(value) {
|
|
231
|
+
if (value === null || value === void 0) return value;
|
|
232
|
+
if (typeof value === "object" && value.isEntityReference?.()) {
|
|
233
|
+
return {
|
|
234
|
+
id: ObjectId.isValid(value.id) ? new ObjectId(value.id) : value.id,
|
|
235
|
+
path: value.path
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (value instanceof Date) {
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
if (Array.isArray(value)) {
|
|
242
|
+
return value.map((v) => this.convertToMongoValue(v));
|
|
243
|
+
}
|
|
244
|
+
if (typeof value === "object") {
|
|
245
|
+
return this.convertToMongoValues(value);
|
|
246
|
+
}
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
// =============================================================
|
|
250
|
+
// EntityRepository Implementation
|
|
251
|
+
// =============================================================
|
|
252
|
+
/**
|
|
253
|
+
* Fetch a single entity by ID
|
|
254
|
+
*/
|
|
255
|
+
async fetchEntity(collectionPath, entityId, _databaseId) {
|
|
256
|
+
const collection = this.getCollection(collectionPath);
|
|
257
|
+
const id = this.toObjectId(entityId);
|
|
258
|
+
const doc = await collection.findOne({ _id: id });
|
|
259
|
+
if (!doc) return void 0;
|
|
260
|
+
return this.documentToEntity(doc, collectionPath);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Fetch a collection of entities with optional filtering, ordering, and pagination
|
|
264
|
+
*/
|
|
265
|
+
async fetchCollection(collectionPath, options = {}) {
|
|
266
|
+
const collection = this.getCollection(collectionPath);
|
|
267
|
+
const query = MongoConditionBuilder.buildQuery({
|
|
268
|
+
filter: options.filter,
|
|
269
|
+
searchString: options.searchString,
|
|
270
|
+
properties: options.collection?.properties ?? {}
|
|
271
|
+
});
|
|
272
|
+
const findOptions = {};
|
|
273
|
+
const sort = MongoConditionBuilder.buildSort(options.orderBy, options.order);
|
|
274
|
+
if (sort) {
|
|
275
|
+
findOptions.sort = sort;
|
|
276
|
+
}
|
|
277
|
+
if (options.limit) {
|
|
278
|
+
findOptions.limit = options.limit;
|
|
279
|
+
}
|
|
280
|
+
if (options.startAfter !== void 0) {
|
|
281
|
+
findOptions.skip = Number(options.startAfter);
|
|
282
|
+
}
|
|
283
|
+
const docs = await collection.find(query, findOptions).toArray();
|
|
284
|
+
return docs.map((doc) => this.documentToEntity(doc, collectionPath));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Search entities by text
|
|
288
|
+
*/
|
|
289
|
+
async searchEntities(collectionPath, searchString, options = {}) {
|
|
290
|
+
return this.fetchCollection(collectionPath, {
|
|
291
|
+
...options,
|
|
292
|
+
searchString
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Count entities in a collection
|
|
297
|
+
*/
|
|
298
|
+
async countEntities(collectionPath, options = {}) {
|
|
299
|
+
const collection = this.getCollection(collectionPath);
|
|
300
|
+
const query = options.filter ? MongoConditionBuilder.buildQuery({ filter: options.filter }) : {};
|
|
301
|
+
return collection.countDocuments(query);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Save an entity (create or update)
|
|
305
|
+
*/
|
|
306
|
+
async saveEntity(collectionPath, values, entityId, _databaseId) {
|
|
307
|
+
const collection = this.getCollection(collectionPath);
|
|
308
|
+
const mongoValues = this.convertToMongoValues(values);
|
|
309
|
+
if (entityId) {
|
|
310
|
+
const id = this.toObjectId(entityId);
|
|
311
|
+
await collection.updateOne(
|
|
312
|
+
{ _id: id },
|
|
313
|
+
{ $set: mongoValues },
|
|
314
|
+
{ upsert: true }
|
|
315
|
+
);
|
|
316
|
+
return {
|
|
317
|
+
id: entityId.toString(),
|
|
318
|
+
path: collectionPath,
|
|
319
|
+
values
|
|
320
|
+
};
|
|
321
|
+
} else {
|
|
322
|
+
const newId = new ObjectId();
|
|
323
|
+
await collection.insertOne({
|
|
324
|
+
_id: newId,
|
|
325
|
+
...mongoValues
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
id: newId.toString(),
|
|
329
|
+
path: collectionPath,
|
|
330
|
+
values
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Delete an entity by ID
|
|
336
|
+
*/
|
|
337
|
+
async deleteEntity(collectionPath, entityId, _databaseId) {
|
|
338
|
+
const collection = this.getCollection(collectionPath);
|
|
339
|
+
const id = this.toObjectId(entityId);
|
|
340
|
+
const result = await collection.deleteOne({ _id: id });
|
|
341
|
+
if (result.deletedCount === 0) {
|
|
342
|
+
console.warn(`Entity ${entityId} not found in collection ${collectionPath}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if a field value is unique in a collection
|
|
347
|
+
*/
|
|
348
|
+
async checkUniqueField(collectionPath, fieldName, value, excludeEntityId, _databaseId) {
|
|
349
|
+
const collection = this.getCollection(collectionPath);
|
|
350
|
+
const query = { [fieldName]: value };
|
|
351
|
+
if (excludeEntityId) {
|
|
352
|
+
const id = this.toObjectId(excludeEntityId);
|
|
353
|
+
query._id = { $ne: id };
|
|
354
|
+
}
|
|
355
|
+
const count = await collection.countDocuments(query);
|
|
356
|
+
return count === 0;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Generate a new entity ID
|
|
360
|
+
*/
|
|
361
|
+
generateEntityId() {
|
|
362
|
+
return new ObjectId().toString();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
class MongoRealtimeService {
|
|
366
|
+
constructor(db) {
|
|
367
|
+
this.db = db;
|
|
368
|
+
this.entityService = new MongoEntityService(db);
|
|
369
|
+
}
|
|
370
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
371
|
+
clients = /* @__PURE__ */ new Map();
|
|
372
|
+
entityService;
|
|
373
|
+
/**
|
|
374
|
+
* Get the collection name from a path
|
|
375
|
+
*/
|
|
376
|
+
getCollectionName(path) {
|
|
377
|
+
return path.replace(/\//g, "_");
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Subscribe to collection changes
|
|
381
|
+
*/
|
|
382
|
+
subscribeToCollection(subscriptionId, config, callback) {
|
|
383
|
+
this.unsubscribe(subscriptionId);
|
|
384
|
+
const collectionName = this.getCollectionName(config.path);
|
|
385
|
+
const collection = this.db.collection(collectionName);
|
|
386
|
+
const pipeline = [];
|
|
387
|
+
pipeline.push({
|
|
388
|
+
$match: {
|
|
389
|
+
operationType: { $in: ["insert", "update", "replace", "delete"] }
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
try {
|
|
393
|
+
const changeStream = collection.watch(pipeline, {
|
|
394
|
+
fullDocument: "updateLookup"
|
|
395
|
+
});
|
|
396
|
+
const subscription = {
|
|
397
|
+
type: "collection",
|
|
398
|
+
config,
|
|
399
|
+
changeStream,
|
|
400
|
+
callback
|
|
401
|
+
};
|
|
402
|
+
this.subscriptions.set(subscriptionId, subscription);
|
|
403
|
+
this.fetchAndNotifyCollection(subscriptionId, config, callback);
|
|
404
|
+
changeStream.on("change", async (change) => {
|
|
405
|
+
await this.fetchAndNotifyCollection(subscriptionId, config, callback);
|
|
406
|
+
});
|
|
407
|
+
changeStream.on("error", (error) => {
|
|
408
|
+
console.error(`Change stream error for subscription ${subscriptionId}:`, error);
|
|
409
|
+
});
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.warn("Change streams not available, falling back to polling:", error);
|
|
412
|
+
const subscription = {
|
|
413
|
+
type: "collection",
|
|
414
|
+
config,
|
|
415
|
+
callback
|
|
416
|
+
};
|
|
417
|
+
this.subscriptions.set(subscriptionId, subscription);
|
|
418
|
+
this.fetchAndNotifyCollection(subscriptionId, config, callback);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Fetch collection and notify callback
|
|
423
|
+
*/
|
|
424
|
+
async fetchAndNotifyCollection(subscriptionId, config, callback) {
|
|
425
|
+
try {
|
|
426
|
+
const entities = await this.entityService.fetchCollection(config.path, {
|
|
427
|
+
filter: config.filter,
|
|
428
|
+
orderBy: config.orderBy,
|
|
429
|
+
order: config.order,
|
|
430
|
+
limit: config.limit,
|
|
431
|
+
startAfter: config.startAfter,
|
|
432
|
+
searchString: config.searchString
|
|
433
|
+
});
|
|
434
|
+
if (callback) {
|
|
435
|
+
callback(entities);
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error(`Error fetching collection for subscription ${subscriptionId}:`, error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Subscribe to single entity changes
|
|
443
|
+
*/
|
|
444
|
+
subscribeToEntity(subscriptionId, config, callback) {
|
|
445
|
+
this.unsubscribe(subscriptionId);
|
|
446
|
+
const collectionName = this.getCollectionName(config.path);
|
|
447
|
+
const collection = this.db.collection(collectionName);
|
|
448
|
+
const entityId = typeof config.entityId === "string" && ObjectId.isValid(config.entityId) ? new ObjectId(config.entityId) : config.entityId;
|
|
449
|
+
const pipeline = [
|
|
450
|
+
{
|
|
451
|
+
$match: {
|
|
452
|
+
"documentKey._id": entityId,
|
|
453
|
+
operationType: { $in: ["insert", "update", "replace", "delete"] }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
];
|
|
457
|
+
try {
|
|
458
|
+
const changeStream = collection.watch(pipeline, {
|
|
459
|
+
fullDocument: "updateLookup"
|
|
460
|
+
});
|
|
461
|
+
const subscription = {
|
|
462
|
+
type: "entity",
|
|
463
|
+
config,
|
|
464
|
+
changeStream,
|
|
465
|
+
callback
|
|
466
|
+
};
|
|
467
|
+
this.subscriptions.set(subscriptionId, subscription);
|
|
468
|
+
this.fetchAndNotifyEntity(subscriptionId, config, callback);
|
|
469
|
+
changeStream.on("change", async (change) => {
|
|
470
|
+
if (change.operationType === "delete") {
|
|
471
|
+
if (callback) {
|
|
472
|
+
callback(null);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
await this.fetchAndNotifyEntity(subscriptionId, config, callback);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
changeStream.on("error", (error) => {
|
|
479
|
+
console.error(`Change stream error for subscription ${subscriptionId}:`, error);
|
|
480
|
+
});
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.warn("Change streams not available, falling back to polling:", error);
|
|
483
|
+
const subscription = {
|
|
484
|
+
type: "entity",
|
|
485
|
+
config,
|
|
486
|
+
callback
|
|
487
|
+
};
|
|
488
|
+
this.subscriptions.set(subscriptionId, subscription);
|
|
489
|
+
this.fetchAndNotifyEntity(subscriptionId, config, callback);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Fetch entity and notify callback
|
|
494
|
+
*/
|
|
495
|
+
async fetchAndNotifyEntity(subscriptionId, config, callback) {
|
|
496
|
+
try {
|
|
497
|
+
const entity = await this.entityService.fetchEntity(config.path, config.entityId);
|
|
498
|
+
if (callback) {
|
|
499
|
+
callback(entity || null);
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.error(`Error fetching entity for subscription ${subscriptionId}:`, error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Unsubscribe from a subscription
|
|
507
|
+
*/
|
|
508
|
+
unsubscribe(subscriptionId) {
|
|
509
|
+
const subscription = this.subscriptions.get(subscriptionId);
|
|
510
|
+
if (subscription) {
|
|
511
|
+
if (subscription.changeStream) {
|
|
512
|
+
subscription.changeStream.close().catch(console.error);
|
|
513
|
+
}
|
|
514
|
+
this.subscriptions.delete(subscriptionId);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Notify all relevant subscribers of an entity update
|
|
519
|
+
* This is called after save/delete operations to push updates
|
|
520
|
+
*/
|
|
521
|
+
async notifyEntityUpdate(path, entityId, entity, _databaseId) {
|
|
522
|
+
for (const [subscriptionId, subscription] of this.subscriptions) {
|
|
523
|
+
if (subscription.type === "entity") {
|
|
524
|
+
const config = subscription.config;
|
|
525
|
+
if (config.path === path && config.entityId.toString() === entityId) {
|
|
526
|
+
if (subscription.callback) {
|
|
527
|
+
subscription.callback(entity);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} else if (subscription.type === "collection") {
|
|
531
|
+
const config = subscription.config;
|
|
532
|
+
if (config.path === path) {
|
|
533
|
+
await this.fetchAndNotifyCollection(subscriptionId, config, subscription.callback);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get all active subscriptions (for debugging)
|
|
540
|
+
*/
|
|
541
|
+
getSubscriptions() {
|
|
542
|
+
return this.subscriptions;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Close all subscriptions
|
|
546
|
+
*/
|
|
547
|
+
async closeAll() {
|
|
548
|
+
for (const [subscriptionId] of this.subscriptions) {
|
|
549
|
+
this.unsubscribe(subscriptionId);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// =============================================================================
|
|
553
|
+
// WebSocket Client Management (parity with PostgreSQL RealtimeService)
|
|
554
|
+
// =============================================================================
|
|
555
|
+
/**
|
|
556
|
+
* Register a WebSocket client for real-time communication
|
|
557
|
+
*/
|
|
558
|
+
addClient(clientId, ws) {
|
|
559
|
+
this.clients.set(clientId, ws);
|
|
560
|
+
ws.on("close", () => {
|
|
561
|
+
this.removeClient(clientId);
|
|
562
|
+
});
|
|
563
|
+
ws.on("error", (error) => {
|
|
564
|
+
console.error("WebSocket error for client", clientId, error);
|
|
565
|
+
this.removeClient(clientId);
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Remove a WebSocket client and clean up its subscriptions
|
|
570
|
+
*/
|
|
571
|
+
removeClient(clientId) {
|
|
572
|
+
this.clients.delete(clientId);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Handle an incoming WebSocket message for subscription management
|
|
576
|
+
*/
|
|
577
|
+
async handleClientMessage(clientId, message, _authContext) {
|
|
578
|
+
const ws = this.clients.get(clientId);
|
|
579
|
+
if (!ws) return;
|
|
580
|
+
switch (message.type) {
|
|
581
|
+
case "subscribe_collection": {
|
|
582
|
+
const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
|
|
583
|
+
if (!subscriptionId) return;
|
|
584
|
+
this.subscribeToCollection(
|
|
585
|
+
subscriptionId,
|
|
586
|
+
{
|
|
587
|
+
clientId,
|
|
588
|
+
path: message.payload?.path,
|
|
589
|
+
filter: message.payload?.filter,
|
|
590
|
+
orderBy: message.payload?.orderBy,
|
|
591
|
+
order: message.payload?.order,
|
|
592
|
+
limit: message.payload?.limit,
|
|
593
|
+
startAfter: message.payload?.startAfter,
|
|
594
|
+
searchString: message.payload?.searchString
|
|
595
|
+
},
|
|
596
|
+
(entities) => {
|
|
597
|
+
ws.send(JSON.stringify({
|
|
598
|
+
type: "collection_update",
|
|
599
|
+
subscriptionId,
|
|
600
|
+
entities
|
|
601
|
+
}));
|
|
602
|
+
}
|
|
603
|
+
);
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case "subscribe_entity": {
|
|
607
|
+
const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
|
|
608
|
+
if (!subscriptionId) return;
|
|
609
|
+
this.subscribeToEntity(
|
|
610
|
+
subscriptionId,
|
|
611
|
+
{
|
|
612
|
+
clientId,
|
|
613
|
+
path: message.payload?.path,
|
|
614
|
+
entityId: message.payload?.entityId
|
|
615
|
+
},
|
|
616
|
+
(entity) => {
|
|
617
|
+
ws.send(JSON.stringify({
|
|
618
|
+
type: "entity_update",
|
|
619
|
+
subscriptionId,
|
|
620
|
+
entity
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
case "unsubscribe": {
|
|
627
|
+
const subscriptionId = message.payload?.subscriptionId ?? message.subscriptionId;
|
|
628
|
+
if (subscriptionId) {
|
|
629
|
+
this.unsubscribe(subscriptionId);
|
|
630
|
+
}
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function findChangedFields(oldValues, newValues) {
|
|
637
|
+
const changed = [];
|
|
638
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
639
|
+
...Object.keys(oldValues),
|
|
640
|
+
...Object.keys(newValues)
|
|
641
|
+
]);
|
|
642
|
+
for (const key of allKeys) {
|
|
643
|
+
const oldVal = oldValues[key];
|
|
644
|
+
const newVal = newValues[key];
|
|
645
|
+
if (key.startsWith("__")) continue;
|
|
646
|
+
if (oldVal !== newVal) {
|
|
647
|
+
if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
|
|
648
|
+
if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
|
649
|
+
changed.push(key);
|
|
650
|
+
}
|
|
651
|
+
} else {
|
|
652
|
+
changed.push(key);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return changed.length > 0 ? changed : null;
|
|
657
|
+
}
|
|
658
|
+
const DEFAULT_RETENTION = {
|
|
659
|
+
maxEntries: 200,
|
|
660
|
+
ttlDays: 90
|
|
661
|
+
};
|
|
662
|
+
class MongoHistoryService {
|
|
663
|
+
constructor(db, retention) {
|
|
664
|
+
this.db = db;
|
|
665
|
+
this.retention = { ...DEFAULT_RETENTION, ...retention };
|
|
666
|
+
}
|
|
667
|
+
retention;
|
|
668
|
+
async recordHistory(params) {
|
|
669
|
+
const {
|
|
670
|
+
tableName,
|
|
671
|
+
entityId,
|
|
672
|
+
action,
|
|
673
|
+
values,
|
|
674
|
+
previousValues,
|
|
675
|
+
updatedBy
|
|
676
|
+
} = params;
|
|
677
|
+
const changedFields = previousValues && values ? findChangedFields(previousValues, values) : null;
|
|
678
|
+
if (action === "update" && (!changedFields || changedFields.length === 0)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const entry = {
|
|
683
|
+
id: new ObjectId().toString(),
|
|
684
|
+
table_name: tableName,
|
|
685
|
+
entity_id: String(entityId),
|
|
686
|
+
action,
|
|
687
|
+
changed_fields: changedFields,
|
|
688
|
+
values: values || null,
|
|
689
|
+
previous_values: previousValues || null,
|
|
690
|
+
updated_by: updatedBy || null,
|
|
691
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
692
|
+
};
|
|
693
|
+
await this.db.collection("__rebase_history").insertOne(entry);
|
|
694
|
+
this.pruneHistory(String(entityId), tableName).catch((e) => {
|
|
695
|
+
console.error(`[HistoryService] Failed to prune history for ${tableName}/${entityId}:`, e);
|
|
696
|
+
});
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error(`[HistoryService] Failed to record history for ${tableName}/${entityId}:`, error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async pruneHistory(entityId, tableName) {
|
|
702
|
+
const collection = this.db.collection("__rebase_history");
|
|
703
|
+
const count = await collection.countDocuments({ entity_id: entityId, table_name: tableName });
|
|
704
|
+
if (count > this.retention.maxEntries) {
|
|
705
|
+
const toDelete = count - this.retention.maxEntries;
|
|
706
|
+
const oldestEntries = await collection.find({ entity_id: entityId, table_name: tableName }).sort({ updated_at: 1 }).limit(toDelete).toArray();
|
|
707
|
+
if (oldestEntries.length > 0) {
|
|
708
|
+
const idsToDelete = oldestEntries.map((entry) => entry._id);
|
|
709
|
+
await collection.deleteMany({ _id: { $in: idsToDelete } });
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
713
|
+
cutoffDate.setDate(cutoffDate.getDate() - this.retention.ttlDays);
|
|
714
|
+
await collection.deleteMany({
|
|
715
|
+
entity_id: entityId,
|
|
716
|
+
table_name: tableName,
|
|
717
|
+
updated_at: { $lt: cutoffDate }
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const MongoHistoryService$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
722
|
+
__proto__: null,
|
|
723
|
+
MongoHistoryService,
|
|
724
|
+
findChangedFields
|
|
725
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
726
|
+
class MongoDriver {
|
|
727
|
+
constructor(db, realtimeService, historyService) {
|
|
728
|
+
this.db = db;
|
|
729
|
+
this.entityService = new MongoEntityService(db);
|
|
730
|
+
this.realtimeService = realtimeService ?? new MongoRealtimeService(db);
|
|
731
|
+
this.historyService = historyService ?? new MongoHistoryService(db);
|
|
732
|
+
}
|
|
733
|
+
key = "mongodb";
|
|
734
|
+
initialised = true;
|
|
735
|
+
entityService;
|
|
736
|
+
realtimeService;
|
|
737
|
+
historyService;
|
|
738
|
+
/**
|
|
739
|
+
* Get the current timestamp
|
|
740
|
+
*/
|
|
741
|
+
currentTime() {
|
|
742
|
+
return /* @__PURE__ */ new Date();
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Fetch a collection of entities
|
|
746
|
+
*/
|
|
747
|
+
async fetchCollection({
|
|
748
|
+
path,
|
|
749
|
+
collection,
|
|
750
|
+
filter,
|
|
751
|
+
limit,
|
|
752
|
+
startAfter,
|
|
753
|
+
orderBy,
|
|
754
|
+
searchString,
|
|
755
|
+
order
|
|
756
|
+
}) {
|
|
757
|
+
return this.entityService.fetchCollection(path, {
|
|
758
|
+
filter,
|
|
759
|
+
limit,
|
|
760
|
+
startAfter,
|
|
761
|
+
orderBy,
|
|
762
|
+
order,
|
|
763
|
+
searchString,
|
|
764
|
+
collection
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Listen to collection changes
|
|
769
|
+
*/
|
|
770
|
+
listenCollection({
|
|
771
|
+
path,
|
|
772
|
+
collection,
|
|
773
|
+
filter,
|
|
774
|
+
limit,
|
|
775
|
+
startAfter,
|
|
776
|
+
orderBy,
|
|
777
|
+
searchString,
|
|
778
|
+
order,
|
|
779
|
+
onUpdate,
|
|
780
|
+
onError
|
|
781
|
+
}) {
|
|
782
|
+
const subscriptionId = this.generateSubscriptionId();
|
|
783
|
+
const callback = (entities) => {
|
|
784
|
+
try {
|
|
785
|
+
onUpdate(entities);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.error("Error in collection update callback:", error);
|
|
788
|
+
if (onError) {
|
|
789
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
this.realtimeService.subscribeToCollection(
|
|
794
|
+
subscriptionId,
|
|
795
|
+
{
|
|
796
|
+
clientId: "driver",
|
|
797
|
+
path,
|
|
798
|
+
filter,
|
|
799
|
+
orderBy,
|
|
800
|
+
order,
|
|
801
|
+
limit,
|
|
802
|
+
startAfter,
|
|
803
|
+
searchString
|
|
804
|
+
},
|
|
805
|
+
callback
|
|
806
|
+
);
|
|
807
|
+
return () => {
|
|
808
|
+
this.realtimeService.unsubscribe(subscriptionId);
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Fetch a single entity
|
|
813
|
+
*/
|
|
814
|
+
async fetchEntity({
|
|
815
|
+
path,
|
|
816
|
+
entityId,
|
|
817
|
+
databaseId,
|
|
818
|
+
collection
|
|
819
|
+
}) {
|
|
820
|
+
return this.entityService.fetchEntity(path, entityId, databaseId);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Listen to entity changes
|
|
824
|
+
*/
|
|
825
|
+
listenEntity({
|
|
826
|
+
path,
|
|
827
|
+
entityId,
|
|
828
|
+
collection,
|
|
829
|
+
onUpdate,
|
|
830
|
+
onError
|
|
831
|
+
}) {
|
|
832
|
+
const subscriptionId = this.generateSubscriptionId();
|
|
833
|
+
const callback = (entity) => {
|
|
834
|
+
try {
|
|
835
|
+
onUpdate(entity);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
console.error("Error in entity update callback:", error);
|
|
838
|
+
if (onError) {
|
|
839
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
this.realtimeService.subscribeToEntity(
|
|
844
|
+
subscriptionId,
|
|
845
|
+
{
|
|
846
|
+
clientId: "driver",
|
|
847
|
+
path,
|
|
848
|
+
entityId
|
|
849
|
+
},
|
|
850
|
+
callback
|
|
851
|
+
);
|
|
852
|
+
return () => {
|
|
853
|
+
this.realtimeService.unsubscribe(subscriptionId);
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Save an entity (create or update)
|
|
858
|
+
*/
|
|
859
|
+
async saveEntity({
|
|
860
|
+
path,
|
|
861
|
+
entityId,
|
|
862
|
+
values,
|
|
863
|
+
previousValues,
|
|
864
|
+
collection,
|
|
865
|
+
status
|
|
866
|
+
}) {
|
|
867
|
+
const isNew = status === "new" || !previousValues;
|
|
868
|
+
const entity = await this.entityService.saveEntity(path, values, entityId);
|
|
869
|
+
if (collection?.history) {
|
|
870
|
+
this.historyService.recordHistory({
|
|
871
|
+
action: isNew ? "create" : "update",
|
|
872
|
+
entityId: String(entity.id),
|
|
873
|
+
tableName: path,
|
|
874
|
+
values: entity.values,
|
|
875
|
+
previousValues: isNew ? void 0 : previousValues
|
|
876
|
+
}).catch((err) => {
|
|
877
|
+
console.error(`Failed to record history for ${path}/${entity.id}:`, err);
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
await this.realtimeService.notifyEntityUpdate(path, String(entity.id), entity);
|
|
881
|
+
return entity;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Delete an entity
|
|
885
|
+
*/
|
|
886
|
+
async deleteEntity({
|
|
887
|
+
entity,
|
|
888
|
+
collection
|
|
889
|
+
}) {
|
|
890
|
+
await this.entityService.deleteEntity(entity.path, entity.id);
|
|
891
|
+
if (collection?.history) {
|
|
892
|
+
this.historyService.recordHistory({
|
|
893
|
+
action: "delete",
|
|
894
|
+
entityId: String(entity.id),
|
|
895
|
+
tableName: entity.path,
|
|
896
|
+
previousValues: entity.values
|
|
897
|
+
}).catch((err) => {
|
|
898
|
+
console.error(`Failed to record history for ${entity.path}/${entity.id}:`, err);
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
await this.realtimeService.notifyEntityUpdate(entity.path, String(entity.id), null);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Check if a field value is unique
|
|
905
|
+
*/
|
|
906
|
+
async checkUniqueField(path, name, value, entityId, collection) {
|
|
907
|
+
return this.entityService.checkUniqueField(path, name, value, entityId);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Generate a new entity ID
|
|
911
|
+
*/
|
|
912
|
+
generateEntityId(path, collection) {
|
|
913
|
+
return this.entityService.generateEntityId();
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Count entities in a collection
|
|
917
|
+
*/
|
|
918
|
+
async countEntities({
|
|
919
|
+
path,
|
|
920
|
+
collection,
|
|
921
|
+
filter
|
|
922
|
+
}) {
|
|
923
|
+
return this.entityService.countEntities(path, { filter });
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Generate a unique subscription ID
|
|
927
|
+
*/
|
|
928
|
+
generateSubscriptionId() {
|
|
929
|
+
return `mongo_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Check if the delegate is ready
|
|
933
|
+
*/
|
|
934
|
+
isReady() {
|
|
935
|
+
return this.initialised;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Get the underlying entity service for direct access
|
|
939
|
+
*/
|
|
940
|
+
getEntityService() {
|
|
941
|
+
return this.entityService;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Get the underlying realtime service for direct access
|
|
945
|
+
*/
|
|
946
|
+
getRealtimeService() {
|
|
947
|
+
return this.realtimeService;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
class MongoCollectionRegistry {
|
|
951
|
+
collections = /* @__PURE__ */ new Map();
|
|
952
|
+
/**
|
|
953
|
+
* Register a collection
|
|
954
|
+
*/
|
|
955
|
+
register(collection) {
|
|
956
|
+
this.collections.set(collection.name, collection);
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Get a collection by its path
|
|
960
|
+
*/
|
|
961
|
+
getCollectionByPath(path) {
|
|
962
|
+
return this.collections.get(path);
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Get all registered collections
|
|
966
|
+
*/
|
|
967
|
+
getCollections() {
|
|
968
|
+
return Array.from(this.collections.values());
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function createMongoBackend(config) {
|
|
972
|
+
const { connection: db, client, collections } = config;
|
|
973
|
+
const collectionRegistry = new MongoCollectionRegistry();
|
|
974
|
+
if (collections) {
|
|
975
|
+
collections.forEach((collection) => collectionRegistry.register(collection));
|
|
976
|
+
}
|
|
977
|
+
const entityService = new MongoEntityService(db);
|
|
978
|
+
const realtimeService = new MongoRealtimeService(db);
|
|
979
|
+
const historyService = new MongoHistoryService(db, config.historyRetention);
|
|
980
|
+
const driver = new MongoDriver(db, realtimeService, historyService);
|
|
981
|
+
const mongoConnection = new MongoDBConnection(db, client);
|
|
982
|
+
const admin = {
|
|
983
|
+
async executeAggregate(pipeline) {
|
|
984
|
+
const firstStage = pipeline[0];
|
|
985
|
+
const collName = typeof firstStage.$from === "string" ? firstStage.$from : "__admin__";
|
|
986
|
+
const cursor = db.collection(collName).aggregate(pipeline);
|
|
987
|
+
return await cursor.toArray();
|
|
988
|
+
},
|
|
989
|
+
async fetchCollectionStats(collectionName) {
|
|
990
|
+
const stats = await db.command({ collStats: collectionName });
|
|
991
|
+
return {
|
|
992
|
+
count: stats.count,
|
|
993
|
+
sizeBytes: stats.size
|
|
994
|
+
};
|
|
995
|
+
},
|
|
996
|
+
async fetchUnmappedTables(mappedPaths) {
|
|
997
|
+
const allCollections = await db.listCollections().toArray();
|
|
998
|
+
const names = allCollections.map((c) => c.name).filter((n) => !n.startsWith("system."));
|
|
999
|
+
if (!mappedPaths || mappedPaths.length === 0) return names;
|
|
1000
|
+
const mappedSet = new Set(mappedPaths.map((p) => p.toLowerCase()));
|
|
1001
|
+
return names.filter((n) => !mappedSet.has(n.toLowerCase()));
|
|
1002
|
+
},
|
|
1003
|
+
async fetchTableMetadata(collectionName) {
|
|
1004
|
+
const sample = await db.collection(collectionName).findOne();
|
|
1005
|
+
if (!sample) return {
|
|
1006
|
+
columns: [],
|
|
1007
|
+
foreignKeys: [],
|
|
1008
|
+
junctions: [],
|
|
1009
|
+
policies: []
|
|
1010
|
+
};
|
|
1011
|
+
const columns = Object.entries(sample).map(([key, value]) => ({
|
|
1012
|
+
column_name: key,
|
|
1013
|
+
data_type: typeof value,
|
|
1014
|
+
udt_name: typeof value,
|
|
1015
|
+
is_nullable: "YES",
|
|
1016
|
+
column_default: null,
|
|
1017
|
+
character_maximum_length: null
|
|
1018
|
+
}));
|
|
1019
|
+
return {
|
|
1020
|
+
columns,
|
|
1021
|
+
foreignKeys: [],
|
|
1022
|
+
junctions: [],
|
|
1023
|
+
policies: []
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
return {
|
|
1028
|
+
// Abstract interface implementations
|
|
1029
|
+
connection: mongoConnection,
|
|
1030
|
+
entityRepository: entityService,
|
|
1031
|
+
realtimeProvider: realtimeService,
|
|
1032
|
+
collectionRegistry,
|
|
1033
|
+
admin,
|
|
1034
|
+
// Lifecycle
|
|
1035
|
+
async initialize() {
|
|
1036
|
+
},
|
|
1037
|
+
async healthCheck() {
|
|
1038
|
+
const start = Date.now();
|
|
1039
|
+
try {
|
|
1040
|
+
await db.command({ ping: 1 });
|
|
1041
|
+
return {
|
|
1042
|
+
healthy: true,
|
|
1043
|
+
latencyMs: Date.now() - start
|
|
1044
|
+
};
|
|
1045
|
+
} catch {
|
|
1046
|
+
return {
|
|
1047
|
+
healthy: false,
|
|
1048
|
+
latencyMs: Date.now() - start
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
async destroy() {
|
|
1053
|
+
await client.close();
|
|
1054
|
+
},
|
|
1055
|
+
// MongoDB-specific accessors
|
|
1056
|
+
db,
|
|
1057
|
+
client,
|
|
1058
|
+
driver,
|
|
1059
|
+
entityService,
|
|
1060
|
+
realtimeService
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
function createMongoDelegate(db, realtimeService, historyService) {
|
|
1064
|
+
const realtime = realtimeService ?? new MongoRealtimeService(db);
|
|
1065
|
+
const history = historyService ?? new MongoHistoryService(db);
|
|
1066
|
+
return new MongoDriver(db, realtime, history);
|
|
1067
|
+
}
|
|
1068
|
+
function createMongoRealtimeService(db) {
|
|
1069
|
+
return new MongoRealtimeService(db);
|
|
1070
|
+
}
|
|
1071
|
+
function createMongoEntityRepository(db) {
|
|
1072
|
+
return new MongoEntityService(db);
|
|
1073
|
+
}
|
|
1074
|
+
function isMongoBackendConfig(config) {
|
|
1075
|
+
return config.type === "mongodb" && typeof config.connection !== "undefined" && typeof config.client !== "undefined";
|
|
1076
|
+
}
|
|
1077
|
+
function isMongoDriverConfig(obj) {
|
|
1078
|
+
return typeof obj === "object" && obj !== null && "type" in obj && obj.type === "mongodb" && "connection" in obj && "client" in obj;
|
|
1079
|
+
}
|
|
1080
|
+
function toUser(doc) {
|
|
1081
|
+
return {
|
|
1082
|
+
id: doc._id || doc.id,
|
|
1083
|
+
email: doc.email,
|
|
1084
|
+
passwordHash: doc.passwordHash ?? null,
|
|
1085
|
+
displayName: doc.displayName ?? null,
|
|
1086
|
+
photoUrl: doc.photoUrl ?? null,
|
|
1087
|
+
emailVerified: doc.emailVerified ?? false,
|
|
1088
|
+
emailVerificationToken: doc.emailVerificationToken ?? null,
|
|
1089
|
+
emailVerificationSentAt: doc.emailVerificationSentAt ? new Date(doc.emailVerificationSentAt) : null,
|
|
1090
|
+
createdAt: new Date(doc.createdAt),
|
|
1091
|
+
updatedAt: new Date(doc.updatedAt)
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
class MongoUserService {
|
|
1095
|
+
constructor(db) {
|
|
1096
|
+
this.db = db;
|
|
1097
|
+
}
|
|
1098
|
+
get collection() {
|
|
1099
|
+
return this.db.collection("rebase_users");
|
|
1100
|
+
}
|
|
1101
|
+
get identitiesCollection() {
|
|
1102
|
+
return this.db.collection("rebase_user_identities");
|
|
1103
|
+
}
|
|
1104
|
+
get userRolesCollection() {
|
|
1105
|
+
return this.db.collection("rebase_user_roles");
|
|
1106
|
+
}
|
|
1107
|
+
get rolesCollection() {
|
|
1108
|
+
return this.db.collection("rebase_roles");
|
|
1109
|
+
}
|
|
1110
|
+
async createUser(data) {
|
|
1111
|
+
const id = new ObjectId().toString();
|
|
1112
|
+
const now = /* @__PURE__ */ new Date();
|
|
1113
|
+
const doc = {
|
|
1114
|
+
_id: id,
|
|
1115
|
+
id,
|
|
1116
|
+
email: data.email.toLowerCase(),
|
|
1117
|
+
passwordHash: data.passwordHash ?? null,
|
|
1118
|
+
displayName: data.displayName ?? null,
|
|
1119
|
+
photoUrl: data.photoUrl ?? null,
|
|
1120
|
+
emailVerified: data.emailVerified ?? false,
|
|
1121
|
+
createdAt: now,
|
|
1122
|
+
updatedAt: now
|
|
1123
|
+
};
|
|
1124
|
+
await this.collection.insertOne(doc);
|
|
1125
|
+
return toUser(doc);
|
|
1126
|
+
}
|
|
1127
|
+
async getUserById(id) {
|
|
1128
|
+
const doc = await this.collection.findOne({ id });
|
|
1129
|
+
return doc ? toUser(doc) : null;
|
|
1130
|
+
}
|
|
1131
|
+
async getUserByEmail(email) {
|
|
1132
|
+
const doc = await this.collection.findOne({ email: email.toLowerCase() });
|
|
1133
|
+
return doc ? toUser(doc) : null;
|
|
1134
|
+
}
|
|
1135
|
+
async getUserByIdentity(provider, providerId) {
|
|
1136
|
+
const identity = await this.identitiesCollection.findOne({ provider, providerId });
|
|
1137
|
+
if (!identity) return null;
|
|
1138
|
+
return this.getUserById(identity.userId);
|
|
1139
|
+
}
|
|
1140
|
+
async getUserIdentities(userId) {
|
|
1141
|
+
const docs = await this.identitiesCollection.find({ userId }).toArray();
|
|
1142
|
+
return docs.map((doc) => ({
|
|
1143
|
+
id: doc.id,
|
|
1144
|
+
userId: doc.userId,
|
|
1145
|
+
provider: doc.provider,
|
|
1146
|
+
providerId: doc.providerId,
|
|
1147
|
+
profileData: doc.profileData ?? null,
|
|
1148
|
+
createdAt: new Date(doc.createdAt),
|
|
1149
|
+
updatedAt: new Date(doc.updatedAt)
|
|
1150
|
+
}));
|
|
1151
|
+
}
|
|
1152
|
+
async linkUserIdentity(userId, provider, providerId, profileData) {
|
|
1153
|
+
const now = /* @__PURE__ */ new Date();
|
|
1154
|
+
await this.identitiesCollection.updateOne(
|
|
1155
|
+
{ provider, providerId },
|
|
1156
|
+
{
|
|
1157
|
+
$setOnInsert: {
|
|
1158
|
+
_id: new ObjectId().toString(),
|
|
1159
|
+
id: new ObjectId().toString(),
|
|
1160
|
+
userId,
|
|
1161
|
+
provider,
|
|
1162
|
+
providerId,
|
|
1163
|
+
createdAt: now
|
|
1164
|
+
},
|
|
1165
|
+
$set: {
|
|
1166
|
+
profileData: profileData ?? null,
|
|
1167
|
+
updatedAt: now
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
{ upsert: true }
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
async updateUser(id, data) {
|
|
1174
|
+
const updateData = { ...data, updatedAt: /* @__PURE__ */ new Date() };
|
|
1175
|
+
if (typeof updateData.email === "string") updateData.email = updateData.email.toLowerCase();
|
|
1176
|
+
await this.collection.updateOne({ id }, { $set: updateData });
|
|
1177
|
+
return this.getUserById(id);
|
|
1178
|
+
}
|
|
1179
|
+
async deleteUser(id) {
|
|
1180
|
+
await this.collection.deleteOne({ id });
|
|
1181
|
+
await this.identitiesCollection.deleteMany({ userId: id });
|
|
1182
|
+
await this.userRolesCollection.deleteMany({ userId: id });
|
|
1183
|
+
}
|
|
1184
|
+
async listUsers() {
|
|
1185
|
+
const docs = await this.collection.find().toArray();
|
|
1186
|
+
return docs.map(toUser);
|
|
1187
|
+
}
|
|
1188
|
+
async listUsersPaginated(options) {
|
|
1189
|
+
const limit = options?.limit ?? 25;
|
|
1190
|
+
const offset = options?.offset ?? 0;
|
|
1191
|
+
const search = options?.search?.trim() || "";
|
|
1192
|
+
const orderBy = options?.orderBy || "createdAt";
|
|
1193
|
+
const orderDir = options?.orderDir || "desc";
|
|
1194
|
+
const roleId = options?.roleId;
|
|
1195
|
+
const query = {};
|
|
1196
|
+
if (search) {
|
|
1197
|
+
query.$or = [
|
|
1198
|
+
{ email: { $regex: search, $options: "i" } },
|
|
1199
|
+
{ displayName: { $regex: search, $options: "i" } }
|
|
1200
|
+
];
|
|
1201
|
+
}
|
|
1202
|
+
if (roleId) {
|
|
1203
|
+
const userRoles = await this.userRolesCollection.find({ roleId }).toArray();
|
|
1204
|
+
const userIds = userRoles.map((ur) => ur.userId);
|
|
1205
|
+
query.id = { $in: userIds };
|
|
1206
|
+
}
|
|
1207
|
+
const sort = {};
|
|
1208
|
+
sort[orderBy] = orderDir === "asc" ? 1 : -1;
|
|
1209
|
+
const total = await this.collection.countDocuments(query);
|
|
1210
|
+
const docs = await this.collection.find(query).sort(sort).skip(offset).limit(limit).toArray();
|
|
1211
|
+
return {
|
|
1212
|
+
users: docs.map(toUser),
|
|
1213
|
+
total,
|
|
1214
|
+
limit,
|
|
1215
|
+
offset
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async updatePassword(id, passwordHash) {
|
|
1219
|
+
await this.collection.updateOne(
|
|
1220
|
+
{ id },
|
|
1221
|
+
{ $set: { passwordHash, updatedAt: /* @__PURE__ */ new Date() } }
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
async setEmailVerified(id, verified) {
|
|
1225
|
+
await this.collection.updateOne(
|
|
1226
|
+
{ id },
|
|
1227
|
+
{ $set: { emailVerified: verified, emailVerificationToken: null, updatedAt: /* @__PURE__ */ new Date() } }
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
async setVerificationToken(id, token) {
|
|
1231
|
+
await this.collection.updateOne(
|
|
1232
|
+
{ id },
|
|
1233
|
+
{ $set: { emailVerificationToken: token, emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null, updatedAt: /* @__PURE__ */ new Date() } }
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
async getUserByVerificationToken(token) {
|
|
1237
|
+
const doc = await this.collection.findOne({ emailVerificationToken: token });
|
|
1238
|
+
return doc ? toUser(doc) : null;
|
|
1239
|
+
}
|
|
1240
|
+
async getUserRoles(userId) {
|
|
1241
|
+
const userRoles = await this.userRolesCollection.find({ userId }).toArray();
|
|
1242
|
+
const roleIds = userRoles.map((ur) => ur.roleId);
|
|
1243
|
+
if (roleIds.length === 0) return [];
|
|
1244
|
+
const roles = await this.rolesCollection.find({ id: { $in: roleIds } }).toArray();
|
|
1245
|
+
return roles.map((r) => ({
|
|
1246
|
+
id: r.id,
|
|
1247
|
+
name: r.name,
|
|
1248
|
+
isAdmin: r.isAdmin ?? false,
|
|
1249
|
+
defaultPermissions: r.defaultPermissions ?? null,
|
|
1250
|
+
collectionPermissions: r.collectionPermissions ?? null,
|
|
1251
|
+
config: r.config ?? null
|
|
1252
|
+
}));
|
|
1253
|
+
}
|
|
1254
|
+
async getUserRoleIds(userId) {
|
|
1255
|
+
const userRoles = await this.userRolesCollection.find({ userId }).toArray();
|
|
1256
|
+
return userRoles.map((ur) => ur.roleId);
|
|
1257
|
+
}
|
|
1258
|
+
async setUserRoles(userId, roleIds) {
|
|
1259
|
+
await this.userRolesCollection.deleteMany({ userId });
|
|
1260
|
+
if (roleIds.length > 0) {
|
|
1261
|
+
const docs = roleIds.map((roleId) => ({
|
|
1262
|
+
_id: new ObjectId().toString(),
|
|
1263
|
+
userId,
|
|
1264
|
+
roleId
|
|
1265
|
+
}));
|
|
1266
|
+
await this.userRolesCollection.insertMany(docs);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async assignDefaultRole(userId, roleId) {
|
|
1270
|
+
await this.userRolesCollection.updateOne(
|
|
1271
|
+
{ userId, roleId },
|
|
1272
|
+
{ $setOnInsert: { _id: new ObjectId().toString(), userId, roleId } },
|
|
1273
|
+
{ upsert: true }
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
async getUserWithRoles(userId) {
|
|
1277
|
+
const user = await this.getUserById(userId);
|
|
1278
|
+
if (!user) return null;
|
|
1279
|
+
const roles = await this.getUserRoles(userId);
|
|
1280
|
+
return { user, roles };
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
class MongoRoleService {
|
|
1284
|
+
constructor(db) {
|
|
1285
|
+
this.db = db;
|
|
1286
|
+
}
|
|
1287
|
+
get collection() {
|
|
1288
|
+
return this.db.collection("rebase_roles");
|
|
1289
|
+
}
|
|
1290
|
+
async getRoleById(id) {
|
|
1291
|
+
const doc = await this.collection.findOne({ id });
|
|
1292
|
+
if (!doc) return null;
|
|
1293
|
+
return {
|
|
1294
|
+
id: doc.id,
|
|
1295
|
+
name: doc.name,
|
|
1296
|
+
isAdmin: doc.isAdmin ?? false,
|
|
1297
|
+
defaultPermissions: doc.defaultPermissions ?? null,
|
|
1298
|
+
collectionPermissions: doc.collectionPermissions ?? null,
|
|
1299
|
+
config: doc.config ?? null
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
async listRoles() {
|
|
1303
|
+
const docs = await this.collection.find().sort({ name: 1 }).toArray();
|
|
1304
|
+
return docs.map((doc) => ({
|
|
1305
|
+
id: doc.id,
|
|
1306
|
+
name: doc.name,
|
|
1307
|
+
isAdmin: doc.isAdmin ?? false,
|
|
1308
|
+
defaultPermissions: doc.defaultPermissions ?? null,
|
|
1309
|
+
collectionPermissions: doc.collectionPermissions ?? null,
|
|
1310
|
+
config: doc.config ?? null
|
|
1311
|
+
}));
|
|
1312
|
+
}
|
|
1313
|
+
async createRole(data) {
|
|
1314
|
+
const doc = {
|
|
1315
|
+
_id: data.id,
|
|
1316
|
+
id: data.id,
|
|
1317
|
+
name: data.name,
|
|
1318
|
+
isAdmin: data.isAdmin ?? false,
|
|
1319
|
+
defaultPermissions: data.defaultPermissions ?? null,
|
|
1320
|
+
collectionPermissions: data.collectionPermissions ?? null,
|
|
1321
|
+
config: data.config ?? null
|
|
1322
|
+
};
|
|
1323
|
+
await this.collection.insertOne(doc);
|
|
1324
|
+
return { ...doc };
|
|
1325
|
+
}
|
|
1326
|
+
async updateRole(id, data) {
|
|
1327
|
+
await this.collection.updateOne({ id }, { $set: data });
|
|
1328
|
+
return this.getRoleById(id);
|
|
1329
|
+
}
|
|
1330
|
+
async deleteRole(id) {
|
|
1331
|
+
await this.collection.deleteOne({ id });
|
|
1332
|
+
await this.db.collection("rebase_user_roles").deleteMany({ roleId: id });
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
class MongoRefreshTokenService {
|
|
1336
|
+
constructor(db) {
|
|
1337
|
+
this.db = db;
|
|
1338
|
+
}
|
|
1339
|
+
get collection() {
|
|
1340
|
+
return this.db.collection("rebase_refresh_tokens");
|
|
1341
|
+
}
|
|
1342
|
+
async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
1343
|
+
const safeUserAgent = userAgent || "";
|
|
1344
|
+
const safeIpAddress = ipAddress || "";
|
|
1345
|
+
await this.collection.deleteMany({
|
|
1346
|
+
userId,
|
|
1347
|
+
userAgent: safeUserAgent,
|
|
1348
|
+
ipAddress: safeIpAddress
|
|
1349
|
+
});
|
|
1350
|
+
await this.collection.insertOne({
|
|
1351
|
+
_id: new ObjectId().toString(),
|
|
1352
|
+
id: new ObjectId().toString(),
|
|
1353
|
+
userId,
|
|
1354
|
+
tokenHash,
|
|
1355
|
+
expiresAt,
|
|
1356
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1357
|
+
userAgent: safeUserAgent,
|
|
1358
|
+
ipAddress: safeIpAddress
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
async findByHash(tokenHash) {
|
|
1362
|
+
const doc = await this.collection.findOne({ tokenHash });
|
|
1363
|
+
if (!doc) return null;
|
|
1364
|
+
return {
|
|
1365
|
+
id: doc.id,
|
|
1366
|
+
userId: doc.userId,
|
|
1367
|
+
tokenHash: doc.tokenHash,
|
|
1368
|
+
expiresAt: new Date(doc.expiresAt),
|
|
1369
|
+
createdAt: new Date(doc.createdAt),
|
|
1370
|
+
userAgent: doc.userAgent,
|
|
1371
|
+
ipAddress: doc.ipAddress
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
async deleteByHash(tokenHash) {
|
|
1375
|
+
await this.collection.deleteOne({ tokenHash });
|
|
1376
|
+
}
|
|
1377
|
+
async deleteAllForUser(userId) {
|
|
1378
|
+
await this.collection.deleteMany({ userId });
|
|
1379
|
+
}
|
|
1380
|
+
async listForUser(userId) {
|
|
1381
|
+
const docs = await this.collection.find({ userId }).sort({ createdAt: 1 }).toArray();
|
|
1382
|
+
return docs.map((doc) => ({
|
|
1383
|
+
id: doc.id,
|
|
1384
|
+
userId: doc.userId,
|
|
1385
|
+
tokenHash: doc.tokenHash,
|
|
1386
|
+
expiresAt: new Date(doc.expiresAt),
|
|
1387
|
+
createdAt: new Date(doc.createdAt),
|
|
1388
|
+
userAgent: doc.userAgent,
|
|
1389
|
+
ipAddress: doc.ipAddress
|
|
1390
|
+
}));
|
|
1391
|
+
}
|
|
1392
|
+
async deleteById(id, userId) {
|
|
1393
|
+
await this.collection.deleteOne({ id, userId });
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
class MongoPasswordResetTokenService {
|
|
1397
|
+
constructor(db) {
|
|
1398
|
+
this.db = db;
|
|
1399
|
+
}
|
|
1400
|
+
get collection() {
|
|
1401
|
+
return this.db.collection("rebase_password_reset_tokens");
|
|
1402
|
+
}
|
|
1403
|
+
async createToken(userId, tokenHash, expiresAt) {
|
|
1404
|
+
await this.collection.deleteMany({ userId, usedAt: null });
|
|
1405
|
+
await this.collection.insertOne({
|
|
1406
|
+
_id: new ObjectId().toString(),
|
|
1407
|
+
userId,
|
|
1408
|
+
tokenHash,
|
|
1409
|
+
expiresAt,
|
|
1410
|
+
usedAt: null
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
async findValidByHash(tokenHash) {
|
|
1414
|
+
const doc = await this.collection.findOne({
|
|
1415
|
+
tokenHash,
|
|
1416
|
+
usedAt: null,
|
|
1417
|
+
expiresAt: { $gt: /* @__PURE__ */ new Date() }
|
|
1418
|
+
});
|
|
1419
|
+
if (!doc) return null;
|
|
1420
|
+
return {
|
|
1421
|
+
userId: doc.userId,
|
|
1422
|
+
expiresAt: new Date(doc.expiresAt)
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
async markAsUsed(tokenHash) {
|
|
1426
|
+
await this.collection.updateOne(
|
|
1427
|
+
{ tokenHash },
|
|
1428
|
+
{ $set: { usedAt: /* @__PURE__ */ new Date() } }
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
async deleteAllForUser(userId) {
|
|
1432
|
+
await this.collection.deleteMany({ userId });
|
|
1433
|
+
}
|
|
1434
|
+
async deleteExpired() {
|
|
1435
|
+
await this.collection.deleteMany({ expiresAt: { $lt: /* @__PURE__ */ new Date() } });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
class MongoTokenRepository {
|
|
1439
|
+
constructor(db) {
|
|
1440
|
+
this.db = db;
|
|
1441
|
+
this.refreshTokenService = new MongoRefreshTokenService(db);
|
|
1442
|
+
this.passwordResetTokenService = new MongoPasswordResetTokenService(db);
|
|
1443
|
+
}
|
|
1444
|
+
refreshTokenService;
|
|
1445
|
+
passwordResetTokenService;
|
|
1446
|
+
async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
1447
|
+
await this.refreshTokenService.createToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
|
|
1448
|
+
}
|
|
1449
|
+
async findRefreshTokenByHash(tokenHash) {
|
|
1450
|
+
return this.refreshTokenService.findByHash(tokenHash);
|
|
1451
|
+
}
|
|
1452
|
+
async deleteRefreshToken(tokenHash) {
|
|
1453
|
+
await this.refreshTokenService.deleteByHash(tokenHash);
|
|
1454
|
+
}
|
|
1455
|
+
async deleteAllRefreshTokensForUser(userId) {
|
|
1456
|
+
await this.refreshTokenService.deleteAllForUser(userId);
|
|
1457
|
+
}
|
|
1458
|
+
async listRefreshTokensForUser(userId) {
|
|
1459
|
+
return this.refreshTokenService.listForUser(userId);
|
|
1460
|
+
}
|
|
1461
|
+
async deleteRefreshTokenById(id, userId) {
|
|
1462
|
+
await this.refreshTokenService.deleteById(id, userId);
|
|
1463
|
+
}
|
|
1464
|
+
async createPasswordResetToken(userId, tokenHash, expiresAt) {
|
|
1465
|
+
await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
|
|
1466
|
+
}
|
|
1467
|
+
async findValidPasswordResetToken(tokenHash) {
|
|
1468
|
+
return this.passwordResetTokenService.findValidByHash(tokenHash);
|
|
1469
|
+
}
|
|
1470
|
+
async markPasswordResetTokenUsed(tokenHash) {
|
|
1471
|
+
await this.passwordResetTokenService.markAsUsed(tokenHash);
|
|
1472
|
+
}
|
|
1473
|
+
async deleteAllPasswordResetTokensForUser(userId) {
|
|
1474
|
+
await this.passwordResetTokenService.deleteAllForUser(userId);
|
|
1475
|
+
}
|
|
1476
|
+
async deleteExpiredTokens() {
|
|
1477
|
+
await this.passwordResetTokenService.deleteExpired();
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
class MongoAuthRepository {
|
|
1481
|
+
constructor(db) {
|
|
1482
|
+
this.db = db;
|
|
1483
|
+
this.userService = new MongoUserService(db);
|
|
1484
|
+
this.roleService = new MongoRoleService(db);
|
|
1485
|
+
this.tokenRepository = new MongoTokenRepository(db);
|
|
1486
|
+
}
|
|
1487
|
+
userService;
|
|
1488
|
+
roleService;
|
|
1489
|
+
tokenRepository;
|
|
1490
|
+
async createUser(data) {
|
|
1491
|
+
return this.userService.createUser(data);
|
|
1492
|
+
}
|
|
1493
|
+
async getUserById(id) {
|
|
1494
|
+
return this.userService.getUserById(id);
|
|
1495
|
+
}
|
|
1496
|
+
async getUserByEmail(email) {
|
|
1497
|
+
return this.userService.getUserByEmail(email);
|
|
1498
|
+
}
|
|
1499
|
+
async getUserByIdentity(provider, providerId) {
|
|
1500
|
+
return this.userService.getUserByIdentity(provider, providerId);
|
|
1501
|
+
}
|
|
1502
|
+
async getUserIdentities(userId) {
|
|
1503
|
+
return this.userService.getUserIdentities(userId);
|
|
1504
|
+
}
|
|
1505
|
+
async linkUserIdentity(userId, provider, providerId, profileData) {
|
|
1506
|
+
return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
|
|
1507
|
+
}
|
|
1508
|
+
async updateUser(id, data) {
|
|
1509
|
+
return this.userService.updateUser(id, data);
|
|
1510
|
+
}
|
|
1511
|
+
async deleteUser(id) {
|
|
1512
|
+
await this.userService.deleteUser(id);
|
|
1513
|
+
}
|
|
1514
|
+
async listUsers() {
|
|
1515
|
+
return this.userService.listUsers();
|
|
1516
|
+
}
|
|
1517
|
+
async listUsersPaginated(options) {
|
|
1518
|
+
return this.userService.listUsersPaginated(options);
|
|
1519
|
+
}
|
|
1520
|
+
async updatePassword(id, passwordHash) {
|
|
1521
|
+
await this.userService.updatePassword(id, passwordHash);
|
|
1522
|
+
}
|
|
1523
|
+
async setEmailVerified(id, verified) {
|
|
1524
|
+
await this.userService.setEmailVerified(id, verified);
|
|
1525
|
+
}
|
|
1526
|
+
async setVerificationToken(id, token) {
|
|
1527
|
+
await this.userService.setVerificationToken(id, token);
|
|
1528
|
+
}
|
|
1529
|
+
async getUserByVerificationToken(token) {
|
|
1530
|
+
return this.userService.getUserByVerificationToken(token);
|
|
1531
|
+
}
|
|
1532
|
+
async getUserRoles(userId) {
|
|
1533
|
+
return this.userService.getUserRoles(userId);
|
|
1534
|
+
}
|
|
1535
|
+
async getUserRoleIds(userId) {
|
|
1536
|
+
return this.userService.getUserRoleIds(userId);
|
|
1537
|
+
}
|
|
1538
|
+
async setUserRoles(userId, roleIds) {
|
|
1539
|
+
await this.userService.setUserRoles(userId, roleIds);
|
|
1540
|
+
}
|
|
1541
|
+
async assignDefaultRole(userId, roleId) {
|
|
1542
|
+
await this.userService.assignDefaultRole(userId, roleId);
|
|
1543
|
+
}
|
|
1544
|
+
async getUserWithRoles(userId) {
|
|
1545
|
+
return this.userService.getUserWithRoles(userId);
|
|
1546
|
+
}
|
|
1547
|
+
async getRoleById(id) {
|
|
1548
|
+
return this.roleService.getRoleById(id);
|
|
1549
|
+
}
|
|
1550
|
+
async listRoles() {
|
|
1551
|
+
return this.roleService.listRoles();
|
|
1552
|
+
}
|
|
1553
|
+
async createRole(data) {
|
|
1554
|
+
return this.roleService.createRole(data);
|
|
1555
|
+
}
|
|
1556
|
+
async updateRole(id, data) {
|
|
1557
|
+
return this.roleService.updateRole(id, data);
|
|
1558
|
+
}
|
|
1559
|
+
async deleteRole(id) {
|
|
1560
|
+
await this.roleService.deleteRole(id);
|
|
1561
|
+
}
|
|
1562
|
+
async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
1563
|
+
await this.tokenRepository.createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
|
|
1564
|
+
}
|
|
1565
|
+
async findRefreshTokenByHash(tokenHash) {
|
|
1566
|
+
return this.tokenRepository.findRefreshTokenByHash(tokenHash);
|
|
1567
|
+
}
|
|
1568
|
+
async deleteRefreshToken(tokenHash) {
|
|
1569
|
+
await this.tokenRepository.deleteRefreshToken(tokenHash);
|
|
1570
|
+
}
|
|
1571
|
+
async deleteAllRefreshTokensForUser(userId) {
|
|
1572
|
+
await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
|
|
1573
|
+
}
|
|
1574
|
+
async listRefreshTokensForUser(userId) {
|
|
1575
|
+
return this.tokenRepository.listRefreshTokensForUser(userId);
|
|
1576
|
+
}
|
|
1577
|
+
async deleteRefreshTokenById(id, userId) {
|
|
1578
|
+
await this.tokenRepository.deleteRefreshTokenById(id, userId);
|
|
1579
|
+
}
|
|
1580
|
+
async createPasswordResetToken(userId, tokenHash, expiresAt) {
|
|
1581
|
+
await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
|
|
1582
|
+
}
|
|
1583
|
+
async findValidPasswordResetToken(tokenHash) {
|
|
1584
|
+
return this.tokenRepository.findValidPasswordResetToken(tokenHash);
|
|
1585
|
+
}
|
|
1586
|
+
async markPasswordResetTokenUsed(tokenHash) {
|
|
1587
|
+
await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
|
|
1588
|
+
}
|
|
1589
|
+
async deleteAllPasswordResetTokensForUser(userId) {
|
|
1590
|
+
await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
|
|
1591
|
+
}
|
|
1592
|
+
async deleteExpiredTokens() {
|
|
1593
|
+
await this.tokenRepository.deleteExpiredTokens();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function createMongoBootstrapper(mongoConfig) {
|
|
1597
|
+
let cachedAdmin;
|
|
1598
|
+
return {
|
|
1599
|
+
type: "mongodb",
|
|
1600
|
+
async initializeDriver(config) {
|
|
1601
|
+
const { collections } = config;
|
|
1602
|
+
const registry = new MongoCollectionRegistry();
|
|
1603
|
+
if (collections) {
|
|
1604
|
+
collections.forEach((collection) => registry.register(collection));
|
|
1605
|
+
}
|
|
1606
|
+
const db = mongoConfig.connection;
|
|
1607
|
+
const client = mongoConfig.client;
|
|
1608
|
+
try {
|
|
1609
|
+
await db.command({ ping: 1 });
|
|
1610
|
+
} catch (err) {
|
|
1611
|
+
console.error("❌ Failed to connect to MongoDB:", err);
|
|
1612
|
+
}
|
|
1613
|
+
const realtimeService = new MongoRealtimeService(db);
|
|
1614
|
+
const driver = new MongoDriver(db, realtimeService);
|
|
1615
|
+
const internals = {
|
|
1616
|
+
db,
|
|
1617
|
+
client,
|
|
1618
|
+
registry,
|
|
1619
|
+
realtimeService,
|
|
1620
|
+
driver
|
|
1621
|
+
};
|
|
1622
|
+
return {
|
|
1623
|
+
driver,
|
|
1624
|
+
realtimeProvider: realtimeService,
|
|
1625
|
+
collectionRegistry: registry,
|
|
1626
|
+
internals
|
|
1627
|
+
};
|
|
1628
|
+
},
|
|
1629
|
+
async initializeAuth(config, driverResult) {
|
|
1630
|
+
const internals = driverResult.internals;
|
|
1631
|
+
const db = internals.db;
|
|
1632
|
+
const { ensureAuthCollectionsExist } = await import("./ensure-collections-CNrcwVgY.js");
|
|
1633
|
+
await ensureAuthCollectionsExist(db);
|
|
1634
|
+
const { createEmailService } = await import("@rebasepro/server-core");
|
|
1635
|
+
const authConfig = config;
|
|
1636
|
+
let emailService;
|
|
1637
|
+
if (authConfig?.email) {
|
|
1638
|
+
emailService = createEmailService(authConfig.email);
|
|
1639
|
+
}
|
|
1640
|
+
const userService = new MongoUserService(db);
|
|
1641
|
+
const roleService = new MongoRoleService(db);
|
|
1642
|
+
const authRepository = new MongoAuthRepository(db);
|
|
1643
|
+
return {
|
|
1644
|
+
userService,
|
|
1645
|
+
roleService,
|
|
1646
|
+
authRepository,
|
|
1647
|
+
emailService
|
|
1648
|
+
};
|
|
1649
|
+
},
|
|
1650
|
+
async initializeHistory(config, driverResult) {
|
|
1651
|
+
const historyConfig = config;
|
|
1652
|
+
if (!historyConfig) return void 0;
|
|
1653
|
+
const internals = driverResult.internals;
|
|
1654
|
+
const db = internals.db;
|
|
1655
|
+
const { ensureHistoryCollectionExists } = await import("./ensure-history-collection-DBIiwmCm.js");
|
|
1656
|
+
await ensureHistoryCollectionExists(db);
|
|
1657
|
+
const { MongoHistoryService: MongoHistoryService2 } = await Promise.resolve().then(() => MongoHistoryService$1);
|
|
1658
|
+
const retention = typeof historyConfig === "object" && historyConfig !== null ? historyConfig.retention : void 0;
|
|
1659
|
+
const historyService = new MongoHistoryService2(db, retention ? { ttlDays: retention } : void 0);
|
|
1660
|
+
return { historyService };
|
|
1661
|
+
},
|
|
1662
|
+
async initializeRealtime(_config, driverResult) {
|
|
1663
|
+
const internals = driverResult.internals;
|
|
1664
|
+
return internals.realtimeService;
|
|
1665
|
+
},
|
|
1666
|
+
getAdmin(driverResult) {
|
|
1667
|
+
const internals = driverResult.internals;
|
|
1668
|
+
const db = internals.db;
|
|
1669
|
+
const admin = {
|
|
1670
|
+
async executeAggregate(pipeline) {
|
|
1671
|
+
const firstStage = pipeline[0];
|
|
1672
|
+
const collName = firstStage?.$from ?? "__admin__";
|
|
1673
|
+
const cursor = db.collection(collName).aggregate(pipeline);
|
|
1674
|
+
return await cursor.toArray();
|
|
1675
|
+
},
|
|
1676
|
+
async fetchCollectionStats(collectionName) {
|
|
1677
|
+
const stats = await db.command({ collStats: collectionName });
|
|
1678
|
+
return { count: stats.count, sizeBytes: stats.size };
|
|
1679
|
+
},
|
|
1680
|
+
async fetchUnmappedTables(mappedPaths) {
|
|
1681
|
+
const allCollections = await db.listCollections().toArray();
|
|
1682
|
+
const names = allCollections.map((c) => c.name).filter((n) => !n.startsWith("system."));
|
|
1683
|
+
if (!mappedPaths || mappedPaths.length === 0) return names;
|
|
1684
|
+
const mappedSet = new Set(mappedPaths.map((p) => p.toLowerCase()));
|
|
1685
|
+
return names.filter((n) => !mappedSet.has(n.toLowerCase()));
|
|
1686
|
+
},
|
|
1687
|
+
async fetchTableMetadata(collectionName) {
|
|
1688
|
+
const sample = await db.collection(collectionName).findOne();
|
|
1689
|
+
if (!sample) return { columns: [], foreignKeys: [], junctions: [], policies: [] };
|
|
1690
|
+
const columns = Object.entries(sample).map(([key, value]) => ({
|
|
1691
|
+
column_name: key,
|
|
1692
|
+
data_type: typeof value,
|
|
1693
|
+
udt_name: typeof value,
|
|
1694
|
+
is_nullable: "YES",
|
|
1695
|
+
column_default: null,
|
|
1696
|
+
character_maximum_length: null
|
|
1697
|
+
}));
|
|
1698
|
+
return { columns, foreignKeys: [], junctions: [], policies: [] };
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
cachedAdmin = admin;
|
|
1702
|
+
return admin;
|
|
1703
|
+
},
|
|
1704
|
+
mountRoutes() {
|
|
1705
|
+
},
|
|
1706
|
+
async initializeWebsockets(server, realtimeService, driver, config) {
|
|
1707
|
+
const { createMongoWebSocket } = await import("./websocket-BZlPuJrt.js");
|
|
1708
|
+
createMongoWebSocket(
|
|
1709
|
+
server,
|
|
1710
|
+
realtimeService,
|
|
1711
|
+
driver,
|
|
1712
|
+
config,
|
|
1713
|
+
cachedAdmin
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
export {
|
|
1719
|
+
MongoCollectionRegistry,
|
|
1720
|
+
MongoConditionBuilder,
|
|
1721
|
+
MongoDBConnection,
|
|
1722
|
+
MongoDriver,
|
|
1723
|
+
MongoEntityService,
|
|
1724
|
+
MongoRealtimeService,
|
|
1725
|
+
createMongoBackend,
|
|
1726
|
+
createMongoBootstrapper,
|
|
1727
|
+
createMongoDBConnection,
|
|
1728
|
+
createMongoDelegate,
|
|
1729
|
+
createMongoEntityRepository,
|
|
1730
|
+
createMongoRealtimeService,
|
|
1731
|
+
isMongoBackendConfig,
|
|
1732
|
+
isMongoDriverConfig
|
|
1733
|
+
};
|
|
1734
|
+
//# sourceMappingURL=index.es.js.map
|