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