@okf/ootils 1.41.2 → 1.43.0
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/dist/node.d.mts +6559 -1
- package/dist/node.d.ts +6559 -1
- package/dist/node.js +871 -19
- package/dist/node.mjs +861 -19
- package/package.json +2 -1
package/dist/node.js
CHANGED
|
@@ -935,7 +935,7 @@ var init_Tpl = __esm({
|
|
|
935
935
|
var require_MongoConnector = __commonJS({
|
|
936
936
|
"src/db/MongoConnector.js"(exports2, module2) {
|
|
937
937
|
"use strict";
|
|
938
|
-
var
|
|
938
|
+
var mongoose10 = require("mongoose");
|
|
939
939
|
var { ServerApiVersion } = require("mongodb");
|
|
940
940
|
var AnnotationsSchema = (init_Annotations(), __toCommonJS(Annotations_exports));
|
|
941
941
|
var PlatformConfigsSchema2 = (init_PlatformConfigs(), __toCommonJS(PlatformConfigs_exports));
|
|
@@ -999,7 +999,7 @@ var require_MongoConnector = __commonJS({
|
|
|
999
999
|
`Missing CLUSTER_NAME or CLUSTER_URI in cluster conf: ${JSON.stringify(clusterConf)}`
|
|
1000
1000
|
);
|
|
1001
1001
|
}
|
|
1002
|
-
this.clusterConnections[CLUSTER_NAME] =
|
|
1002
|
+
this.clusterConnections[CLUSTER_NAME] = mongoose10.createConnection(
|
|
1003
1003
|
CLUSTER_URI,
|
|
1004
1004
|
this.mongoOptions
|
|
1005
1005
|
);
|
|
@@ -1026,7 +1026,7 @@ var require_MongoConnector = __commonJS({
|
|
|
1026
1026
|
this.skippedClusters.add(CLUSTER_NAME);
|
|
1027
1027
|
return;
|
|
1028
1028
|
}
|
|
1029
|
-
const connection =
|
|
1029
|
+
const connection = mongoose10.createConnection(CLUSTER_URI, this.mongoOptions);
|
|
1030
1030
|
this.clusterConnections[CLUSTER_NAME] = connection;
|
|
1031
1031
|
this.initiateConnectionEventListeners(CLUSTER_NAME);
|
|
1032
1032
|
await new Promise((resolve, reject) => {
|
|
@@ -1491,7 +1491,7 @@ var require_getModelByTenant = __commonJS({
|
|
|
1491
1491
|
"use strict";
|
|
1492
1492
|
var { getDbByTenant: getDbByTenant2 } = (init_getDbByTenant(), __toCommonJS(getDbByTenant_exports));
|
|
1493
1493
|
var { AIChatSchema: AIChatSchema2, AnnotationSchema: AnnotationSchema2, PlatformConfigsSchema: PlatformConfigsSchema2, TplSchema: TplSchema2, GeneratedTopicsSchema: GeneratedTopicsSchema2, GeneratedEntitiesSchema: GeneratedEntitiesSchema2 } = (init_models(), __toCommonJS(models_exports));
|
|
1494
|
-
var
|
|
1494
|
+
var getModelByTenant4 = ({ tenant, modelName, schema, env }) => {
|
|
1495
1495
|
if (!tenant) {
|
|
1496
1496
|
throw new Error("tenant id has not been provided");
|
|
1497
1497
|
}
|
|
@@ -1501,48 +1501,48 @@ var require_getModelByTenant = __commonJS({
|
|
|
1501
1501
|
}
|
|
1502
1502
|
return db.model(modelName);
|
|
1503
1503
|
};
|
|
1504
|
-
var getAnnotationsModelByTenant2 = ({ tenant, env, mongodb, modelName }) =>
|
|
1504
|
+
var getAnnotationsModelByTenant2 = ({ tenant, env, mongodb, modelName }) => getModelByTenant4({
|
|
1505
1505
|
tenant,
|
|
1506
1506
|
modelName: modelName || "annotations",
|
|
1507
1507
|
schema: AnnotationSchema2,
|
|
1508
1508
|
env
|
|
1509
1509
|
});
|
|
1510
|
-
var getPlatformConfigsModelByTenant2 = ({ tenant, env, mongodb }) =>
|
|
1510
|
+
var getPlatformConfigsModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant4({
|
|
1511
1511
|
tenant,
|
|
1512
1512
|
modelName: "platformConfigs",
|
|
1513
1513
|
schema: PlatformConfigsSchema2,
|
|
1514
1514
|
env
|
|
1515
1515
|
});
|
|
1516
|
-
var
|
|
1516
|
+
var getTplModelByTenant3 = ({ tenant, env, mongodb }) => getModelByTenant4({
|
|
1517
1517
|
tenant,
|
|
1518
1518
|
modelName: "tpl",
|
|
1519
1519
|
schema: TplSchema2,
|
|
1520
1520
|
env
|
|
1521
1521
|
});
|
|
1522
|
-
var getAIChatModelByTenant2 = ({ tenant, env, mongodb }) =>
|
|
1522
|
+
var getAIChatModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant4({
|
|
1523
1523
|
tenant,
|
|
1524
1524
|
modelName: "aiChat",
|
|
1525
1525
|
schema: AIChatSchema2,
|
|
1526
1526
|
env
|
|
1527
1527
|
});
|
|
1528
|
-
var getGeneratedTopicsModelByTenant2 = ({ tenant, env }) =>
|
|
1528
|
+
var getGeneratedTopicsModelByTenant2 = ({ tenant, env }) => getModelByTenant4({
|
|
1529
1529
|
tenant,
|
|
1530
1530
|
modelName: "generatedTopics",
|
|
1531
1531
|
schema: GeneratedTopicsSchema2,
|
|
1532
1532
|
env
|
|
1533
1533
|
});
|
|
1534
|
-
var getGeneratedEntitiesModelByTenant2 = ({ tenant, env }) =>
|
|
1534
|
+
var getGeneratedEntitiesModelByTenant2 = ({ tenant, env }) => getModelByTenant4({
|
|
1535
1535
|
tenant,
|
|
1536
1536
|
modelName: "generatedEntities",
|
|
1537
1537
|
schema: GeneratedEntitiesSchema2,
|
|
1538
1538
|
env
|
|
1539
1539
|
});
|
|
1540
1540
|
module2.exports = {
|
|
1541
|
-
getModelByTenant:
|
|
1541
|
+
getModelByTenant: getModelByTenant4,
|
|
1542
1542
|
getAIChatModelByTenant: getAIChatModelByTenant2,
|
|
1543
1543
|
getAnnotationsModelByTenant: getAnnotationsModelByTenant2,
|
|
1544
1544
|
getPlatformConfigsModelByTenant: getPlatformConfigsModelByTenant2,
|
|
1545
|
-
getTplModelByTenant:
|
|
1545
|
+
getTplModelByTenant: getTplModelByTenant3,
|
|
1546
1546
|
getGeneratedTopicsModelByTenant: getGeneratedTopicsModelByTenant2,
|
|
1547
1547
|
getGeneratedEntitiesModelByTenant: getGeneratedEntitiesModelByTenant2
|
|
1548
1548
|
};
|
|
@@ -2280,6 +2280,91 @@ var require_AnnosElasticSyncProducer = __commonJS({
|
|
|
2280
2280
|
}
|
|
2281
2281
|
});
|
|
2282
2282
|
|
|
2283
|
+
// src/bullmq/ContentElasticSyncProducer.js
|
|
2284
|
+
var require_ContentElasticSyncProducer = __commonJS({
|
|
2285
|
+
"src/bullmq/ContentElasticSyncProducer.js"(exports2, module2) {
|
|
2286
|
+
"use strict";
|
|
2287
|
+
var { BaseProducer: BaseProducer2 } = require_BaseProducer();
|
|
2288
|
+
var { GET_GLOBAL_BULLMQ_CONFIG: GET_GLOBAL_BULLMQ_CONFIG2 } = require_GET_GLOBAL_BULLMQ_CONFIG();
|
|
2289
|
+
var { SecretManagerConnector: SecretManagerConnector2 } = (init_SecretManagerConnector(), __toCommonJS(SecretManagerConnector_exports));
|
|
2290
|
+
var ContentElasticSyncProducer2 = class _ContentElasticSyncProducer extends BaseProducer2 {
|
|
2291
|
+
// Preserve class name through minification for ProducerManager lookup
|
|
2292
|
+
static get name() {
|
|
2293
|
+
return "ContentElasticSyncProducer";
|
|
2294
|
+
}
|
|
2295
|
+
// env is OPTIONAL — cross-env routing: jobs land in the target env's Redis
|
|
2296
|
+
// so that env's deployed workers pick them up (see okf-be
|
|
2297
|
+
// CreateChunksProducer for the rationale and the localhost `_local_<user>`
|
|
2298
|
+
// queue-id quirk).
|
|
2299
|
+
constructor(options = {}) {
|
|
2300
|
+
const env = options.env || process.env.ENV;
|
|
2301
|
+
const c = SecretManagerConnector2.getEnvSpecificConfigs(env);
|
|
2302
|
+
const redisCredentials = options.redisCredentials || {
|
|
2303
|
+
REDIS_HOST: c.REDIS_HOST,
|
|
2304
|
+
REDIS_PORT: c.REDIS_PORT,
|
|
2305
|
+
REDIS_PASSWORD: c.REDIS_PASSWORD
|
|
2306
|
+
};
|
|
2307
|
+
const GLOBAL_BULLMQ_CONFIG = GET_GLOBAL_BULLMQ_CONFIG2({
|
|
2308
|
+
env,
|
|
2309
|
+
redisCredentials
|
|
2310
|
+
});
|
|
2311
|
+
super(GLOBAL_BULLMQ_CONFIG.CONTENT_ELASTIC_SYNC_QUEUE);
|
|
2312
|
+
this.env = env;
|
|
2313
|
+
}
|
|
2314
|
+
async addJobs({ documents, tenant, contentType, operationType, sourceMetadata }) {
|
|
2315
|
+
if (!documents || documents.length === 0 || contentType === "staticPages") {
|
|
2316
|
+
return { jobCount: 0, jobIds: [] };
|
|
2317
|
+
}
|
|
2318
|
+
const batchSize = 200;
|
|
2319
|
+
let jobs = [];
|
|
2320
|
+
for (let i = 0; i < documents.length; i += batchSize) {
|
|
2321
|
+
const batch = documents.slice(i, i + batchSize);
|
|
2322
|
+
jobs.push({
|
|
2323
|
+
name: "processContentElasticSync",
|
|
2324
|
+
data: {
|
|
2325
|
+
docIds: batch.map((doc) => doc._id),
|
|
2326
|
+
tenant,
|
|
2327
|
+
env: this.env,
|
|
2328
|
+
contentType,
|
|
2329
|
+
operationType,
|
|
2330
|
+
sourceMetadata: {
|
|
2331
|
+
...sourceMetadata,
|
|
2332
|
+
contentId: batch.map((d) => d._id)
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
const addedJobs = await this.addBulkJobs(jobs);
|
|
2338
|
+
const jobIds = addedJobs.map((job) => job.id);
|
|
2339
|
+
console.log(`\u{1F4E6} Added ${addedJobs.length} jobs for ${contentType} contents sync atomically`);
|
|
2340
|
+
return {
|
|
2341
|
+
jobCount: addedJobs.length,
|
|
2342
|
+
jobIds
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
static async addJobsAndClose(params) {
|
|
2346
|
+
const producer = new _ContentElasticSyncProducer({ env: params?.env });
|
|
2347
|
+
try {
|
|
2348
|
+
return await producer.addJobs(params);
|
|
2349
|
+
} finally {
|
|
2350
|
+
await producer.stop();
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
static async removeJobsAndClose(jobIds, env) {
|
|
2354
|
+
const producer = new _ContentElasticSyncProducer({ env });
|
|
2355
|
+
try {
|
|
2356
|
+
return await producer.removeJobs(jobIds);
|
|
2357
|
+
} finally {
|
|
2358
|
+
await producer.stop();
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
module2.exports = {
|
|
2363
|
+
ContentElasticSyncProducer: ContentElasticSyncProducer2
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2283
2368
|
// src/node.ts
|
|
2284
2369
|
var node_exports = {};
|
|
2285
2370
|
__export(node_exports, {
|
|
@@ -2293,6 +2378,7 @@ __export(node_exports, {
|
|
|
2293
2378
|
BlockRegistry: () => BlockRegistry,
|
|
2294
2379
|
CHUNKING_PRESETS: () => CHUNKING_PRESETS,
|
|
2295
2380
|
ChunksElasticSyncProducer: () => import_ChunksElasticSyncProducer.ChunksElasticSyncProducer,
|
|
2381
|
+
ContentElasticSyncProducer: () => import_ContentElasticSyncProducer.ContentElasticSyncProducer,
|
|
2296
2382
|
ELASTIC_MAPPING_PRESETS: () => ELASTIC_MAPPING_PRESETS,
|
|
2297
2383
|
ElasticSearchConnector: () => import_ElasticSearchConnector.ElasticSearchConnector,
|
|
2298
2384
|
FILTER_IDS: () => FILTER_IDS,
|
|
@@ -2306,9 +2392,13 @@ __export(node_exports, {
|
|
|
2306
2392
|
RedisCacheConnector: () => RedisCacheConnector,
|
|
2307
2393
|
SecretManagerConnector: () => SecretManagerConnector,
|
|
2308
2394
|
TEMP_removeDuplicateFilters: () => TEMP_removeDuplicateFilters,
|
|
2395
|
+
TenantRegistry: () => TenantRegistry,
|
|
2309
2396
|
TplSchema: () => Tpl_default,
|
|
2310
2397
|
UI_CONTENT: () => UI_CONTENT,
|
|
2311
2398
|
WorkerManager: () => import_WorkerManager.WorkerManager,
|
|
2399
|
+
_backOfficeFields__contr: () => _backOfficeFields__contr,
|
|
2400
|
+
_constantMeta__contr: () => _constantMeta__contr,
|
|
2401
|
+
_fixedMainFields__contr: () => _fixedMainFields__contr,
|
|
2312
2402
|
_self_managed_buildAnnoHierarchyConfig: () => _self_managed_buildAnnoHierarchyConfig,
|
|
2313
2403
|
_self_managed_buildDocHierarchyConfig: () => _self_managed_buildDocHierarchyConfig,
|
|
2314
2404
|
_self_managed_getFixedAnnoRollupBlocks: () => _self_managed_getFixedAnnoRollupBlocks,
|
|
@@ -2320,19 +2410,23 @@ __export(node_exports, {
|
|
|
2320
2410
|
compareAndGroupBlocks: () => compareAndGroupBlocks,
|
|
2321
2411
|
deleteVal: () => deleteVal,
|
|
2322
2412
|
extractAllBlocksFromTpl: () => extractAllBlocksFromTpl,
|
|
2413
|
+
extractAllUniqueAnnoTagTypesFromTpl: () => extractAllUniqueAnnoTagTypesFromTpl,
|
|
2323
2414
|
extractAndOrganizeBlocks: () => extractAndOrganizeBlocks,
|
|
2415
|
+
extractNumberFromValue: () => extractNumberFromValue,
|
|
2324
2416
|
genCleanCamelCaseId: () => genCleanCamelCaseId,
|
|
2417
|
+
genContentTypeSchema: () => genContentTypeSchema,
|
|
2325
2418
|
genTagId: () => genTagId,
|
|
2326
2419
|
generateFilterKey: () => generateFilterKey,
|
|
2327
|
-
getAIChatModelByTenant: () =>
|
|
2420
|
+
getAIChatModelByTenant: () => import_getModelByTenant4.getAIChatModelByTenant,
|
|
2328
2421
|
getAnnoFilterBucketKey: () => getAnnoFilterBucketKey,
|
|
2329
|
-
getAnnotationsModelByTenant: () =>
|
|
2422
|
+
getAnnotationsModelByTenant: () => import_getModelByTenant4.getAnnotationsModelByTenant,
|
|
2423
|
+
getContrModelByTenant: () => getContrModelByTenant,
|
|
2330
2424
|
getDbByTenant: () => getDbByTenant,
|
|
2331
2425
|
getFilterKeyForBlock: () => getFilterKeyForBlock,
|
|
2332
|
-
getGeneratedEntitiesModelByTenant: () =>
|
|
2333
|
-
getGeneratedTopicsModelByTenant: () =>
|
|
2334
|
-
getModelByTenant: () =>
|
|
2335
|
-
getPlatformConfigsModelByTenant: () =>
|
|
2426
|
+
getGeneratedEntitiesModelByTenant: () => import_getModelByTenant4.getGeneratedEntitiesModelByTenant,
|
|
2427
|
+
getGeneratedTopicsModelByTenant: () => import_getModelByTenant4.getGeneratedTopicsModelByTenant,
|
|
2428
|
+
getModelByTenant: () => import_getModelByTenant4.getModelByTenant,
|
|
2429
|
+
getPlatformConfigsModelByTenant: () => import_getModelByTenant4.getPlatformConfigsModelByTenant,
|
|
2336
2430
|
getPlatformContextContent: () => getPlatformContextContent,
|
|
2337
2431
|
getRollupPossibilities: () => getRollupPossibilities,
|
|
2338
2432
|
getRoutePathToContentTypeLanding: () => getRoutePathToContentTypeLanding,
|
|
@@ -2343,7 +2437,9 @@ __export(node_exports, {
|
|
|
2343
2437
|
getRoutePathToReviewDashboard: () => getRoutePathToReviewDashboard,
|
|
2344
2438
|
getRoutePathToTCI: () => getRoutePathToTCI,
|
|
2345
2439
|
getRoutePathToTagCategoryLanding: () => getRoutePathToTagCategoryLanding,
|
|
2346
|
-
|
|
2440
|
+
getTpl: () => getTpl,
|
|
2441
|
+
getTplModelByTenant: () => import_getModelByTenant4.getTplModelByTenant,
|
|
2442
|
+
getTypesForSchemaFromTpl: () => getTypesForSchemaFromTpl,
|
|
2347
2443
|
getVal: () => getVal,
|
|
2348
2444
|
isTplAnnotationEnabled: () => isTplAnnotationEnabled,
|
|
2349
2445
|
mergeAnnoDataIntoAnnotationsTags: () => mergeAnnoDataIntoAnnotationsTags,
|
|
@@ -5006,7 +5102,751 @@ RedisCacheConnector.instance = null;
|
|
|
5006
5102
|
// src/node.ts
|
|
5007
5103
|
init_SecretManagerConnector();
|
|
5008
5104
|
init_getDbByTenant();
|
|
5105
|
+
var import_getModelByTenant4 = __toESM(require_getModelByTenant());
|
|
5106
|
+
|
|
5107
|
+
// src/contentSchema/genContentTypeSchema.js
|
|
5108
|
+
var import_mongoose12 = __toESM(require("mongoose"));
|
|
5109
|
+
|
|
5110
|
+
// src/contentSchema/components/_backOfficeFields__contr.js
|
|
5111
|
+
var import_mongoose8 = __toESM(require("mongoose"));
|
|
5112
|
+
var _backOfficeFields__contr = (thisContentModelRef) => ({
|
|
5113
|
+
//----- (USUSED AT THE MOMENT) version 2 properties for better tracking & interlinking of document clones ---- //
|
|
5114
|
+
isPrimaryDoc: Boolean,
|
|
5115
|
+
cloneStack: [import_mongoose8.default.Schema.Types.ObjectId],
|
|
5116
|
+
// the last item in the array being the last clone
|
|
5117
|
+
ogStack: [import_mongoose8.default.Schema.Types.ObjectId],
|
|
5118
|
+
// the last item in the array being the og-est og ( the primaryDoc )
|
|
5119
|
+
//----- END version 2 properties ---- //
|
|
5120
|
+
tagId: String,
|
|
5121
|
+
kp_published_status: {
|
|
5122
|
+
type: String,
|
|
5123
|
+
// options : draft | published | editPublished | awaitingModeration | underModeration
|
|
5124
|
+
default: "draft",
|
|
5125
|
+
required: true
|
|
5126
|
+
},
|
|
5127
|
+
warnings: [Object],
|
|
5128
|
+
kp_og_published_doc: {
|
|
5129
|
+
type: import_mongoose8.default.Schema.Types.ObjectId,
|
|
5130
|
+
ref: thisContentModelRef
|
|
5131
|
+
},
|
|
5132
|
+
// ----- mod rel --- //
|
|
5133
|
+
kp_pre_mod_doc: {
|
|
5134
|
+
type: import_mongoose8.default.Schema.Types.ObjectId
|
|
5135
|
+
},
|
|
5136
|
+
kp_is_pbl_taken_into_mod: Boolean,
|
|
5137
|
+
kp_is_mod_clone: Boolean,
|
|
5138
|
+
kp_moderator: {
|
|
5139
|
+
type: import_mongoose8.default.Schema.Types.ObjectId,
|
|
5140
|
+
ref: "user"
|
|
5141
|
+
},
|
|
5142
|
+
kp_date_mod_begin: Date,
|
|
5143
|
+
kp_date_mod_end: Date,
|
|
5144
|
+
kp_date_submitted: Date,
|
|
5145
|
+
// ----- END mod rel --- //
|
|
5146
|
+
kp_date_created: Date,
|
|
5147
|
+
kp_date_last_saved: Date,
|
|
5148
|
+
kp_date_published: {
|
|
5149
|
+
type: Date
|
|
5150
|
+
},
|
|
5151
|
+
editPublishedDocId: {
|
|
5152
|
+
//only comes on og docs that have an editPbl clone
|
|
5153
|
+
type: import_mongoose8.default.Schema.Types.ObjectId,
|
|
5154
|
+
ref: thisContentModelRef
|
|
5155
|
+
},
|
|
5156
|
+
currentEditor: {
|
|
5157
|
+
type: import_mongoose8.default.Schema.Types.ObjectId,
|
|
5158
|
+
//relevant for segments : staticPages & collection, where author dont matter.
|
|
5159
|
+
ref: "user"
|
|
5160
|
+
},
|
|
5161
|
+
lastActivity: {
|
|
5162
|
+
date: {
|
|
5163
|
+
type: Date
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
});
|
|
5167
|
+
|
|
5168
|
+
// src/contentSchema/components/_comments__contr.js
|
|
5169
|
+
var import_mongoose9 = __toESM(require("mongoose"));
|
|
5170
|
+
var baseCommentsObj = {
|
|
5171
|
+
user: {
|
|
5172
|
+
userId: { type: import_mongoose9.default.Schema.Types.ObjectId, ref: "user" },
|
|
5173
|
+
profileType: String,
|
|
5174
|
+
name: String,
|
|
5175
|
+
avatar: Object
|
|
5176
|
+
},
|
|
5177
|
+
text: Object,
|
|
5178
|
+
dateCreated: Date,
|
|
5179
|
+
dateModified: [Date]
|
|
5180
|
+
//the start of tracking mod dates on things..experiment...
|
|
5181
|
+
};
|
|
5182
|
+
var _comments__contr = {
|
|
5183
|
+
comments: [
|
|
5184
|
+
{
|
|
5185
|
+
...baseCommentsObj,
|
|
5186
|
+
responses: [baseCommentsObj]
|
|
5187
|
+
}
|
|
5188
|
+
]
|
|
5189
|
+
};
|
|
5190
|
+
|
|
5191
|
+
// src/contentSchema/components/_reaction_contr.js
|
|
5192
|
+
var import_mongoose10 = __toESM(require("mongoose"));
|
|
5193
|
+
var baseSchema = {
|
|
5194
|
+
type: {
|
|
5195
|
+
type: String,
|
|
5196
|
+
enum: ["like", "celebrate", "inspiring", "learnings"]
|
|
5197
|
+
},
|
|
5198
|
+
dateCreated: Date,
|
|
5199
|
+
userId: { type: import_mongoose10.default.Schema.Types.ObjectId, ref: "user" }
|
|
5200
|
+
};
|
|
5201
|
+
var _reaction_contr = {
|
|
5202
|
+
reactions: [
|
|
5203
|
+
{
|
|
5204
|
+
...baseSchema
|
|
5205
|
+
}
|
|
5206
|
+
]
|
|
5207
|
+
};
|
|
5208
|
+
|
|
5209
|
+
// src/contentSchema/components/_constantMeta__contr.js
|
|
5210
|
+
var import_mongoose11 = __toESM(require("mongoose"));
|
|
5211
|
+
var _constantMeta__contr = {
|
|
5212
|
+
kp_contributed_by: {
|
|
5213
|
+
type: import_mongoose11.default.Schema.Types.ObjectId,
|
|
5214
|
+
ref: "user",
|
|
5215
|
+
required: true
|
|
5216
|
+
},
|
|
5217
|
+
kp_content_type: {
|
|
5218
|
+
type: String,
|
|
5219
|
+
required: true
|
|
5220
|
+
},
|
|
5221
|
+
// "thematic" or "attribute" — distinguishes thematic sub-themes/tags from
|
|
5222
|
+
// attribute classification categories (speech act, emotion, etc.).
|
|
5223
|
+
taxonomyRole: {
|
|
5224
|
+
type: String
|
|
5225
|
+
},
|
|
5226
|
+
// Only meaningful on sub-theme docs. When false, new tags cannot be created under
|
|
5227
|
+
// this sub-theme (by humans via API or by the inductive AI annotator).
|
|
5228
|
+
// Absent/true = allowed (default).
|
|
5229
|
+
allowNewTags: {
|
|
5230
|
+
type: Boolean
|
|
5231
|
+
},
|
|
5232
|
+
// Only meaningful on attribute-role sub-themes. "single" = pick one tag,
|
|
5233
|
+
// "multi" = pick one or more. Default "multi".
|
|
5234
|
+
selectMode: {
|
|
5235
|
+
type: String
|
|
5236
|
+
},
|
|
5237
|
+
// "autoAI" | "humanInTheLoop" — absent means human editor (default).
|
|
5238
|
+
createdVia: {
|
|
5239
|
+
type: String
|
|
5240
|
+
}
|
|
5241
|
+
};
|
|
5242
|
+
|
|
5243
|
+
// src/contentSchema/components/_fixedMainFields__contr.js
|
|
5244
|
+
var _fixedMainFields__contr = {
|
|
5245
|
+
/**
|
|
5246
|
+
* hard coding title & subtitle is required because,
|
|
5247
|
+
* in tpl categories like 'researchNotes' the title comp is never defined in the template.
|
|
5248
|
+
* instead, it is hardcoded in the code, and it expects a 'main.title' value path.
|
|
5249
|
+
* Hence the hardcode below. Besides its not a problem, since, if in the template a
|
|
5250
|
+
* main.title component is indeed defined, then these hardcodes would be overwritten by
|
|
5251
|
+
* mainExtract. Which is what we want.
|
|
5252
|
+
*/
|
|
5253
|
+
title: {
|
|
5254
|
+
type: String,
|
|
5255
|
+
cast: (v) => typeof v === "string" ? String(v) : Object(v)
|
|
5256
|
+
},
|
|
5257
|
+
subtitle: {
|
|
5258
|
+
type: String,
|
|
5259
|
+
cast: (v) => typeof v === "string" ? String(v) : Object(v)
|
|
5260
|
+
},
|
|
5261
|
+
titleInfo: String
|
|
5262
|
+
// this is used to show the meta text ("This story is supported by AI-assisted bot") on the published page
|
|
5263
|
+
};
|
|
5264
|
+
|
|
5265
|
+
// src/contentSchema/extractAllUniqueAnnoTagTypesFromTpl.js
|
|
5266
|
+
var extractAllUniqueAnnoTagTypesFromTpl = (extractedBlocks, { smTagTypesConfig } = {}) => {
|
|
5267
|
+
return Array.from(
|
|
5268
|
+
new Set(
|
|
5269
|
+
blockRegistry.getAnnotationEnabledBlocks(extractedBlocks).flatMap((bl) => [
|
|
5270
|
+
...blockRegistry.getTagTypesConfig(bl, smTagTypesConfig).map((tt) => tt.tagType),
|
|
5271
|
+
...bl.props?.annotation?.toggleTagTypesConfig?.map((tt) => tt.tagType) || []
|
|
5272
|
+
])
|
|
5273
|
+
)
|
|
5274
|
+
);
|
|
5275
|
+
};
|
|
5276
|
+
|
|
5277
|
+
// src/contentSchema/getTypesForSchemaFromTpl/compToTypeMap/getConditionalDataType.js
|
|
5278
|
+
var getConditionalDataType = ({ block }) => {
|
|
5279
|
+
if (block.props?.saveValueAsString)
|
|
5280
|
+
return {
|
|
5281
|
+
type: Number,
|
|
5282
|
+
cast: (v) => {
|
|
5283
|
+
if (typeof v === "number" && !isNaN(v)) {
|
|
5284
|
+
return Number(v);
|
|
5285
|
+
} else if (typeof v === "string") {
|
|
5286
|
+
return String(v);
|
|
5287
|
+
} else {
|
|
5288
|
+
return Boolean(v);
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
};
|
|
5292
|
+
else {
|
|
5293
|
+
const returnValue = ["DropdownMulti", "CheckboxList"].includes(block.comp) ? { type: Array } : { type: Object };
|
|
5294
|
+
return returnValue;
|
|
5295
|
+
}
|
|
5296
|
+
};
|
|
5297
|
+
|
|
5298
|
+
// src/contentSchema/utils/extractNumberFromValue.js
|
|
5299
|
+
var extractNumberFromValue = (value) => {
|
|
5300
|
+
if (value === null || value === void 0) return void 0;
|
|
5301
|
+
if (typeof value === "number") return isNaN(value) ? void 0 : value;
|
|
5302
|
+
const str = String(value).trim();
|
|
5303
|
+
if (str === "") return void 0;
|
|
5304
|
+
const directParse = Number(str);
|
|
5305
|
+
if (!isNaN(directParse)) return directParse;
|
|
5306
|
+
const commaNumberMatch = str.match(/-?\d[\d,]*\d(?:\.\d+)?/);
|
|
5307
|
+
if (commaNumberMatch && commaNumberMatch[0].includes(",")) {
|
|
5308
|
+
const num = Number(commaNumberMatch[0].replace(/,/g, ""));
|
|
5309
|
+
if (!isNaN(num)) return num;
|
|
5310
|
+
}
|
|
5311
|
+
const numberMatch = str.match(/-?\d+(?:\.\d+)?/);
|
|
5312
|
+
if (numberMatch) {
|
|
5313
|
+
const num = Number(numberMatch[0]);
|
|
5314
|
+
return isNaN(num) ? void 0 : num;
|
|
5315
|
+
}
|
|
5316
|
+
return void 0;
|
|
5317
|
+
};
|
|
5318
|
+
|
|
5319
|
+
// src/contentSchema/getTypesForSchemaFromTpl/compToTypeMap/index.js
|
|
5320
|
+
var compToTypeMap = ({ block }) => {
|
|
5321
|
+
const comp = block.comp;
|
|
5322
|
+
const blockDef = blockRegistry.getBlock(comp);
|
|
5323
|
+
if (blockDef) return blockDef.mongoSchemaType;
|
|
5324
|
+
switch (comp) {
|
|
5325
|
+
case "SummaryInput":
|
|
5326
|
+
case "KPRichInput":
|
|
5327
|
+
//deprecated
|
|
5328
|
+
case "RichTextEditor":
|
|
5329
|
+
//deprecated
|
|
5330
|
+
case "StaticRichText":
|
|
5331
|
+
case "CardRadioInput":
|
|
5332
|
+
case "NumberRangeInput":
|
|
5333
|
+
case "KPLikeContactChannelsBlock":
|
|
5334
|
+
case "TitleInput":
|
|
5335
|
+
case "SubtitleInput":
|
|
5336
|
+
case "PluginSentinel":
|
|
5337
|
+
case "DynamicRefetchDropdown":
|
|
5338
|
+
case "VideoInput":
|
|
5339
|
+
// data type for videoinput should actually be array, but since the old version of the comp used a string data type, we are currently setting it to object to avoid error
|
|
5340
|
+
case "SimpleTable":
|
|
5341
|
+
case "GroupQuestionsInputSingle":
|
|
5342
|
+
return { type: Object };
|
|
5343
|
+
case "ProfileImageInput":
|
|
5344
|
+
case "ResourceInput":
|
|
5345
|
+
case "ImageInput":
|
|
5346
|
+
case "VideoInput":
|
|
5347
|
+
case "Repeater":
|
|
5348
|
+
case "RepeaterTabs":
|
|
5349
|
+
case "PDFInput":
|
|
5350
|
+
case "TextInputToArray":
|
|
5351
|
+
return { type: Array };
|
|
5352
|
+
case "RichTextAsPlainText":
|
|
5353
|
+
case "PasswordInput":
|
|
5354
|
+
case "EmailInput":
|
|
5355
|
+
case "TextInput":
|
|
5356
|
+
case "URLInput":
|
|
5357
|
+
case "PhoneInput":
|
|
5358
|
+
case "KPMetaPrimary":
|
|
5359
|
+
case "IFrameInput":
|
|
5360
|
+
case "JSONInput":
|
|
5361
|
+
return { type: String };
|
|
5362
|
+
case "NumberInput":
|
|
5363
|
+
return {
|
|
5364
|
+
type: Number,
|
|
5365
|
+
// Smart extraction of numbers from messy values (e.g. "18+", "18 years old", "$100")
|
|
5366
|
+
// Uses extractNumberFromValue utility for consistent behavior with direct import
|
|
5367
|
+
set: extractNumberFromValue
|
|
5368
|
+
};
|
|
5369
|
+
case "DropdownSingleLanguage":
|
|
5370
|
+
case "ComboboxSingleLanguage":
|
|
5371
|
+
return {
|
|
5372
|
+
type: [
|
|
5373
|
+
{
|
|
5374
|
+
display: String,
|
|
5375
|
+
value: String
|
|
5376
|
+
}
|
|
5377
|
+
]
|
|
5378
|
+
};
|
|
5379
|
+
case "DatePicker":
|
|
5380
|
+
return { type: Date };
|
|
5381
|
+
case "DateTimePicker":
|
|
5382
|
+
return { type: Date };
|
|
5383
|
+
case "TimePicker":
|
|
5384
|
+
return {
|
|
5385
|
+
type: String,
|
|
5386
|
+
// Validates time is in 24-hour format (00:00-23:59) with hours 00-23, a colon, and minutes 00-59
|
|
5387
|
+
validate: {
|
|
5388
|
+
validator: function(v) {
|
|
5389
|
+
return /^([01]\d|2[0-3]):([0-5]\d)$/.test(v);
|
|
5390
|
+
},
|
|
5391
|
+
message: (props) => `${props.value} is not a valid time format!`
|
|
5392
|
+
}
|
|
5393
|
+
};
|
|
5394
|
+
case "CheckboxButton":
|
|
5395
|
+
return { type: Boolean };
|
|
5396
|
+
case "TagsInputMulti":
|
|
5397
|
+
case "FragmentsSet":
|
|
5398
|
+
case "FragmentsDict":
|
|
5399
|
+
case "TagsInputSingle":
|
|
5400
|
+
return {
|
|
5401
|
+
type: {
|
|
5402
|
+
collectionId: String,
|
|
5403
|
+
data: [
|
|
5404
|
+
{
|
|
5405
|
+
_id: {
|
|
5406
|
+
type: String,
|
|
5407
|
+
index: block.index === true
|
|
5408
|
+
},
|
|
5409
|
+
display: String,
|
|
5410
|
+
tagId: String
|
|
5411
|
+
}
|
|
5412
|
+
]
|
|
5413
|
+
}
|
|
5414
|
+
};
|
|
5415
|
+
case "SPECIAL_AnnotationTagsField":
|
|
5416
|
+
return {
|
|
5417
|
+
/**
|
|
5418
|
+
* very important that we wrap the whole thing in type,
|
|
5419
|
+
* because, if the tag name itself is type (like in the case of noora),
|
|
5420
|
+
* then it completely confuses mongoose, since type is a reserved word,
|
|
5421
|
+
* and causes all sorts of bugs.
|
|
5422
|
+
*/
|
|
5423
|
+
type: {
|
|
5424
|
+
collectionId: String,
|
|
5425
|
+
data: Array
|
|
5426
|
+
//leaving this weakly typed for now cuz annotations have several other properties in here which i am not aware of..
|
|
5427
|
+
}
|
|
5428
|
+
};
|
|
5429
|
+
case "LinkEmbedWithInput":
|
|
5430
|
+
return {
|
|
5431
|
+
type: {
|
|
5432
|
+
url: String,
|
|
5433
|
+
metadata: Object
|
|
5434
|
+
}
|
|
5435
|
+
};
|
|
5436
|
+
case "CheckboxList":
|
|
5437
|
+
//new
|
|
5438
|
+
case "RadioList":
|
|
5439
|
+
//new
|
|
5440
|
+
case "DropdownSingle":
|
|
5441
|
+
//new
|
|
5442
|
+
case "DropdownMulti":
|
|
5443
|
+
//new
|
|
5444
|
+
case "SwitchDouble":
|
|
5445
|
+
case "SwitchSingle":
|
|
5446
|
+
return getConditionalDataType({ block });
|
|
5447
|
+
case "DateTimeRangePicker":
|
|
5448
|
+
return {
|
|
5449
|
+
type: {
|
|
5450
|
+
date: Date,
|
|
5451
|
+
timeRange: Array
|
|
5452
|
+
}
|
|
5453
|
+
};
|
|
5454
|
+
case "DateRangePicker":
|
|
5455
|
+
return { type: Array };
|
|
5456
|
+
}
|
|
5457
|
+
};
|
|
5458
|
+
|
|
5459
|
+
// src/contentSchema/getTypesForSchemaFromTpl/index.js
|
|
5460
|
+
var getTypesForSchemaFromTpl = (options = {}) => {
|
|
5461
|
+
const { tpl, smTagTypesConfig } = options;
|
|
5462
|
+
const defaultSchemaFieldsByTplLayout = {
|
|
5463
|
+
researchNotes2: {
|
|
5464
|
+
tags: {
|
|
5465
|
+
quotes: {
|
|
5466
|
+
collectionId: String,
|
|
5467
|
+
data: Array
|
|
5468
|
+
},
|
|
5469
|
+
vocabs: {
|
|
5470
|
+
collectionId: String,
|
|
5471
|
+
data: Array
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
};
|
|
5476
|
+
let mainExtract = {};
|
|
5477
|
+
let metaExtract = {};
|
|
5478
|
+
let tagsExtract = {};
|
|
5479
|
+
let rootExtract = {};
|
|
5480
|
+
let annoExtract = {
|
|
5481
|
+
...defaultSchemaFieldsByTplLayout[tpl?.category] || {}
|
|
5482
|
+
};
|
|
5483
|
+
const extractedBlocks = extractAllBlocksFromTpl({ tpl });
|
|
5484
|
+
const annotationTagTypes = extractAllUniqueAnnoTagTypesFromTpl(extractedBlocks, {
|
|
5485
|
+
smTagTypesConfig
|
|
5486
|
+
});
|
|
5487
|
+
annotationTagTypes.forEach((annoTagType) => {
|
|
5488
|
+
const annotationFieldDef = compToTypeMap({
|
|
5489
|
+
block: { comp: "SPECIAL_AnnotationTagsField" }
|
|
5490
|
+
});
|
|
5491
|
+
annoExtract = setVal(annoExtract, `tags.${annoTagType}`, annotationFieldDef);
|
|
5492
|
+
});
|
|
5493
|
+
extractedBlocks.forEach((block) => {
|
|
5494
|
+
const { valuePath } = block;
|
|
5495
|
+
if (!valuePath) return;
|
|
5496
|
+
const pathSegments = valuePath.split(".");
|
|
5497
|
+
const [prefix, ...remainingPath] = pathSegments;
|
|
5498
|
+
const nestedPath = remainingPath.join(".");
|
|
5499
|
+
const fieldDefinition = compToTypeMap({ block });
|
|
5500
|
+
switch (prefix) {
|
|
5501
|
+
case "meta":
|
|
5502
|
+
metaExtract = setVal(
|
|
5503
|
+
metaExtract,
|
|
5504
|
+
nestedPath,
|
|
5505
|
+
// "author.name" (without "meta" prefix)
|
|
5506
|
+
fieldDefinition
|
|
5507
|
+
);
|
|
5508
|
+
break;
|
|
5509
|
+
case "main":
|
|
5510
|
+
mainExtract = setVal(
|
|
5511
|
+
mainExtract,
|
|
5512
|
+
nestedPath,
|
|
5513
|
+
// "content.title" (without "main" prefix)
|
|
5514
|
+
fieldDefinition
|
|
5515
|
+
);
|
|
5516
|
+
break;
|
|
5517
|
+
case "tags":
|
|
5518
|
+
tagsExtract = setVal(
|
|
5519
|
+
tagsExtract,
|
|
5520
|
+
nestedPath,
|
|
5521
|
+
// "categories.primary" (without "tags" prefix)
|
|
5522
|
+
fieldDefinition
|
|
5523
|
+
);
|
|
5524
|
+
break;
|
|
5525
|
+
default:
|
|
5526
|
+
if (block.comp && block.comp !== "TplMetaBlock" && valuePath !== ".") {
|
|
5527
|
+
rootExtract = setVal(
|
|
5528
|
+
rootExtract,
|
|
5529
|
+
valuePath,
|
|
5530
|
+
// Use full path since it's not prefixed
|
|
5531
|
+
fieldDefinition
|
|
5532
|
+
);
|
|
5533
|
+
}
|
|
5534
|
+
break;
|
|
5535
|
+
}
|
|
5536
|
+
});
|
|
5537
|
+
return {
|
|
5538
|
+
metaExtract,
|
|
5539
|
+
mainExtract,
|
|
5540
|
+
tagsExtract,
|
|
5541
|
+
rootExtract,
|
|
5542
|
+
annoExtract
|
|
5543
|
+
};
|
|
5544
|
+
};
|
|
5545
|
+
|
|
5546
|
+
// src/contentSchema/genContentTypeSchema.js
|
|
5547
|
+
var genContentTypeSchema = ({ contentType, segment, contentTypeConfig, smTagTypesConfig }) => {
|
|
5548
|
+
let SchemaToReturn;
|
|
5549
|
+
let {
|
|
5550
|
+
mainExtract = {},
|
|
5551
|
+
metaExtract = {},
|
|
5552
|
+
tagsExtract = {},
|
|
5553
|
+
rootExtract = {},
|
|
5554
|
+
annoExtract = {}
|
|
5555
|
+
} = getTypesForSchemaFromTpl({ tpl: contentTypeConfig, smTagTypesConfig });
|
|
5556
|
+
const schemaDef = {
|
|
5557
|
+
/**
|
|
5558
|
+
* this metadata is v.important from an optimization point of view.
|
|
5559
|
+
* it helps with getAllContrModelsByTenant, without having to
|
|
5560
|
+
* query the database for content type configurations.
|
|
5561
|
+
*/
|
|
5562
|
+
metadata: {
|
|
5563
|
+
isContentType: { type: String, default: true },
|
|
5564
|
+
contentType: { type: String, default: contentType },
|
|
5565
|
+
segment: { type: String, default: segment }
|
|
5566
|
+
},
|
|
5567
|
+
..._backOfficeFields__contr(contentType),
|
|
5568
|
+
...rootExtract,
|
|
5569
|
+
tags: {
|
|
5570
|
+
...tagsExtract
|
|
5571
|
+
},
|
|
5572
|
+
annotations: {
|
|
5573
|
+
...annoExtract
|
|
5574
|
+
},
|
|
5575
|
+
meta: {
|
|
5576
|
+
..._constantMeta__contr,
|
|
5577
|
+
...metaExtract
|
|
5578
|
+
},
|
|
5579
|
+
main: {
|
|
5580
|
+
..._fixedMainFields__contr,
|
|
5581
|
+
...mainExtract
|
|
5582
|
+
},
|
|
5583
|
+
archive: Object,
|
|
5584
|
+
//burial ground for past fields that have eventually been removed from the tpl (and therefore the schema)
|
|
5585
|
+
translations: Object,
|
|
5586
|
+
reviewerNotes: Object,
|
|
5587
|
+
..._reaction_contr,
|
|
5588
|
+
..._comments__contr,
|
|
5589
|
+
...segment === "publishing" && {
|
|
5590
|
+
shares: {
|
|
5591
|
+
count: {
|
|
5592
|
+
type: Number,
|
|
5593
|
+
default: 0
|
|
5594
|
+
}
|
|
5595
|
+
},
|
|
5596
|
+
views: {
|
|
5597
|
+
count: {
|
|
5598
|
+
type: Number,
|
|
5599
|
+
default: 0
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
},
|
|
5603
|
+
updates: Object
|
|
5604
|
+
// DEPRECATED. DONT USE IT. DONT DELETE EITHER. ELSE WE WILL HAVE TO MAKE DATA UPDATES
|
|
5605
|
+
};
|
|
5606
|
+
SchemaToReturn = new import_mongoose12.default.Schema(schemaDef, {
|
|
5607
|
+
collection: contentType,
|
|
5608
|
+
toJSON: { virtuals: true },
|
|
5609
|
+
// So `res.json()` and other `JSON.stringify()` functions include virtuals
|
|
5610
|
+
toObject: { virtuals: true },
|
|
5611
|
+
// So `toObject()` output includes virtuals
|
|
5612
|
+
strict: "throw"
|
|
5613
|
+
//will throw an error if we try to add properties that arent on this schema
|
|
5614
|
+
});
|
|
5615
|
+
SchemaToReturn.index(
|
|
5616
|
+
{ tagId: 1 },
|
|
5617
|
+
...segment === "collections" ? [
|
|
5618
|
+
{
|
|
5619
|
+
unique: true,
|
|
5620
|
+
partialFilterExpression: {
|
|
5621
|
+
kp_published_status: { $eq: "published" }
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
] : []
|
|
5625
|
+
);
|
|
5626
|
+
SchemaToReturn.index({
|
|
5627
|
+
kp_published_status: 1,
|
|
5628
|
+
kp_date_published: -1,
|
|
5629
|
+
_id: -1
|
|
5630
|
+
});
|
|
5631
|
+
SchemaToReturn.index(
|
|
5632
|
+
{ kp_published_status: 1, "main.title": 1, _id: 1 },
|
|
5633
|
+
{ collation: { locale: "en", numericOrdering: true } }
|
|
5634
|
+
);
|
|
5635
|
+
SchemaToReturn.index({ kp_published_status: 1, kp_is_mod_clone: 1 });
|
|
5636
|
+
return SchemaToReturn;
|
|
5637
|
+
};
|
|
5638
|
+
|
|
5639
|
+
// src/TenantRegistry.js
|
|
5640
|
+
var import_storage = require("@google-cloud/storage");
|
|
5641
|
+
var BUCKET_NAME = "dots-system-configs";
|
|
5642
|
+
var FILE_NAME = "tenantRegistry.json";
|
|
5643
|
+
var TenantRegistry = class {
|
|
5644
|
+
// ─── Initialization ─────────────────────────────────
|
|
5645
|
+
/**
|
|
5646
|
+
* Must be awaited at startup before any sync getter is called.
|
|
5647
|
+
* Uses Application Default Credentials (ADC) for GCS auth.
|
|
5648
|
+
*/
|
|
5649
|
+
static async init() {
|
|
5650
|
+
this._data = await this._load();
|
|
5651
|
+
this._rebuildCaches();
|
|
5652
|
+
this._lastRefreshAt = Date.now();
|
|
5653
|
+
}
|
|
5654
|
+
/**
|
|
5655
|
+
* Reload from GCS when this instance sees an unknown tenant/origin — likely one
|
|
5656
|
+
* provisioned on another instance after startup. Rate-limited; on failure keeps
|
|
5657
|
+
* the existing cache (never throws). Returns true if a reload actually ran.
|
|
5658
|
+
*/
|
|
5659
|
+
static async refreshIfStale() {
|
|
5660
|
+
const now = Date.now();
|
|
5661
|
+
if (now - this._lastRefreshAt < this.REFRESH_COOLDOWN_MS) return false;
|
|
5662
|
+
this._lastRefreshAt = now;
|
|
5663
|
+
try {
|
|
5664
|
+
this._data = await this._load();
|
|
5665
|
+
this._rebuildCaches();
|
|
5666
|
+
return true;
|
|
5667
|
+
} catch (err) {
|
|
5668
|
+
console.error(`[TenantRegistry] refresh failed (keeping cache): ${err.message}`);
|
|
5669
|
+
return false;
|
|
5670
|
+
}
|
|
5671
|
+
}
|
|
5672
|
+
// ─── Storage layer (swap only these to change backend) ───
|
|
5673
|
+
static async _load() {
|
|
5674
|
+
const storage = new import_storage.Storage();
|
|
5675
|
+
const file = storage.bucket(BUCKET_NAME).file(FILE_NAME);
|
|
5676
|
+
const [buffer] = await file.download();
|
|
5677
|
+
const { data } = JSON.parse(buffer.toString("utf-8"));
|
|
5678
|
+
console.log(`[TenantRegistry] Loaded ${data.length} tenants from GCS`);
|
|
5679
|
+
return data;
|
|
5680
|
+
}
|
|
5681
|
+
static async _save() {
|
|
5682
|
+
const storage = new import_storage.Storage();
|
|
5683
|
+
const file = storage.bucket(BUCKET_NAME).file(FILE_NAME);
|
|
5684
|
+
await file.save(JSON.stringify({ data: this._data }, null, 2), {
|
|
5685
|
+
contentType: "application/json"
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
// ─── End storage layer ──────────────────────────────
|
|
5689
|
+
static _rebuildCaches() {
|
|
5690
|
+
this._byEnv = {};
|
|
5691
|
+
this._selfManagedSet = /* @__PURE__ */ new Set();
|
|
5692
|
+
for (const entry of this._data) {
|
|
5693
|
+
const { tenantId, urls, isSelfManaged } = entry;
|
|
5694
|
+
if (isSelfManaged) this._selfManagedSet.add(tenantId);
|
|
5695
|
+
for (const env of ["dev", "staging", "prod"]) {
|
|
5696
|
+
if (!urls[env]) continue;
|
|
5697
|
+
if (!this._byEnv[env]) this._byEnv[env] = {};
|
|
5698
|
+
this._byEnv[env][tenantId] = urls[env];
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
// ─── Sync getters ───────────────────────────────────
|
|
5703
|
+
/** { tenantId: url } for the given env */
|
|
5704
|
+
static getMappingsForEnv(env) {
|
|
5705
|
+
return this._byEnv[env] || {};
|
|
5706
|
+
}
|
|
5707
|
+
/** [url, url, ...] for the given env */
|
|
5708
|
+
static getUrlsForEnv(env) {
|
|
5709
|
+
return Object.values(this._byEnv[env] || {});
|
|
5710
|
+
}
|
|
5711
|
+
/** [tenantId, tenantId, ...] for the given env */
|
|
5712
|
+
static getTenantIdsForEnv(env) {
|
|
5713
|
+
return Object.keys(this._byEnv[env] || {});
|
|
5714
|
+
}
|
|
5715
|
+
/** Single URL for a tenant+env, or undefined */
|
|
5716
|
+
static getUrl(tenant, env) {
|
|
5717
|
+
return (this._byEnv[env] || {})[tenant];
|
|
5718
|
+
}
|
|
5719
|
+
static isSelfManaged(tenant) {
|
|
5720
|
+
return this._selfManagedSet.has(tenant);
|
|
5721
|
+
}
|
|
5722
|
+
static getSelfManagedTenantIds() {
|
|
5723
|
+
return [...this._selfManagedSet];
|
|
5724
|
+
}
|
|
5725
|
+
/** Full registry data array (read-only snapshot) */
|
|
5726
|
+
static getData() {
|
|
5727
|
+
return this._data;
|
|
5728
|
+
}
|
|
5729
|
+
// ─── Mutator ────────────────────────────────────────
|
|
5730
|
+
/**
|
|
5731
|
+
* Idempotent: removes existing entry for tenantId (if any), then adds.
|
|
5732
|
+
* Persists to GCS and updates in-memory caches.
|
|
5733
|
+
*/
|
|
5734
|
+
static async addTenant({ tenantId, urls, isSelfManaged = false }) {
|
|
5735
|
+
this._data = this._data.filter((e) => e.tenantId !== tenantId);
|
|
5736
|
+
this._data.push({ tenantId, urls, isSelfManaged });
|
|
5737
|
+
await this._save();
|
|
5738
|
+
this._rebuildCaches();
|
|
5739
|
+
}
|
|
5740
|
+
/**
|
|
5741
|
+
* Idempotent inverse of addTenant: drops the tenant's entry from the registry,
|
|
5742
|
+
* persists to GCS, and rebuilds in-memory caches (so the tenant stops resolving
|
|
5743
|
+
* on THIS instance immediately). Returns true if an entry was actually removed.
|
|
5744
|
+
* Other running instances pick up the removal via refreshIfStale() on their next
|
|
5745
|
+
* unknown-origin miss.
|
|
5746
|
+
*/
|
|
5747
|
+
static async removeTenant(tenantId) {
|
|
5748
|
+
const before = this._data.length;
|
|
5749
|
+
this._data = this._data.filter((e) => e.tenantId !== tenantId);
|
|
5750
|
+
const removed = this._data.length < before;
|
|
5751
|
+
if (removed) {
|
|
5752
|
+
await this._save();
|
|
5753
|
+
this._rebuildCaches();
|
|
5754
|
+
}
|
|
5755
|
+
return removed;
|
|
5756
|
+
}
|
|
5757
|
+
};
|
|
5758
|
+
// ─── In-memory state ────────────────────────────────
|
|
5759
|
+
__publicField(TenantRegistry, "_data", null);
|
|
5760
|
+
// [{ tenantId, urls: { dev, staging, prod }, isSelfManaged }]
|
|
5761
|
+
__publicField(TenantRegistry, "_byEnv", {});
|
|
5762
|
+
// { dev: { tenantId: url }, staging: {...}, prod: {...} }
|
|
5763
|
+
__publicField(TenantRegistry, "_selfManagedSet", /* @__PURE__ */ new Set());
|
|
5764
|
+
// On-miss refresh rate-limit (see refreshIfStale).
|
|
5765
|
+
__publicField(TenantRegistry, "_lastRefreshAt", 0);
|
|
5766
|
+
__publicField(TenantRegistry, "REFRESH_COOLDOWN_MS", 3e4);
|
|
5767
|
+
|
|
5768
|
+
// src/contentSchema/selfManagedAnnotationTagTypesConfig.js
|
|
5769
|
+
var SM_ANNOTATION_TAG_TYPES_CONFIG = [{ tagType: "tags" }];
|
|
5770
|
+
var getSmTagTypesConfigForTenant = async (tenant) => {
|
|
5771
|
+
return TenantRegistry.isSelfManaged(tenant) ? SM_ANNOTATION_TAG_TYPES_CONFIG : null;
|
|
5772
|
+
};
|
|
5773
|
+
|
|
5774
|
+
// src/db/getContrModelByTenant.js
|
|
5775
|
+
var import_getModelByTenant3 = __toESM(require_getModelByTenant());
|
|
5776
|
+
init_getDbByTenant();
|
|
5777
|
+
|
|
5778
|
+
// src/db/getTpl.js
|
|
5009
5779
|
var import_getModelByTenant2 = __toESM(require_getModelByTenant());
|
|
5780
|
+
var getTpl = async ({ tenant, contentType, env, source = "db" }) => {
|
|
5781
|
+
if (source === "cache") {
|
|
5782
|
+
return RedisCacheConnector.findOneConfigInCache({
|
|
5783
|
+
tenant,
|
|
5784
|
+
modelName: "tpl",
|
|
5785
|
+
type: contentType,
|
|
5786
|
+
env
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
return (0, import_getModelByTenant2.getTplModelByTenant)({ tenant, env }).findOne({ kp_content_type: contentType }).lean();
|
|
5790
|
+
};
|
|
5791
|
+
|
|
5792
|
+
// src/db/getContrModelByTenant.js
|
|
5793
|
+
var getContrModelByTenant = async ({
|
|
5794
|
+
tenant,
|
|
5795
|
+
contentType,
|
|
5796
|
+
env = process.env.ENV,
|
|
5797
|
+
// v.imp, else the reuse check below always fails when env isn't passed (env is meant to be optional)
|
|
5798
|
+
tpl,
|
|
5799
|
+
//optional : if passed uses it instead of fetching the tpl for this contenttype from db. important in the case of _createTpl, where, within a transaction we create a tpl and then use this tpl to create some content against it. since this is within a transaction, this newly created tpl wont be available in the db just yet.
|
|
5800
|
+
tplSource = "db",
|
|
5801
|
+
// "db" (okf-be default, original semantics) | "cache" (okf-sub — tpl cache is kept in sync on every tpl write)
|
|
5802
|
+
/**
|
|
5803
|
+
* When true, the registered-model reuse is SKIPPED — the schema is rebuilt
|
|
5804
|
+
* from the current tpl on every call (~5-10ms CPU + the tpl read).
|
|
5805
|
+
*
|
|
5806
|
+
* okf-sub passes this on every collab call: unlike okf-be — which
|
|
5807
|
+
* invalidates registered models on tpl change via
|
|
5808
|
+
* reRegisterModelInLocalCache + the '${env}-tpl-model-recache' Pub/Sub —
|
|
5809
|
+
* okf-sub subscribes to nothing, so a reused model could carry a stale
|
|
5810
|
+
* schema after a tpl edit, and strict:"throw" would reject collab flushes
|
|
5811
|
+
* for newly added fields. Rebuilding every time is correct by
|
|
5812
|
+
* construction: no freshness bookkeeping for tpl write paths (or raw
|
|
5813
|
+
* mongodbscripts that mutate tpls + recache) to ever get wrong.
|
|
5814
|
+
*/
|
|
5815
|
+
rebuild = false
|
|
5816
|
+
}) => {
|
|
5817
|
+
try {
|
|
5818
|
+
const db = getDbByTenant({ tenant });
|
|
5819
|
+
if (!rebuild && db.models[contentType] && env === process.env.ENV && !tpl) {
|
|
5820
|
+
const modelToReturn = db.model(contentType);
|
|
5821
|
+
if (modelToReturn.schema.obj.metadata && modelToReturn.schema.obj.metadata.isContentType && modelToReturn.schema.obj.metadata.isContentType.default === true) {
|
|
5822
|
+
return modelToReturn;
|
|
5823
|
+
}
|
|
5824
|
+
return void 0;
|
|
5825
|
+
}
|
|
5826
|
+
const contentTypeConfig = tpl || await getTpl({ tenant, contentType, env, source: tplSource });
|
|
5827
|
+
if (!contentTypeConfig) return void 0;
|
|
5828
|
+
const smTagTypesConfig = await getSmTagTypesConfigForTenant(tenant);
|
|
5829
|
+
const thisSchema = genContentTypeSchema({
|
|
5830
|
+
contentType,
|
|
5831
|
+
segment: contentTypeConfig.general?.segment,
|
|
5832
|
+
contentTypeConfig,
|
|
5833
|
+
smTagTypesConfig
|
|
5834
|
+
});
|
|
5835
|
+
if (rebuild && db.models[contentType]) {
|
|
5836
|
+
db.deleteModel(contentType);
|
|
5837
|
+
}
|
|
5838
|
+
return (0, import_getModelByTenant3.getModelByTenant)({
|
|
5839
|
+
tenant,
|
|
5840
|
+
modelName: contentType,
|
|
5841
|
+
schema: thisSchema,
|
|
5842
|
+
env
|
|
5843
|
+
});
|
|
5844
|
+
} catch (err) {
|
|
5845
|
+
throw err;
|
|
5846
|
+
}
|
|
5847
|
+
};
|
|
5848
|
+
|
|
5849
|
+
// src/node.ts
|
|
5010
5850
|
init_models();
|
|
5011
5851
|
var import_WorkerManager = __toESM(require_WorkerManager());
|
|
5012
5852
|
var import_ProducerManager = __toESM(require_ProducerManager());
|
|
@@ -5015,6 +5855,7 @@ var import_BaseFlowProducer = __toESM(require_BaseFlowProducer());
|
|
|
5015
5855
|
var import_BaseWorker = __toESM(require_BaseWorker());
|
|
5016
5856
|
var import_ChunksElasticSyncProducer = __toESM(require_ChunksElasticSyncProducer());
|
|
5017
5857
|
var import_AnnosElasticSyncProducer = __toESM(require_AnnosElasticSyncProducer());
|
|
5858
|
+
var import_ContentElasticSyncProducer = __toESM(require_ContentElasticSyncProducer());
|
|
5018
5859
|
var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG());
|
|
5019
5860
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5020
5861
|
0 && (module.exports = {
|
|
@@ -5028,6 +5869,7 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
|
|
|
5028
5869
|
BlockRegistry,
|
|
5029
5870
|
CHUNKING_PRESETS,
|
|
5030
5871
|
ChunksElasticSyncProducer,
|
|
5872
|
+
ContentElasticSyncProducer,
|
|
5031
5873
|
ELASTIC_MAPPING_PRESETS,
|
|
5032
5874
|
ElasticSearchConnector,
|
|
5033
5875
|
FILTER_IDS,
|
|
@@ -5041,9 +5883,13 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
|
|
|
5041
5883
|
RedisCacheConnector,
|
|
5042
5884
|
SecretManagerConnector,
|
|
5043
5885
|
TEMP_removeDuplicateFilters,
|
|
5886
|
+
TenantRegistry,
|
|
5044
5887
|
TplSchema,
|
|
5045
5888
|
UI_CONTENT,
|
|
5046
5889
|
WorkerManager,
|
|
5890
|
+
_backOfficeFields__contr,
|
|
5891
|
+
_constantMeta__contr,
|
|
5892
|
+
_fixedMainFields__contr,
|
|
5047
5893
|
_self_managed_buildAnnoHierarchyConfig,
|
|
5048
5894
|
_self_managed_buildDocHierarchyConfig,
|
|
5049
5895
|
_self_managed_getFixedAnnoRollupBlocks,
|
|
@@ -5055,13 +5901,17 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
|
|
|
5055
5901
|
compareAndGroupBlocks,
|
|
5056
5902
|
deleteVal,
|
|
5057
5903
|
extractAllBlocksFromTpl,
|
|
5904
|
+
extractAllUniqueAnnoTagTypesFromTpl,
|
|
5058
5905
|
extractAndOrganizeBlocks,
|
|
5906
|
+
extractNumberFromValue,
|
|
5059
5907
|
genCleanCamelCaseId,
|
|
5908
|
+
genContentTypeSchema,
|
|
5060
5909
|
genTagId,
|
|
5061
5910
|
generateFilterKey,
|
|
5062
5911
|
getAIChatModelByTenant,
|
|
5063
5912
|
getAnnoFilterBucketKey,
|
|
5064
5913
|
getAnnotationsModelByTenant,
|
|
5914
|
+
getContrModelByTenant,
|
|
5065
5915
|
getDbByTenant,
|
|
5066
5916
|
getFilterKeyForBlock,
|
|
5067
5917
|
getGeneratedEntitiesModelByTenant,
|
|
@@ -5078,7 +5928,9 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
|
|
|
5078
5928
|
getRoutePathToReviewDashboard,
|
|
5079
5929
|
getRoutePathToTCI,
|
|
5080
5930
|
getRoutePathToTagCategoryLanding,
|
|
5931
|
+
getTpl,
|
|
5081
5932
|
getTplModelByTenant,
|
|
5933
|
+
getTypesForSchemaFromTpl,
|
|
5082
5934
|
getVal,
|
|
5083
5935
|
isTplAnnotationEnabled,
|
|
5084
5936
|
mergeAnnoDataIntoAnnotationsTags,
|