@drodil/backstage-plugin-qeta-backend 3.55.2 → 3.55.4

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.
@@ -9,7 +9,9 @@ function isQetaFilter(filter) {
9
9
  class BaseStore {
10
10
  constructor(db) {
11
11
  this.db = db;
12
+ this.initializePgTrgm();
12
13
  }
14
+ pgTrgmAvailable = false;
13
15
  mapToInteger(val) {
14
16
  return typeof val === "string" ? Number.parseInt(val, 10) : val ?? 0;
15
17
  }
@@ -20,7 +22,7 @@ class BaseStore {
20
22
  return typeof val === "number" ? val > 0 : !!val;
21
23
  }
22
24
  applySearchQuery(query, columns, searchQuery) {
23
- if (this.db.client.config.client === "pg") {
25
+ if (this.db.client.config.client === "pg" && this.pgTrgmAvailable) {
24
26
  const terms = searchQuery.trim().split(/\s+/);
25
27
  query.andWhere((builder) => {
26
28
  const coalescedColumns = columns.map((col) => `COALESCE(${col}::text, '')`).join(` || ' ' || `);
@@ -124,6 +126,21 @@ class BaseStore {
124
126
  }
125
127
  });
126
128
  }
129
+ async initializePgTrgm() {
130
+ if (this.db.client.config.client === "pg") {
131
+ try {
132
+ const result = await this.db.raw(`
133
+ SELECT EXISTS (
134
+ SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'
135
+ ) as available
136
+ `);
137
+ this.pgTrgmAvailable = result.rows?.[0]?.available ?? false;
138
+ } catch (error) {
139
+ console.warn(`Failed to check for pg_trgm extension: ${error}`);
140
+ this.pgTrgmAvailable = false;
141
+ }
142
+ }
143
+ }
127
144
  }
128
145
 
129
146
  exports.BaseStore = BaseStore;
@@ -1 +1 @@
1
- {"version":3,"file":"BaseStore.cjs.js","sources":["../../../src/database/stores/BaseStore.ts"],"sourcesContent":["import { Knex } from 'knex';\nimport { QetaFilter, QetaFilters } from '../../service/util';\nimport { PermissionCriteria } from '@backstage/plugin-permission-common';\nimport {\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from '@backstage/plugin-permission-node';\nimport { compact } from 'lodash';\n\nfunction isQetaFilter(filter: any): filter is QetaFilter {\n return filter.hasOwnProperty('property');\n}\n\nexport abstract class BaseStore {\n constructor(protected readonly db: Knex) {}\n\n protected mapToInteger(val: string | number | undefined): number {\n return typeof val === 'string' ? Number.parseInt(val, 10) : val ?? 0;\n }\n\n protected mapToBoolean(val: string | number | boolean | undefined): boolean {\n if (typeof val === 'string') {\n return val === 'true' || val === '1';\n }\n return typeof val === 'number' ? val > 0 : !!val;\n }\n\n protected applySearchQuery(\n query: Knex.QueryBuilder,\n columns: string[],\n searchQuery: string,\n ) {\n if (this.db.client.config.client === 'pg') {\n const terms = searchQuery.trim().split(/\\s+/);\n\n query.andWhere(builder => {\n const coalescedColumns = columns\n .map(col => `COALESCE(${col}::text, '')`)\n .join(` || ' ' || `);\n\n terms.forEach(term => {\n builder.andWhereRaw(`(${coalescedColumns}) %> ?`, [term]);\n });\n\n const concatenatedColumns = `LOWER(CONCAT(${columns.join(',')}))`;\n terms.forEach(term => {\n builder.andWhereRaw(`${concatenatedColumns} LIKE LOWER(?)`, [\n `%${term}%`,\n ]);\n });\n });\n } else {\n query.andWhere(builder => {\n const terms = searchQuery.trim().split(/\\s+/);\n\n terms.forEach(term => {\n builder.orWhereRaw(\n `LOWER(CONCAT(${columns.join(',')})) LIKE LOWER(?)`,\n [`%${term}%`],\n );\n });\n });\n }\n }\n\n protected async updateAttachments(\n key: 'postId' | 'answerId' | 'collectionId',\n content: string,\n images: number[],\n id: number,\n headerImage?: string,\n ) {\n if (images.length > 0) {\n await this.db('attachments')\n .whereIn('id', images)\n .update({ [key]: id });\n }\n\n const attachments = await this.db('attachments')\n .where(key, id)\n .select('uuid');\n const uuids = attachments.map(a => a.uuid);\n const toRemove = uuids.filter(uuid => {\n return !(content.includes(uuid) || headerImage?.includes(uuid));\n });\n await this.db('attachments')\n .whereIn('uuid', toRemove)\n .update({ [key]: null });\n }\n\n protected parseFilter(\n filter: PermissionCriteria<QetaFilters>,\n query: Knex.QueryBuilder,\n db: Knex,\n type: 'post' | 'answer' | 'collection' | 'tags' | 'comments' = 'post',\n negate: boolean = false,\n ): Knex.QueryBuilder {\n if (isNotCriteria(filter)) {\n return this.parseFilter(filter.not, query, db, type, !negate);\n }\n\n if (isQetaFilter(filter)) {\n const values: string[] = compact(filter.values) ?? [];\n\n let fk = 'posts.id';\n if (type === 'answer') {\n fk = 'answers.postId';\n } else if (type === 'collection') {\n fk = 'collection_posts.postId';\n }\n if (filter.property === 'tags') {\n const postIds = db('tags')\n .leftJoin('post_tags', 'tags.id', 'post_tags.tagId')\n .where('tags.tag', 'in', values)\n .select('post_tags.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n }\n if (filter.property === 'tag.experts') {\n if (type === 'post') {\n const postIds = db('tags')\n .leftJoin('post_tags', 'tags.id', 'post_tags.tagId')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('post_tags.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n } else if (type === 'answer') {\n const answerIds = db('answers')\n .leftJoin('posts', 'answers.postId', 'posts.id')\n .leftJoin('post_tags', 'post_tags.postId', 'posts.id')\n .leftJoin('tags', 'post_tags.tagId', 'tags.id')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('answers.id');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, answerIds);\n return query;\n } else if (type === 'tags') {\n const tagIds = db('tag_experts')\n .leftJoin('tags', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('tag_experts.tagId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, tagIds);\n return query;\n } else if (type === 'collection') {\n const collectionIds = db('collection_posts')\n .leftJoin('posts', 'collection_posts.postId', 'posts.id')\n .leftJoin('post_tags', 'post_tags.postId', 'posts.id')\n .leftJoin('tags', 'post_tags.tagId', 'tags.id')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('collection_posts.collectionId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, collectionIds);\n return query;\n }\n }\n\n if (filter.property === 'entityRefs') {\n const postIds = db('entities')\n .leftJoin('post_entities', 'entities.id', 'post_entities.entityId')\n .where('entities.entity_ref', 'in', values)\n .select('post_entities.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n }\n\n if (values.length === 0) {\n return negate\n ? query.whereNotNull(filter.property)\n : query.whereNull(filter.property);\n }\n\n return query[negate ? 'whereNotIn' : 'whereIn'](filter.property, values);\n }\n\n return query[negate ? 'andWhereNot' : 'andWhere'](builder => {\n const f = filter as PermissionCriteria<QetaFilters>;\n if (isOrCriteria(f)) {\n for (const subFilter of f.anyOf ?? []) {\n builder.orWhere((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(subFilter, subQuery, db, type, false),\n );\n }\n } else if (isAndCriteria(f)) {\n for (const subFilter of f.allOf ?? []) {\n builder.andWhere((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(subFilter, subQuery, db, type, false),\n );\n }\n } else if (isNotCriteria(f)) {\n builder.whereNot((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(f.not, subQuery, db, type, false),\n );\n }\n });\n }\n}\n"],"names":["isNotCriteria","compact","isOrCriteria","isAndCriteria"],"mappings":"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"}
1
+ {"version":3,"file":"BaseStore.cjs.js","sources":["../../../src/database/stores/BaseStore.ts"],"sourcesContent":["import { Knex } from 'knex';\nimport { QetaFilter, QetaFilters } from '../../service/util';\nimport { PermissionCriteria } from '@backstage/plugin-permission-common';\nimport {\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from '@backstage/plugin-permission-node';\nimport { compact } from 'lodash';\n\nfunction isQetaFilter(filter: any): filter is QetaFilter {\n return filter.hasOwnProperty('property');\n}\n\nexport abstract class BaseStore {\n private pgTrgmAvailable: boolean = false;\n\n constructor(protected readonly db: Knex) {\n this.initializePgTrgm();\n }\n\n protected mapToInteger(val: string | number | undefined): number {\n return typeof val === 'string' ? Number.parseInt(val, 10) : val ?? 0;\n }\n\n protected mapToBoolean(val: string | number | boolean | undefined): boolean {\n if (typeof val === 'string') {\n return val === 'true' || val === '1';\n }\n return typeof val === 'number' ? val > 0 : !!val;\n }\n\n protected applySearchQuery(\n query: Knex.QueryBuilder,\n columns: string[],\n searchQuery: string,\n ) {\n if (this.db.client.config.client === 'pg' && this.pgTrgmAvailable) {\n const terms = searchQuery.trim().split(/\\s+/);\n\n query.andWhere(builder => {\n const coalescedColumns = columns\n .map(col => `COALESCE(${col}::text, '')`)\n .join(` || ' ' || `);\n\n terms.forEach(term => {\n builder.andWhereRaw(`(${coalescedColumns}) %> ?`, [term]);\n });\n\n const concatenatedColumns = `LOWER(CONCAT(${columns.join(',')}))`;\n terms.forEach(term => {\n builder.andWhereRaw(`${concatenatedColumns} LIKE LOWER(?)`, [\n `%${term}%`,\n ]);\n });\n });\n } else {\n query.andWhere(builder => {\n const terms = searchQuery.trim().split(/\\s+/);\n\n terms.forEach(term => {\n builder.orWhereRaw(\n `LOWER(CONCAT(${columns.join(',')})) LIKE LOWER(?)`,\n [`%${term}%`],\n );\n });\n });\n }\n }\n\n protected async updateAttachments(\n key: 'postId' | 'answerId' | 'collectionId',\n content: string,\n images: number[],\n id: number,\n headerImage?: string,\n ) {\n if (images.length > 0) {\n await this.db('attachments')\n .whereIn('id', images)\n .update({ [key]: id });\n }\n\n const attachments = await this.db('attachments')\n .where(key, id)\n .select('uuid');\n const uuids = attachments.map(a => a.uuid);\n const toRemove = uuids.filter(uuid => {\n return !(content.includes(uuid) || headerImage?.includes(uuid));\n });\n await this.db('attachments')\n .whereIn('uuid', toRemove)\n .update({ [key]: null });\n }\n\n protected parseFilter(\n filter: PermissionCriteria<QetaFilters>,\n query: Knex.QueryBuilder,\n db: Knex,\n type: 'post' | 'answer' | 'collection' | 'tags' | 'comments' = 'post',\n negate: boolean = false,\n ): Knex.QueryBuilder {\n if (isNotCriteria(filter)) {\n return this.parseFilter(filter.not, query, db, type, !negate);\n }\n\n if (isQetaFilter(filter)) {\n const values: string[] = compact(filter.values) ?? [];\n\n let fk = 'posts.id';\n if (type === 'answer') {\n fk = 'answers.postId';\n } else if (type === 'collection') {\n fk = 'collection_posts.postId';\n }\n if (filter.property === 'tags') {\n const postIds = db('tags')\n .leftJoin('post_tags', 'tags.id', 'post_tags.tagId')\n .where('tags.tag', 'in', values)\n .select('post_tags.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n }\n if (filter.property === 'tag.experts') {\n if (type === 'post') {\n const postIds = db('tags')\n .leftJoin('post_tags', 'tags.id', 'post_tags.tagId')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('post_tags.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n } else if (type === 'answer') {\n const answerIds = db('answers')\n .leftJoin('posts', 'answers.postId', 'posts.id')\n .leftJoin('post_tags', 'post_tags.postId', 'posts.id')\n .leftJoin('tags', 'post_tags.tagId', 'tags.id')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('answers.id');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, answerIds);\n return query;\n } else if (type === 'tags') {\n const tagIds = db('tag_experts')\n .leftJoin('tags', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('tag_experts.tagId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, tagIds);\n return query;\n } else if (type === 'collection') {\n const collectionIds = db('collection_posts')\n .leftJoin('posts', 'collection_posts.postId', 'posts.id')\n .leftJoin('post_tags', 'post_tags.postId', 'posts.id')\n .leftJoin('tags', 'post_tags.tagId', 'tags.id')\n .leftJoin('tag_experts', 'tag_experts.tagId', 'tags.id')\n .where('tag_experts.entityRef', 'in', values)\n .select('collection_posts.collectionId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, collectionIds);\n return query;\n }\n }\n\n if (filter.property === 'entityRefs') {\n const postIds = db('entities')\n .leftJoin('post_entities', 'entities.id', 'post_entities.entityId')\n .where('entities.entity_ref', 'in', values)\n .select('post_entities.postId');\n query[negate ? 'whereNotIn' : 'whereIn'](fk, postIds);\n return query;\n }\n\n if (values.length === 0) {\n return negate\n ? query.whereNotNull(filter.property)\n : query.whereNull(filter.property);\n }\n\n return query[negate ? 'whereNotIn' : 'whereIn'](filter.property, values);\n }\n\n return query[negate ? 'andWhereNot' : 'andWhere'](builder => {\n const f = filter as PermissionCriteria<QetaFilters>;\n if (isOrCriteria(f)) {\n for (const subFilter of f.anyOf ?? []) {\n builder.orWhere((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(subFilter, subQuery, db, type, false),\n );\n }\n } else if (isAndCriteria(f)) {\n for (const subFilter of f.allOf ?? []) {\n builder.andWhere((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(subFilter, subQuery, db, type, false),\n );\n }\n } else if (isNotCriteria(f)) {\n builder.whereNot((subQuery: Knex.QueryBuilder) =>\n this.parseFilter(f.not, subQuery, db, type, false),\n );\n }\n });\n }\n\n private async initializePgTrgm() {\n if (this.db.client.config.client === 'pg') {\n try {\n const result = await this.db.raw(`\n SELECT EXISTS (\n SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'\n ) as available\n `);\n this.pgTrgmAvailable = result.rows?.[0]?.available ?? false;\n } catch (error) {\n console.warn(`Failed to check for pg_trgm extension: ${error}`);\n this.pgTrgmAvailable = false;\n }\n }\n }\n}\n"],"names":["isNotCriteria","compact","isOrCriteria","isAndCriteria"],"mappings":"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"}
@@ -515,6 +515,32 @@ class NotificationManager {
515
515
  }
516
516
  return followingUsers;
517
517
  }
518
+ async onBadgeAwarded(userRef, badge) {
519
+ if (!this.notifications || !this.enabled) {
520
+ return;
521
+ }
522
+ try {
523
+ const baseUrl = this.config.getOptionalString("qeta.route") ?? "qeta";
524
+ const link = `/${baseUrl}/users/${encodeURIComponent(userRef)}`;
525
+ await this.notifications.send({
526
+ recipients: {
527
+ type: "entity",
528
+ entityRef: [userRef]
529
+ },
530
+ payload: {
531
+ title: `New badge earned: ${badge.badge.name}`,
532
+ description: this.formatDescription(
533
+ `Congratulations! You've earned the "${badge.badge.name}" badge: ${badge.badge.description}`
534
+ ),
535
+ link,
536
+ topic: "Badge awarded",
537
+ scope: `badge:${badge.badge.key}`
538
+ }
539
+ });
540
+ } catch (e) {
541
+ this.logger.error(`Failed to send notification for badge award: ${e}`);
542
+ }
543
+ }
518
544
  async getUserDisplayName(username) {
519
545
  try {
520
546
  const cached = await this.cache?.get(
@@ -540,32 +566,6 @@ class NotificationManager {
540
566
  }
541
567
  return username;
542
568
  }
543
- async onBadgeAwarded(userRef, badge) {
544
- if (!this.notifications || !this.enabled) {
545
- return;
546
- }
547
- try {
548
- const baseUrl = this.config.getOptionalString("qeta.baseUrl") ?? "/qeta";
549
- const link = `${baseUrl}/users/${encodeURIComponent(userRef)}`;
550
- await this.notifications.send({
551
- recipients: {
552
- type: "entity",
553
- entityRef: [userRef]
554
- },
555
- payload: {
556
- title: `New badge earned: ${badge.badge.name}`,
557
- description: this.formatDescription(
558
- `Congratulations! You've earned the "${badge.badge.name}" badge: ${badge.badge.description}`
559
- ),
560
- link,
561
- topic: "Badge awarded",
562
- scope: `badge:${badge.badge.key}`
563
- }
564
- });
565
- } catch (e) {
566
- this.logger.error(`Failed to send notification for badge award: ${e}`);
567
- }
568
- }
569
569
  formatDescription(description) {
570
570
  return backstagePluginQetaCommon.truncate(backstagePluginQetaCommon.removeMarkdownFormatting(description), 150);
571
571
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationManager.cjs.js","sources":["../../src/service/NotificationManager.ts"],"sourcesContent":["import { NotificationService } from '@backstage/plugin-notifications-node';\nimport {\n Answer,\n Collection,\n Post,\n removeMarkdownFormatting,\n selectByPostType,\n truncate,\n UserBadge,\n} from '@drodil/backstage-plugin-qeta-common';\nimport {\n AuthService,\n CacheService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { UserEntity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotificationReceiversHandler } from '@drodil/backstage-plugin-qeta-node';\nimport { getResourceUrl } from './util';\n\nexport class NotificationManager {\n private readonly enabled: boolean;\n\n constructor(\n private readonly logger: LoggerService,\n private readonly catalog: CatalogApi,\n private readonly auth: AuthService,\n private readonly config: Config,\n private readonly notifications?: NotificationService,\n private readonly cache?: CacheService,\n private readonly notificationReceivers?: NotificationReceiversHandler,\n ) {\n this.enabled = config.getOptionalBoolean('qeta.notifications') ?? true;\n }\n\n async onNewPost(\n username: string,\n post: Post,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...(post?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewPost?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onCreate.${post.type}`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New ${post.type}`,\n description: this.formatDescription(\n selectByPostType(\n post.type,\n `${user} asked a question: ${post.title}`,\n `${user} wrote an article: ${post.title}`,\n `${user} created a link: ${post.title}`,\n ),\n ),\n link: getResourceUrl(post, this.config),\n topic: `New ${post.type} about entity`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new ${post.type}: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onNewPostComment(\n username: string,\n post: Post,\n comment: string,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const commenters = new Set<string>(post.comments?.map(c => c.author));\n\n const notificationReceivers = [\n ...new Set<string>([\n post.author,\n ...(post?.entities ?? []),\n ...commenters,\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewPostComment?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.comment',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New comment on ${post.type}`,\n description: this.formatDescription(\n `${user} commented on ${post.type} \"${post.title}\": ${comment}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `New ${post.type} comment`,\n scope: `${post.type}:comment:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new post comment: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onPostDelete(\n username: string,\n post: Post,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || post.author === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n post.author,\n ...((await this.notificationReceivers?.onPostDelete?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.${post.type}`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted ${post.type}`,\n description: this.formatDescription(\n `${user} deleted your ${post.type} \"${post.title}\" with reason: ${\n reason || 'No reason provided'\n }`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `${post.type} deleted`,\n scope: `${post.type}:delete:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for post delete: ${e}`);\n }\n return [post.author];\n }\n\n async onCollectionDelete(\n username: string,\n collection: Collection,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || collection.owner === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n collection.owner,\n ...((await this.notificationReceivers?.onCollectionDelete?.(\n collection,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.collection`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted collection`,\n description: this.formatDescription(\n `${user} deleted your collection \"${\n collection.title\n }\" with reason: ${reason || 'No reason provided'}`,\n ),\n link: getResourceUrl(collection, this.config),\n topic: `Collection deleted`,\n scope: `collection:delete:${collection.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for collection delete: ${e}`,\n );\n }\n return [collection.owner];\n }\n\n async onAnswerDelete(\n username: string,\n post: Post,\n answer: Answer,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || answer.author === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n ...((await this.notificationReceivers?.onAnswerDelete?.(\n post,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.answer`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted answer`,\n description: this.formatDescription(\n `${user} deleted your answer from question \"${\n post.title\n }\" with reason: ${reason || 'No reason provided'}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `Answer deleted`,\n scope: `answer:delete:${answer.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for answer delete: ${e}`);\n }\n return [answer.author];\n }\n\n async onPostEdit(\n username: string,\n post: Post,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([post.author, ...followingUsers]),\n ...((await this.notificationReceivers?.onPostEdit?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onUpdate.${post.type}`,\n ) ?? []),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `Edit for ${post.type}`,\n description: this.formatDescription(\n `${user} edited ${post.type}: ${post.title}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `${post.type} edited`,\n scope: `${post.type}:edit:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for post edit: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onNewAnswer(\n username: string,\n question: Post,\n answer: Answer,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n question.author,\n ...(question?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewAnswer?.(\n question,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onCreate.answer`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New answer on question`,\n description: this.formatDescription(\n `${user} answered question \"${question.title}\": ${answer.content}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'New answer on question',\n scope: `question:answer:${question.id}:author`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for new answer: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onAnswerComment(\n username: string,\n question: Post,\n answer: Answer,\n comment: string,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const commenters = new Set<string>(answer.comments?.map(c => c.author));\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n ...commenters,\n ...(question?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onAnswerComment?.(\n question,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.comment',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New comment on answer`,\n description: this.formatDescription(\n `${user} commented on answer to \"${question.title}\": ${comment}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'New answer comment',\n scope: `answer:comment:${answer.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new answer comment: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onCorrectAnswer(\n username: string,\n question: Post,\n answer: Answer,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n question.author,\n ...(question?.entities ?? []),\n ...((await this.notificationReceivers?.onCorrectAnswer?.(\n question,\n answer,\n )) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `Correct answer on question`,\n description: this.formatDescription(\n `${user} marked answer as correct: ${answer.content}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'Correct answer on question',\n scope: `question:correct:${question.id}:answer`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for correct answer: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onMention(\n username: string,\n post: Post | Answer,\n mentions: string[],\n alreadySent: string[],\n isComment?: boolean,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...mentions.map(m => m.replaceAll('@', '')),\n ...((await this.notificationReceivers?.onMention?.(post)) ?? []),\n ]),\n ].filter(m => !alreadySent.includes(m));\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const isPost = 'title' in post;\n const description = isPost\n ? `${user} mentioned you in a post${isComment ? ' comment' : ''}: ${\n post.title\n }`\n : `${user} mentioned you in an answer${isComment ? ' comment' : ''}: ${\n post.content\n }`;\n const link = getResourceUrl(post, this.config, false);\n const scope = isPost\n ? `post:mention:${post.id}`\n : `answer:mention:${post.id}`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New mention`,\n description: this.formatDescription(description),\n link,\n topic: 'New mention',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for mentions: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onNewCollection(\n username: string,\n collection: Collection,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || followingUsers.length === 0) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewCollection?.(collection)) ??\n []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.collection',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const description = `${user} created a new collection: ${collection.title}`;\n const link = getResourceUrl(collection, this.config);\n const scope = `collection:${collection.id}:created`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New collection`,\n description: this.formatDescription(description),\n link,\n topic: 'New collection',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for collection: ${e}`);\n }\n return followingUsers;\n }\n\n async onNewPostToCollection(\n username: string,\n collection: Collection,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || followingUsers.length === 0) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewCollection?.(collection)) ??\n []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onUpdate.collection',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const description = `${user} added a new post to ${collection.title}`;\n const link = getResourceUrl(collection, this.config);\n const scope = `collection:${collection.id}:new_post`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New post in collection`,\n description: this.formatDescription(description),\n link,\n topic: 'New post in collection',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for collection: ${e}`);\n }\n return followingUsers;\n }\n\n private async getUserDisplayName(username: string) {\n try {\n const cached = await this.cache?.get<string>(\n `user:displayName:${username}`,\n );\n if (cached) {\n return cached;\n }\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await this.catalog.getEntityByRef(username, { token });\n if (entity) {\n const displayName =\n (entity as UserEntity).spec?.profile?.displayName ??\n entity.metadata.title ??\n entity.metadata.name;\n await this.cache?.set(`user:displayName:${username}`, displayName, {\n ttl: 1000 * 60 * 60 * 24,\n });\n return displayName;\n }\n } catch (e) {\n console.error(e);\n }\n return username;\n }\n\n async onBadgeAwarded(userRef: string, badge: UserBadge): Promise<void> {\n if (!this.notifications || !this.enabled) {\n return;\n }\n\n try {\n const baseUrl = this.config.getOptionalString('qeta.baseUrl') ?? '/qeta';\n const link = `${baseUrl}/users/${encodeURIComponent(userRef)}`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: [userRef],\n },\n payload: {\n title: `New badge earned: ${badge.badge.name}`,\n description: this.formatDescription(\n `Congratulations! You've earned the \"${badge.badge.name}\" badge: ${badge.badge.description}`,\n ),\n link,\n topic: 'Badge awarded',\n scope: `badge:${badge.badge.key}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for badge award: ${e}`);\n }\n }\n\n private formatDescription(description: string) {\n return truncate(removeMarkdownFormatting(description), 150);\n }\n}\n"],"names":["selectByPostType","getResourceUrl","truncate","removeMarkdownFormatting"],"mappings":"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"}
1
+ {"version":3,"file":"NotificationManager.cjs.js","sources":["../../src/service/NotificationManager.ts"],"sourcesContent":["import { NotificationService } from '@backstage/plugin-notifications-node';\nimport {\n Answer,\n Collection,\n Post,\n removeMarkdownFormatting,\n selectByPostType,\n truncate,\n UserBadge,\n} from '@drodil/backstage-plugin-qeta-common';\nimport {\n AuthService,\n CacheService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { UserEntity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotificationReceiversHandler } from '@drodil/backstage-plugin-qeta-node';\nimport { getResourceUrl } from './util';\n\nexport class NotificationManager {\n private readonly enabled: boolean;\n\n constructor(\n private readonly logger: LoggerService,\n private readonly catalog: CatalogApi,\n private readonly auth: AuthService,\n private readonly config: Config,\n private readonly notifications?: NotificationService,\n private readonly cache?: CacheService,\n private readonly notificationReceivers?: NotificationReceiversHandler,\n ) {\n this.enabled = config.getOptionalBoolean('qeta.notifications') ?? true;\n }\n\n async onNewPost(\n username: string,\n post: Post,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...(post?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewPost?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onCreate.${post.type}`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New ${post.type}`,\n description: this.formatDescription(\n selectByPostType(\n post.type,\n `${user} asked a question: ${post.title}`,\n `${user} wrote an article: ${post.title}`,\n `${user} created a link: ${post.title}`,\n ),\n ),\n link: getResourceUrl(post, this.config),\n topic: `New ${post.type} about entity`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new ${post.type}: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onNewPostComment(\n username: string,\n post: Post,\n comment: string,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const commenters = new Set<string>(post.comments?.map(c => c.author));\n\n const notificationReceivers = [\n ...new Set<string>([\n post.author,\n ...(post?.entities ?? []),\n ...commenters,\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewPostComment?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.comment',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New comment on ${post.type}`,\n description: this.formatDescription(\n `${user} commented on ${post.type} \"${post.title}\": ${comment}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `New ${post.type} comment`,\n scope: `${post.type}:comment:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new post comment: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onPostDelete(\n username: string,\n post: Post,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || post.author === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n post.author,\n ...((await this.notificationReceivers?.onPostDelete?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.${post.type}`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted ${post.type}`,\n description: this.formatDescription(\n `${user} deleted your ${post.type} \"${post.title}\" with reason: ${\n reason || 'No reason provided'\n }`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `${post.type} deleted`,\n scope: `${post.type}:delete:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for post delete: ${e}`);\n }\n return [post.author];\n }\n\n async onCollectionDelete(\n username: string,\n collection: Collection,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || collection.owner === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n collection.owner,\n ...((await this.notificationReceivers?.onCollectionDelete?.(\n collection,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.collection`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted collection`,\n description: this.formatDescription(\n `${user} deleted your collection \"${\n collection.title\n }\" with reason: ${reason || 'No reason provided'}`,\n ),\n link: getResourceUrl(collection, this.config),\n topic: `Collection deleted`,\n scope: `collection:delete:${collection.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for collection delete: ${e}`,\n );\n }\n return [collection.owner];\n }\n\n async onAnswerDelete(\n username: string,\n post: Post,\n answer: Answer,\n reason?: string,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || answer.author === username) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n ...((await this.notificationReceivers?.onAnswerDelete?.(\n post,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onDelete.answer`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n },\n payload: {\n title: `Deleted answer`,\n description: this.formatDescription(\n `${user} deleted your answer from question \"${\n post.title\n }\" with reason: ${reason || 'No reason provided'}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `Answer deleted`,\n scope: `answer:delete:${answer.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for answer delete: ${e}`);\n }\n return [answer.author];\n }\n\n async onPostEdit(\n username: string,\n post: Post,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([post.author, ...followingUsers]),\n ...((await this.notificationReceivers?.onPostEdit?.(post)) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onUpdate.${post.type}`,\n ) ?? []),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `Edit for ${post.type}`,\n description: this.formatDescription(\n `${user} edited ${post.type}: ${post.title}`,\n ),\n link: getResourceUrl(post, this.config),\n topic: `${post.type} edited`,\n scope: `${post.type}:edit:${post.id}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for post edit: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onNewAnswer(\n username: string,\n question: Post,\n answer: Answer,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n question.author,\n ...(question?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewAnswer?.(\n question,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n `qeta.notificationSettings.onCreate.answer`,\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New answer on question`,\n description: this.formatDescription(\n `${user} answered question \"${question.title}\": ${answer.content}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'New answer on question',\n scope: `question:answer:${question.id}:author`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for new answer: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onAnswerComment(\n username: string,\n question: Post,\n answer: Answer,\n comment: string,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const commenters = new Set<string>(answer.comments?.map(c => c.author));\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n ...commenters,\n ...(question?.entities ?? []),\n ...followingUsers,\n ...((await this.notificationReceivers?.onAnswerComment?.(\n question,\n answer,\n )) ?? []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.comment',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New comment on answer`,\n description: this.formatDescription(\n `${user} commented on answer to \"${question.title}\": ${comment}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'New answer comment',\n scope: `answer:comment:${answer.id}`,\n },\n });\n } catch (e) {\n this.logger.error(\n `Failed to send notification for new answer comment: ${e}`,\n );\n }\n return notificationReceivers;\n }\n\n async onCorrectAnswer(\n username: string,\n question: Post,\n answer: Answer,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n answer.author,\n question.author,\n ...(question?.entities ?? []),\n ...((await this.notificationReceivers?.onCorrectAnswer?.(\n question,\n answer,\n )) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `Correct answer on question`,\n description: this.formatDescription(\n `${user} marked answer as correct: ${answer.content}`,\n ),\n link: getResourceUrl(answer, this.config),\n topic: 'Correct answer on question',\n scope: `question:correct:${question.id}:answer`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for correct answer: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onMention(\n username: string,\n post: Post | Answer,\n mentions: string[],\n alreadySent: string[],\n isComment?: boolean,\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...mentions.map(m => m.replaceAll('@', '')),\n ...((await this.notificationReceivers?.onMention?.(post)) ?? []),\n ]),\n ].filter(m => !alreadySent.includes(m));\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const isPost = 'title' in post;\n const description = isPost\n ? `${user} mentioned you in a post${isComment ? ' comment' : ''}: ${\n post.title\n }`\n : `${user} mentioned you in an answer${isComment ? ' comment' : ''}: ${\n post.content\n }`;\n const link = getResourceUrl(post, this.config, false);\n const scope = isPost\n ? `post:mention:${post.id}`\n : `answer:mention:${post.id}`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New mention`,\n description: this.formatDescription(description),\n link,\n topic: 'New mention',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for mentions: ${e}`);\n }\n return notificationReceivers;\n }\n\n async onNewCollection(\n username: string,\n collection: Collection,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || followingUsers.length === 0) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewCollection?.(collection)) ??\n []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onCreate.collection',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const description = `${user} created a new collection: ${collection.title}`;\n const link = getResourceUrl(collection, this.config);\n const scope = `collection:${collection.id}:created`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New collection`,\n description: this.formatDescription(description),\n link,\n topic: 'New collection',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for collection: ${e}`);\n }\n return followingUsers;\n }\n\n async onNewPostToCollection(\n username: string,\n collection: Collection,\n followingUsers: string[],\n ): Promise<string[]> {\n if (!this.notifications || !this.enabled || followingUsers.length === 0) {\n return [];\n }\n\n const notificationReceivers = [\n ...new Set<string>([\n ...followingUsers,\n ...((await this.notificationReceivers?.onNewCollection?.(collection)) ??\n []),\n ...(this.config.getOptionalStringArray(\n 'qeta.notificationSettings.onUpdate.collection',\n ) ?? []),\n ]),\n ];\n\n if (notificationReceivers.length === 0) {\n return [];\n }\n\n try {\n const user = await this.getUserDisplayName(username);\n\n const description = `${user} added a new post to ${collection.title}`;\n const link = getResourceUrl(collection, this.config);\n const scope = `collection:${collection.id}:new_post`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: notificationReceivers,\n excludeEntityRef: username,\n },\n payload: {\n title: `New post in collection`,\n description: this.formatDescription(description),\n link,\n topic: 'New post in collection',\n scope,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for collection: ${e}`);\n }\n return followingUsers;\n }\n\n async onBadgeAwarded(userRef: string, badge: UserBadge): Promise<void> {\n if (!this.notifications || !this.enabled) {\n return;\n }\n\n try {\n const baseUrl = this.config.getOptionalString('qeta.route') ?? 'qeta';\n const link = `/${baseUrl}/users/${encodeURIComponent(userRef)}`;\n\n await this.notifications.send({\n recipients: {\n type: 'entity',\n entityRef: [userRef],\n },\n payload: {\n title: `New badge earned: ${badge.badge.name}`,\n description: this.formatDescription(\n `Congratulations! You've earned the \"${badge.badge.name}\" badge: ${badge.badge.description}`,\n ),\n link,\n topic: 'Badge awarded',\n scope: `badge:${badge.badge.key}`,\n },\n });\n } catch (e) {\n this.logger.error(`Failed to send notification for badge award: ${e}`);\n }\n }\n\n private async getUserDisplayName(username: string) {\n try {\n const cached = await this.cache?.get<string>(\n `user:displayName:${username}`,\n );\n if (cached) {\n return cached;\n }\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await this.catalog.getEntityByRef(username, { token });\n if (entity) {\n const displayName =\n (entity as UserEntity).spec?.profile?.displayName ??\n entity.metadata.title ??\n entity.metadata.name;\n await this.cache?.set(`user:displayName:${username}`, displayName, {\n ttl: 1000 * 60 * 60 * 24,\n });\n return displayName;\n }\n } catch (e) {\n console.error(e);\n }\n return username;\n }\n\n private formatDescription(description: string) {\n return truncate(removeMarkdownFormatting(description), 150);\n }\n}\n"],"names":["selectByPostType","getResourceUrl","truncate","removeMarkdownFormatting"],"mappings":"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"}
@@ -207,7 +207,7 @@ const helperRoutes = (router, options) => {
207
207
  },
208
208
  { token }
209
209
  );
210
- const entityTags = entityResponse.items.flatMap((e) => e?.metadata?.tags).filter((t) => !!t).map((tag) => tag.toLocaleLowerCase()).filter(backstagePluginQetaCommon.filterTags).slice(0, 5);
210
+ const entityTags = entityResponse.items.flatMap((e) => e?.metadata?.tags).filter((t) => !!t).map((tag) => tag.toLocaleLowerCase()).filter(backstagePluginQetaCommon.filterTags);
211
211
  suggestedTags.push(...entityTags);
212
212
  } catch (_error) {
213
213
  }
@@ -216,7 +216,7 @@ const helperRoutes = (router, options) => {
216
216
  if (aiHandler?.suggestTags) {
217
217
  const { tags } = await aiHandler.suggestTags(title, content);
218
218
  suggestedTags.unshift(...tags);
219
- return [...new Set(suggestedTags)].filter(backstagePluginQetaCommon.filterTags).map((tag) => tag.toLocaleLowerCase()).slice(0, 10);
219
+ return [...new Set(suggestedTags)].map((tag) => tag.toLocaleLowerCase()).filter(backstagePluginQetaCommon.filterTags);
220
220
  }
221
221
  } catch (_error) {
222
222
  }
@@ -242,7 +242,7 @@ const helperRoutes = (router, options) => {
242
242
  suggestedTags.push(tag.tag);
243
243
  }
244
244
  });
245
- return [...new Set(suggestedTags)].filter(backstagePluginQetaCommon.filterTags).slice(0, 10);
245
+ return [...new Set(suggestedTags)].filter(backstagePluginQetaCommon.filterTags);
246
246
  };
247
247
  router.post("/tags/suggest", async (request, response) => {
248
248
  const validateRequestBody = ajv.compile(types.DraftQuestionSchema);
@@ -259,7 +259,9 @@ const helperRoutes = (router, options) => {
259
259
  );
260
260
  const allTags = request.body.tags ? [.../* @__PURE__ */ new Set([...request.body.tags, ...suggestedTags])] : suggestedTags;
261
261
  const uniqueTags = [...new Set(allTags)];
262
- response.json({ tags: uniqueTags });
262
+ const limit = request.body.limit && request.body.limit > 0 ? request.body.limit : uniqueTags.length;
263
+ const randomizedTags = uniqueTags.sort(() => Math.random() - 0.5);
264
+ response.json({ tags: randomizedTags.slice(0, limit) });
263
265
  } catch (error) {
264
266
  logger.error(`Failed to generate tag suggestions: ${error}`);
265
267
  response.status(500).json({ error: "Failed to generate tag suggestions" });
@@ -474,7 +476,9 @@ const helperRoutes = (router, options) => {
474
476
  const ref = catalogModel.stringifyEntityRef(entity);
475
477
  return !request.body.entities?.includes(ref);
476
478
  });
477
- response.json({ entities: notSetEntities });
479
+ const limit = request.body.limit && request.body.limit > 0 ? request.body.limit : notSetEntities.length;
480
+ const randomizedEntities = notSetEntities.sort(() => Math.random() - 0.5);
481
+ response.json({ entities: randomizedEntities.slice(0, limit) });
478
482
  } catch (error) {
479
483
  logger.error(`Failed to generate entity suggestions: ${error}`);
480
484
  response.status(500).json({ error: "Failed to generate entity suggestions" });