@dragonmastery/dragoncore-api 0.0.2 → 0.0.5

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/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { drizzle } from "drizzle-orm/d1";
2
2
  import { container, inject, injectable } from "tsyringe";
3
3
  import { and, asc, desc, eq, getTableColumns, getTableName, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
4
4
  import { customAlphabet } from "nanoid";
5
- import { AddCreditsSchema, ApproveSupportTicketSchema, AttachmentFiltersSchema, AttachmentFolderReadSchema, AttachmentPageSchema, CompleteSupportTicketSchema, CreditBalanceSchema, CreditTransactionFiltersSchema, CreditTransactionPageSchema, CustomerSupportTicketCreateSchema, CustomerSupportTicketFiltersSchema, CustomerSupportTicketPageSchema, CustomerSupportTicketReadSchema, CustomerSupportTicketUpdateSchema, NoteCreateSchema, NoteFiltersSchema, NoteReadSchema, NoteUpdateSchema, OPERATORS, ReadNotificationEmailsSchema, RecordConst, RecordConst as RecordConst$1, RejectSupportTicketSchema, RevertSupportTicketSchema, SetMonthlyAllocationSchema, StaffSupportTicketCreateSchema, StaffSupportTicketFiltersSchema, StaffSupportTicketPageSchema, StaffSupportTicketReadSchema, StaffSupportTicketUpdateSchema, SupportTicketApprovalEnum, SupportTicketDevLifecycleEnum, SupportTicketPriorityEnum, SupportTicketTypeEnum, TeamCreateSchema, TeamFiltersSchema, TeamMemberCreateSchema, TeamMemberFiltersSchema, TeamMemberReadSchema, TeamMemberUpdateSchema, TeamPageSchema, TeamReadSchema, TeamUpdateSchema, USER_TYPES, UpdateNotificationEmailsSchema, UserProfileReadSchema, UserProfileUpdateSchema, UserReadSchema, UserTeamMembersSchema, UserTeamsSchema, UserUpdateSchema, changePasswordSchema, createPaginatedSchema, createUserSchema, createUserSchemaOutput, loginResponseSchema, loginSchema, recordVersionFiltersInputBreadcrumbSchema, recordVersionFiltersInputSchema, recordVersionPageBreadcrumbSchema, recordVersionPageSchema, recordVersionSchema, resetPasswordSchema, sanitizeFileName, signupSchema, userSessionSchema } from "@dragonmastery/dragoncore-shared";
5
+ import { AddCreditsSchema, ApproveSupportTicketSchema, ArchiveSupportTicketSchema, AttachmentFiltersSchema, AttachmentFolderReadSchema, AttachmentPageSchema, CompleteSupportTicketSchema, CreditBalanceSchema, CreditTransactionFiltersSchema, CreditTransactionPageSchema, CustomerSupportTicketCreateSchema, CustomerSupportTicketFiltersSchema, CustomerSupportTicketPageSchema, CustomerSupportTicketReadSchema, CustomerSupportTicketUpdateSchema, DEFAULT_USER_TYPE, MAX_PRESETS_PER_CONTEXT, NoteCreateSchema, NoteFiltersSchema, NoteReadSchema, NoteUpdateSchema, OPERATORS, RecordConst, RecordConst as RecordConst$1, RecordSubscriberReadSchema, RejectSupportTicketSchema, RevertSupportTicketSchema, SUPPORT_TICKET_PRIORITY_TO_NUMBER, SavedFilterCreateSchema, SavedFilterReadSchema, SavedFilterUpdateSchema, SetMonthlyAllocationSchema, StaffSupportTicketCreateSchema, StaffSupportTicketFiltersSchema, StaffSupportTicketPageSchema, StaffSupportTicketReadSchema, StaffSupportTicketUpdateSchema, SupportTicketApprovalEnum, SupportTicketDevLifecycleEnum, SupportTicketSubscriberCreateSchema, SupportTicketTypeEnum, TeamCreateSchema, TeamFiltersSchema, TeamMemberCreateSchema, TeamMemberFiltersSchema, TeamMemberReadSchema, TeamMemberUpdateSchema, TeamPageSchema, TeamReadSchema, TeamUpdateSchema, USER_TYPES, UserProfileReadSchema, UserProfileUpdateSchema, UserReadSchema, UserTeamMembersSchema, UserTeamsSchema, UserUpdateSchema, changePasswordSchema, createPaginatedSchema, createUserSchema, createUserSchemaOutput, loginResponseSchema, loginSchema, recordVersionFiltersInputBreadcrumbSchema, recordVersionFiltersInputSchema, recordVersionPageBreadcrumbSchema, recordVersionPageSchema, recordVersionSchema, recordVersionTrackerActivityInputSchema, resetPasswordSchema, sanitizeFileName, signupSchema, supportTicketNumberToPriority, userSessionSchema } from "@dragonmastery/dragoncore-shared";
6
6
  import { blob, index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
7
7
  import { parseSigned, serializeSigned } from "hono/utils/cookie";
8
8
  import { hashPassword, verifyPassword } from "worker-password-auth";
@@ -329,6 +329,139 @@ const OperationConst = {
329
329
  INSERT: "insert"
330
330
  };
331
331
 
332
+ //#endregion
333
+ //#region src/db/schemas/app_setting/app_settings_table.ts
334
+ /**
335
+ * Application settings table for storing global configuration
336
+ * that doesn't need to be sharded.
337
+ *
338
+ * This includes default rates and other application-wide settings.
339
+ */
340
+ const app_settings_table = sqliteTable("app_settings", {
341
+ key: text("key").primaryKey(),
342
+ value: text("value", { mode: "json" }).$type().notNull(),
343
+ created_at: text("created_at").notNull(),
344
+ created_by: text("created_by").notNull(),
345
+ updated_at: text("updated_at").notNull(),
346
+ updated_by: text("updated_by")
347
+ }, (table) => ({
348
+ created_at_idx: index("app_settings_created_at_idx").on(table.created_at),
349
+ updated_at_idx: index("app_settings_updated_at_idx").on(table.updated_at)
350
+ }));
351
+ /**
352
+ * Known setting keys for type safety
353
+ */
354
+ const AppSettingKey = {
355
+ GLOBAL_TAX_SETTINGS: "global_tax_settings",
356
+ DEFAULT_PRICING_LEVEL: "default_pricing_level",
357
+ TERMS_OF_SERVICE: "terms_of_service",
358
+ PRIVACY_POLICY: "privacy_policy",
359
+ MAX_SUPPORT_TICKET_ITEMS: "max_support_ticket_items",
360
+ CUSTOMER_MONTHLY_BALANCE: "customer_monthly_balance",
361
+ CUSTOMER_ROLLOVER_BALANCE: "customer_rollover_balance",
362
+ CUSTOMER_MONTHLY_ALLOCATION: "customer_monthly_allocation",
363
+ LAST_SUPPORT_TICKET_ASSIGNEE: "last_support_ticket_assignee"
364
+ };
365
+
366
+ //#endregion
367
+ //#region src/db/schemas/app_setting/app_settings_repo.ts
368
+ let AppSettingsRepo = class AppSettingsRepo$1 {
369
+ constructor(appSettingsDb) {
370
+ this.appSettingsDb = appSettingsDb;
371
+ }
372
+ /**
373
+ * Get a setting value by key
374
+ * @param key Setting key
375
+ * @returns The setting value or undefined if not found
376
+ */
377
+ async readSetting(key) {
378
+ const [setting] = await this.appSettingsDb.select().from(app_settings_table).where(eq(app_settings_table.key, key)).limit(1);
379
+ return setting;
380
+ }
381
+ /**
382
+ * Save a setting value
383
+ * @param key Setting key
384
+ * @param value Setting value
385
+ * @param user User making the change
386
+ */
387
+ async createSetting(key, value, user) {
388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
389
+ await this.appSettingsDb.insert(app_settings_table).values({
390
+ key,
391
+ value,
392
+ created_at: now,
393
+ created_by: user.userId,
394
+ updated_at: now,
395
+ updated_by: user.userId
396
+ });
397
+ }
398
+ async updateSetting(key, value, user) {
399
+ const now = (/* @__PURE__ */ new Date()).toISOString();
400
+ const [updated] = await this.appSettingsDb.update(app_settings_table).set({
401
+ value,
402
+ updated_at: now,
403
+ updated_by: user.userId
404
+ }).where(eq(app_settings_table.key, key)).returning();
405
+ if (!updated) {
406
+ const [inserted] = await this.appSettingsDb.insert(app_settings_table).values({
407
+ key,
408
+ value,
409
+ created_at: now,
410
+ created_by: user.userId,
411
+ updated_at: now,
412
+ updated_by: user.userId
413
+ }).returning();
414
+ return inserted?.value;
415
+ }
416
+ return updated.value;
417
+ }
418
+ /**
419
+ * Delete a setting
420
+ * @param key Setting key
421
+ * @returns True if the setting was deleted, false if it didn't exist
422
+ */
423
+ async deleteSetting(key) {
424
+ return (await this.appSettingsDb.delete(app_settings_table).where(eq(app_settings_table.key, key)).returning()).length > 0;
425
+ }
426
+ /**
427
+ * Get all settings
428
+ * @returns All settings as a map of key to value
429
+ */
430
+ async getAllSettings() {
431
+ const settings = await this.appSettingsDb.select().from(app_settings_table);
432
+ const settingsMap = /* @__PURE__ */ new Map();
433
+ for (const setting of settings) settingsMap.set(setting.key, setting.value);
434
+ return settingsMap;
435
+ }
436
+ async readMultipleSettings(keys) {
437
+ const settings = await this.appSettingsDb.select().from(app_settings_table).where(inArray(app_settings_table.key, keys));
438
+ const settingsMap = /* @__PURE__ */ new Map();
439
+ for (const setting of settings) settingsMap.set(setting.key, setting.value);
440
+ return settingsMap;
441
+ }
442
+ async getNextSequentialId(recordType, userId) {
443
+ const counterKey = `${recordType}:counter`;
444
+ const now = (/* @__PURE__ */ new Date()).toISOString();
445
+ const [result] = await this.appSettingsDb.insert(app_settings_table).values({
446
+ key: counterKey,
447
+ value: 1,
448
+ created_at: now,
449
+ created_by: userId,
450
+ updated_at: now,
451
+ updated_by: userId
452
+ }).onConflictDoUpdate({
453
+ target: app_settings_table.key,
454
+ set: {
455
+ value: sql`json(json_extract(${app_settings_table.value}, '$') + 1)`,
456
+ updated_at: now,
457
+ updated_by: userId
458
+ }
459
+ }).returning({ value: app_settings_table.value });
460
+ return String(result.value);
461
+ }
462
+ };
463
+ AppSettingsRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IAppSettingsDb))], AppSettingsRepo);
464
+
332
465
  //#endregion
333
466
  //#region src/slices/record_version/db/record_version_table.ts
334
467
  const record_version_table = sqliteTable("record_version", {
@@ -481,18 +614,81 @@ const note_table = sqliteTable("note", {
481
614
  archived_at: text("archived_at"),
482
615
  deleted_by: text("deleted_by"),
483
616
  deleted_at: text("deleted_at")
617
+ }, (table) => [
618
+ index("note_record_id_idx").on(table.record_id),
619
+ index("note_record_type_idx").on(table.record_type),
620
+ index("note_tag_idx").on(table.tag),
621
+ index("note_title_idx").on(table.title),
622
+ index("note_is_internal_idx").on(table.is_internal),
623
+ index("note_created_at_idx").on(table.created_at),
624
+ index("note_updated_at_idx").on(table.updated_at),
625
+ index("note_deleted_at_idx").on(table.deleted_at),
626
+ index("note_deleted_updated_idx").on(table.deleted_at, table.updated_at)
627
+ ]);
628
+
629
+ //#endregion
630
+ //#region src/slices/saved_filter/db/saved_filter_table.ts
631
+ const saved_filter_table = sqliteTable("saved_filter", {
632
+ id: text("id").primaryKey(),
633
+ user_id: text("user_id").notNull(),
634
+ name: text("name").notNull(),
635
+ context: text("context").notNull(),
636
+ route_path: text("route_path").notNull(),
637
+ filters: text("filters").notNull(),
638
+ sort_by: text("sort_by"),
639
+ sort_direction: text("sort_direction"),
640
+ created_at: text("created_at").notNull(),
641
+ updated_at: text("updated_at").notNull()
642
+ }, (table) => [
643
+ index("saved_filter_user_id_idx").on(table.user_id),
644
+ index("saved_filter_context_idx").on(table.context),
645
+ index("saved_filter_user_context_idx").on(table.user_id, table.context)
646
+ ]);
647
+
648
+ //#endregion
649
+ //#region src/slices/saved_filter/db/user_pinned_preset_table.ts
650
+ const MAX_PINNED_PRESETS = 5;
651
+ const user_pinned_preset_table = sqliteTable("user_pinned_preset", {
652
+ id: text("id").primaryKey(),
653
+ user_id: text("user_id").notNull(),
654
+ preset_id: text("preset_id").notNull(),
655
+ position: integer("position").notNull()
656
+ }, (table) => [index("user_pinned_preset_user_id_idx").on(table.user_id), index("user_pinned_preset_preset_id_idx").on(table.preset_id)]);
657
+
658
+ //#endregion
659
+ //#region src/slices/record_subscriber/db/record_subscriber_table.ts
660
+ /**
661
+ * Generic record subscribers - users who receive notifications on record events.
662
+ * Reusable for support_ticket, tracker, and other record types.
663
+ * Email address comes from user's notification_email (user_profile) or user.email if not set.
664
+ */
665
+ const record_subscriber_table = sqliteTable("record_subscriber", {
666
+ id: text("id").primaryKey(),
667
+ record_type: text("record_type").$type().notNull(),
668
+ record_id: text("record_id").notNull(),
669
+ user_id: text("user_id").notNull(),
670
+ subscribed_events: text("subscribed_events", { mode: "json" }).$type(),
671
+ created_at: text("created_at").notNull(),
672
+ created_by: text("created_by").notNull()
484
673
  }, (table) => ({
485
- record_id_idx: index("note_record_id_idx").on(table.record_id),
486
- record_type_idx: index("note_record_type_idx").on(table.record_type),
487
- tag_idx: index("note_tag_idx").on(table.tag),
488
- title_idx: index("note_title_idx").on(table.title),
489
- is_internal_idx: index("note_is_internal_idx").on(table.is_internal),
490
- created_at_idx: index("note_created_at_idx").on(table.created_at),
491
- updated_at_idx: index("note_updated_at_idx").on(table.updated_at),
492
- deleted_at_idx: index("note_deleted_at_idx").on(table.deleted_at),
493
- deletedUpdatedIdx: index("note_deleted_updated_idx").on(table.deleted_at, table.updated_at)
674
+ recordTypeIdIdx: index("record_subscriber_record_type_id_idx").on(table.record_type, table.record_id),
675
+ userIdIdx: index("record_subscriber_user_id_idx").on(table.user_id),
676
+ uniqueSubscriberIdx: index("record_subscriber_unique_idx").on(table.record_type, table.record_id, table.user_id)
494
677
  }));
495
678
 
679
+ //#endregion
680
+ //#region src/slices/support_staff/db/support_staff_table.ts
681
+ /**
682
+ * Support staff - users who can triage support tickets.
683
+ * Small table instead of flagging every user with can_triage.
684
+ */
685
+ const support_staff_table = sqliteTable("support_staff", {
686
+ id: text("id").primaryKey(),
687
+ user_id: text("user_id").notNull().unique(),
688
+ created_at: text("created_at").notNull(),
689
+ created_by: text("created_by").notNull()
690
+ }, (table) => ({ userIdIdx: index("support_staff_user_id_idx").on(table.user_id) }));
691
+
496
692
  //#endregion
497
693
  //#region src/slices/support_ticket/db/support_ticket_table.ts
498
694
  /**
@@ -504,10 +700,9 @@ const support_ticket_table = sqliteTable("support_ticket", {
504
700
  title: text("title").notNull(),
505
701
  description: text("description").notNull(),
506
702
  type: text("type", { enum: SupportTicketTypeEnum }).notNull().default("FEATURE_REQUEST"),
507
- priority: text("priority", { enum: SupportTicketPriorityEnum }).notNull().default("MEDIUM"),
703
+ priority: integer("priority", { mode: "number" }).notNull().default(2),
508
704
  approval_status: text("approval_status", { enum: SupportTicketApprovalEnum }).notNull().default("PENDING"),
509
- requester_email: text("requester_email").notNull(),
510
- requester_name: text("requester_name").notNull(),
705
+ assigned_to: text("assigned_to"),
511
706
  credit_value: text("credit_value"),
512
707
  delivered_value: text("delivered_value"),
513
708
  dev_lifecycle: text("dev_lifecycle", { enum: SupportTicketDevLifecycleEnum }),
@@ -531,8 +726,7 @@ const support_ticket_table = sqliteTable("support_ticket", {
531
726
  priorityIdx: index("support_ticket_priority_idx").on(table.priority),
532
727
  approvalStatusIdx: index("support_ticket_approval_status_idx").on(table.approval_status),
533
728
  devLifecycleIdx: index("support_ticket_dev_lifecycle_idx").on(table.dev_lifecycle),
534
- requesterEmailIdx: index("support_ticket_requester_email_idx").on(table.requester_email),
535
- requesterNameIdx: index("support_ticket_requester_name_idx").on(table.requester_name),
729
+ assignedToIdx: index("support_ticket_assigned_to_idx").on(table.assigned_to),
536
730
  creditValueIdx: index("support_ticket_credit_value_idx").on(table.credit_value),
537
731
  deliveredValueIdx: index("support_ticket_delivered_value_idx").on(table.delivered_value),
538
732
  createdAtIdx: index("support_ticket_created_at_idx").on(table.created_at),
@@ -659,6 +853,7 @@ const user_profile_table = sqliteTable("user_profile", {
659
853
  last_name: text("last_name", { length: 255 }),
660
854
  additional_name: text("additional_name", { length: 255 }),
661
855
  avatar_url: text("avatar_url"),
856
+ notification_email: text("notification_email"),
662
857
  gender: text("gender", { length: 255 }),
663
858
  bio: text("bio"),
664
859
  birthday: text("birthday"),
@@ -1351,6 +1546,14 @@ function resolveTeamAccess(userTeamIds, teamFilter) {
1351
1546
  };
1352
1547
  return { denied: true };
1353
1548
  }
1549
+ if (teamFilter?.operator === "in" && teamFilter.values?.length) {
1550
+ const allowed = teamFilter.values.filter((id) => userTeamIds.includes(id));
1551
+ if (allowed.length === 0) return { denied: true };
1552
+ return {
1553
+ denied: false,
1554
+ effectiveTeamIds: allowed
1555
+ };
1556
+ }
1354
1557
  return {
1355
1558
  denied: false,
1356
1559
  effectiveTeamIds: userTeamIds
@@ -1616,6 +1819,11 @@ var CursorUtils = class {
1616
1819
  * Get items AFTER this cursor position in the sorted order
1617
1820
  */
1618
1821
  static buildAfterCondition(table, column, sortValue, id, isDesc) {
1822
+ if (sortValue === null || sortValue === void 0) {
1823
+ const sortCol = column.column;
1824
+ if (isDesc) return [and(isNull(sortCol), lt(table.id, id))];
1825
+ else return [and(isNull(sortCol), gt(table.id, id))];
1826
+ }
1619
1827
  const { sortColumn, processedValue } = this.processSortColumn(column, sortValue);
1620
1828
  if (isDesc) return [or(lt(sortColumn, processedValue), and(eq(sortColumn, processedValue), lt(table.id, id)))];
1621
1829
  else return [or(gt(sortColumn, processedValue), and(eq(sortColumn, processedValue), gt(table.id, id)))];
@@ -1739,7 +1947,7 @@ var PaginationUtils = class {
1739
1947
  responseState = BreadcrumbUtils.navigateNext(state, nextPageCursor);
1740
1948
  responseState.currentPageIndex = state.currentPageIndex;
1741
1949
  }
1742
- const prevPageCursor = BreadcrumbUtils.canNavigatePrevious(state) ? state.cursors[state.currentPageIndex - 1] || void 0 : void 0;
1950
+ const prevPageCursor = BreadcrumbUtils.canNavigatePrevious(state) ? state.cursors[state.currentPageIndex - 1] : void 0;
1743
1951
  return {
1744
1952
  items,
1745
1953
  pageInfo: {
@@ -2727,214 +2935,41 @@ function createIsAuthenticatedMiddleware(options) {
2727
2935
  }
2728
2936
 
2729
2937
  //#endregion
2730
- //#region src/db/schemas/app_setting/app_settings_table.ts
2731
- /**
2732
- * Application settings table for storing global configuration
2733
- * that doesn't need to be sharded.
2734
- *
2735
- * This includes default rates and other application-wide settings.
2736
- */
2737
- const app_settings_table = sqliteTable("app_settings", {
2738
- key: text("key").primaryKey(),
2739
- value: text("value", { mode: "json" }).$type().notNull(),
2740
- created_at: text("created_at").notNull(),
2741
- created_by: text("created_by").notNull(),
2742
- updated_at: text("updated_at").notNull(),
2743
- updated_by: text("updated_by")
2744
- }, (table) => ({
2745
- created_at_idx: index("app_settings_created_at_idx").on(table.created_at),
2746
- updated_at_idx: index("app_settings_updated_at_idx").on(table.updated_at)
2747
- }));
2748
- /**
2749
- * Known setting keys for type safety
2750
- */
2751
- const AppSettingKey = {
2752
- GLOBAL_TAX_SETTINGS: "global_tax_settings",
2753
- DEFAULT_PRICING_LEVEL: "default_pricing_level",
2754
- TERMS_OF_SERVICE: "terms_of_service",
2755
- PRIVACY_POLICY: "privacy_policy",
2756
- MAX_SUPPORT_TICKET_ITEMS: "max_support_ticket_items",
2757
- CUSTOMER_MONTHLY_BALANCE: "customer_monthly_balance",
2758
- CUSTOMER_ROLLOVER_BALANCE: "customer_rollover_balance",
2759
- CUSTOMER_MONTHLY_ALLOCATION: "customer_monthly_allocation",
2760
- SUPPORT_TICKET_NOTIFICATION_EMAILS: "support_ticket_notification_emails"
2938
+ //#region src/slices/app_settings/app_settings_container.ts
2939
+ function registerAppSettingsContainer() {}
2940
+
2941
+ //#endregion
2942
+ //#region src/slices/attachment/attachment_interfaces.ts
2943
+ const ATTACHMENT_TOKENS = {
2944
+ IAttachmentRepo: Symbol("IAttachmentRepo"),
2945
+ IReadAllAttachmentsByRecord: Symbol("IReadAllAttachmentsByRecord"),
2946
+ IReadAttachment: Symbol("IReadAttachment"),
2947
+ ICreateAttachment: Symbol("ICreateAttachment"),
2948
+ IUpdateAttachment: Symbol("IUpdateAttachment"),
2949
+ IDeleteAttachment: Symbol("IDeleteAttachment")
2950
+ };
2951
+ const ATTACHMENT_FOLDER_TOKENS = {
2952
+ IAttachmentFolderRepo: Symbol("IAttachmentFolderRepo"),
2953
+ ICreateAttachmentFolder: Symbol("ICreateAttachmentFolder")
2761
2954
  };
2762
2955
 
2763
2956
  //#endregion
2764
- //#region src/db/schemas/app_setting/app_settings_repo.ts
2765
- let AppSettingsRepo = class AppSettingsRepo$1 {
2766
- constructor(appSettingsDb) {
2767
- this.appSettingsDb = appSettingsDb;
2957
+ //#region src/slices/attachment/db/attachment_folder_repo.ts
2958
+ let AttachmentFolderRepo = class AttachmentFolderRepo$1 {
2959
+ constructor(router) {
2960
+ this.router = router;
2768
2961
  }
2769
- /**
2770
- * Get a setting value by key
2771
- * @param key Setting key
2772
- * @returns The setting value or undefined if not found
2773
- */
2774
- async readSetting(key) {
2775
- const [setting] = await this.appSettingsDb.select().from(app_settings_table).where(eq(app_settings_table.key, key)).limit(1);
2776
- return setting;
2962
+ async create_folder(folder) {
2963
+ const id = await this.router.generateId(RecordConst.ATTACHMENT_FOLDER);
2964
+ return (await this.router.queryLatest((db) => db.insert(attachment_folder_table).values({
2965
+ id,
2966
+ ...folder
2967
+ }).returning()))[0];
2777
2968
  }
2778
- /**
2779
- * Save a setting value
2780
- * @param key Setting key
2781
- * @param value Setting value
2782
- * @param user User making the change
2783
- */
2784
- async createSetting(key, value, user) {
2785
- const now = (/* @__PURE__ */ new Date()).toISOString();
2786
- await this.appSettingsDb.insert(app_settings_table).values({
2787
- key,
2788
- value,
2789
- created_at: now,
2790
- created_by: user.email,
2791
- updated_at: now,
2792
- updated_by: user.email
2793
- });
2794
- }
2795
- async updateSetting(key, value, user) {
2796
- const now = (/* @__PURE__ */ new Date()).toISOString();
2797
- const [updated] = await this.appSettingsDb.update(app_settings_table).set({
2798
- value,
2799
- updated_at: now,
2800
- updated_by: user.email
2801
- }).where(eq(app_settings_table.key, key)).returning();
2802
- if (!updated) {
2803
- const [inserted] = await this.appSettingsDb.insert(app_settings_table).values({
2804
- key,
2805
- value,
2806
- created_at: now,
2807
- created_by: user.email,
2808
- updated_at: now,
2809
- updated_by: user.email
2810
- }).returning();
2811
- return inserted?.value;
2812
- }
2813
- return updated.value;
2814
- }
2815
- /**
2816
- * Delete a setting
2817
- * @param key Setting key
2818
- * @returns True if the setting was deleted, false if it didn't exist
2819
- */
2820
- async deleteSetting(key) {
2821
- return (await this.appSettingsDb.delete(app_settings_table).where(eq(app_settings_table.key, key)).returning()).length > 0;
2822
- }
2823
- /**
2824
- * Get all settings
2825
- * @returns All settings as a map of key to value
2826
- */
2827
- async getAllSettings() {
2828
- const settings = await this.appSettingsDb.select().from(app_settings_table);
2829
- const settingsMap = /* @__PURE__ */ new Map();
2830
- for (const setting of settings) settingsMap.set(setting.key, setting.value);
2831
- return settingsMap;
2832
- }
2833
- async readMultipleSettings(keys) {
2834
- const settings = await this.appSettingsDb.select().from(app_settings_table).where(inArray(app_settings_table.key, keys));
2835
- const settingsMap = /* @__PURE__ */ new Map();
2836
- for (const setting of settings) settingsMap.set(setting.key, setting.value);
2837
- return settingsMap;
2838
- }
2839
- async getNextSequentialId(recordType, userId) {
2840
- const counterKey = `${recordType}:counter`;
2841
- const now = (/* @__PURE__ */ new Date()).toISOString();
2842
- const [result] = await this.appSettingsDb.insert(app_settings_table).values({
2843
- key: counterKey,
2844
- value: 1,
2845
- created_at: now,
2846
- created_by: userId,
2847
- updated_at: now,
2848
- updated_by: userId
2849
- }).onConflictDoUpdate({
2850
- target: app_settings_table.key,
2851
- set: {
2852
- value: sql`json(json_extract(${app_settings_table.value}, '$') + 1)`,
2853
- updated_at: now,
2854
- updated_by: userId
2855
- }
2856
- }).returning({ value: app_settings_table.value });
2857
- return String(result.value);
2858
- }
2859
- };
2860
- AppSettingsRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IAppSettingsDb))], AppSettingsRepo);
2861
-
2862
- //#endregion
2863
- //#region src/slices/app_settings/app_settings_tokens.ts
2864
- const APP_SETTINGS_TOKENS = {
2865
- IGetNotificationEmailsFeature: Symbol("IGetNotificationEmailsFeature"),
2866
- IUpdateNotificationEmailsFeature: Symbol("IUpdateNotificationEmailsFeature")
2867
- };
2868
-
2869
- //#endregion
2870
- //#region src/slices/app_settings/features/get_notification_emails_feat.ts
2871
- let GetNotificationEmailsFeat = class GetNotificationEmailsFeat$1 {
2872
- constructor(appSettingsRepo) {
2873
- this.appSettingsRepo = appSettingsRepo;
2874
- }
2875
- async execute() {
2876
- return { emails: (await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS))?.value || [] };
2877
- }
2878
- };
2879
- GetNotificationEmailsFeat = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IAppSettingsRepo))], GetNotificationEmailsFeat);
2880
-
2881
- //#endregion
2882
- //#region src/slices/app_settings/features/update_notification_emails_feat.ts
2883
- let UpdateNotificationEmailsFeat = class UpdateNotificationEmailsFeat$1 {
2884
- constructor(appSettingsRepo, session) {
2885
- this.appSettingsRepo = appSettingsRepo;
2886
- this.session = session;
2887
- }
2888
- async execute(input) {
2889
- await this.appSettingsRepo.updateSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS, input.emails.map((email) => email.email), this.session.user);
2890
- return input;
2891
- }
2892
- };
2893
- UpdateNotificationEmailsFeat = __decorate([
2894
- injectable(),
2895
- __decorateParam(0, inject(TOKENS.IAppSettingsRepo)),
2896
- __decorateParam(1, injectSession())
2897
- ], UpdateNotificationEmailsFeat);
2898
-
2899
- //#endregion
2900
- //#region src/slices/app_settings/app_settings_container.ts
2901
- function registerAppSettingsContainer() {
2902
- container.registerSingleton(APP_SETTINGS_TOKENS.IGetNotificationEmailsFeature, GetNotificationEmailsFeat);
2903
- container.registerSingleton(APP_SETTINGS_TOKENS.IUpdateNotificationEmailsFeature, UpdateNotificationEmailsFeat);
2904
- }
2905
-
2906
- //#endregion
2907
- //#region src/slices/attachment/attachment_interfaces.ts
2908
- const ATTACHMENT_TOKENS = {
2909
- IAttachmentRepo: Symbol("IAttachmentRepo"),
2910
- IReadAllAttachmentsByRecord: Symbol("IReadAllAttachmentsByRecord"),
2911
- IReadAttachment: Symbol("IReadAttachment"),
2912
- ICreateAttachment: Symbol("ICreateAttachment"),
2913
- IUpdateAttachment: Symbol("IUpdateAttachment"),
2914
- IDeleteAttachment: Symbol("IDeleteAttachment")
2915
- };
2916
- const ATTACHMENT_FOLDER_TOKENS = {
2917
- IAttachmentFolderRepo: Symbol("IAttachmentFolderRepo"),
2918
- ICreateAttachmentFolder: Symbol("ICreateAttachmentFolder")
2919
- };
2920
-
2921
- //#endregion
2922
- //#region src/slices/attachment/db/attachment_folder_repo.ts
2923
- let AttachmentFolderRepo = class AttachmentFolderRepo$1 {
2924
- constructor(router) {
2925
- this.router = router;
2926
- }
2927
- async create_folder(folder) {
2928
- const id = await this.router.generateId(RecordConst.ATTACHMENT_FOLDER);
2929
- return (await this.router.queryLatest((db) => db.insert(attachment_folder_table).values({
2930
- id,
2931
- ...folder
2932
- }).returning()))[0];
2933
- }
2934
- async read_folder(id) {
2935
- const { ...rest } = getTableColumns(attachment_folder_table);
2936
- const result = await this.router.queryById(id, (db) => db.select({ ...rest }).from(attachment_folder_table).where(and(eq(attachment_folder_table.id, id), isNull(attachment_folder_table.deleted_at))).limit(1));
2937
- return result.length > 0 ? result[0] : null;
2969
+ async read_folder(id) {
2970
+ const { ...rest } = getTableColumns(attachment_folder_table);
2971
+ const result = await this.router.queryById(id, (db) => db.select({ ...rest }).from(attachment_folder_table).where(and(eq(attachment_folder_table.id, id), isNull(attachment_folder_table.deleted_at))).limit(1));
2972
+ return result.length > 0 ? result[0] : null;
2938
2973
  }
2939
2974
  async delete_folder(id) {
2940
2975
  return (await this.router.queryById(id, (db) => db.update(attachment_folder_table).set({ deleted_at: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(attachment_folder_table.id, id)).returning())).length > 0;
@@ -3037,17 +3072,74 @@ let AttachmentRepo = class AttachmentRepo$1 {
3037
3072
  };
3038
3073
  AttachmentRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], AttachmentRepo);
3039
3074
 
3075
+ //#endregion
3076
+ //#region src/slices/record_version/record_version_interfaces.ts
3077
+ const RECORD_VERSION_TOKENS = {
3078
+ IRecordVersionsRepo: Symbol("IRecordVersionsRepo"),
3079
+ IReadAllRecordVersionsByRecord: Symbol("IReadAllRecordVersionsByRecord"),
3080
+ IReadAllRecordVersionsByRecordPaginated: Symbol("IReadAllRecordVersionsByRecordPaginated"),
3081
+ IReadAllRecordVersionsByRecordPaginatedCustomer: Symbol("IReadAllRecordVersionsByRecordPaginatedCustomer"),
3082
+ IReadTrackerActivityVersions: Symbol("IReadTrackerActivityVersions"),
3083
+ IReadRecordVersion: Symbol("IReadRecordVersion"),
3084
+ ICreateRecordVersion: Symbol("ICreateRecordVersion"),
3085
+ ICreateRecordVersionsBatch: Symbol("ICreateRecordVersionsBatch"),
3086
+ IUpdateRecordVersion: Symbol("IUpdateRecordVersion"),
3087
+ IDeleteRecordVersion: Symbol("IDeleteRecordVersion")
3088
+ };
3089
+
3090
+ //#endregion
3091
+ //#region src/slices/support_ticket/support_ticket_tokens.ts
3092
+ /**
3093
+ * Tokens for dependency injection
3094
+ * Organized by customer vs staff features
3095
+ */
3096
+ const SUPPORT_TICKET_TOKENS = {
3097
+ ISupportTicketRepo: "ISupportTicketRepo",
3098
+ ISupportTicketNotificationService: "ISupportTicketNotificationService",
3099
+ ISupportTicketTriageService: "ISupportTicketTriageService",
3100
+ ICreateSupportTicketFeature: "ICreateSupportTicketFeature",
3101
+ IUpdateSupportTicketCustomerFeature: "IUpdateSupportTicketCustomerFeature",
3102
+ IGetSupportTicketCustomerFeature: "IGetSupportTicketCustomerFeature",
3103
+ IGetSupportTicketsCustomerFeature: "IGetSupportTicketsCustomerFeature",
3104
+ ICustomerToggleSubscriptionFeature: "ICustomerToggleSubscriptionFeature",
3105
+ IApproveSupportTicketFeature: "IApproveSupportTicketFeature",
3106
+ IRejectSupportTicketFeature: "IRejectSupportTicketFeature",
3107
+ IRevertSupportTicketFeature: "IRevertSupportTicketFeature",
3108
+ ICompleteSupportTicketFeature: "ICompleteSupportTicketFeature",
3109
+ IConvertToInternalFeature: "IConvertToInternalFeature",
3110
+ IConvertToCustomerFeature: "IConvertToCustomerFeature",
3111
+ IDeleteSupportTicketFeature: "IDeleteSupportTicketFeature",
3112
+ IArchiveSupportTicketFeature: "IArchiveSupportTicketFeature",
3113
+ ICancelInternalTaskFeature: "ICancelInternalTaskFeature",
3114
+ IReactivateInternalTaskFeature: "IReactivateInternalTaskFeature",
3115
+ ICreateSupportTicketAdminFeature: "ICreateSupportTicketAdminFeature",
3116
+ IUpdateSupportTicketAdminFeature: "IUpdateSupportTicketAdminFeature",
3117
+ IGetSupportTicketAdminFeature: "IGetSupportTicketAdminFeature",
3118
+ IGetSupportTicketsAdminFeature: "IGetSupportTicketsAdminFeature",
3119
+ IGetRequestorsForActiveSupportTicketsFeature: "IGetRequestorsForActiveSupportTicketsFeature",
3120
+ IGetSupportTicketNotesFeature: "IGetSupportTicketNotesFeature",
3121
+ IAddSupportTicketSubscriberFeature: "IAddSupportTicketSubscriberFeature",
3122
+ IListSupportTicketSubscribersFeature: "IListSupportTicketSubscribersFeature",
3123
+ IRemoveSupportTicketSubscriberFeature: "IRemoveSupportTicketSubscriberFeature",
3124
+ IFixSupportTicketUserIdsFeature: "IFixSupportTicketUserIdsFeature"
3125
+ };
3126
+
3040
3127
  //#endregion
3041
3128
  //#region src/slices/attachment/features/create_attachment.ts
3042
3129
  let CreateAttachment = class CreateAttachment$1 {
3043
- constructor(session, attachment_repo, attachment_folder_repo, env, logger$1) {
3130
+ constructor(session, attachment_repo, attachment_folder_repo, support_ticket_repo, create_record_version, env, logger$1) {
3044
3131
  this.session = session;
3045
3132
  this.attachment_repo = attachment_repo;
3046
3133
  this.attachment_folder_repo = attachment_folder_repo;
3134
+ this.support_ticket_repo = support_ticket_repo;
3135
+ this.create_record_version = create_record_version;
3047
3136
  this.env = env;
3048
3137
  this.logger = logger$1;
3049
3138
  }
3050
3139
  async execute(attachment) {
3140
+ if (attachment.record_type === RecordConst$1.SUPPORT_TICKET) {
3141
+ if ((await this.support_ticket_repo.read(attachment.record_id))?.archived_at) throw new BusinessError("Cannot add attachments to an archived support ticket.");
3142
+ }
3051
3143
  this.logger.debug("[CreateAttachment] Starting file upload");
3052
3144
  if (!attachment.file) throw new Error("File is required");
3053
3145
  const envWithOptional = this.env;
@@ -3090,6 +3182,17 @@ let CreateAttachment = class CreateAttachment$1 {
3090
3182
  this.logger.debug("[CreateAttachment] Failed to upload file to R2");
3091
3183
  throw new Error("Failed to upload file to storage");
3092
3184
  } else this.logger.debug(`[CreateAttachment] File uploaded to R2 with etag: ${r2_res.etag}`);
3185
+ if (attachment.record_type === RecordConst$1.SUPPORT_TICKET) await this.create_record_version.execute({
3186
+ record_id: attachment.record_id,
3187
+ operation: OperationConst.UPDATE,
3188
+ recorded_at: now,
3189
+ record_type: RecordConst$1.SUPPORT_TICKET_ACTIVITY,
3190
+ record: { attachment_added: attachment.file_name },
3191
+ old_record: {},
3192
+ auth_uid: this.session.user.userId,
3193
+ auth_role: this.session.user.user_type ?? void 0,
3194
+ auth_username: this.session.user.username ?? void 0
3195
+ });
3093
3196
  this.logger.debug("[CreateAttachment] Attachment created successfully");
3094
3197
  return new_attachment;
3095
3198
  }
@@ -3099,19 +3202,25 @@ CreateAttachment = __decorate([
3099
3202
  __decorateParam(0, injectSession()),
3100
3203
  __decorateParam(1, inject(ATTACHMENT_TOKENS.IAttachmentRepo)),
3101
3204
  __decorateParam(2, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)),
3102
- __decorateParam(3, inject(TOKENS.ENV)),
3103
- __decorateParam(4, inject(TOKENS.LOGGER))
3205
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
3206
+ __decorateParam(4, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
3207
+ __decorateParam(5, inject(TOKENS.ENV)),
3208
+ __decorateParam(6, inject(TOKENS.LOGGER))
3104
3209
  ], CreateAttachment);
3105
3210
 
3106
3211
  //#endregion
3107
3212
  //#region src/slices/attachment/features/create_attachment_folder.ts
3108
3213
  let CreateAttachmentFolder = class CreateAttachmentFolder$1 {
3109
- constructor(session, attachment_folder_repo, logger$1) {
3214
+ constructor(session, attachment_folder_repo, support_ticket_repo, logger$1) {
3110
3215
  this.session = session;
3111
3216
  this.attachment_folder_repo = attachment_folder_repo;
3217
+ this.support_ticket_repo = support_ticket_repo;
3112
3218
  this.logger = logger$1;
3113
3219
  }
3114
3220
  async execute(input) {
3221
+ if (input.record_type === RecordConst$1.SUPPORT_TICKET) {
3222
+ if ((await this.support_ticket_repo.read(input.record_id))?.archived_at) throw new BusinessError("Cannot add folders to an archived support ticket.");
3223
+ }
3115
3224
  this.logger.debug("[CreateAttachmentFolder] Starting folder creation");
3116
3225
  if (!input.folder_name) throw new Error("Folder name is required");
3117
3226
  const sanitized_name = sanitizeFileName(input.folder_name);
@@ -3159,28 +3268,52 @@ CreateAttachmentFolder = __decorate([
3159
3268
  injectable(),
3160
3269
  __decorateParam(0, injectSession()),
3161
3270
  __decorateParam(1, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)),
3162
- __decorateParam(2, inject(TOKENS.LOGGER))
3271
+ __decorateParam(2, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
3272
+ __decorateParam(3, inject(TOKENS.LOGGER))
3163
3273
  ], CreateAttachmentFolder);
3164
3274
 
3165
3275
  //#endregion
3166
3276
  //#region src/slices/attachment/features/delete_attachment.ts
3167
3277
  let DeleteAttachment = class DeleteAttachment$1 {
3168
- constructor(attachment_repo, attachment_folder_repo) {
3278
+ constructor(session, attachment_repo, attachment_folder_repo, support_ticket_repo, create_record_version) {
3279
+ this.session = session;
3169
3280
  this.attachment_repo = attachment_repo;
3170
3281
  this.attachment_folder_repo = attachment_folder_repo;
3282
+ this.support_ticket_repo = support_ticket_repo;
3283
+ this.create_record_version = create_record_version;
3171
3284
  }
3172
3285
  async execute(id) {
3173
3286
  const attachment = await this.attachment_repo.read_attachment(id);
3174
3287
  if (!attachment) return false;
3288
+ if (attachment.record_type === RecordConst$1.SUPPORT_TICKET) {
3289
+ if ((await this.support_ticket_repo.read(attachment.record_id))?.archived_at) throw new BusinessError("Cannot delete attachments from an archived support ticket.");
3290
+ }
3175
3291
  const deleted = await this.attachment_repo.delete_attachment(id);
3292
+ if (deleted && attachment.record_type === RecordConst$1.SUPPORT_TICKET) {
3293
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3294
+ await this.create_record_version.execute({
3295
+ record_id: attachment.record_id,
3296
+ operation: OperationConst.UPDATE,
3297
+ recorded_at: now,
3298
+ record_type: RecordConst$1.SUPPORT_TICKET_ACTIVITY,
3299
+ record: { attachment_removed: attachment.original_name },
3300
+ old_record: {},
3301
+ auth_uid: this.session.user.userId,
3302
+ auth_role: this.session.user.user_type ?? void 0,
3303
+ auth_username: this.session.user.username ?? void 0
3304
+ });
3305
+ }
3176
3306
  if (deleted && attachment.folder_id) await this.attachment_folder_repo.recompute_file_count(attachment.folder_id);
3177
3307
  return deleted;
3178
3308
  }
3179
3309
  };
3180
3310
  DeleteAttachment = __decorate([
3181
3311
  injectable(),
3182
- __decorateParam(0, inject(ATTACHMENT_TOKENS.IAttachmentRepo)),
3183
- __decorateParam(1, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo))
3312
+ __decorateParam(0, injectSession()),
3313
+ __decorateParam(1, inject(ATTACHMENT_TOKENS.IAttachmentRepo)),
3314
+ __decorateParam(2, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)),
3315
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
3316
+ __decorateParam(4, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
3184
3317
  ], DeleteAttachment);
3185
3318
 
3186
3319
  //#endregion
@@ -3474,7 +3607,7 @@ let CreditService = class CreditService$1 {
3474
3607
  type: "DEDUCTION",
3475
3608
  balance_after: CreditMath.toString(newMonthlyBalance + newRolloverBalance),
3476
3609
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3477
- created_by: this.systemUser.email
3610
+ created_by: this.systemUser.userId
3478
3611
  };
3479
3612
  await this.transactionRepo.createCreditTransaction(transaction);
3480
3613
  return true;
@@ -3489,7 +3622,7 @@ let CreditService = class CreditService$1 {
3489
3622
  type: "REFUND",
3490
3623
  balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),
3491
3624
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3492
- created_by: this.systemUser.email
3625
+ created_by: this.systemUser.userId
3493
3626
  };
3494
3627
  await this.transactionRepo.createCreditTransaction(transaction);
3495
3628
  }
@@ -3527,7 +3660,7 @@ let CreditService = class CreditService$1 {
3527
3660
  type: "PURCHASE_ONETIME",
3528
3661
  balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),
3529
3662
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3530
- created_by: this.systemUser.email
3663
+ created_by: this.systemUser.userId
3531
3664
  };
3532
3665
  await this.transactionRepo.createCreditTransaction(transaction);
3533
3666
  }
@@ -3600,34 +3733,6 @@ BusinessLogicRouter = __decorate([
3600
3733
  __decorateParam(3, inject("DeleteNoteSupportTicketProcessor"))
3601
3734
  ], BusinessLogicRouter);
3602
3735
 
3603
- //#endregion
3604
- //#region src/slices/support_ticket/support_ticket_tokens.ts
3605
- /**
3606
- * Tokens for dependency injection
3607
- * Organized by customer vs staff features
3608
- */
3609
- const SUPPORT_TICKET_TOKENS = {
3610
- ISupportTicketRepo: "ISupportTicketRepo",
3611
- ICreateSupportTicketFeature: "ICreateSupportTicketFeature",
3612
- IUpdateSupportTicketCustomerFeature: "IUpdateSupportTicketCustomerFeature",
3613
- IGetSupportTicketCustomerFeature: "IGetSupportTicketCustomerFeature",
3614
- IGetSupportTicketsCustomerFeature: "IGetSupportTicketsCustomerFeature",
3615
- IApproveSupportTicketFeature: "IApproveSupportTicketFeature",
3616
- IRejectSupportTicketFeature: "IRejectSupportTicketFeature",
3617
- IRevertSupportTicketFeature: "IRevertSupportTicketFeature",
3618
- ICompleteSupportTicketFeature: "ICompleteSupportTicketFeature",
3619
- IConvertToInternalFeature: "IConvertToInternalFeature",
3620
- IConvertToCustomerFeature: "IConvertToCustomerFeature",
3621
- IDeleteSupportTicketFeature: "IDeleteSupportTicketFeature",
3622
- ICancelInternalTaskFeature: "ICancelInternalTaskFeature",
3623
- IReactivateInternalTaskFeature: "IReactivateInternalTaskFeature",
3624
- ICreateSupportTicketAdminFeature: "ICreateSupportTicketAdminFeature",
3625
- IUpdateSupportTicketAdminFeature: "IUpdateSupportTicketAdminFeature",
3626
- IGetSupportTicketAdminFeature: "IGetSupportTicketAdminFeature",
3627
- IGetSupportTicketsAdminFeature: "IGetSupportTicketsAdminFeature",
3628
- IGetSupportTicketNotesFeature: "IGetSupportTicketNotesFeature"
3629
- };
3630
-
3631
3736
  //#endregion
3632
3737
  //#region src/slices/note/business_logic/support_ticket/create_note_support_ticket_processor.ts
3633
3738
  let CreateNoteSupportTicketProcessor = class CreateNoteSupportTicketProcessor$1 {
@@ -3638,18 +3743,18 @@ let CreateNoteSupportTicketProcessor = class CreateNoteSupportTicketProcessor$1
3638
3743
  async process(input) {
3639
3744
  const supportTicket = await this.supportTicketRepo.read(input.record_id);
3640
3745
  if (!supportTicket) throw new Error("Support ticket not found");
3746
+ if (supportTicket.archived_at) throw new Error("Cannot add comments to an archived ticket.");
3641
3747
  const user = this.session.user;
3642
3748
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(input, supportTicket);
3643
- else if (user.user_type === "consumer") return await this.processConsumerRequest(input, supportTicket);
3644
- else throw new Error("Invalid user type");
3749
+ else if (user.user_type === "consumer" || user.user_type === "lead") return await this.processConsumerRequest(input, supportTicket);
3750
+ else throw new BusinessError("Invalid user type");
3645
3751
  }
3646
3752
  async processStaffRequest(input, _supportTicket) {
3647
3753
  return input;
3648
3754
  }
3649
3755
  async processConsumerRequest(input, supportTicket) {
3650
- const user = this.session.user;
3651
- if (supportTicket.created_by !== user.userId) throw new Error("You do not have access to this support ticket");
3652
- if (input.is_internal) throw new Error("Consumers cannot create internal notes");
3756
+ if (supportTicket.approval_status === "INTERNAL") throw new BusinessError("You do not have access to this support ticket");
3757
+ if (input.is_internal) throw new BusinessError("Consumers cannot create internal notes");
3653
3758
  return {
3654
3759
  ...input,
3655
3760
  is_internal: false
@@ -3674,7 +3779,7 @@ const NOTE_TOKENS = {
3674
3779
  IUpdateNoteFeature: "IUpdateNoteFeature",
3675
3780
  IGetNotesFeature: "IGetNotesFeature",
3676
3781
  IDeleteNoteFeature: "IDeleteNoteFeature",
3677
- IBusinessLogicRouter: "IBusinessLogicRouter",
3782
+ IBusinessLogicRouter: "Note.IBusinessLogicRouter",
3678
3783
  CreateNoteSupportTicketProcessor: "CreateNoteSupportTicketProcessor",
3679
3784
  UpdateNoteSupportTicketProcessor: "UpdateNoteSupportTicketProcessor",
3680
3785
  GetNotesSupportTicketProcessor: "GetNotesSupportTicketProcessor",
@@ -3691,21 +3796,27 @@ let DeleteNoteSupportTicketProcessor = class DeleteNoteSupportTicketProcessor$1
3691
3796
  }
3692
3797
  async process(id) {
3693
3798
  const existingNote = await this.noteRepo.read(id);
3694
- if (!existingNote) throw new Error("Note not found");
3695
- if (existingNote.record_type !== "support_ticket") throw new Error("This note does not belong to a support ticket");
3799
+ if (!existingNote) throw new BusinessError("Note not found");
3800
+ if (existingNote.record_type !== "support_ticket") throw new BusinessError("This note does not belong to a support ticket");
3696
3801
  const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);
3697
- if (!supportTicket) throw new Error("Support ticket not found");
3802
+ if (!supportTicket) throw new BusinessError("Support ticket not found");
3698
3803
  const user = this.session.user;
3699
3804
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(id, existingNote, supportTicket);
3700
- else if (user.user_type === "consumer") return await this.processConsumerRequest(id, existingNote, supportTicket);
3701
- else throw new Error("Invalid user type");
3805
+ else if (user.user_type === "consumer" || user.user_type === "lead") return await this.processConsumerRequest(id, existingNote, supportTicket);
3806
+ else throw new BusinessError("Invalid user type");
3702
3807
  }
3703
- async processStaffRequest(_id, _existingNote, supportTicket) {
3704
- if (supportTicket.dev_lifecycle === "DEPLOYED" || supportTicket.approval_status === "REJECTED") throw new Error("Cannot delete notes on completed or cancelled support tickets");
3705
- return _id;
3808
+ async processStaffRequest(id, existingNote, supportTicket) {
3809
+ if (existingNote.created_by !== this.session.user.userId) throw new BusinessError("You can only delete notes that you created");
3810
+ if (supportTicket.dev_lifecycle === "DEPLOYED" || supportTicket.approval_status === "REJECTED") throw new BusinessError("Cannot delete notes on completed or cancelled support tickets");
3811
+ if (supportTicket.archived_at) throw new BusinessError("Cannot delete comments on an archived ticket.");
3812
+ return id;
3706
3813
  }
3707
- async processConsumerRequest(_id, _existingNote, _supportTicket) {
3708
- throw new Error("Only staff can delete notes");
3814
+ async processConsumerRequest(id, existingNote, supportTicket) {
3815
+ const user = this.session.user;
3816
+ if (supportTicket.approval_status === "INTERNAL") throw new BusinessError("You do not have access to this support ticket");
3817
+ if (existingNote.created_by !== user.userId) throw new BusinessError("You can only delete notes that you created");
3818
+ if (supportTicket.archived_at) throw new BusinessError("Cannot delete comments on an archived ticket.");
3819
+ return id;
3709
3820
  }
3710
3821
  };
3711
3822
  DeleteNoteSupportTicketProcessor = __decorate([
@@ -3729,8 +3840,8 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3729
3840
  const supportTicket = await this.supportTicketRepo.read(recordIdValue);
3730
3841
  if (!supportTicket) throw new Error("Support ticket not found");
3731
3842
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(filters, supportTicket);
3732
- else if (user.user_type === "consumer") return await this.processConsumerRequest(filters, supportTicket);
3733
- else throw new Error("Invalid user type");
3843
+ else if (user.user_type === "consumer" || user.user_type === "lead") return await this.processConsumerRequest(filters, supportTicket);
3844
+ else throw new BusinessError("Invalid user type");
3734
3845
  }
3735
3846
  return await this.applyVisibilityRules(filters);
3736
3847
  }
@@ -3738,8 +3849,7 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3738
3849
  return filters;
3739
3850
  }
3740
3851
  async processConsumerRequest(filters, supportTicket) {
3741
- const user = this.session.user;
3742
- if (supportTicket.created_by !== user.userId) throw new Error("You do not have access to this support ticket");
3852
+ if (supportTicket.approval_status === "INTERNAL") throw new BusinessError("You do not have access to this support ticket");
3743
3853
  return {
3744
3854
  ...filters,
3745
3855
  is_internal: {
@@ -3749,7 +3859,8 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3749
3859
  };
3750
3860
  }
3751
3861
  async applyVisibilityRules(filters) {
3752
- if (this.session.user.user_type === "consumer") return {
3862
+ const user = this.session.user;
3863
+ if (user.user_type === "consumer" || user.user_type === "lead") return {
3753
3864
  ...filters,
3754
3865
  is_internal: {
3755
3866
  operator: OPERATORS.EQUALS,
@@ -3775,25 +3886,26 @@ let UpdateNoteSupportTicketProcessor = class UpdateNoteSupportTicketProcessor$1
3775
3886
  }
3776
3887
  async process(input) {
3777
3888
  const existingNote = await this.noteRepo.read(input.id);
3778
- if (!existingNote) throw new Error("Note not found");
3779
- if (existingNote.record_type !== "support_ticket") throw new Error("This note does not belong to a support ticket");
3889
+ if (!existingNote) throw new BusinessError("Note not found");
3890
+ if (existingNote.record_type !== "support_ticket") throw new BusinessError("This note does not belong to a support ticket");
3780
3891
  const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);
3781
- if (!supportTicket) throw new Error("Support ticket not found");
3892
+ if (!supportTicket) throw new BusinessError("Support ticket not found");
3893
+ if (supportTicket.archived_at) throw new BusinessError("Cannot edit comments on an archived ticket.");
3782
3894
  const user = this.session.user;
3783
3895
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(input, existingNote, supportTicket);
3784
- else if (user.user_type === "consumer") return await this.processConsumerRequest(input, existingNote, supportTicket);
3785
- else throw new Error("Invalid user type");
3896
+ else if (user.user_type === "consumer" || user.user_type === "lead") return await this.processConsumerRequest(input, existingNote, supportTicket);
3897
+ else throw new BusinessError("Invalid user type");
3786
3898
  }
3787
3899
  async processStaffRequest(input, existingNote, _supportTicket) {
3788
- if (existingNote.created_by !== this.session.user.userId) throw new Error("You can only update notes that you created");
3900
+ if (existingNote.created_by !== this.session.user.userId) throw new BusinessError("You can only update notes that you created");
3789
3901
  return input;
3790
3902
  }
3791
3903
  async processConsumerRequest(input, existingNote, supportTicket) {
3792
3904
  const user = this.session.user;
3793
- if (supportTicket.created_by !== user.userId) throw new Error("You do not have access to this support ticket");
3794
- if (existingNote.created_by !== user.userId) throw new Error("You can only update notes you created");
3795
- if (existingNote.is_internal) throw new Error("Consumers cannot update internal notes");
3796
- if (input.is_internal !== void 0 && input.is_internal !== existingNote.is_internal) throw new Error("Consumers cannot change note visibility");
3905
+ if (supportTicket.approval_status === "INTERNAL") throw new BusinessError("You do not have access to this support ticket");
3906
+ if (existingNote.created_by !== user.userId) throw new BusinessError("You can only update notes you created");
3907
+ if (existingNote.is_internal) throw new BusinessError("Consumers cannot update internal notes");
3908
+ if (input.is_internal !== void 0 && input.is_internal !== existingNote.is_internal) throw new BusinessError("Consumers cannot change note visibility");
3797
3909
  return input;
3798
3910
  }
3799
3911
  };
@@ -3949,6 +4061,15 @@ let NoteRepo = class NoteRepo$1 {
3949
4061
  return result[0];
3950
4062
  }
3951
4063
  /**
4064
+ * Get all note IDs for records (e.g. tracker + its followups).
4065
+ * Used to include note activity in tracker activity timeline.
4066
+ */
4067
+ async readAllIdsForRecords(records) {
4068
+ if (records.length === 0) return [];
4069
+ const conditions = [isNull(note_table.deleted_at), or(...records.map((r) => and(eq(note_table.record_id, r.record_id), eq(note_table.record_type, r.record_type))))];
4070
+ return (await this.router.queryLatest((db) => db.select({ id: note_table.id }).from(note_table).where(and(...conditions)))).map((r) => r.id);
4071
+ }
4072
+ /**
3952
4073
  * Get unique tags for notes
3953
4074
  */
3954
4075
  async getUniqueTags() {
@@ -3963,28 +4084,34 @@ let NoteRepo = class NoteRepo$1 {
3963
4084
  NoteRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], NoteRepo);
3964
4085
 
3965
4086
  //#endregion
3966
- //#region src/slices/record_version/record_version_interfaces.ts
3967
- const RECORD_VERSION_TOKENS = {
3968
- IRecordVersionsRepo: Symbol("IRecordVersionsRepo"),
3969
- IReadAllRecordVersionsByRecord: Symbol("IReadAllRecordVersionsByRecord"),
3970
- IReadAllRecordVersionsByRecordPaginated: Symbol("IReadAllRecordVersionsByRecordPaginated"),
3971
- IReadAllRecordVersionsByRecordPaginatedCustomer: Symbol("IReadAllRecordVersionsByRecordPaginatedCustomer"),
3972
- IReadRecordVersion: Symbol("IReadRecordVersion"),
3973
- ICreateRecordVersion: Symbol("ICreateRecordVersion"),
3974
- ICreateRecordVersionsBatch: Symbol("ICreateRecordVersionsBatch"),
3975
- IUpdateRecordVersion: Symbol("IUpdateRecordVersion"),
3976
- IDeleteRecordVersion: Symbol("IDeleteRecordVersion")
4087
+ //#region src/slices/user/user_interfaces.ts
4088
+ const USER_TOKENS = {
4089
+ IUserDisplayLookup: Symbol("IUserDisplayLookup"),
4090
+ IUsersRepo: Symbol("IUsersRepo"),
4091
+ ISignUpUser: Symbol("ISignUpUser"),
4092
+ IReadAllUsers: Symbol("IReadAllUsers"),
4093
+ IReadUser: Symbol("IReadUser"),
4094
+ ICreateUser: Symbol("ICreateUser"),
4095
+ IDeleteUser: Symbol("IDeleteUser"),
4096
+ IReadConsumers: Symbol("IReadConsumers"),
4097
+ IGetAllUsersFeature: "IGetAllUsersFeature",
4098
+ IGetUserFeature: "IGetUserFeature",
4099
+ IUpdateUserFeature: "IUpdateUserFeature",
4100
+ IGetUsersForSelectionFeature: "IGetUsersForSelectionFeature",
4101
+ IGetTriageUsersFeature: "IGetTriageUsersFeature"
3977
4102
  };
3978
4103
 
3979
4104
  //#endregion
3980
4105
  //#region src/slices/note/features/create_note_feat.ts
3981
4106
  let CreateNoteFeat = class CreateNoteFeat$1 {
3982
- constructor(session, noteRepo, businessLogicRouter, create_record_version, supportTicketRepo) {
4107
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup, supportTicketRepo, notificationService) {
3983
4108
  this.session = session;
3984
4109
  this.noteRepo = noteRepo;
3985
4110
  this.businessLogicRouter = businessLogicRouter;
3986
4111
  this.create_record_version = create_record_version;
4112
+ this.userDisplayLookup = userDisplayLookup;
3987
4113
  this.supportTicketRepo = supportTicketRepo;
4114
+ this.notificationService = notificationService;
3988
4115
  }
3989
4116
  async execute(input) {
3990
4117
  const processor = this.businessLogicRouter.getCreateProcessor(input.record_type);
@@ -4014,7 +4141,13 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4014
4141
  auth_role: this.session.user.user_type,
4015
4142
  auth_username: this.session.user.username
4016
4143
  });
4017
- return noteCreated;
4144
+ await this.notifySupportTicketSubscribers(processedInput.record_type, processedInput.record_id, noteCreated.is_internal, noteCreated.body ?? "");
4145
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames([noteCreated.created_by]);
4146
+ return {
4147
+ ...noteCreated,
4148
+ created_by_display_name: displayMap.get(noteCreated.created_by) ?? noteCreated.created_by,
4149
+ updated_by_display_name: displayMap.get(noteCreated.updated_by ?? "") ?? noteCreated.updated_by ?? null
4150
+ };
4018
4151
  }
4019
4152
  /**
4020
4153
  * Bumps parent record's updated_at timestamp when note is created
@@ -4036,6 +4169,21 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4036
4169
  default: break;
4037
4170
  }
4038
4171
  }
4172
+ /**
4173
+ * Notify support ticket subscribers when a note is added (best-effort)
4174
+ */
4175
+ async notifySupportTicketSubscribers(recordType, recordId, isInternal, noteBody) {
4176
+ if (recordType !== RecordConst.SUPPORT_TICKET || !this.supportTicketRepo || !this.notificationService) return;
4177
+ try {
4178
+ const ticket = await this.supportTicketRepo.read(recordId);
4179
+ if (!ticket) return;
4180
+ const eventType = isInternal ? "NEW_INTERNAL_NOTE" : "NEW_CUSTOMER_NOTE";
4181
+ await this.notificationService.notifyFollowers(recordId, ticket, eventType, {
4182
+ noteBody: noteBody || void 0,
4183
+ actorUserId: this.session.user.userId
4184
+ });
4185
+ } catch {}
4186
+ }
4039
4187
  };
4040
4188
  CreateNoteFeat = __decorate([
4041
4189
  injectable(),
@@ -4043,7 +4191,9 @@ CreateNoteFeat = __decorate([
4043
4191
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4044
4192
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4045
4193
  __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4046
- __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))
4194
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup)),
4195
+ __decorateParam(5, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
4196
+ __decorateParam(6, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService))
4047
4197
  ], CreateNoteFeat);
4048
4198
 
4049
4199
  //#endregion
@@ -4087,10 +4237,19 @@ DeleteNoteFeat = __decorate([
4087
4237
 
4088
4238
  //#endregion
4089
4239
  //#region src/slices/note/features/get_notes_feat.ts
4240
+ function collectUserIdsFromNotes(notes) {
4241
+ const ids = [];
4242
+ for (const n of notes) {
4243
+ if (n.created_by) ids.push(n.created_by);
4244
+ if (n.updated_by) ids.push(n.updated_by);
4245
+ }
4246
+ return ids;
4247
+ }
4090
4248
  let GetNotesFeat = class GetNotesFeat$1 {
4091
- constructor(noteRepo, businessLogicRouter) {
4249
+ constructor(noteRepo, businessLogicRouter, userDisplayLookup) {
4092
4250
  this.noteRepo = noteRepo;
4093
4251
  this.businessLogicRouter = businessLogicRouter;
4252
+ this.userDisplayLookup = userDisplayLookup;
4094
4253
  }
4095
4254
  async execute(filters) {
4096
4255
  let processedFilters = filters;
@@ -4101,23 +4260,36 @@ let GetNotesFeat = class GetNotesFeat$1 {
4101
4260
  if (processor) processedFilters = await processor.process(filters);
4102
4261
  }
4103
4262
  }
4104
- return await this.noteRepo.read_all(processedFilters);
4263
+ const result = await this.noteRepo.read_all(processedFilters);
4264
+ const userIds = collectUserIdsFromNotes(result.items);
4265
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4266
+ const enrichedItems = result.items.map((n) => ({
4267
+ ...n,
4268
+ created_by_display_name: n.created_by ? displayMap.get(n.created_by) ?? n.created_by : null,
4269
+ updated_by_display_name: n.updated_by ? displayMap.get(n.updated_by) ?? n.updated_by : null
4270
+ }));
4271
+ return {
4272
+ ...result,
4273
+ items: enrichedItems
4274
+ };
4105
4275
  }
4106
4276
  };
4107
4277
  GetNotesFeat = __decorate([
4108
4278
  injectable(),
4109
4279
  __decorateParam(0, inject(NOTE_TOKENS.INoteRepo)),
4110
- __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter))
4280
+ __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4281
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup))
4111
4282
  ], GetNotesFeat);
4112
4283
 
4113
4284
  //#endregion
4114
4285
  //#region src/slices/note/features/update_note_feat.ts
4115
4286
  let UpdateNoteFeat = class UpdateNoteFeat$1 {
4116
- constructor(session, noteRepo, businessLogicRouter, create_record_version) {
4287
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup) {
4117
4288
  this.session = session;
4118
4289
  this.noteRepo = noteRepo;
4119
4290
  this.businessLogicRouter = businessLogicRouter;
4120
4291
  this.create_record_version = create_record_version;
4292
+ this.userDisplayLookup = userDisplayLookup;
4121
4293
  }
4122
4294
  async execute(input) {
4123
4295
  const existingNote = await this.noteRepo.read(input.id);
@@ -4145,7 +4317,13 @@ let UpdateNoteFeat = class UpdateNoteFeat$1 {
4145
4317
  auth_role: this.session.user.user_type,
4146
4318
  auth_username: this.session.user.username
4147
4319
  });
4148
- return noteUpdated;
4320
+ const userIds = [noteUpdated.created_by, noteUpdated.updated_by].filter(Boolean);
4321
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4322
+ return {
4323
+ ...noteUpdated,
4324
+ created_by_display_name: noteUpdated.created_by ? displayMap.get(noteUpdated.created_by) ?? noteUpdated.created_by : null,
4325
+ updated_by_display_name: noteUpdated.updated_by ? displayMap.get(noteUpdated.updated_by) ?? noteUpdated.updated_by : null
4326
+ };
4149
4327
  }
4150
4328
  };
4151
4329
  UpdateNoteFeat = __decorate([
@@ -4153,7 +4331,8 @@ UpdateNoteFeat = __decorate([
4153
4331
  __decorateParam(0, injectSession()),
4154
4332
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4155
4333
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4156
- __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
4334
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4335
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
4157
4336
  ], UpdateNoteFeat);
4158
4337
 
4159
4338
  //#endregion
@@ -4200,22 +4379,6 @@ let PasswordResetTokenVerifier = class PasswordResetTokenVerifier$1 {
4200
4379
  };
4201
4380
  PasswordResetTokenVerifier = __decorate([injectable()], PasswordResetTokenVerifier);
4202
4381
 
4203
- //#endregion
4204
- //#region src/slices/user/user_interfaces.ts
4205
- const USER_TOKENS = {
4206
- IUsersRepo: Symbol("IUsersRepo"),
4207
- ISignUpUser: Symbol("ISignUpUser"),
4208
- IReadAllUsers: Symbol("IReadAllUsers"),
4209
- IReadUser: Symbol("IReadUser"),
4210
- ICreateUser: Symbol("ICreateUser"),
4211
- IDeleteUser: Symbol("IDeleteUser"),
4212
- IReadConsumers: Symbol("IReadConsumers"),
4213
- IGetAllUsersFeature: "IGetAllUsersFeature",
4214
- IGetUserFeature: "IGetUserFeature",
4215
- IUpdateUserFeature: "IUpdateUserFeature",
4216
- IGetUsersForSelectionFeature: "IGetUsersForSelectionFeature"
4217
- };
4218
-
4219
4382
  //#endregion
4220
4383
  //#region src/slices/password_reset/features/change_password.ts
4221
4384
  let ChangeUserPassword = class ChangeUserPassword$1 {
@@ -4360,6 +4523,51 @@ function registerPasswordResetContainer() {
4360
4523
  container.registerSingleton(PASSWORD_RESET_TOKENS.IChangeUserPassword, ChangeUserPassword);
4361
4524
  }
4362
4525
 
4526
+ //#endregion
4527
+ //#region src/slices/record_subscriber/db/record_subscriber_repo.ts
4528
+ let RecordSubscriberRepo = class RecordSubscriberRepo$1 {
4529
+ constructor(router) {
4530
+ this.router = router;
4531
+ }
4532
+ async create(entity) {
4533
+ const id = await this.router.generateId(RecordConst.RECORD_SUBSCRIBER);
4534
+ const [result] = await this.router.queryLatest((db) => db.insert(record_subscriber_table).values({
4535
+ id,
4536
+ ...entity
4537
+ }).returning());
4538
+ return result;
4539
+ }
4540
+ async ensureSubscriber(entity) {
4541
+ const existing = await this.readByRecordAndUser(entity.record_type, entity.record_id, entity.user_id);
4542
+ if (existing) return existing;
4543
+ return this.create(entity);
4544
+ }
4545
+ async readByRecordId(recordType, recordId) {
4546
+ return this.router.queryAll((db) => db.select().from(record_subscriber_table).where(and(eq(record_subscriber_table.record_type, recordType), eq(record_subscriber_table.record_id, recordId))));
4547
+ }
4548
+ async readByRecordAndUser(recordType, recordId, userId) {
4549
+ return (await this.router.queryAll((db) => db.select().from(record_subscriber_table).where(and(eq(record_subscriber_table.record_type, recordType), eq(record_subscriber_table.record_id, recordId), eq(record_subscriber_table.user_id, userId))).limit(1)))[0];
4550
+ }
4551
+ async delete(id) {
4552
+ return (await this.router.queryLatest((db) => db.delete(record_subscriber_table).where(eq(record_subscriber_table.id, id)).returning())).length > 0;
4553
+ }
4554
+ async updateSubscribedEvents(id, subscribedEvents) {
4555
+ const [result] = await this.router.queryLatest((db) => db.update(record_subscriber_table).set({ subscribed_events: subscribedEvents }).where(eq(record_subscriber_table.id, id)).returning());
4556
+ return result;
4557
+ }
4558
+ };
4559
+ RecordSubscriberRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], RecordSubscriberRepo);
4560
+
4561
+ //#endregion
4562
+ //#region src/slices/record_subscriber/record_subscriber_tokens.ts
4563
+ const RECORD_SUBSCRIBER_TOKENS = { IRecordSubscriberRepo: "IRecordSubscriberRepo" };
4564
+
4565
+ //#endregion
4566
+ //#region src/slices/record_subscriber/record_subscriber_registration.ts
4567
+ function registerRecordSubscriberDependencies() {
4568
+ container.registerSingleton(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo, RecordSubscriberRepo);
4569
+ }
4570
+
4363
4571
  //#endregion
4364
4572
  //#region src/slices/team/team_interfaces.ts
4365
4573
  const TEAM_TOKENS = {
@@ -4407,10 +4615,11 @@ var RecordAccessValidatorRegistry = class {
4407
4615
  }
4408
4616
  };
4409
4617
  let ValidateRecordAccess = class ValidateRecordAccess$1 {
4410
- constructor(session, teamRepo, validatorRegistry) {
4618
+ constructor(session, teamRepo, validatorRegistry, logger$1) {
4411
4619
  this.session = session;
4412
4620
  this.teamRepo = teamRepo;
4413
4621
  this.validatorRegistry = validatorRegistry;
4622
+ this.logger = logger$1;
4414
4623
  }
4415
4624
  async execute(record_id, record_type) {
4416
4625
  const user = this.session.user;
@@ -4423,8 +4632,43 @@ let ValidateRecordAccess = class ValidateRecordAccess$1 {
4423
4632
  case "team":
4424
4633
  await this.validateTeamAccess(record_id, user.user_type);
4425
4634
  break;
4426
- default: if (user.user_type !== "lead" && user.user_type !== "staff" && user.user_type !== "super_admin") throw new Error(`Access denied: Record type '${record_type}' requires lead or staff permissions. Register a custom validator for this record type.`);
4635
+ default: if (user.user_type !== "lead" && user.user_type !== "staff" && user.user_type !== "super_admin") {
4636
+ this.logger.warn("[ValidateRecordAccess] Access denied", {
4637
+ record_id,
4638
+ record_type,
4639
+ user_id: user.userId,
4640
+ user_type: user.user_type,
4641
+ reason: "Record type requires lead/staff/admin; no custom validator registered"
4642
+ });
4643
+ throw new BusinessError("Access denied: You do not have permission to view this record.");
4644
+ }
4645
+ }
4646
+ }
4647
+ async filterAllowedRecordTypes(record_id, record_types) {
4648
+ const allowed = (await Promise.all(record_types.map(async (rt) => {
4649
+ try {
4650
+ await this.execute(record_id, rt);
4651
+ return {
4652
+ rt,
4653
+ allowed: true
4654
+ };
4655
+ } catch {
4656
+ return {
4657
+ rt,
4658
+ allowed: false
4659
+ };
4660
+ }
4661
+ }))).filter((r) => r.allowed).map((r) => r.rt);
4662
+ if (allowed.length === 0) {
4663
+ this.logger.warn("[ValidateRecordAccess] Access denied - no requested record types allowed", {
4664
+ record_id,
4665
+ requested_record_types: record_types,
4666
+ user_id: this.session.user.userId,
4667
+ user_type: this.session.user.user_type
4668
+ });
4669
+ throw new BusinessError("Access denied: You do not have permission to access any of the requested record types.");
4427
4670
  }
4671
+ return allowed;
4428
4672
  }
4429
4673
  /**
4430
4674
  * Validates access to a team record
@@ -4432,16 +4676,24 @@ let ValidateRecordAccess = class ValidateRecordAccess$1 {
4432
4676
  * Consumers cannot access team records
4433
4677
  */
4434
4678
  async validateTeamAccess(record_id, user_type) {
4435
- if (!await this.teamRepo.read(record_id)) throw new Error("Team not found");
4679
+ if (!await this.teamRepo.read(record_id)) {
4680
+ this.logger.warn("[ValidateRecordAccess] Team not found", { record_id });
4681
+ throw new BusinessError("Team not found");
4682
+ }
4436
4683
  if (user_type === "lead" || user_type === "staff" || user_type === "super_admin") return;
4437
- throw new Error("Access denied: Team records require lead or staff permissions");
4684
+ this.logger.warn("[ValidateRecordAccess] Access denied - team requires lead/staff", {
4685
+ record_id,
4686
+ user_type
4687
+ });
4688
+ throw new BusinessError("Access denied: Team records require lead or staff permissions");
4438
4689
  }
4439
4690
  };
4440
4691
  ValidateRecordAccess = __decorate([
4441
4692
  injectable(),
4442
4693
  __decorateParam(0, injectSession()),
4443
4694
  __decorateParam(1, inject(TEAM_TOKENS.ITeamRepository)),
4444
- __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry))
4695
+ __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry)),
4696
+ __decorateParam(3, inject(TOKENS.LOGGER))
4445
4697
  ], ValidateRecordAccess);
4446
4698
 
4447
4699
  //#endregion
@@ -4515,9 +4767,13 @@ let RecordVersionRepo = class RecordVersionRepo$1 {
4515
4767
  /**
4516
4768
  * NEW: Breadcrumb pagination for record versions
4517
4769
  * Use this for new implementations requiring bidirectional navigation
4770
+ * Supports single record_type or multiple record_types (merged results)
4518
4771
  */
4519
- async read_all_record_versions_paginated(record_id, record_type, filters) {
4520
- const baseConditions = [eq(record_version_table.record_type, record_type), eq(record_version_table.record_id, record_id)];
4772
+ async read_all_record_versions_paginated(record_id, record_type_or_types, filters) {
4773
+ const record_types = Array.isArray(record_type_or_types) ? record_type_or_types : [record_type_or_types];
4774
+ const recordTypeCondition = record_types.length === 1 ? eq(record_version_table.record_type, record_types[0]) : inArray(record_version_table.record_type, record_types);
4775
+ const record_ids = filters?.record_ids;
4776
+ const baseConditions = [recordTypeCondition, record_ids && record_ids.length > 0 ? inArray(record_version_table.record_id, record_ids) : eq(record_version_table.record_id, record_id)];
4521
4777
  if (filters?.start_date) baseConditions.push(gte(record_version_table.recorded_at, filters.start_date));
4522
4778
  if (filters?.end_date) baseConditions.push(lte(record_version_table.recorded_at, filters.end_date));
4523
4779
  return PaginationUtils.findAllPaginated({
@@ -4647,19 +4903,49 @@ ReadAllRecordVersionsByRecord = __decorate([
4647
4903
 
4648
4904
  //#endregion
4649
4905
  //#region src/slices/record_version/features/read_all_record_versions_by_record_paginated_customer_feat.ts
4906
+ const USER_ID_FIELDS$1 = new Set([
4907
+ "assigned_to",
4908
+ "created_by",
4909
+ "updated_by",
4910
+ "archived_by",
4911
+ "deleted_by"
4912
+ ]);
4913
+ function collectUserIdsFromRecordVersions$1(items) {
4914
+ const ids = /* @__PURE__ */ new Set();
4915
+ for (const item of items) for (const raw of [item.record, item.old_record]) {
4916
+ if (!raw || typeof raw !== "string") continue;
4917
+ try {
4918
+ const rec = JSON.parse(raw);
4919
+ for (const [key, val] of Object.entries(rec)) if (USER_ID_FIELDS$1.has(key) && typeof val === "string" && val.trim()) ids.add(val);
4920
+ } catch {}
4921
+ }
4922
+ return [...ids];
4923
+ }
4650
4924
  let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersionsByRecordPaginatedCustomer$1 {
4651
- constructor(record_version_repo) {
4925
+ constructor(record_version_repo, userDisplayLookup) {
4652
4926
  this.record_version_repo = record_version_repo;
4927
+ this.userDisplayLookup = userDisplayLookup;
4653
4928
  }
4654
4929
  async execute(record_id, record_type, filters) {
4655
- const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_type, filters);
4656
- records.items = records.items.map((record) => {
4930
+ const record_types = filters?.record_types ?? [record_type];
4931
+ const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_types, filters);
4932
+ const mappedRecords = records.items.map((record) => {
4933
+ const recordType = record.record_type;
4657
4934
  return {
4658
4935
  ...record,
4659
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4660
- old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), record_type)
4936
+ record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),
4937
+ old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), recordType)
4661
4938
  };
4662
4939
  });
4940
+ records.items = mappedRecords;
4941
+ const userIds = collectUserIdsFromRecordVersions$1(mappedRecords);
4942
+ if (userIds.length > 0) {
4943
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4944
+ return {
4945
+ ...records,
4946
+ user_display_map: Object.fromEntries(displayMap)
4947
+ };
4948
+ }
4663
4949
  return records;
4664
4950
  }
4665
4951
  /**
@@ -4669,7 +4955,9 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4669
4955
  filterFieldsByRecordType(recordData, recordType) {
4670
4956
  if (!recordData) return recordData;
4671
4957
  switch (recordType) {
4672
- case "support_ticket": return this.filterSupportTicketFields(recordData);
4958
+ case RecordConst$1.SUPPORT_TICKET: return this.filterSupportTicketFields(recordData);
4959
+ case RecordConst$1.SUPPORT_TICKET_ACTIVITY: return recordData;
4960
+ case RecordConst$1.TRACKER_ACTIVITY: return recordData;
4673
4961
  default: return recordData;
4674
4962
  }
4675
4963
  }
@@ -4704,26 +4992,60 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4704
4992
  }
4705
4993
  }
4706
4994
  };
4707
- ReadAllRecordVersionsByRecordPaginatedCustomer = __decorate([injectable(), __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo))], ReadAllRecordVersionsByRecordPaginatedCustomer);
4995
+ ReadAllRecordVersionsByRecordPaginatedCustomer = __decorate([
4996
+ injectable(),
4997
+ __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)),
4998
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
4999
+ ], ReadAllRecordVersionsByRecordPaginatedCustomer);
4708
5000
 
4709
5001
  //#endregion
4710
5002
  //#region src/slices/record_version/features/read_all_record_versions_by_record_paginated_feat.ts
5003
+ const USER_ID_FIELDS = new Set([
5004
+ "assigned_to",
5005
+ "created_by",
5006
+ "updated_by",
5007
+ "archived_by",
5008
+ "deleted_by"
5009
+ ]);
5010
+ function collectUserIdsFromRecordVersions(items) {
5011
+ const ids = /* @__PURE__ */ new Set();
5012
+ for (const item of items) for (const raw of [item.record, item.old_record]) {
5013
+ if (!raw || typeof raw !== "string") continue;
5014
+ try {
5015
+ const rec = JSON.parse(raw);
5016
+ for (const [key, val] of Object.entries(rec)) if (USER_ID_FIELDS.has(key) && typeof val === "string" && val.trim()) ids.add(val);
5017
+ } catch {}
5018
+ }
5019
+ return [...ids];
5020
+ }
4711
5021
  let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecordPaginated$1 {
4712
- constructor(record_version_repo, validateRecordAccess, session) {
5022
+ constructor(record_version_repo, validateRecordAccess, userDisplayLookup, session) {
4713
5023
  this.record_version_repo = record_version_repo;
4714
5024
  this.validateRecordAccess = validateRecordAccess;
5025
+ this.userDisplayLookup = userDisplayLookup;
4715
5026
  this.session = session;
4716
5027
  }
4717
5028
  async execute(record_id, record_type, filters) {
4718
- await this.validateRecordAccess.execute(record_id, record_type);
4719
- const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_type, filters);
4720
- records.items = records.items.map((record) => {
5029
+ const requested_types = filters?.record_types ?? [record_type];
5030
+ const allowed_record_types = await this.validateRecordAccess.filterAllowedRecordTypes(record_id, requested_types);
5031
+ const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, allowed_record_types, filters);
5032
+ const mappedRecords = records.items.map((record) => {
5033
+ const recordType = record.record_type;
4721
5034
  return {
4722
5035
  ...record,
4723
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4724
- old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), record_type)
5036
+ record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),
5037
+ old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), recordType)
4725
5038
  };
4726
5039
  });
5040
+ records.items = mappedRecords;
5041
+ const userIds = collectUserIdsFromRecordVersions(mappedRecords);
5042
+ if (userIds.length > 0) {
5043
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5044
+ return {
5045
+ ...records,
5046
+ user_display_map: Object.fromEntries(displayMap)
5047
+ };
5048
+ }
4727
5049
  return records;
4728
5050
  }
4729
5051
  /**
@@ -4734,7 +5056,9 @@ let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecord
4734
5056
  if (!recordData) return recordData;
4735
5057
  if (this.session.user.user_type === "staff" || this.session.user.user_type === "super_admin") return recordData;
4736
5058
  switch (recordType) {
4737
- case "support_ticket": return this.filterSupportTicketFields(recordData);
5059
+ case RecordConst$1.SUPPORT_TICKET: return this.filterSupportTicketFields(recordData);
5060
+ case RecordConst$1.SUPPORT_TICKET_ACTIVITY: return recordData;
5061
+ case RecordConst$1.TRACKER_ACTIVITY: return recordData;
4738
5062
  default: return recordData;
4739
5063
  }
4740
5064
  }
@@ -4773,7 +5097,8 @@ ReadAllRecordVersionsByRecordPaginated = __decorate([
4773
5097
  injectable(),
4774
5098
  __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)),
4775
5099
  __decorateParam(1, inject(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess)),
4776
- __decorateParam(2, injectSession())
5100
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
5101
+ __decorateParam(3, injectSession())
4777
5102
  ], ReadAllRecordVersionsByRecordPaginated);
4778
5103
 
4779
5104
  //#endregion
@@ -4808,9 +5133,492 @@ function registerRecordVersionContainer() {
4808
5133
  container.registerSingleton(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess, ValidateRecordAccess);
4809
5134
  }
4810
5135
 
5136
+ //#endregion
5137
+ //#region src/slices/saved_filter/saved_filter_interfaces.ts
5138
+ const SAVED_FILTER_TOKENS = {
5139
+ ISavedFilterRepo: Symbol("ISavedFilterRepo"),
5140
+ IListSavedFilters: Symbol("IListSavedFilters"),
5141
+ IListAllSavedFilters: Symbol("IListAllSavedFilters"),
5142
+ ICreateSavedFilter: Symbol("ICreateSavedFilter"),
5143
+ IUpdateSavedFilter: Symbol("IUpdateSavedFilter"),
5144
+ IDeleteSavedFilter: Symbol("IDeleteSavedFilter")
5145
+ };
5146
+
5147
+ //#endregion
5148
+ //#region src/slices/saved_filter/db/saved_filter_mapper.ts
5149
+ function mapSavedFilterEntityToDto(entity) {
5150
+ let filters;
5151
+ try {
5152
+ filters = JSON.parse(entity.filters);
5153
+ } catch {
5154
+ filters = {};
5155
+ }
5156
+ return {
5157
+ id: entity.id,
5158
+ user_id: entity.user_id,
5159
+ name: entity.name,
5160
+ context: entity.context,
5161
+ route_path: entity.route_path,
5162
+ filters,
5163
+ sort_by: entity.sort_by ?? void 0,
5164
+ sort_direction: entity.sort_direction ?? void 0,
5165
+ created_at: entity.created_at,
5166
+ updated_at: entity.updated_at
5167
+ };
5168
+ }
5169
+
5170
+ //#endregion
5171
+ //#region src/slices/saved_filter/features/create_saved_filter_feat.ts
5172
+ let CreateSavedFilterFeature = class CreateSavedFilterFeature$1 {
5173
+ constructor(session, repo) {
5174
+ this.session = session;
5175
+ this.repo = repo;
5176
+ }
5177
+ async execute(input) {
5178
+ const userId = this.session.user.userId;
5179
+ if ((await this.repo.listByUserAndContext(userId, input.context)).length >= MAX_PRESETS_PER_CONTEXT) throw new BusinessError(`You can have at most ${MAX_PRESETS_PER_CONTEXT} presets per page. Delete one to save a new preset.`);
5180
+ return mapSavedFilterEntityToDto(await this.repo.create({
5181
+ user_id: userId,
5182
+ name: input.name,
5183
+ context: input.context,
5184
+ route_path: input.route_path,
5185
+ filters: JSON.stringify(input.filters),
5186
+ sort_by: input.sort_by ?? null,
5187
+ sort_direction: input.sort_direction ?? null
5188
+ }));
5189
+ }
5190
+ };
5191
+ CreateSavedFilterFeature = __decorate([
5192
+ injectable(),
5193
+ __decorateParam(0, injectSession()),
5194
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5195
+ ], CreateSavedFilterFeature);
5196
+
5197
+ //#endregion
5198
+ //#region src/slices/saved_filter/user_pinned_preset_interfaces.ts
5199
+ const USER_PINNED_PRESET_TOKENS = {
5200
+ IUserPinnedPresetRepo: Symbol("IUserPinnedPresetRepo"),
5201
+ IListPinnedPresets: Symbol("IListPinnedPresets"),
5202
+ IAddPinnedPreset: Symbol("IAddPinnedPreset"),
5203
+ IRemovePinnedPreset: Symbol("IRemovePinnedPreset"),
5204
+ IReorderPinnedPresets: Symbol("IRearrangePinnedPresets")
5205
+ };
5206
+
5207
+ //#endregion
5208
+ //#region src/slices/saved_filter/features/delete_saved_filter_feat.ts
5209
+ let DeleteSavedFilterFeature = class DeleteSavedFilterFeature$1 {
5210
+ constructor(session, repo, pinnedRepo) {
5211
+ this.session = session;
5212
+ this.repo = repo;
5213
+ this.pinnedRepo = pinnedRepo;
5214
+ }
5215
+ async execute(id) {
5216
+ const existing = await this.repo.read(id);
5217
+ if (!existing) return false;
5218
+ if (existing.user_id !== this.session.user.userId) throw new BusinessError("You can only delete your own saved filters");
5219
+ await this.pinnedRepo.deleteByPresetId(id);
5220
+ return await this.repo.delete(id);
5221
+ }
5222
+ };
5223
+ DeleteSavedFilterFeature = __decorate([
5224
+ injectable(),
5225
+ __decorateParam(0, injectSession()),
5226
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)),
5227
+ __decorateParam(2, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5228
+ ], DeleteSavedFilterFeature);
5229
+
5230
+ //#endregion
5231
+ //#region src/slices/saved_filter/features/list_saved_filters_feat.ts
5232
+ let ListSavedFiltersFeature = class ListSavedFiltersFeature$1 {
5233
+ constructor(session, repo) {
5234
+ this.session = session;
5235
+ this.repo = repo;
5236
+ }
5237
+ async execute(context) {
5238
+ const userId = this.session.user.userId;
5239
+ return (await this.repo.listByUserAndContext(userId, context)).map(mapSavedFilterEntityToDto);
5240
+ }
5241
+ };
5242
+ ListSavedFiltersFeature = __decorate([
5243
+ injectable(),
5244
+ __decorateParam(0, injectSession()),
5245
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5246
+ ], ListSavedFiltersFeature);
5247
+
5248
+ //#endregion
5249
+ //#region src/slices/saved_filter/features/update_saved_filter_feat.ts
5250
+ let UpdateSavedFilterFeature = class UpdateSavedFilterFeature$1 {
5251
+ constructor(session, repo) {
5252
+ this.session = session;
5253
+ this.repo = repo;
5254
+ }
5255
+ async execute(input) {
5256
+ const existing = await this.repo.read(input.id);
5257
+ if (!existing) return null;
5258
+ if (existing.user_id !== this.session.user.userId) throw new BusinessError("You can only update your own saved filters");
5259
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5260
+ return mapSavedFilterEntityToDto(await this.repo.update({
5261
+ id: input.id,
5262
+ ...input.name !== void 0 && { name: input.name },
5263
+ ...input.route_path !== void 0 && { route_path: input.route_path },
5264
+ ...input.filters !== void 0 && { filters: JSON.stringify(input.filters) },
5265
+ ...input.sort_by !== void 0 && { sort_by: input.sort_by },
5266
+ ...input.sort_direction !== void 0 && { sort_direction: input.sort_direction },
5267
+ updated_at: now
5268
+ }));
5269
+ }
5270
+ };
5271
+ UpdateSavedFilterFeature = __decorate([
5272
+ injectable(),
5273
+ __decorateParam(0, injectSession()),
5274
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5275
+ ], UpdateSavedFilterFeature);
5276
+
5277
+ //#endregion
5278
+ //#region src/slices/saved_filter/features/add_pinned_preset_feat.ts
5279
+ let AddPinnedPresetFeature = class AddPinnedPresetFeature$1 {
5280
+ constructor(session, pinnedRepo, savedFilterRepo) {
5281
+ this.session = session;
5282
+ this.pinnedRepo = pinnedRepo;
5283
+ this.savedFilterRepo = savedFilterRepo;
5284
+ }
5285
+ async execute(presetId) {
5286
+ const userId = this.session.user.userId;
5287
+ const preset = await this.savedFilterRepo.read(presetId);
5288
+ if (!preset) return null;
5289
+ if (preset.user_id !== userId) throw new BusinessError("You can only pin your own presets");
5290
+ if (await this.pinnedRepo.findByUserAndPreset(userId, presetId)) return mapSavedFilterEntityToDto(preset);
5291
+ const pins = await this.pinnedRepo.listByUser(userId);
5292
+ if (pins.length >= MAX_PINNED_PRESETS) throw new BusinessError(`You can pin at most ${MAX_PINNED_PRESETS} presets. Unpin one to add another.`);
5293
+ const position = pins.length;
5294
+ await this.pinnedRepo.create(userId, presetId, position);
5295
+ return mapSavedFilterEntityToDto(preset);
5296
+ }
5297
+ };
5298
+ AddPinnedPresetFeature = __decorate([
5299
+ injectable(),
5300
+ __decorateParam(0, injectSession()),
5301
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)),
5302
+ __decorateParam(2, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5303
+ ], AddPinnedPresetFeature);
5304
+
5305
+ //#endregion
5306
+ //#region src/slices/saved_filter/features/list_all_saved_filters_feat.ts
5307
+ let ListAllSavedFiltersFeature = class ListAllSavedFiltersFeature$1 {
5308
+ constructor(session, repo) {
5309
+ this.session = session;
5310
+ this.repo = repo;
5311
+ }
5312
+ async execute() {
5313
+ const userId = this.session.user.userId;
5314
+ return (await this.repo.listByUser(userId)).map(mapSavedFilterEntityToDto);
5315
+ }
5316
+ };
5317
+ ListAllSavedFiltersFeature = __decorate([
5318
+ injectable(),
5319
+ __decorateParam(0, injectSession()),
5320
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5321
+ ], ListAllSavedFiltersFeature);
5322
+
5323
+ //#endregion
5324
+ //#region src/slices/saved_filter/features/list_pinned_presets_feat.ts
5325
+ let ListPinnedPresetsFeature = class ListPinnedPresetsFeature$1 {
5326
+ constructor(session, pinnedRepo, savedFilterRepo) {
5327
+ this.session = session;
5328
+ this.pinnedRepo = pinnedRepo;
5329
+ this.savedFilterRepo = savedFilterRepo;
5330
+ }
5331
+ async execute() {
5332
+ const userId = this.session.user.userId;
5333
+ const pins = await this.pinnedRepo.listByUser(userId);
5334
+ if (pins.length === 0) return [];
5335
+ const presetIds = pins.map((p) => p.preset_id);
5336
+ const presets = await this.savedFilterRepo.readByIds(presetIds);
5337
+ const presetMap = new Map(presets.map((p) => [p.id, p]));
5338
+ const result = [];
5339
+ for (const pin of pins) {
5340
+ const preset = presetMap.get(pin.preset_id);
5341
+ if (preset && preset.user_id === userId) result.push(mapSavedFilterEntityToDto(preset));
5342
+ }
5343
+ return result;
5344
+ }
5345
+ };
5346
+ ListPinnedPresetsFeature = __decorate([
5347
+ injectable(),
5348
+ __decorateParam(0, injectSession()),
5349
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)),
5350
+ __decorateParam(2, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5351
+ ], ListPinnedPresetsFeature);
5352
+
5353
+ //#endregion
5354
+ //#region src/slices/saved_filter/features/remove_pinned_preset_feat.ts
5355
+ let RemovePinnedPresetFeature = class RemovePinnedPresetFeature$1 {
5356
+ constructor(session, pinnedRepo) {
5357
+ this.session = session;
5358
+ this.pinnedRepo = pinnedRepo;
5359
+ }
5360
+ async execute(presetId) {
5361
+ const userId = this.session.user.userId;
5362
+ const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);
5363
+ if (!pin) return false;
5364
+ return await this.pinnedRepo.delete(pin.id);
5365
+ }
5366
+ };
5367
+ RemovePinnedPresetFeature = __decorate([
5368
+ injectable(),
5369
+ __decorateParam(0, injectSession()),
5370
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5371
+ ], RemovePinnedPresetFeature);
5372
+
5373
+ //#endregion
5374
+ //#region src/slices/saved_filter/features/reorder_pinned_presets_feat.ts
5375
+ let ReorderPinnedPresetsFeature = class ReorderPinnedPresetsFeature$1 {
5376
+ constructor(session, pinnedRepo) {
5377
+ this.session = session;
5378
+ this.pinnedRepo = pinnedRepo;
5379
+ }
5380
+ async execute(presetIds) {
5381
+ const userId = this.session.user.userId;
5382
+ for (let i = 0; i < presetIds.length; i++) {
5383
+ const presetId = presetIds[i];
5384
+ const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);
5385
+ if (pin) await this.pinnedRepo.updatePosition(pin.id, i);
5386
+ }
5387
+ }
5388
+ };
5389
+ ReorderPinnedPresetsFeature = __decorate([
5390
+ injectable(),
5391
+ __decorateParam(0, injectSession()),
5392
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5393
+ ], ReorderPinnedPresetsFeature);
5394
+
5395
+ //#endregion
5396
+ //#region src/slices/saved_filter/db/saved_filter_repo.ts
5397
+ let SavedFilterRepository = class SavedFilterRepository$1 {
5398
+ constructor(router) {
5399
+ this.router = router;
5400
+ }
5401
+ async create(entity) {
5402
+ const id = await this.router.generateId(RecordConst.SAVED_FILTER);
5403
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5404
+ const [result] = await this.router.queryLatest((db) => db.insert(saved_filter_table).values({
5405
+ id,
5406
+ user_id: entity.user_id,
5407
+ name: entity.name,
5408
+ context: entity.context,
5409
+ route_path: entity.route_path,
5410
+ filters: entity.filters,
5411
+ sort_by: entity.sort_by ?? null,
5412
+ sort_direction: entity.sort_direction ?? null,
5413
+ created_at: now,
5414
+ updated_at: now
5415
+ }).returning());
5416
+ return result;
5417
+ }
5418
+ async read(id) {
5419
+ return (await this.router.queryById(id, (db) => db.select().from(saved_filter_table).where(eq(saved_filter_table.id, id)).limit(1)))[0] ?? null;
5420
+ }
5421
+ async readByIds(ids) {
5422
+ if (ids.length === 0) return [];
5423
+ return this.router.queryByIds(ids, (db, idsForShard) => db.select().from(saved_filter_table).where(inArray(saved_filter_table.id, idsForShard)));
5424
+ }
5425
+ async listByUser(userId) {
5426
+ return await this.router.queryAll((db) => db.select().from(saved_filter_table).where(eq(saved_filter_table.user_id, userId)));
5427
+ }
5428
+ async listByUserAndContext(userId, context) {
5429
+ return await this.router.queryAll((db) => db.select().from(saved_filter_table).where(and(eq(saved_filter_table.user_id, userId), eq(saved_filter_table.context, context))));
5430
+ }
5431
+ async update(entity) {
5432
+ const updated = (await this.router.queryById(entity.id, (db) => db.update(saved_filter_table).set({
5433
+ ...entity.name !== void 0 && { name: entity.name },
5434
+ ...entity.route_path !== void 0 && { route_path: entity.route_path },
5435
+ ...entity.filters !== void 0 && { filters: entity.filters },
5436
+ ...entity.sort_by !== void 0 && { sort_by: entity.sort_by },
5437
+ ...entity.sort_direction !== void 0 && { sort_direction: entity.sort_direction },
5438
+ updated_at: entity.updated_at
5439
+ }).where(eq(saved_filter_table.id, entity.id)).returning()))[0];
5440
+ if (!updated) throw new Error("Saved filter not found or update failed");
5441
+ return updated;
5442
+ }
5443
+ async delete(id) {
5444
+ return (await this.router.queryById(id, (db) => db.delete(saved_filter_table).where(eq(saved_filter_table.id, id)).returning({ id: saved_filter_table.id }))).length > 0;
5445
+ }
5446
+ };
5447
+ SavedFilterRepository = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SavedFilterRepository);
5448
+
5449
+ //#endregion
5450
+ //#region src/slices/saved_filter/db/user_pinned_preset_repo.ts
5451
+ let UserPinnedPresetRepository = class UserPinnedPresetRepository$1 {
5452
+ constructor(router) {
5453
+ this.router = router;
5454
+ }
5455
+ async create(userId, presetId, position) {
5456
+ const id = await this.router.generateId(RecordConst.USER_PINNED_PRESET);
5457
+ const [result] = await this.router.queryLatest((db) => db.insert(user_pinned_preset_table).values({
5458
+ id,
5459
+ user_id: userId,
5460
+ preset_id: presetId,
5461
+ position
5462
+ }).returning());
5463
+ return result;
5464
+ }
5465
+ async listByUser(userId) {
5466
+ return (await this.router.queryAll((db) => db.select().from(user_pinned_preset_table).where(eq(user_pinned_preset_table.user_id, userId)))).sort((a, b) => a.position - b.position);
5467
+ }
5468
+ async findByUserAndPreset(userId, presetId) {
5469
+ return (await this.router.queryAll((db) => db.select().from(user_pinned_preset_table).where(and(eq(user_pinned_preset_table.user_id, userId), eq(user_pinned_preset_table.preset_id, presetId))).limit(1)))[0] ?? null;
5470
+ }
5471
+ async updatePosition(id, position) {
5472
+ await this.router.queryById(id, (db) => db.update(user_pinned_preset_table).set({ position }).where(eq(user_pinned_preset_table.id, id)).returning({ id: user_pinned_preset_table.id }));
5473
+ }
5474
+ async delete(id) {
5475
+ return (await this.router.queryById(id, (db) => db.delete(user_pinned_preset_table).where(eq(user_pinned_preset_table.id, id)).returning({ id: user_pinned_preset_table.id }))).length > 0;
5476
+ }
5477
+ async deleteByPresetId(presetId) {
5478
+ const pins = await this.router.queryAll((db) => db.select().from(user_pinned_preset_table).where(eq(user_pinned_preset_table.preset_id, presetId)));
5479
+ let deleted = 0;
5480
+ for (const pin of pins) if (await this.delete(pin.id)) deleted++;
5481
+ return deleted;
5482
+ }
5483
+ };
5484
+ UserPinnedPresetRepository = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], UserPinnedPresetRepository);
5485
+
5486
+ //#endregion
5487
+ //#region src/slices/saved_filter/saved_filter_container.ts
5488
+ function registerSavedFilterContainer() {
5489
+ container.registerSingleton(SAVED_FILTER_TOKENS.ISavedFilterRepo, SavedFilterRepository);
5490
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo, UserPinnedPresetRepository);
5491
+ container.registerSingleton(SAVED_FILTER_TOKENS.IListSavedFilters, ListSavedFiltersFeature);
5492
+ container.registerSingleton(SAVED_FILTER_TOKENS.IListAllSavedFilters, ListAllSavedFiltersFeature);
5493
+ container.registerSingleton(SAVED_FILTER_TOKENS.ICreateSavedFilter, CreateSavedFilterFeature);
5494
+ container.registerSingleton(SAVED_FILTER_TOKENS.IUpdateSavedFilter, UpdateSavedFilterFeature);
5495
+ container.registerSingleton(SAVED_FILTER_TOKENS.IDeleteSavedFilter, DeleteSavedFilterFeature);
5496
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IListPinnedPresets, ListPinnedPresetsFeature);
5497
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IAddPinnedPreset, AddPinnedPresetFeature);
5498
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset, RemovePinnedPresetFeature);
5499
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets, ReorderPinnedPresetsFeature);
5500
+ }
5501
+
5502
+ //#endregion
5503
+ //#region src/slices/support_staff/db/support_staff_repo.ts
5504
+ let SupportStaffRepo = class SupportStaffRepo$1 {
5505
+ constructor(router) {
5506
+ this.router = router;
5507
+ }
5508
+ async readAll() {
5509
+ return this.router.queryAll((db) => db.select().from(support_staff_table).orderBy(support_staff_table.user_id));
5510
+ }
5511
+ async readSupportStaffMembers() {
5512
+ return this.router.queryAll((db) => db.select({
5513
+ id: support_staff_table.id,
5514
+ user_id: support_staff_table.user_id,
5515
+ email: user_table.email,
5516
+ created_at: support_staff_table.created_at
5517
+ }).from(support_staff_table).innerJoin(user_table, eq(support_staff_table.user_id, user_table.id)).orderBy(support_staff_table.user_id));
5518
+ }
5519
+ async readTriageUsers() {
5520
+ return this.router.queryAll((db) => db.select({
5521
+ id: user_table.id,
5522
+ email: user_table.email
5523
+ }).from(support_staff_table).innerJoin(user_table, eq(support_staff_table.user_id, user_table.id)).orderBy(support_staff_table.user_id));
5524
+ }
5525
+ async add(userId, createdBy) {
5526
+ const id = await this.router.generateId(RecordConst.SUPPORT_STAFF);
5527
+ const entity = {
5528
+ user_id: userId,
5529
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
5530
+ created_by: createdBy
5531
+ };
5532
+ const [result] = await this.router.queryLatest((db) => db.insert(support_staff_table).values({
5533
+ id,
5534
+ ...entity
5535
+ }).returning());
5536
+ return result;
5537
+ }
5538
+ async remove(userId) {
5539
+ return (await this.router.queryLatest((db) => db.delete(support_staff_table).where(eq(support_staff_table.user_id, userId)).returning({ id: support_staff_table.id }))).length > 0;
5540
+ }
5541
+ };
5542
+ SupportStaffRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportStaffRepo);
5543
+
5544
+ //#endregion
5545
+ //#region src/slices/support_staff/support_staff_tokens.ts
5546
+ const SUPPORT_STAFF_TOKENS = {
5547
+ ISupportStaffRepo: Symbol("ISupportStaffRepo"),
5548
+ IListSupportStaffFeature: Symbol("IListSupportStaffFeature"),
5549
+ IAddSupportStaffFeature: Symbol("IAddSupportStaffFeature"),
5550
+ IRemoveSupportStaffFeature: Symbol("IRemoveSupportStaffFeature")
5551
+ };
5552
+
5553
+ //#endregion
5554
+ //#region src/slices/support_staff/features/add_support_staff_feat.ts
5555
+ let AddSupportStaffFeat = class AddSupportStaffFeat$1 {
5556
+ constructor(supportStaffRepo, userRepo, session) {
5557
+ this.supportStaffRepo = supportStaffRepo;
5558
+ this.userRepo = userRepo;
5559
+ this.session = session;
5560
+ }
5561
+ async execute(userId) {
5562
+ const user = await this.userRepo.read_user(userId);
5563
+ if (!user) throw new Error("User not found");
5564
+ if (!["staff", "super_admin"].includes(user.user_type)) throw new BusinessError("Only staff and super_admin users can be added to support staff");
5565
+ if ((await this.supportStaffRepo.readAll()).some((s) => s.user_id === userId)) throw new Error("User is already support staff");
5566
+ const created = await this.supportStaffRepo.add(userId, this.session.user.userId);
5567
+ const member = (await this.supportStaffRepo.readSupportStaffMembers()).find((m) => m.id === created.id);
5568
+ if (!member) throw new Error("Failed to fetch created support staff member");
5569
+ return member;
5570
+ }
5571
+ };
5572
+ AddSupportStaffFeat = __decorate([
5573
+ injectable(),
5574
+ __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
5575
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo)),
5576
+ __decorateParam(2, inject(TOKENS.AUTHENTICATED_SESSION))
5577
+ ], AddSupportStaffFeat);
5578
+
5579
+ //#endregion
5580
+ //#region src/slices/support_staff/features/list_support_staff_feat.ts
5581
+ let ListSupportStaffFeat = class ListSupportStaffFeat$1 {
5582
+ constructor(supportStaffRepo) {
5583
+ this.supportStaffRepo = supportStaffRepo;
5584
+ }
5585
+ async execute() {
5586
+ return this.supportStaffRepo.readSupportStaffMembers();
5587
+ }
5588
+ };
5589
+ ListSupportStaffFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], ListSupportStaffFeat);
5590
+
5591
+ //#endregion
5592
+ //#region src/slices/support_staff/features/remove_support_staff_feat.ts
5593
+ let RemoveSupportStaffFeat = class RemoveSupportStaffFeat$1 {
5594
+ constructor(supportStaffRepo) {
5595
+ this.supportStaffRepo = supportStaffRepo;
5596
+ }
5597
+ async execute(userId) {
5598
+ if (!await this.supportStaffRepo.remove(userId)) throw new Error("Support staff member not found");
5599
+ }
5600
+ };
5601
+ RemoveSupportStaffFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], RemoveSupportStaffFeat);
5602
+
5603
+ //#endregion
5604
+ //#region src/slices/support_staff/support_staff_registration.ts
5605
+ function registerSupportStaffDependencies() {
5606
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.ISupportStaffRepo, SupportStaffRepo);
5607
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IListSupportStaffFeature, ListSupportStaffFeat);
5608
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature, AddSupportStaffFeat);
5609
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature, RemoveSupportStaffFeat);
5610
+ }
5611
+
4811
5612
  //#endregion
4812
5613
  //#region src/slices/support_ticket/db/support_ticket_query_config.ts
4813
5614
  const supportTicketFields = {
5615
+ display_id: {
5616
+ column: support_ticket_table.display_id,
5617
+ type: "number",
5618
+ filterable: false,
5619
+ searchable: true,
5620
+ sortable: true
5621
+ },
4814
5622
  type: {
4815
5623
  column: support_ticket_table.type,
4816
5624
  type: "string",
@@ -4820,9 +5628,9 @@ const supportTicketFields = {
4820
5628
  },
4821
5629
  priority: {
4822
5630
  column: support_ticket_table.priority,
4823
- type: "string",
5631
+ type: "number",
4824
5632
  filterable: true,
4825
- searchable: true,
5633
+ searchable: false,
4826
5634
  sortable: true
4827
5635
  },
4828
5636
  approval_status: {
@@ -4839,18 +5647,11 @@ const supportTicketFields = {
4839
5647
  searchable: true,
4840
5648
  sortable: true
4841
5649
  },
4842
- requester_email: {
4843
- column: support_ticket_table.requester_email,
4844
- type: "string",
4845
- filterable: true,
4846
- searchable: true,
4847
- sortable: false
4848
- },
4849
- requester_name: {
4850
- column: support_ticket_table.requester_name,
5650
+ created_by: {
5651
+ column: support_ticket_table.created_by,
4851
5652
  type: "string",
4852
5653
  filterable: true,
4853
- searchable: true,
5654
+ searchable: false,
4854
5655
  sortable: false
4855
5656
  },
4856
5657
  title: {
@@ -4909,20 +5710,6 @@ const supportTicketFields = {
4909
5710
  searchable: false,
4910
5711
  sortable: false
4911
5712
  },
4912
- requesterName: {
4913
- column: support_ticket_table.requester_name,
4914
- type: "string",
4915
- filterable: false,
4916
- searchable: true,
4917
- sortable: false
4918
- },
4919
- requesterEmail: {
4920
- column: support_ticket_table.requester_email,
4921
- type: "string",
4922
- filterable: false,
4923
- searchable: true,
4924
- sortable: false
4925
- },
4926
5713
  approvalStatus: {
4927
5714
  column: support_ticket_table.approval_status,
4928
5715
  type: "string",
@@ -4993,7 +5780,8 @@ const buildFieldFilters$1 = createFilterBuilder({
4993
5780
  processedSeparately: [
4994
5781
  "archived_at",
4995
5782
  "status",
4996
- "is_locked"
5783
+ "is_locked",
5784
+ "dev_lifecycle"
4997
5785
  ]
4998
5786
  });
4999
5787
  /**
@@ -5007,6 +5795,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5007
5795
  case "CANCELLED": return "REJECTED";
5008
5796
  case "FOLLOWUP": return;
5009
5797
  case "IN_PROGRESS": return;
5798
+ case "VERIFICATION": return;
5010
5799
  case "COMPLETED": return;
5011
5800
  default: throw new Error(`Invalid support ticket status: ${status}`);
5012
5801
  }
@@ -5017,6 +5806,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5017
5806
  case "CANCELLED": return;
5018
5807
  case "FOLLOWUP": return;
5019
5808
  case "IN_PROGRESS": return;
5809
+ case "VERIFICATION": return "VERIFICATION";
5020
5810
  case "COMPLETED": return "DEPLOYED";
5021
5811
  default: throw new Error(`Invalid support ticket status: ${status}`);
5022
5812
  }
@@ -5060,7 +5850,21 @@ function buildSupportTicketQuery(filters) {
5060
5850
  } else if (statusValue === "IN_PROGRESS") {
5061
5851
  conditions.push(eq(support_ticket_table.approval_status, "APPROVED"));
5062
5852
  conditions.push(or(eq(support_ticket_table.dev_lifecycle, "DEVELOPMENT"), eq(support_ticket_table.dev_lifecycle, "CODE_REVIEW"), eq(support_ticket_table.dev_lifecycle, "TESTING"), eq(support_ticket_table.dev_lifecycle, "STAGING"), eq(support_ticket_table.dev_lifecycle, "PO_APPROVAL")));
5063
- } else if (statusValue === "COMPLETED") conditions.push(eq(support_ticket_table.dev_lifecycle, "DEPLOYED"));
5853
+ } else if (statusValue === "VERIFICATION") conditions.push(eq(support_ticket_table.dev_lifecycle, "VERIFICATION"));
5854
+ else if (statusValue === "COMPLETED") conditions.push(eq(support_ticket_table.dev_lifecycle, "DEPLOYED"));
5855
+ }
5856
+ }
5857
+ if (filters?.dev_lifecycle) {
5858
+ const dl = filters.dev_lifecycle;
5859
+ const col = support_ticket_table.dev_lifecycle;
5860
+ const isPending = (v) => v === "PENDING";
5861
+ if (dl.value !== void 0 && isPending(dl.value) && dl.operator === OPERATORS.EQUALS) conditions.push(isNull(col));
5862
+ else if (dl.values?.some(isPending) && dl.operator === OPERATORS.IS_ONE_OF) {
5863
+ const others = dl.values.filter((v) => !isPending(v));
5864
+ conditions.push(others.length ? or(isNull(col), inArray(col, others)) : isNull(col));
5865
+ } else {
5866
+ const cond = applyFilter(col, dl, "string");
5867
+ if (cond) conditions.push(cond);
5064
5868
  }
5065
5869
  }
5066
5870
  if (filters?.is_locked) if (typeof filters.is_locked === "object" ? filters.is_locked.value : filters.is_locked) conditions.push(ne(support_ticket_table.approval_status, "PENDING"));
@@ -5102,7 +5906,7 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5102
5906
  const now = (/* @__PURE__ */ new Date()).toISOString();
5103
5907
  const [result] = await this.router.queryById(support_ticket.id, (db) => db.update(support_ticket_table).set({
5104
5908
  ...support_ticket,
5105
- updated_at: support_ticket.updated_at || now
5909
+ updated_at: now
5106
5910
  }).where(eq(support_ticket_table.id, support_ticket.id)).returning());
5107
5911
  return result;
5108
5912
  }
@@ -5157,6 +5961,26 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5157
5961
  return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);
5158
5962
  }
5159
5963
  /**
5964
+ * Get distinct requestor (created_by) user IDs for active support tickets.
5965
+ * Active = not archived (archived_at IS NULL).
5966
+ * @param excludeInternal - when true, exclude internal tickets (for customer view)
5967
+ * Queries all shards.
5968
+ */
5969
+ async read_distinct_requestors_for_active_tickets(options) {
5970
+ const conditions = [isNull(support_ticket_table.deleted_at), isNull(support_ticket_table.archived_at)];
5971
+ if (options?.excludeInternal) conditions.push(ne(support_ticket_table.approval_status, "INTERNAL"));
5972
+ const rows = await this.router.queryAll((db) => db.selectDistinct({ created_by: support_ticket_table.created_by }).from(support_ticket_table).where(and(...conditions)));
5973
+ return [...new Set(rows.map((r) => r.created_by).filter(Boolean))];
5974
+ }
5975
+ /**
5976
+ * Find support tickets with invalid user IDs (e.g. emails instead of 32-char universal IDs).
5977
+ * Queries all shards.
5978
+ */
5979
+ async findWithInvalidUserIds() {
5980
+ const invalidLengthCondition = or(sql`LENGTH(${support_ticket_table.created_by}) != 32`, sql`LENGTH(${support_ticket_table.updated_by}) != 32`, sql`(${support_ticket_table.assigned_to} IS NOT NULL AND LENGTH(${support_ticket_table.assigned_to}) != 32)`, sql`(${support_ticket_table.archived_by} IS NOT NULL AND LENGTH(${support_ticket_table.archived_by}) != 32)`, sql`(${support_ticket_table.deleted_by} IS NOT NULL AND LENGTH(${support_ticket_table.deleted_by}) != 32)`);
5981
+ return this.router.queryAll((db) => db.select().from(support_ticket_table).where(and(isNull(support_ticket_table.deleted_at), invalidLengthCondition)));
5982
+ }
5983
+ /**
5160
5984
  * Soft delete a support ticket by ID
5161
5985
  */
5162
5986
  async soft_delete(id, deleted_by) {
@@ -5169,7 +5993,237 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5169
5993
  return result[0];
5170
5994
  }
5171
5995
  };
5172
- SupportTicketRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportTicketRepo);
5996
+ SupportTicketRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportTicketRepo);
5997
+
5998
+ //#endregion
5999
+ //#region src/slices/user_profile/user_profile_interfaces.ts
6000
+ const USER_PROFILE_TOKENS = {
6001
+ IUserProfilesRepo: Symbol("IUserProfilesRepo"),
6002
+ IReadUserProfilesByUser: Symbol("IReadUserProfilesByUser"),
6003
+ IReadUserProfile: Symbol("IReadUserProfile"),
6004
+ ICreateUserProfile: Symbol("ICreateUserProfile"),
6005
+ IUpdateUserProfile: Symbol("IUpdateUserProfile")
6006
+ };
6007
+
6008
+ //#endregion
6009
+ //#region src/slices/support_ticket/services/support_ticket_notification_service.ts
6010
+ const STAFF_USER_TYPES = ["staff", "super_admin"];
6011
+ /** Escape HTML for safe inclusion in email body. */
6012
+ function escapeHtmlForEmail(s) {
6013
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\n/g, "<br>");
6014
+ }
6015
+ let SupportTicketNotificationService = class SupportTicketNotificationService$1 {
6016
+ constructor(subscriberRepo, userRepo, userProfileRepo, emailService, env, logger$1) {
6017
+ this.subscriberRepo = subscriberRepo;
6018
+ this.userRepo = userRepo;
6019
+ this.userProfileRepo = userProfileRepo;
6020
+ this.emailService = emailService;
6021
+ this.env = env;
6022
+ this.logger = logger$1;
6023
+ }
6024
+ async notifyAssignee(ticket, eventType, context) {
6025
+ try {
6026
+ const assigneeId = ticket.assigned_to;
6027
+ if (!assigneeId) return;
6028
+ if (context?.actorUserId && assigneeId === context.actorUserId) return;
6029
+ const [email] = await this.getNotificationEmailsForFollowers([assigneeId], context?.actorUserId);
6030
+ if (!email) return;
6031
+ const ticketUrl = `${this.env.WEBSITE_URL}/staff/support/${ticket.id}`;
6032
+ const { subject, bodyText, bodyHtml } = this.buildAssigneeEmailContent(ticket, eventType, ticketUrl, context);
6033
+ await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);
6034
+ this.logger.info(`Sent support ticket ${eventType} notification to assignee`);
6035
+ } catch (error) {
6036
+ this.logger.error("Failed to send support ticket assignee notification", {
6037
+ error,
6038
+ supportTicketId: ticket.id,
6039
+ eventType
6040
+ });
6041
+ }
6042
+ }
6043
+ async notifyFollowers(supportTicketId, ticket, eventType, context) {
6044
+ try {
6045
+ await this.ensureAssigneeIsSubscriber(ticket);
6046
+ const followers = await this.subscriberRepo.readByRecordId(RecordConst$1.SUPPORT_TICKET, supportTicketId);
6047
+ if (followers.length === 0) return;
6048
+ const filteredFollowers = eventType === "NEW_INTERNAL_NOTE" ? await this.filterStaffFollowersOnly(followers) : followers;
6049
+ if (filteredFollowers.length === 0) return;
6050
+ const subscribed = filteredFollowers.filter((f) => {
6051
+ const events = f.subscribed_events;
6052
+ if (!events || events.length === 0) return true;
6053
+ return events.includes(eventType);
6054
+ });
6055
+ if (subscribed.length === 0) return;
6056
+ const assigneeExcluded = eventType === "TICKET_CREATED" || eventType === "TICKET_ASSIGNED" ? subscribed.filter((f) => f.user_id !== ticket.assigned_to) : subscribed;
6057
+ const recipients = await this.getNotificationRecipientsForFollowers(assigneeExcluded.map((f) => f.user_id), context?.actorUserId);
6058
+ if (recipients.length === 0) return;
6059
+ const baseUrl = this.env.WEBSITE_URL;
6060
+ for (const { email, userType } of recipients) {
6061
+ const ticketUrl = STAFF_USER_TYPES.includes(userType) ? `${baseUrl}/staff/support/${ticket.id}` : `${baseUrl}/support/${ticket.id}`;
6062
+ const { subject, bodyText, bodyHtml } = this.buildEmailContent(ticket, eventType, ticketUrl, context);
6063
+ await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);
6064
+ }
6065
+ this.logger.info(`Sent support ticket ${eventType} notification to ${recipients.length} followers`);
6066
+ } catch (error) {
6067
+ this.logger.error("Failed to send support ticket follower notifications", {
6068
+ error,
6069
+ supportTicketId,
6070
+ eventType
6071
+ });
6072
+ }
6073
+ }
6074
+ /**
6075
+ * Ensures the assigned staff member is a subscriber so they receive the same
6076
+ * notifications as other followers (notes, approvals, etc.). Idempotent.
6077
+ */
6078
+ async ensureAssigneeIsSubscriber(ticket) {
6079
+ const assigneeId = ticket.assigned_to;
6080
+ if (!assigneeId) return;
6081
+ try {
6082
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6083
+ await this.subscriberRepo.ensureSubscriber({
6084
+ record_type: RecordConst$1.SUPPORT_TICKET,
6085
+ record_id: ticket.id,
6086
+ user_id: assigneeId,
6087
+ subscribed_events: null,
6088
+ created_at: now,
6089
+ created_by: assigneeId
6090
+ });
6091
+ } catch (error) {
6092
+ this.logger.error("Failed to ensure assignee is subscriber", {
6093
+ error,
6094
+ supportTicketId: ticket.id,
6095
+ assigneeId
6096
+ });
6097
+ }
6098
+ }
6099
+ async filterStaffFollowersOnly(followers) {
6100
+ if (followers.length === 0) return [];
6101
+ const userIds = followers.map((f) => f.user_id);
6102
+ const users = await this.userRepo.read_users_by_ids(userIds);
6103
+ const staffUserIds = new Set(users.filter((u) => STAFF_USER_TYPES.includes(u.user_type)).map((u) => u.id));
6104
+ return followers.filter((f) => staffUserIds.has(f.user_id));
6105
+ }
6106
+ async getNotificationEmailsForFollowers(userIds, excludeUserId) {
6107
+ return (await this.getNotificationRecipientsForFollowers(userIds, excludeUserId)).map((r) => r.email);
6108
+ }
6109
+ async getNotificationRecipientsForFollowers(userIds, excludeUserId) {
6110
+ const filteredIds = excludeUserId ? userIds.filter((id) => id !== excludeUserId) : userIds;
6111
+ if (filteredIds.length === 0) return [];
6112
+ const [users, profiles] = await Promise.all([this.userRepo.read_users_by_ids(filteredIds), this.userProfileRepo.read_user_profiles_by_user_ids(filteredIds)]);
6113
+ const profileByUserId = new Map(profiles.map((p) => [p.user_id, p]));
6114
+ const seen = /* @__PURE__ */ new Set();
6115
+ const recipients = [];
6116
+ for (const user of users) {
6117
+ const email = profileByUserId.get(user.id)?.notification_email ?? user.email;
6118
+ if (!email || seen.has(email)) continue;
6119
+ seen.add(email);
6120
+ recipients.push({
6121
+ email,
6122
+ userType: user.user_type ?? DEFAULT_USER_TYPE
6123
+ });
6124
+ }
6125
+ return recipients;
6126
+ }
6127
+ buildEmailContent(ticket, eventType, ticketUrl, context) {
6128
+ const eventLabel = eventType.replace(/_/g, " ").toLowerCase();
6129
+ const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;
6130
+ const details = [
6131
+ `Ticket #: ${ticket.display_id || ticket.id}`,
6132
+ `Title: ${ticket.title}`,
6133
+ `Priority: ${supportTicketNumberToPriority(ticket.priority)}`
6134
+ ];
6135
+ if (context?.noteBody) details.push(`\nNote: ${context.noteBody}`);
6136
+ if (eventType === "TICKET_CREATED" && ticket.description?.trim()) details.push(`\nDescription:\n${ticket.description}`);
6137
+ return {
6138
+ subject,
6139
+ bodyText: `A support ticket you're following has been updated.\n\n${details.join("\n")}\n\nView ticket: ${ticketUrl}`,
6140
+ bodyHtml: `
6141
+ <h2>Support Ticket Update</h2>
6142
+ <p>A support ticket you're following has been updated (${eventLabel}).</p>
6143
+ <ul>
6144
+ <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
6145
+ <li><strong>Title:</strong> ${ticket.title}</li>
6146
+ <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>
6147
+ </ul>
6148
+ ${eventType === "TICKET_CREATED" && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ""}
6149
+ ${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ""}
6150
+ <p><a href="${ticketUrl}">View Ticket</a></p>
6151
+ `.trim()
6152
+ };
6153
+ }
6154
+ buildAssigneeEmailContent(ticket, eventType, ticketUrl, context) {
6155
+ const eventLabel = eventType.replace(/_/g, " ").toLowerCase();
6156
+ const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;
6157
+ const isAssigned = eventType === "TICKET_ASSIGNED";
6158
+ const intro = isAssigned ? "You have been assigned to a support ticket." : "A new support ticket has been assigned to you.";
6159
+ const details = [
6160
+ `Ticket #: ${ticket.display_id || ticket.id}`,
6161
+ `Title: ${ticket.title}`,
6162
+ `Priority: ${supportTicketNumberToPriority(ticket.priority)}`
6163
+ ];
6164
+ if (context?.noteBody) details.push(`\nNote: ${context.noteBody}`);
6165
+ if (!isAssigned && ticket.description?.trim()) details.push(`\nDescription:\n${ticket.description}`);
6166
+ return {
6167
+ subject,
6168
+ bodyText: `${intro}\n\n${details.join("\n")}\n\nView ticket: ${ticketUrl}`,
6169
+ bodyHtml: `
6170
+ <h2>Support Ticket ${isAssigned ? "Assigned" : "Created"}</h2>
6171
+ <p>${intro}</p>
6172
+ <ul>
6173
+ <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
6174
+ <li><strong>Title:</strong> ${ticket.title}</li>
6175
+ <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>
6176
+ </ul>
6177
+ ${!isAssigned && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ""}
6178
+ ${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ""}
6179
+ <p><a href="${ticketUrl}">View Ticket</a></p>
6180
+ `.trim()
6181
+ };
6182
+ }
6183
+ };
6184
+ SupportTicketNotificationService = __decorate([
6185
+ injectable(),
6186
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6187
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo)),
6188
+ __decorateParam(2, inject(USER_PROFILE_TOKENS.IUserProfilesRepo)),
6189
+ __decorateParam(3, inject(TOKENS.IEmailService)),
6190
+ __decorateParam(4, inject(TOKENS.ENV)),
6191
+ __decorateParam(5, inject(TOKENS.LOGGER))
6192
+ ], SupportTicketNotificationService);
6193
+
6194
+ //#endregion
6195
+ //#region src/slices/support_ticket/services/support_ticket_triage_service.ts
6196
+ let SupportTicketTriageService = class SupportTicketTriageService$1 {
6197
+ constructor(supportStaffRepo, appSettingsRepo, logger$1) {
6198
+ this.supportStaffRepo = supportStaffRepo;
6199
+ this.appSettingsRepo = appSettingsRepo;
6200
+ this.logger = logger$1;
6201
+ }
6202
+ async getNextAssignee(updatedBy) {
6203
+ try {
6204
+ const triageUsers = await this.supportStaffRepo.readTriageUsers();
6205
+ if (triageUsers.length === 0) {
6206
+ this.logger.debug("No triage users found; skipping assignee");
6207
+ return null;
6208
+ }
6209
+ const ids = triageUsers.map((u) => u.id);
6210
+ const lastId = (await this.appSettingsRepo.readSetting(AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE))?.value;
6211
+ const lastIndex = lastId ? ids.indexOf(lastId) : -1;
6212
+ const nextUserId = ids[lastIndex < ids.length - 1 ? lastIndex + 1 : 0];
6213
+ await this.appSettingsRepo.updateSetting(AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE, nextUserId, updatedBy);
6214
+ return nextUserId;
6215
+ } catch (error) {
6216
+ this.logger.error("Failed to get next triage assignee", { error });
6217
+ return null;
6218
+ }
6219
+ }
6220
+ };
6221
+ SupportTicketTriageService = __decorate([
6222
+ injectable(),
6223
+ __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
6224
+ __decorateParam(1, inject(TOKENS.IAppSettingsRepo)),
6225
+ __decorateParam(2, inject(TOKENS.LOGGER))
6226
+ ], SupportTicketTriageService);
5173
6227
 
5174
6228
  //#endregion
5175
6229
  //#region src/slices/support_ticket/mappers/support_ticket_mapper.ts
@@ -5188,6 +6242,7 @@ function computeStatus(approvalStatus, devLifecycle) {
5188
6242
  case "TESTING":
5189
6243
  case "STAGING":
5190
6244
  case "PO_APPROVAL": return "IN_PROGRESS";
6245
+ case "VERIFICATION": return "VERIFICATION";
5191
6246
  case "DEPLOYED": return "COMPLETED";
5192
6247
  default: return "FOLLOWUP";
5193
6248
  }
@@ -5221,11 +6276,10 @@ function toCustomerSupportTicketReadDto(entity) {
5221
6276
  title: entity.title,
5222
6277
  description: entity.description,
5223
6278
  type: entity.type,
5224
- priority: entity.priority,
6279
+ priority: supportTicketNumberToPriority(entity.priority),
5225
6280
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5226
6281
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5227
- requester_name: entity.requester_name,
5228
- requester_email: entity.requester_email,
6282
+ created_by_display_name: null,
5229
6283
  credit_value: entity.credit_value,
5230
6284
  start_at: startAt,
5231
6285
  target_at: targetAt,
@@ -5234,7 +6288,8 @@ function toCustomerSupportTicketReadDto(entity) {
5234
6288
  created_at: createdAt,
5235
6289
  created_by: entity.created_by,
5236
6290
  updated_at: updatedAt,
5237
- updated_by: entity.updated_by
6291
+ updated_by: entity.updated_by,
6292
+ archived_at: entity.archived_at ?? null
5238
6293
  };
5239
6294
  }
5240
6295
  /**
@@ -5257,13 +6312,13 @@ function toStaffSupportTicketReadDto(entity) {
5257
6312
  title: entity.title,
5258
6313
  description: entity.description,
5259
6314
  type: entity.type,
5260
- priority: entity.priority,
6315
+ priority: supportTicketNumberToPriority(entity.priority),
5261
6316
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5262
6317
  approval_status: entity.approval_status,
5263
6318
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5264
6319
  can_delete: canDelete,
5265
- requester_name: entity.requester_name,
5266
- requester_email: entity.requester_email,
6320
+ created_by_display_name: null,
6321
+ assigned_to: entity.assigned_to ?? null,
5267
6322
  dev_lifecycle: entity.dev_lifecycle || "PENDING",
5268
6323
  credit_value: entity.credit_value,
5269
6324
  delivered_value: entity.delivered_value,
@@ -5274,7 +6329,9 @@ function toStaffSupportTicketReadDto(entity) {
5274
6329
  created_at: createdAt,
5275
6330
  created_by: entity.created_by,
5276
6331
  updated_at: updatedAt,
5277
- updated_by: entity.updated_by
6332
+ updated_by: entity.updated_by,
6333
+ archived_at: entity.archived_at ?? null,
6334
+ archived_by: entity.archived_by ?? null
5278
6335
  };
5279
6336
  }
5280
6337
  function dateIsValid(date) {
@@ -5286,13 +6343,14 @@ function dateIsValid(date) {
5286
6343
  //#endregion
5287
6344
  //#region src/slices/support_ticket/features/customer/create_support_ticket_feat.ts
5288
6345
  let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5289
- constructor(session, support_ticketRepo, create_record_version, appSettingsRepo, emailService, env, logger$1) {
6346
+ constructor(session, support_ticketRepo, subscriberRepo, notificationService, triageService, create_record_version, appSettingsRepo, logger$1) {
5290
6347
  this.session = session;
5291
6348
  this.support_ticketRepo = support_ticketRepo;
6349
+ this.subscriberRepo = subscriberRepo;
6350
+ this.notificationService = notificationService;
6351
+ this.triageService = triageService;
5292
6352
  this.create_record_version = create_record_version;
5293
6353
  this.appSettingsRepo = appSettingsRepo;
5294
- this.emailService = emailService;
5295
- this.env = env;
5296
6354
  this.logger = logger$1;
5297
6355
  }
5298
6356
  /**
@@ -5303,9 +6361,8 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5303
6361
  const userId = this.session.user.userId;
5304
6362
  const frEntity = {
5305
6363
  ...input,
6364
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5306
6365
  description: input.description ?? "",
5307
- requester_email: this.session.user.email,
5308
- requester_name: this.session.user.username,
5309
6366
  created_at: now,
5310
6367
  created_by: userId,
5311
6368
  updated_at: now,
@@ -5313,7 +6370,11 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5313
6370
  };
5314
6371
  const support_ticketCreated = await this.support_ticketRepo.create(frEntity);
5315
6372
  support_ticketCreated.display_id = await this.appSettingsRepo.getNextSequentialId(RecordConst.SUPPORT_TICKET, this.session.user.userId);
5316
- await this.support_ticketRepo.update(support_ticketCreated);
6373
+ const assigneeId = await this.triageService.getNextAssignee(this.session.user);
6374
+ if (assigneeId) {
6375
+ support_ticketCreated.assigned_to = assigneeId;
6376
+ await this.support_ticketRepo.update(support_ticketCreated);
6377
+ } else await this.support_ticketRepo.update(support_ticketCreated);
5317
6378
  await this.create_record_version.execute({
5318
6379
  record_id: support_ticketCreated.id,
5319
6380
  operation: OperationConst.INSERT,
@@ -5324,58 +6385,30 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5324
6385
  auth_role: this.session.user.user_type,
5325
6386
  auth_username: this.session.user.username
5326
6387
  });
5327
- await this.sendNotificationEmails(support_ticketCreated);
5328
- return toCustomerSupportTicketReadDto(support_ticketCreated);
6388
+ await this.addRequesterAsFollower(support_ticketCreated);
6389
+ await this.notificationService.notifyAssignee(support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
6390
+ await this.notificationService.notifyFollowers(support_ticketCreated.id, support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
6391
+ return {
6392
+ ...toCustomerSupportTicketReadDto(support_ticketCreated),
6393
+ created_by_display_name: this.session.user.email
6394
+ };
5329
6395
  }
5330
6396
  /**
5331
- * Send email notifications to staff members
6397
+ * Auto-add requester (created_by) as follower
5332
6398
  */
5333
- async sendNotificationEmails(ticket) {
6399
+ async addRequesterAsFollower(ticket) {
5334
6400
  try {
5335
- const emailsSetting = await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS);
5336
- if (!emailsSetting?.value) {
5337
- this.logger.debug("No notification emails configured for support tickets");
5338
- return;
5339
- }
5340
- const emails = emailsSetting.value;
5341
- if (!Array.isArray(emails) || emails.length === 0) {
5342
- this.logger.debug("No valid notification emails found");
5343
- return;
5344
- }
5345
- const ticketUrl = `${this.env.WEBSITE_URL}/support-tickets/${ticket.id}`;
5346
- const subject = `New Support Ticket Created: ${ticket.title}`;
5347
- const bodyText = `
5348
- A new support ticket has been created:
5349
-
5350
- Ticket #${ticket.display_id || ticket.id}
5351
- Title: ${ticket.title}
5352
- Type: ${ticket.type}
5353
- Priority: ${ticket.priority}
5354
- Requester: ${ticket.requester_name} (${ticket.requester_email})
5355
-
5356
- Description:
5357
- ${ticket.description}
5358
-
5359
- View ticket: ${ticketUrl}
5360
- `.trim();
5361
- const bodyHtml = `
5362
- <h2>New Support Ticket Created</h2>
5363
- <p>A new support ticket has been created:</p>
5364
- <ul>
5365
- <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
5366
- <li><strong>Title:</strong> ${ticket.title}</li>
5367
- <li><strong>Type:</strong> ${ticket.type}</li>
5368
- <li><strong>Priority:</strong> ${ticket.priority}</li>
5369
- <li><strong>Requester:</strong> ${ticket.requester_name} (${ticket.requester_email})</li>
5370
- </ul>
5371
- <p><strong>Description:</strong></p>
5372
- <p>${ticket.description}</p>
5373
- <p><a href="${ticketUrl}">View Ticket</a></p>
5374
- `.trim();
5375
- await this.emailService.sendBulkEmail(emails, subject, bodyText, bodyHtml);
5376
- this.logger.info(`Sent support ticket notification to ${emails.length} recipients`);
6401
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6402
+ await this.subscriberRepo.ensureSubscriber({
6403
+ record_type: RecordConst.SUPPORT_TICKET,
6404
+ record_id: ticket.id,
6405
+ user_id: ticket.created_by,
6406
+ subscribed_events: null,
6407
+ created_at: now,
6408
+ created_by: ticket.created_by
6409
+ });
5377
6410
  } catch (error) {
5378
- this.logger.error("Failed to send support ticket notification emails", { error });
6411
+ this.logger.error("Failed to add requester as follower", { error });
5379
6412
  }
5380
6413
  }
5381
6414
  };
@@ -5383,19 +6416,87 @@ CreateSupportTicketFeat = __decorate([
5383
6416
  injectable(),
5384
6417
  __decorateParam(0, injectSession()),
5385
6418
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5386
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5387
- __decorateParam(3, inject(TOKENS.IAppSettingsRepo)),
5388
- __decorateParam(4, inject(TOKENS.IEmailService)),
5389
- __decorateParam(5, inject(TOKENS.ENV)),
5390
- __decorateParam(6, inject(TOKENS.LOGGER))
6419
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6420
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
6421
+ __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)),
6422
+ __decorateParam(5, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6423
+ __decorateParam(6, inject(TOKENS.IAppSettingsRepo)),
6424
+ __decorateParam(7, inject(TOKENS.LOGGER))
5391
6425
  ], CreateSupportTicketFeat);
5392
6426
 
6427
+ //#endregion
6428
+ //#region src/slices/support_ticket/features/customer/customer_toggle_subscription_feat.ts
6429
+ let CustomerToggleSubscriptionFeat = class CustomerToggleSubscriptionFeat$1 {
6430
+ constructor(session, subscriberRepo, supportTicketRepo) {
6431
+ this.session = session;
6432
+ this.subscriberRepo = subscriberRepo;
6433
+ this.supportTicketRepo = supportTicketRepo;
6434
+ }
6435
+ async execute(supportTicketId) {
6436
+ const ticket = await this.supportTicketRepo.read(supportTicketId);
6437
+ if (!ticket) throw new BusinessError("Support ticket not found");
6438
+ if (ticket.approval_status === "INTERNAL") throw new BusinessError("Support ticket not found");
6439
+ const subscription = await this.subscriberRepo.readByRecordAndUser(RecordConst.SUPPORT_TICKET, supportTicketId, this.session.user.userId);
6440
+ if (subscription) {
6441
+ if (!await this.subscriberRepo.delete(subscription.id)) throw new BusinessError("Failed to unsubscribe");
6442
+ return { subscribed: false };
6443
+ }
6444
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6445
+ const userId = this.session.user.userId;
6446
+ await this.subscriberRepo.ensureSubscriber({
6447
+ record_type: RecordConst.SUPPORT_TICKET,
6448
+ record_id: supportTicketId,
6449
+ user_id: userId,
6450
+ subscribed_events: null,
6451
+ created_at: now,
6452
+ created_by: userId
6453
+ });
6454
+ return { subscribed: true };
6455
+ }
6456
+ };
6457
+ CustomerToggleSubscriptionFeat = __decorate([
6458
+ injectable(),
6459
+ __decorateParam(0, injectSession()),
6460
+ __decorateParam(1, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6461
+ __decorateParam(2, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))
6462
+ ], CustomerToggleSubscriptionFeat);
6463
+
6464
+ //#endregion
6465
+ //#region src/slices/support_ticket/features/customer/enrich_customer_ticket.ts
6466
+ /**
6467
+ * Enriches a CustomerSupportTicketReadDto with requester info from created_by lookup.
6468
+ * Requester name/email are derived from created_by (user display = email).
6469
+ */
6470
+ function enrichCustomerTicket(dto, displayMap) {
6471
+ const createdByDisplay = dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null;
6472
+ return {
6473
+ ...dto,
6474
+ created_by_display_name: createdByDisplay ?? dto.created_by ?? "—"
6475
+ };
6476
+ }
6477
+ function collectUserIdsFromCustomerTicket(ticket) {
6478
+ return [ticket.created_by].filter((id) => Boolean(id));
6479
+ }
6480
+
5393
6481
  //#endregion
5394
6482
  //#region src/slices/support_ticket/features/customer/get_support_ticket_customer_feat.ts
6483
+ function toRecordSubscriberReadDto$2(e) {
6484
+ return {
6485
+ id: e.id,
6486
+ record_type: e.record_type,
6487
+ record_id: e.record_id,
6488
+ user_id: e.user_id,
6489
+ subscribed_events: e.subscribed_events ?? null,
6490
+ created_at: e.created_at,
6491
+ created_by: e.created_by
6492
+ };
6493
+ }
5395
6494
  let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5396
- constructor(session, support_ticketRepo, appSettingsRepo, logger$1) {
6495
+ constructor(session, support_ticketRepo, subscriberRepo, userDisplayLookup, appSettingsRepo, logger$1) {
5397
6496
  this.session = session;
5398
6497
  this.support_ticketRepo = support_ticketRepo;
6498
+ this.subscriberRepo = subscriberRepo;
6499
+ this.userDisplayLookup = userDisplayLookup;
5399
6500
  this.appSettingsRepo = appSettingsRepo;
5400
6501
  this.logger = logger$1;
5401
6502
  }
@@ -5413,22 +6514,31 @@ let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5413
6514
  updated_by: this.session.user.userId
5414
6515
  });
5415
6516
  }
5416
- return toCustomerSupportTicketReadDto(support_ticket);
6517
+ const base = toCustomerSupportTicketReadDto(support_ticket);
6518
+ const enriched = enrichCustomerTicket(base, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromCustomerTicket(base)));
6519
+ const subscription = await this.subscriberRepo.readByRecordAndUser(RecordConst.SUPPORT_TICKET, id, this.session.user.userId);
6520
+ return {
6521
+ ...enriched,
6522
+ my_subscription: subscription ? toRecordSubscriberReadDto$2(subscription) : null
6523
+ };
5417
6524
  }
5418
6525
  };
5419
6526
  GetSupportTicketCustomerFeature = __decorate([
5420
6527
  injectable(),
5421
6528
  __decorateParam(0, injectSession()),
5422
6529
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5423
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
5424
- __decorateParam(3, inject(TOKENS.LOGGER))
6530
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6531
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup)),
6532
+ __decorateParam(4, inject(TOKENS.IAppSettingsRepo)),
6533
+ __decorateParam(5, inject(TOKENS.LOGGER))
5425
6534
  ], GetSupportTicketCustomerFeature);
5426
6535
 
5427
6536
  //#endregion
5428
6537
  //#region src/slices/support_ticket/features/customer/get_support_tickets_customer_feat.ts
5429
6538
  let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1 {
5430
- constructor(support_ticketRepo) {
6539
+ constructor(support_ticketRepo, userDisplayLookup) {
5431
6540
  this.support_ticketRepo = support_ticketRepo;
6541
+ this.userDisplayLookup = userDisplayLookup;
5432
6542
  }
5433
6543
  async execute(filters) {
5434
6544
  const customerFilters = {
@@ -5439,20 +6549,28 @@ let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1
5439
6549
  }
5440
6550
  };
5441
6551
  const result = await this.support_ticketRepo.read_all(customerFilters);
6552
+ const dtos = result.items.map(toCustomerSupportTicketReadDto);
6553
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromCustomerTicket))];
6554
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5442
6555
  return {
5443
- items: result.items.map(toCustomerSupportTicketReadDto),
6556
+ items: dtos.map((dto) => enrichCustomerTicket(dto, displayMap)),
5444
6557
  pageInfo: result.pageInfo
5445
6558
  };
5446
6559
  }
5447
6560
  };
5448
- GetSupportTicketsCustomerFeature = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))], GetSupportTicketsCustomerFeature);
6561
+ GetSupportTicketsCustomerFeature = __decorate([
6562
+ injectable(),
6563
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6564
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
6565
+ ], GetSupportTicketsCustomerFeature);
5449
6566
 
5450
6567
  //#endregion
5451
6568
  //#region src/slices/support_ticket/features/customer/update_support_ticket_customer_feat.ts
5452
6569
  let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5453
- constructor(session, support_ticketRepo, create_record_version) {
6570
+ constructor(session, support_ticketRepo, userDisplayLookup, create_record_version) {
5454
6571
  this.session = session;
5455
6572
  this.support_ticketRepo = support_ticketRepo;
6573
+ this.userDisplayLookup = userDisplayLookup;
5456
6574
  this.create_record_version = create_record_version;
5457
6575
  }
5458
6576
  /**
@@ -5460,8 +6578,9 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5460
6578
  */
5461
6579
  async execute(input) {
5462
6580
  const support_ticket = await this.support_ticketRepo.read(input.id);
5463
- if (!support_ticket) throw new Error("SupportTicket not found");
5464
- if (support_ticket.approval_status !== "PENDING") throw new Error("SupportTicket is locked - cannot be edited");
6581
+ if (!support_ticket) throw new BusinessError("SupportTicket not found");
6582
+ if (support_ticket.approval_status !== "PENDING") throw new BusinessError("SupportTicket is locked - cannot be edited");
6583
+ if (support_ticket.archived_at) throw new BusinessError("Cannot edit an archived support ticket.");
5465
6584
  const frEntity = {
5466
6585
  ...support_ticket,
5467
6586
  title: input.title,
@@ -5483,24 +6602,102 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5483
6602
  auth_role: this.session.user.user_type,
5484
6603
  auth_username: this.session.user.username
5485
6604
  });
5486
- return toCustomerSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
6605
+ const base = toCustomerSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
6606
+ return enrichCustomerTicket(base, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromCustomerTicket(base)));
5487
6607
  }
5488
6608
  };
5489
6609
  UpdateSupportTicketCustomerFeat = __decorate([
5490
6610
  injectable(),
5491
6611
  __decorateParam(0, injectSession()),
5492
6612
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5493
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
6613
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
6614
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5494
6615
  ], UpdateSupportTicketCustomerFeat);
5495
6616
 
6617
+ //#endregion
6618
+ //#region src/slices/support_ticket/features/staff/add_support_ticket_subscriber_feat.ts
6619
+ function toRecordSubscriberReadDto$1(e) {
6620
+ return {
6621
+ id: e.id,
6622
+ record_type: e.record_type,
6623
+ record_id: e.record_id,
6624
+ user_id: e.user_id,
6625
+ subscribed_events: e.subscribed_events ?? null,
6626
+ created_at: e.created_at,
6627
+ created_by: e.created_by
6628
+ };
6629
+ }
6630
+ let AddSupportTicketSubscriberFeat = class AddSupportTicketSubscriberFeat$1 {
6631
+ constructor(subscriberRepo, supportTicketRepo, session, userDisplayLookup) {
6632
+ this.subscriberRepo = subscriberRepo;
6633
+ this.supportTicketRepo = supportTicketRepo;
6634
+ this.session = session;
6635
+ this.userDisplayLookup = userDisplayLookup;
6636
+ }
6637
+ async execute(input) {
6638
+ if (!await this.supportTicketRepo.read(input.support_ticket_id)) throw new BusinessError("Support ticket not found");
6639
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6640
+ const dto = toRecordSubscriberReadDto$1(await this.subscriberRepo.ensureSubscriber({
6641
+ record_type: RecordConst.SUPPORT_TICKET,
6642
+ record_id: input.support_ticket_id,
6643
+ user_id: input.user_id,
6644
+ subscribed_events: input.subscribed_events ?? null,
6645
+ created_at: now,
6646
+ created_by: this.session.user.userId
6647
+ }));
6648
+ const userIds = [dto.user_id, dto.created_by].filter(Boolean);
6649
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
6650
+ return {
6651
+ ...dto,
6652
+ user_id_display_name: dto.user_id ? displayMap.get(dto.user_id) ?? dto.user_id : null,
6653
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null
6654
+ };
6655
+ }
6656
+ };
6657
+ AddSupportTicketSubscriberFeat = __decorate([
6658
+ injectable(),
6659
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6660
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6661
+ __decorateParam(2, injectSession()),
6662
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6663
+ ], AddSupportTicketSubscriberFeat);
6664
+
6665
+ //#endregion
6666
+ //#region src/slices/support_ticket/features/staff/enrich_staff_ticket.ts
6667
+ /**
6668
+ * Enriches a StaffSupportTicketReadDto with display names from a lookup map.
6669
+ * Used by all staff features that return a ticket.
6670
+ */
6671
+ function enrichStaffTicket(dto, displayMap) {
6672
+ const createdByDisplay = dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null;
6673
+ return {
6674
+ ...dto,
6675
+ assigned_to_display_name: dto.assigned_to ? displayMap.get(dto.assigned_to) ?? dto.assigned_to : null,
6676
+ created_by_display_name: createdByDisplay ?? dto.created_by ?? "—",
6677
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
6678
+ };
6679
+ }
6680
+ /**
6681
+ * Collects user IDs from a staff ticket for display name lookup.
6682
+ */
6683
+ function collectUserIdsFromStaffTicket(ticket) {
6684
+ return [
6685
+ ticket.assigned_to,
6686
+ ticket.created_by,
6687
+ ticket.updated_by
6688
+ ].filter((id) => Boolean(id));
6689
+ }
6690
+
5496
6691
  //#endregion
5497
6692
  //#region src/slices/support_ticket/features/staff/approve_support_ticket_feat.ts
5498
6693
  let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5499
- constructor(repo, creditService, create_record_version, session) {
6694
+ constructor(repo, notificationService, creditService, create_record_version, session, userDisplayLookup) {
5500
6695
  this.repo = repo;
6696
+ this.notificationService = notificationService;
5501
6697
  this.creditService = creditService;
5502
6698
  this.create_record_version = create_record_version;
5503
6699
  this.session = session;
6700
+ this.userDisplayLookup = userDisplayLookup;
5504
6701
  }
5505
6702
  /**
5506
6703
  * Approves a support_ticket item, deducts credits, and locks it.
@@ -5516,15 +6713,18 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5516
6713
  * @returns The updated support_ticket with full staff view.
5517
6714
  */
5518
6715
  async execute(args) {
5519
- const { id } = args;
6716
+ const { id, credit_value: creditValueFromArgs } = args;
5520
6717
  const support_ticket = await this.repo.read(id);
5521
- if (!support_ticket) throw new Error(`Support Ticket with ID ${id} not found`);
5522
- if (support_ticket.approval_status !== "PENDING") throw new Error("Support Ticket must be in PENDING status to be approved.");
5523
- if (!support_ticket.credit_value) throw new Error("Support Ticket must have an estimated credit cost to be approved.");
5524
- if (support_ticket.credit_value === null || support_ticket.credit_value === void 0 || support_ticket.credit_value === "") throw new Error("Support Ticket must have an estimated credit cost to be approved.");
5525
- const creditAmount = parseFloat(support_ticket.credit_value);
5526
- if (isNaN(creditAmount)) throw new Error("Invalid credit value amount.");
5527
- if (!await this.creditService.deductCredits(support_ticket.credit_value, support_ticket.id)) throw new Error("Insufficient credits. Customer must purchase more credits or wait for monthly reset.");
6718
+ if (!support_ticket) throw new BusinessError(`Support Ticket with ID ${id} not found`);
6719
+ if (support_ticket.approval_status !== "PENDING") throw new BusinessError("Support Ticket must be in PENDING status to be approved.");
6720
+ const creditAmount = parseFloat(creditValueFromArgs);
6721
+ if (isNaN(creditAmount) || creditAmount < 0) throw new BusinessError("Invalid credit value amount.");
6722
+ const ticketToUpdate = {
6723
+ ...support_ticket,
6724
+ credit_value: creditValueFromArgs
6725
+ };
6726
+ if (support_ticket.credit_value?.trim() !== creditValueFromArgs) await this.repo.update(ticketToUpdate);
6727
+ if (!await this.creditService.deductCredits(creditValueFromArgs, support_ticket.id)) throw new BusinessError("Insufficient credits. Customer must purchase more credits or wait for monthly reset.");
5528
6728
  const lockedAt = (/* @__PURE__ */ new Date()).toISOString();
5529
6729
  const changed_props = getChangedProperties(support_ticket, {
5530
6730
  ...support_ticket,
@@ -5549,25 +6749,101 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5549
6749
  dev_lifecycle: "BACKLOG",
5550
6750
  locked_approval_at: lockedAt
5551
6751
  });
5552
- if (!updatedSupportTicket) throw new Error("Failed to approve support_ticket.");
5553
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6752
+ if (!updatedSupportTicket) throw new BusinessError("Failed to approve support_ticket.");
6753
+ await this.notificationService.notifyFollowers(id, updatedSupportTicket, "APPROVAL_STATUS_CHANGED", { actorUserId: this.session.user.userId });
6754
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6755
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5554
6756
  }
5555
6757
  };
5556
6758
  ApproveSupportTicketFeat = __decorate([
5557
6759
  injectable(),
5558
6760
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5559
- __decorateParam(1, inject(CREDIT_SERVICE_TOKEN)),
5560
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5561
- __decorateParam(3, injectSession())
6761
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
6762
+ __decorateParam(2, inject(CREDIT_SERVICE_TOKEN)),
6763
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6764
+ __decorateParam(4, injectSession()),
6765
+ __decorateParam(5, inject(USER_TOKENS.IUserDisplayLookup))
5562
6766
  ], ApproveSupportTicketFeat);
5563
6767
 
6768
+ //#endregion
6769
+ //#region src/slices/support_ticket/features/staff/archive_support_ticket_feat.ts
6770
+ let ArchiveSupportTicketFeat = class ArchiveSupportTicketFeat$1 {
6771
+ constructor(repo, create_record_version, session, userDisplayLookup) {
6772
+ this.repo = repo;
6773
+ this.create_record_version = create_record_version;
6774
+ this.session = session;
6775
+ this.userDisplayLookup = userDisplayLookup;
6776
+ }
6777
+ async execute(id) {
6778
+ const support_ticket = await this.repo.read(id);
6779
+ if (!support_ticket) throw new Error(`Support ticket with id ${id} not found`);
6780
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6781
+ const userId = this.session.user.userId;
6782
+ if (support_ticket.archived_at) {
6783
+ await this.create_record_version.execute({
6784
+ record_id: id,
6785
+ operation: OperationConst.UPDATE,
6786
+ recorded_at: now,
6787
+ record_type: RecordConst.SUPPORT_TICKET,
6788
+ record: {
6789
+ archived_at: null,
6790
+ archived_by: null
6791
+ },
6792
+ old_record: support_ticket,
6793
+ auth_uid: userId,
6794
+ auth_role: this.session.user.user_type,
6795
+ auth_username: this.session.user.username
6796
+ });
6797
+ const updated$1 = await this.repo.update({
6798
+ ...support_ticket,
6799
+ archived_at: null,
6800
+ archived_by: null
6801
+ });
6802
+ if (!updated$1) throw new Error("Failed to unarchive support ticket.");
6803
+ const dto$1 = toStaffSupportTicketReadDto(updated$1);
6804
+ return enrichStaffTicket(dto$1, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto$1)));
6805
+ }
6806
+ if (!(support_ticket.dev_lifecycle === "DEPLOYED" || support_ticket.approval_status === "REJECTED")) throw new Error("Only completed or rejected tickets can be archived. Complete or reject the ticket first.");
6807
+ await this.create_record_version.execute({
6808
+ record_id: id,
6809
+ operation: OperationConst.UPDATE,
6810
+ recorded_at: now,
6811
+ record_type: RecordConst.SUPPORT_TICKET,
6812
+ record: {
6813
+ archived_at: now,
6814
+ archived_by: userId
6815
+ },
6816
+ old_record: support_ticket,
6817
+ auth_uid: userId,
6818
+ auth_role: this.session.user.user_type,
6819
+ auth_username: this.session.user.username
6820
+ });
6821
+ const updated = await this.repo.update({
6822
+ ...support_ticket,
6823
+ archived_at: now,
6824
+ archived_by: userId
6825
+ });
6826
+ if (!updated) throw new Error("Failed to archive support ticket.");
6827
+ const dto = toStaffSupportTicketReadDto(updated);
6828
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6829
+ }
6830
+ };
6831
+ ArchiveSupportTicketFeat = __decorate([
6832
+ injectable(),
6833
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6834
+ __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6835
+ __decorateParam(2, injectSession()),
6836
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6837
+ ], ArchiveSupportTicketFeat);
6838
+
5564
6839
  //#endregion
5565
6840
  //#region src/slices/support_ticket/features/staff/cancel_internal_task_feat.ts
5566
6841
  let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5567
- constructor(repo, create_record_version, session) {
6842
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5568
6843
  this.repo = repo;
5569
6844
  this.create_record_version = create_record_version;
5570
6845
  this.session = session;
6846
+ this.userDisplayLookup = userDisplayLookup;
5571
6847
  }
5572
6848
  /**
5573
6849
  * Cancels an internal task by setting dev_lifecycle to CANCELLED.
@@ -5612,23 +6888,26 @@ let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5612
6888
  completed_at: completedAt
5613
6889
  });
5614
6890
  if (!updatedSupportTicket) throw new Error("Failed to cancel internal task.");
5615
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6891
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6892
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5616
6893
  }
5617
6894
  };
5618
6895
  CancelInternalTaskFeat = __decorate([
5619
6896
  injectable(),
5620
6897
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5621
6898
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5622
- __decorateParam(2, injectSession())
6899
+ __decorateParam(2, injectSession()),
6900
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5623
6901
  ], CancelInternalTaskFeat);
5624
6902
 
5625
6903
  //#endregion
5626
6904
  //#region src/slices/support_ticket/features/staff/complete_support_ticket_feat.ts
5627
6905
  let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5628
- constructor(repo, create_record_version, session) {
6906
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5629
6907
  this.repo = repo;
5630
6908
  this.create_record_version = create_record_version;
5631
6909
  this.session = session;
6910
+ this.userDisplayLookup = userDisplayLookup;
5632
6911
  }
5633
6912
  /**
5634
6913
  * Completes a support_ticket item by setting it to DEPLOYED with delivered value.
@@ -5674,23 +6953,26 @@ let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5674
6953
  completed_at: completedAt
5675
6954
  });
5676
6955
  if (!updatedSupportTicket) throw new Error("Failed to complete support_ticket.");
5677
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6956
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6957
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5678
6958
  }
5679
6959
  };
5680
6960
  CompleteSupportTicketFeat = __decorate([
5681
6961
  injectable(),
5682
6962
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5683
6963
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5684
- __decorateParam(2, injectSession())
6964
+ __decorateParam(2, injectSession()),
6965
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5685
6966
  ], CompleteSupportTicketFeat);
5686
6967
 
5687
6968
  //#endregion
5688
6969
  //#region src/slices/support_ticket/features/staff/convert_to_customer_feat.ts
5689
6970
  let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5690
- constructor(session, support_ticketRepo, create_record_version) {
6971
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5691
6972
  this.session = session;
5692
6973
  this.support_ticketRepo = support_ticketRepo;
5693
6974
  this.create_record_version = create_record_version;
6975
+ this.userDisplayLookup = userDisplayLookup;
5694
6976
  }
5695
6977
  async execute(support_ticket_id) {
5696
6978
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5701,7 +6983,7 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5701
6983
  approval_status: "PENDING",
5702
6984
  dev_lifecycle: null,
5703
6985
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5704
- updated_by: this.session.user.email
6986
+ updated_by: this.session.user.userId
5705
6987
  };
5706
6988
  const updated = await this.support_ticketRepo.update(updateEntity);
5707
6989
  await this.create_record_version.execute({
@@ -5715,23 +6997,26 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5715
6997
  auth_role: this.session.user.user_type,
5716
6998
  auth_username: this.session.user.username
5717
6999
  });
5718
- return toStaffSupportTicketReadDto(updated);
7000
+ const dto = toStaffSupportTicketReadDto(updated);
7001
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5719
7002
  }
5720
7003
  };
5721
7004
  ConvertToCustomerFeat = __decorate([
5722
7005
  injectable(),
5723
7006
  __decorateParam(0, injectSession()),
5724
7007
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5725
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7008
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7009
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5726
7010
  ], ConvertToCustomerFeat);
5727
7011
 
5728
7012
  //#endregion
5729
7013
  //#region src/slices/support_ticket/features/staff/convert_to_internal_feat.ts
5730
7014
  let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5731
- constructor(session, support_ticketRepo, create_record_version) {
7015
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5732
7016
  this.session = session;
5733
7017
  this.support_ticketRepo = support_ticketRepo;
5734
7018
  this.create_record_version = create_record_version;
7019
+ this.userDisplayLookup = userDisplayLookup;
5735
7020
  }
5736
7021
  async execute(support_ticket_id) {
5737
7022
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5743,7 +7028,7 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5743
7028
  credit_value: null,
5744
7029
  dev_lifecycle: "BACKLOG",
5745
7030
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5746
- updated_by: this.session.user.email
7031
+ updated_by: this.session.user.userId
5747
7032
  };
5748
7033
  const updated = await this.support_ticketRepo.update(updateEntity);
5749
7034
  await this.create_record_version.execute({
@@ -5757,26 +7042,31 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5757
7042
  auth_role: this.session.user.user_type,
5758
7043
  auth_username: this.session.user.username
5759
7044
  });
5760
- return toStaffSupportTicketReadDto(updated);
7045
+ const dto = toStaffSupportTicketReadDto(updated);
7046
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5761
7047
  }
5762
7048
  };
5763
7049
  ConvertToInternalFeat = __decorate([
5764
7050
  injectable(),
5765
7051
  __decorateParam(0, injectSession()),
5766
7052
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5767
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7053
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7054
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5768
7055
  ], ConvertToInternalFeat);
5769
7056
 
5770
7057
  //#endregion
5771
7058
  //#region src/slices/support_ticket/features/staff/create_support_ticket_admin_feat.ts
5772
7059
  let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5773
- constructor(session, support_ticketRepo, create_record_version, appSettingsRepo, emailService, env, logger$1) {
7060
+ constructor(session, support_ticketRepo, subscriberRepo, notificationService, triageService, supportStaffRepo, userDisplayLookup, create_record_version, appSettingsRepo, logger$1) {
5774
7061
  this.session = session;
5775
7062
  this.support_ticketRepo = support_ticketRepo;
7063
+ this.subscriberRepo = subscriberRepo;
7064
+ this.notificationService = notificationService;
7065
+ this.triageService = triageService;
7066
+ this.supportStaffRepo = supportStaffRepo;
7067
+ this.userDisplayLookup = userDisplayLookup;
5776
7068
  this.create_record_version = create_record_version;
5777
7069
  this.appSettingsRepo = appSettingsRepo;
5778
- this.emailService = emailService;
5779
- this.env = env;
5780
7070
  this.logger = logger$1;
5781
7071
  }
5782
7072
  /**
@@ -5789,10 +7079,8 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5789
7079
  title: input.title || "",
5790
7080
  description: input.description || "",
5791
7081
  type: input.type || "FEATURE_REQUEST",
5792
- priority: input.priority || "MEDIUM",
7082
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5793
7083
  approval_status: input.is_internal ? "INTERNAL" : "PENDING",
5794
- requester_email: this.session.user.email,
5795
- requester_name: this.session.user.username,
5796
7084
  dev_lifecycle: input.dev_lifecycle || null,
5797
7085
  credit_value: input.is_internal ? null : formatCreditValue(input.credit_value),
5798
7086
  delivered_value: input.delivered_value || null,
@@ -5807,6 +7095,12 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5807
7095
  };
5808
7096
  const support_ticketCreated = await this.support_ticketRepo.create(support_ticketEntity);
5809
7097
  support_ticketCreated.display_id = await this.appSettingsRepo.getNextSequentialId(RecordConst.SUPPORT_TICKET, this.session.user.userId);
7098
+ let assigneeId = null;
7099
+ if (input.assigned_to?.trim()) {
7100
+ if (!(await this.supportStaffRepo.readTriageUsers()).some((u) => u.id === input.assigned_to)) throw new BusinessError("Selected assignee is not a support staff member");
7101
+ assigneeId = input.assigned_to;
7102
+ } else assigneeId = await this.triageService.getNextAssignee(this.session.user);
7103
+ if (assigneeId) support_ticketCreated.assigned_to = assigneeId;
5810
7104
  await this.support_ticketRepo.update(support_ticketCreated);
5811
7105
  await this.create_record_version.execute({
5812
7106
  record_id: support_ticketCreated.id,
@@ -5818,58 +7112,30 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5818
7112
  auth_role: this.session.user.user_type,
5819
7113
  auth_username: this.session.user.username
5820
7114
  });
5821
- if (!input.is_internal) await this.sendNotificationEmails(support_ticketCreated);
5822
- return toStaffSupportTicketReadDto(support_ticketCreated);
7115
+ if (!input.is_internal) {
7116
+ await this.addRequesterAsFollower(support_ticketCreated);
7117
+ await this.notificationService.notifyAssignee(support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
7118
+ await this.notificationService.notifyFollowers(support_ticketCreated.id, support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
7119
+ }
7120
+ const dto = toStaffSupportTicketReadDto(support_ticketCreated);
7121
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5823
7122
  }
5824
7123
  /**
5825
- * Send email notifications to staff members
7124
+ * Auto-add requester (created_by) as follower
5826
7125
  */
5827
- async sendNotificationEmails(ticket) {
7126
+ async addRequesterAsFollower(ticket) {
5828
7127
  try {
5829
- const emailsSetting = await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS);
5830
- if (!emailsSetting?.value) {
5831
- this.logger.debug("No notification emails configured for support tickets");
5832
- return;
5833
- }
5834
- const emails = emailsSetting.value;
5835
- if (!Array.isArray(emails) || emails.length === 0) {
5836
- this.logger.debug("No valid notification emails found");
5837
- return;
5838
- }
5839
- const ticketUrl = `${this.env.WEBSITE_URL}/support/${ticket.id}`;
5840
- const subject = `New Support Ticket Created: ${ticket.title}`;
5841
- const bodyText = `
5842
- A new support ticket has been created:
5843
-
5844
- Ticket #${ticket.display_id || ticket.id}
5845
- Title: ${ticket.title}
5846
- Type: ${ticket.type}
5847
- Priority: ${ticket.priority}
5848
- Requester: ${ticket.requester_name} (${ticket.requester_email})
5849
-
5850
- Description:
5851
- ${ticket.description}
5852
-
5853
- View ticket: ${ticketUrl}
5854
- `.trim();
5855
- const bodyHtml = `
5856
- <h2>New Support Ticket Created</h2>
5857
- <p>A new support ticket has been created:</p>
5858
- <ul>
5859
- <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
5860
- <li><strong>Title:</strong> ${ticket.title}</li>
5861
- <li><strong>Type:</strong> ${ticket.type}</li>
5862
- <li><strong>Priority:</strong> ${ticket.priority}</li>
5863
- <li><strong>Requester:</strong> ${ticket.requester_name} (${ticket.requester_email})</li>
5864
- </ul>
5865
- <p><strong>Description:</strong></p>
5866
- <p>${ticket.description}</p>
5867
- <p><a href="${ticketUrl}">View Ticket</a></p>
5868
- `.trim();
5869
- await this.emailService.sendBulkEmail(emails, subject, bodyText, bodyHtml);
5870
- this.logger.info(`Sent support ticket notification to ${emails.length} recipients`);
7128
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7129
+ await this.subscriberRepo.ensureSubscriber({
7130
+ record_type: RecordConst.SUPPORT_TICKET,
7131
+ record_id: ticket.id,
7132
+ user_id: ticket.created_by,
7133
+ subscribed_events: null,
7134
+ created_at: now,
7135
+ created_by: ticket.created_by
7136
+ });
5871
7137
  } catch (error) {
5872
- this.logger.error("Failed to send support ticket notification emails", { error });
7138
+ this.logger.error("Failed to add requester as follower", { error });
5873
7139
  }
5874
7140
  }
5875
7141
  };
@@ -5877,11 +7143,14 @@ CreateSupportTicketAdminFeat = __decorate([
5877
7143
  injectable(),
5878
7144
  __decorateParam(0, injectSession()),
5879
7145
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5880
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5881
- __decorateParam(3, inject(TOKENS.IAppSettingsRepo)),
5882
- __decorateParam(4, inject(TOKENS.IEmailService)),
5883
- __decorateParam(5, inject(TOKENS.ENV)),
5884
- __decorateParam(6, inject(TOKENS.LOGGER))
7146
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
7147
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7148
+ __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)),
7149
+ __decorateParam(5, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
7150
+ __decorateParam(6, inject(USER_TOKENS.IUserDisplayLookup)),
7151
+ __decorateParam(7, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7152
+ __decorateParam(8, inject(TOKENS.IAppSettingsRepo)),
7153
+ __decorateParam(9, inject(TOKENS.LOGGER))
5885
7154
  ], CreateSupportTicketAdminFeat);
5886
7155
 
5887
7156
  //#endregion
@@ -5928,13 +7197,93 @@ DeleteSupportTicketFeat = __decorate([
5928
7197
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5929
7198
  ], DeleteSupportTicketFeat);
5930
7199
 
7200
+ //#endregion
7201
+ //#region src/slices/support_ticket/features/staff/fix_support_ticket_user_ids_feat.ts
7202
+ const UNIVERSAL_ID_LENGTH = 32;
7203
+ let FixSupportTicketUserIdsFeature = class FixSupportTicketUserIdsFeature$1 {
7204
+ constructor(supportTicketRepo, userRepo) {
7205
+ this.supportTicketRepo = supportTicketRepo;
7206
+ this.userRepo = userRepo;
7207
+ }
7208
+ async execute() {
7209
+ const tickets = await this.supportTicketRepo.findWithInvalidUserIds();
7210
+ if (tickets.length === 0) return {
7211
+ ticketsScanned: 0,
7212
+ ticketsFixed: 0,
7213
+ ticketsSkipped: 0
7214
+ };
7215
+ const emailToUserId = await this.buildEmailToUserIdMap(tickets);
7216
+ let ticketsFixed = 0;
7217
+ let ticketsSkipped = 0;
7218
+ for (const ticket of tickets) {
7219
+ const updates = this.buildUpdates(ticket, emailToUserId);
7220
+ if (Object.keys(updates).length === 0) {
7221
+ ticketsSkipped++;
7222
+ continue;
7223
+ }
7224
+ const updated = {
7225
+ ...ticket,
7226
+ ...updates
7227
+ };
7228
+ await this.supportTicketRepo.update(updated);
7229
+ ticketsFixed++;
7230
+ }
7231
+ return {
7232
+ ticketsScanned: tickets.length,
7233
+ ticketsFixed,
7234
+ ticketsSkipped
7235
+ };
7236
+ }
7237
+ async buildEmailToUserIdMap(tickets) {
7238
+ const fieldsToFix = [
7239
+ "created_by",
7240
+ "updated_by",
7241
+ "assigned_to",
7242
+ "archived_by",
7243
+ "deleted_by"
7244
+ ];
7245
+ const emails = /* @__PURE__ */ new Set();
7246
+ for (const ticket of tickets) for (const field of fieldsToFix) {
7247
+ const value = ticket[field];
7248
+ if (value != null && value.length !== UNIVERSAL_ID_LENGTH) emails.add(value);
7249
+ }
7250
+ if (emails.size === 0) return /* @__PURE__ */ new Map();
7251
+ const users = await this.userRepo.read_users_by_emails_case_insensitive([...emails]);
7252
+ const map = /* @__PURE__ */ new Map();
7253
+ for (const u of users) map.set(u.email.toLowerCase(), u.id);
7254
+ return map;
7255
+ }
7256
+ buildUpdates(ticket, emailToUserId) {
7257
+ const updates = {};
7258
+ for (const field of [
7259
+ "created_by",
7260
+ "updated_by",
7261
+ "assigned_to",
7262
+ "archived_by",
7263
+ "deleted_by"
7264
+ ]) {
7265
+ const value = ticket[field];
7266
+ if (value == null || value.length === UNIVERSAL_ID_LENGTH) continue;
7267
+ const userId = emailToUserId.get(value.toLowerCase());
7268
+ if (userId) updates[field] = userId;
7269
+ }
7270
+ return updates;
7271
+ }
7272
+ };
7273
+ FixSupportTicketUserIdsFeature = __decorate([
7274
+ injectable(),
7275
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7276
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo))
7277
+ ], FixSupportTicketUserIdsFeature);
7278
+
5931
7279
  //#endregion
5932
7280
  //#region src/slices/support_ticket/features/staff/get_support_ticket_admin_feat.ts
5933
7281
  let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5934
- constructor(session, support_ticketRepo, appSettingsRepo) {
7282
+ constructor(session, support_ticketRepo, appSettingsRepo, userDisplayLookup) {
5935
7283
  this.session = session;
5936
7284
  this.support_ticketRepo = support_ticketRepo;
5937
7285
  this.appSettingsRepo = appSettingsRepo;
7286
+ this.userDisplayLookup = userDisplayLookup;
5938
7287
  }
5939
7288
  async execute(id) {
5940
7289
  let support_ticket = await this.support_ticketRepo.read(id);
@@ -5948,39 +7297,110 @@ let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5948
7297
  updated_by: this.session.user.userId
5949
7298
  });
5950
7299
  }
5951
- return toStaffSupportTicketReadDto(support_ticket);
7300
+ const dto = toStaffSupportTicketReadDto(support_ticket);
7301
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5952
7302
  }
5953
7303
  };
5954
7304
  GetSupportTicketAdminFeature = __decorate([
5955
7305
  injectable(),
5956
7306
  __decorateParam(0, injectSession()),
5957
7307
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5958
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo))
7308
+ __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
7309
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5959
7310
  ], GetSupportTicketAdminFeature);
5960
7311
 
7312
+ //#endregion
7313
+ //#region src/slices/support_ticket/features/staff/get_requestors_for_active_support_tickets_feat.ts
7314
+ let GetRequestorsForActiveSupportTicketsFeat = class GetRequestorsForActiveSupportTicketsFeat$1 {
7315
+ constructor(supportTicketRepo, userRepo) {
7316
+ this.supportTicketRepo = supportTicketRepo;
7317
+ this.userRepo = userRepo;
7318
+ }
7319
+ async execute(options) {
7320
+ const requestorIds = await this.supportTicketRepo.read_distinct_requestors_for_active_tickets(options);
7321
+ if (requestorIds.length === 0) return [];
7322
+ return (await this.userRepo.read_users_by_ids(requestorIds)).map((u) => ({
7323
+ id: u.id,
7324
+ email: u.email
7325
+ }));
7326
+ }
7327
+ };
7328
+ GetRequestorsForActiveSupportTicketsFeat = __decorate([
7329
+ injectable(),
7330
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7331
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo))
7332
+ ], GetRequestorsForActiveSupportTicketsFeat);
7333
+
5961
7334
  //#endregion
5962
7335
  //#region src/slices/support_ticket/features/staff/get_support_tickets_admin_feat.ts
5963
7336
  let GetSupportTicketsAdminFeature = class GetSupportTicketsAdminFeature$1 {
5964
- constructor(support_ticketRepo) {
7337
+ constructor(support_ticketRepo, userDisplayLookup) {
5965
7338
  this.support_ticketRepo = support_ticketRepo;
7339
+ this.userDisplayLookup = userDisplayLookup;
5966
7340
  }
5967
7341
  async execute(filters) {
5968
7342
  const result = await this.support_ticketRepo.read_all(filters);
7343
+ const dtos = result.items.map(toStaffSupportTicketReadDto);
7344
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromStaffTicket))];
7345
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5969
7346
  return {
5970
- items: result.items.map(toStaffSupportTicketReadDto),
7347
+ items: dtos.map((t) => enrichStaffTicket(t, displayMap)),
5971
7348
  pageInfo: result.pageInfo
5972
7349
  };
5973
7350
  }
5974
7351
  };
5975
- GetSupportTicketsAdminFeature = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))], GetSupportTicketsAdminFeature);
7352
+ GetSupportTicketsAdminFeature = __decorate([
7353
+ injectable(),
7354
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7355
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
7356
+ ], GetSupportTicketsAdminFeature);
7357
+
7358
+ //#endregion
7359
+ //#region src/slices/support_ticket/features/staff/list_support_ticket_subscribers_feat.ts
7360
+ function toRecordSubscriberReadDto(e) {
7361
+ return {
7362
+ id: e.id,
7363
+ record_type: e.record_type,
7364
+ record_id: e.record_id,
7365
+ user_id: e.user_id,
7366
+ subscribed_events: e.subscribed_events ?? null,
7367
+ created_at: e.created_at,
7368
+ created_by: e.created_by
7369
+ };
7370
+ }
7371
+ let ListSupportTicketSubscribersFeat = class ListSupportTicketSubscribersFeat$1 {
7372
+ constructor(subscriberRepo, supportTicketRepo, userDisplayLookup) {
7373
+ this.subscriberRepo = subscriberRepo;
7374
+ this.supportTicketRepo = supportTicketRepo;
7375
+ this.userDisplayLookup = userDisplayLookup;
7376
+ }
7377
+ async execute(supportTicketId) {
7378
+ if (!await this.supportTicketRepo.read(supportTicketId)) throw new BusinessError("Support ticket not found");
7379
+ const dtos = (await this.subscriberRepo.readByRecordId(RecordConst.SUPPORT_TICKET, supportTicketId)).map(toRecordSubscriberReadDto);
7380
+ const userIds = [...new Set(dtos.flatMap((s) => [s.user_id, s.created_by].filter(Boolean)))];
7381
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
7382
+ return dtos.map((s) => ({
7383
+ ...s,
7384
+ user_id_display_name: s.user_id ? displayMap.get(s.user_id) ?? s.user_id : null,
7385
+ created_by_display_name: s.created_by ? displayMap.get(s.created_by) ?? s.created_by : null
7386
+ }));
7387
+ }
7388
+ };
7389
+ ListSupportTicketSubscribersFeat = __decorate([
7390
+ injectable(),
7391
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
7392
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7393
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup))
7394
+ ], ListSupportTicketSubscribersFeat);
5976
7395
 
5977
7396
  //#endregion
5978
7397
  //#region src/slices/support_ticket/features/staff/reactivate_internal_task_feat.ts
5979
7398
  let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
5980
- constructor(repo, create_record_version, session) {
7399
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5981
7400
  this.repo = repo;
5982
7401
  this.create_record_version = create_record_version;
5983
7402
  this.session = session;
7403
+ this.userDisplayLookup = userDisplayLookup;
5984
7404
  }
5985
7405
  /**
5986
7406
  * Reactivates a terminal internal task.
@@ -6028,23 +7448,27 @@ let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
6028
7448
  completed_at: null
6029
7449
  });
6030
7450
  if (!updatedSupportTicket) throw new Error("Failed to reactivate internal task.");
6031
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7451
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7452
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6032
7453
  }
6033
7454
  };
6034
7455
  ReactivateInternalTaskFeat = __decorate([
6035
7456
  injectable(),
6036
7457
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6037
7458
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6038
- __decorateParam(2, injectSession())
7459
+ __decorateParam(2, injectSession()),
7460
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6039
7461
  ], ReactivateInternalTaskFeat);
6040
7462
 
6041
7463
  //#endregion
6042
7464
  //#region src/slices/support_ticket/features/staff/reject_support_ticket_feat.ts
6043
7465
  let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6044
- constructor(repo, create_record_version, session) {
7466
+ constructor(repo, notificationService, create_record_version, session, userDisplayLookup) {
6045
7467
  this.repo = repo;
7468
+ this.notificationService = notificationService;
6046
7469
  this.create_record_version = create_record_version;
6047
7470
  this.session = session;
7471
+ this.userDisplayLookup = userDisplayLookup;
6048
7472
  }
6049
7473
  /**
6050
7474
  * Rejects a support_ticket item and locks it.
@@ -6085,24 +7509,41 @@ let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6085
7509
  locked_approval_at: lockedAt
6086
7510
  });
6087
7511
  if (!updatedSupportTicket) throw new Error("Failed to reject support_ticket.");
6088
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7512
+ await this.notificationService.notifyFollowers(id, updatedSupportTicket, "APPROVAL_STATUS_CHANGED", { actorUserId: this.session.user.userId });
7513
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7514
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6089
7515
  }
6090
7516
  };
6091
7517
  RejectSupportTicketFeat = __decorate([
6092
7518
  injectable(),
6093
7519
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6094
- __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6095
- __decorateParam(2, injectSession())
7520
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7521
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7522
+ __decorateParam(3, injectSession()),
7523
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6096
7524
  ], RejectSupportTicketFeat);
6097
7525
 
7526
+ //#endregion
7527
+ //#region src/slices/support_ticket/features/staff/remove_support_ticket_subscriber_feat.ts
7528
+ let RemoveSupportTicketSubscriberFeat = class RemoveSupportTicketSubscriberFeat$1 {
7529
+ constructor(subscriberRepo) {
7530
+ this.subscriberRepo = subscriberRepo;
7531
+ }
7532
+ async execute(supportTicketId, subscriberId) {
7533
+ if (!await this.subscriberRepo.delete(subscriberId)) throw new BusinessError("Subscriber not found or already removed");
7534
+ }
7535
+ };
7536
+ RemoveSupportTicketSubscriberFeat = __decorate([injectable(), __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo))], RemoveSupportTicketSubscriberFeat);
7537
+
6098
7538
  //#endregion
6099
7539
  //#region src/slices/support_ticket/features/staff/revert_support_ticket_feat.ts
6100
7540
  let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6101
- constructor(repo, creditService, create_record_version, session) {
7541
+ constructor(repo, creditService, create_record_version, session, userDisplayLookup) {
6102
7542
  this.repo = repo;
6103
7543
  this.creditService = creditService;
6104
7544
  this.create_record_version = create_record_version;
6105
7545
  this.session = session;
7546
+ this.userDisplayLookup = userDisplayLookup;
6106
7547
  }
6107
7548
  /**
6108
7549
  * Reverts a support_ticket item back to PENDING status and refunds credits.
@@ -6151,7 +7592,8 @@ let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6151
7592
  locked_approval_at: null
6152
7593
  });
6153
7594
  if (!updatedSupportTicket) throw new Error("Failed to revert support_ticket.");
6154
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7595
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7596
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6155
7597
  }
6156
7598
  };
6157
7599
  RevertSupportTicketFeat = __decorate([
@@ -6159,7 +7601,8 @@ RevertSupportTicketFeat = __decorate([
6159
7601
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6160
7602
  __decorateParam(1, inject(CREDIT_SERVICE_TOKEN)),
6161
7603
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6162
- __decorateParam(3, injectSession())
7604
+ __decorateParam(3, injectSession()),
7605
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6163
7606
  ], RevertSupportTicketFeat);
6164
7607
 
6165
7608
  //#endregion
@@ -6168,17 +7611,18 @@ RevertSupportTicketFeat = __decorate([
6168
7611
  * Validate business rules for staff updates based on permissions matrix
6169
7612
  */
6170
7613
  const validateBusinessRules = (input, existing) => {
7614
+ if (existing.archived_at) throw new BusinessError("Cannot edit an archived support ticket.");
6171
7615
  const currentApproval = existing.approval_status;
6172
7616
  const currentDevLifecycle = existing.dev_lifecycle;
6173
7617
  if (input.dev_lifecycle !== void 0 && input.dev_lifecycle !== currentDevLifecycle) {
6174
- if (!currentApproval || !["APPROVED", "INTERNAL"].includes(currentApproval)) throw new Error("Can only set dev lifecycle when support_ticket is APPROVED or INTERNAL");
7618
+ if (!currentApproval || !["APPROVED", "INTERNAL"].includes(currentApproval)) throw new BusinessError("Can only set dev lifecycle when support_ticket is APPROVED or INTERNAL");
6175
7619
  }
6176
7620
  if (input.credit_value !== void 0 && input.credit_value !== existing.credit_value) {
6177
- if (currentApproval === "INTERNAL") throw new Error("Internal support_ticket cannot have credit values");
6178
- if (currentApproval !== "PENDING") throw new Error("Can only edit credit value when support_ticket is PENDING");
7621
+ if (currentApproval === "INTERNAL") throw new BusinessError("Internal support_ticket cannot have credit values");
7622
+ if (currentApproval !== "PENDING") throw new BusinessError("Can only edit credit value when support_ticket is PENDING");
6179
7623
  }
6180
7624
  if (input.delivered_value !== void 0 && input.delivered_value !== existing.delivered_value) {
6181
- if (currentDevLifecycle !== "DEPLOYED") throw new Error("Can only set delivered value when dev lifecycle is DEPLOYED");
7625
+ if (currentDevLifecycle !== "DEPLOYED") throw new BusinessError("Can only set delivered value when dev lifecycle is DEPLOYED");
6182
7626
  }
6183
7627
  const lockedDevStages = [
6184
7628
  "DEVELOPMENT",
@@ -6186,21 +7630,22 @@ const validateBusinessRules = (input, existing) => {
6186
7630
  "TESTING",
6187
7631
  "STAGING",
6188
7632
  "PO_APPROVAL",
7633
+ "VERIFICATION",
6189
7634
  "DEPLOYED"
6190
7635
  ];
6191
7636
  if (input.start_at !== void 0 && input.start_at !== existing.start_at) {
6192
- if (currentApproval === "REJECTED") throw new Error("Cannot edit start date when support_ticket is REJECTED");
6193
- if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new Error("Cannot edit start date once development has started (DEVELOPMENT or higher)");
7637
+ if (currentApproval === "REJECTED") throw new BusinessError("Cannot edit start date when support_ticket is REJECTED");
7638
+ if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new BusinessError("Cannot edit start date once development has started (DEVELOPMENT or higher)");
6194
7639
  }
6195
7640
  if (input.target_at !== void 0 && input.target_at !== existing.target_at) {
6196
- if (currentApproval === "REJECTED") throw new Error("Cannot edit target date when support_ticket is REJECTED");
6197
- if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new Error("Cannot edit target date once development has started (DEVELOPMENT or higher)");
7641
+ if (currentApproval === "REJECTED") throw new BusinessError("Cannot edit target date when support_ticket is REJECTED");
7642
+ if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new BusinessError("Cannot edit target date once development has started (DEVELOPMENT or higher)");
6198
7643
  }
6199
7644
  if (input.completed_at !== void 0 && input.completed_at !== existing.completed_at) {
6200
- if (!currentApproval || !["APPROVED", "INTERNAL"].includes(currentApproval) || !currentDevLifecycle || !["DEPLOYED", "CANCELLED"].includes(currentDevLifecycle)) throw new Error("Can only set completed date when dev lifecycle is DEPLOYED or CANCELLED");
7645
+ if (!currentApproval || !["APPROVED", "INTERNAL"].includes(currentApproval) || !currentDevLifecycle || !["DEPLOYED", "CANCELLED"].includes(currentDevLifecycle)) throw new BusinessError("Can only set completed date when dev lifecycle is DEPLOYED or CANCELLED");
6201
7646
  }
6202
7647
  if (currentApproval === "REJECTED") {
6203
- if (input.title !== void 0 && input.title !== existing.title || input.description !== void 0 && input.description !== existing.description || input.type !== void 0 && input.type !== existing.type || input.priority !== void 0 && input.priority !== existing.priority || input.dev_lifecycle !== void 0 && input.dev_lifecycle !== existing.dev_lifecycle || input.credit_value !== void 0 && input.credit_value !== existing.credit_value || input.delivered_value !== void 0 && input.delivered_value !== existing.delivered_value) throw new Error("Cannot edit REJECTED support_ticket. Use revert workflow to unlock");
7648
+ if (input.title !== void 0 && input.title !== existing.title || input.description !== void 0 && input.description !== existing.description || input.type !== void 0 && input.type !== existing.type || input.priority !== void 0 && input.priority !== existing.priority || input.dev_lifecycle !== void 0 && input.dev_lifecycle !== existing.dev_lifecycle || input.credit_value !== void 0 && input.credit_value !== existing.credit_value || input.delivered_value !== void 0 && input.delivered_value !== existing.delivered_value) throw new BusinessError("Cannot edit REJECTED support_ticket. Use revert workflow to unlock");
6204
7649
  }
6205
7650
  };
6206
7651
 
@@ -6213,24 +7658,26 @@ function shouldUpdateCreditsSetAt(oldValue, newValue) {
6213
7658
  return isCreditValueEmpty(oldValue) !== isCreditValueEmpty(newValue);
6214
7659
  }
6215
7660
  let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6216
- constructor(session, support_ticketRepo, create_record_version) {
7661
+ constructor(session, support_ticketRepo, create_record_version, notificationService, userDisplayLookup) {
6217
7662
  this.session = session;
6218
7663
  this.support_ticketRepo = support_ticketRepo;
6219
7664
  this.create_record_version = create_record_version;
7665
+ this.notificationService = notificationService;
7666
+ this.userDisplayLookup = userDisplayLookup;
6220
7667
  }
6221
7668
  /**
6222
7669
  * Update support_ticket - comprehensive update for staff
6223
7670
  */
6224
7671
  async execute(input) {
6225
7672
  const support_ticket = await this.support_ticketRepo.read(input.id);
6226
- if (!support_ticket) throw new Error("SupportTicket not found");
7673
+ if (!support_ticket) throw new BusinessError("SupportTicket not found");
6227
7674
  validateBusinessRules(input, support_ticket);
6228
7675
  const isoTime = (/* @__PURE__ */ new Date()).toISOString();
6229
7676
  const frEntity = {
6230
7677
  ...support_ticket,
6231
7678
  id: input.id,
6232
7679
  updated_at: isoTime,
6233
- updated_by: this.session.user.email
7680
+ updated_by: this.session.user.userId
6234
7681
  };
6235
7682
  if (input.title !== void 0 && input.title !== null) frEntity.title = input.title;
6236
7683
  if (input.description !== void 0 && input.description !== null) frEntity.description = input.description;
@@ -6245,6 +7692,7 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6245
7692
  if (input.start_at !== void 0) frEntity.start_at = input.start_at ?? void 0;
6246
7693
  if (input.target_at !== void 0) frEntity.target_at = input.target_at ?? void 0;
6247
7694
  if (input.completed_at !== void 0) frEntity.completed_at = input.completed_at ?? void 0;
7695
+ if (input.assigned_to !== void 0) frEntity.assigned_to = input.assigned_to?.trim() || null;
6248
7696
  const changed_props = getChangedProperties(support_ticket, frEntity);
6249
7697
  if (Object.keys(changed_props).length > 0) await this.create_record_version.execute({
6250
7698
  record_id: frEntity.id,
@@ -6257,14 +7705,22 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6257
7705
  auth_role: this.session.user.user_type,
6258
7706
  auth_username: this.session.user.username
6259
7707
  });
6260
- return toStaffSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
7708
+ const support_ticketUpdated = await this.support_ticketRepo.update(frEntity);
7709
+ if (input.assigned_to !== void 0 && support_ticket.assigned_to !== support_ticketUpdated.assigned_to && support_ticketUpdated.assigned_to) {
7710
+ await this.notificationService.notifyAssignee(support_ticketUpdated, "TICKET_ASSIGNED", { actorUserId: this.session.user.userId });
7711
+ await this.notificationService.notifyFollowers(support_ticketUpdated.id, support_ticketUpdated, "TICKET_ASSIGNED", { actorUserId: this.session.user.userId });
7712
+ }
7713
+ const dto = toStaffSupportTicketReadDto(support_ticketUpdated);
7714
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6261
7715
  }
6262
7716
  };
6263
7717
  UpdateSupportTicketAdminFeat = __decorate([
6264
7718
  injectable(),
6265
7719
  __decorateParam(0, injectSession()),
6266
7720
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6267
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7721
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7722
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7723
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6268
7724
  ], UpdateSupportTicketAdminFeat);
6269
7725
 
6270
7726
  //#endregion
@@ -6274,12 +7730,16 @@ UpdateSupportTicketAdminFeat = __decorate([
6274
7730
  */
6275
7731
  function registerSupportTicketDependencies() {
6276
7732
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketRepo, SupportTicketRepo);
7733
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService, SupportTicketNotificationService);
7734
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService, SupportTicketTriageService);
6277
7735
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICreateSupportTicketFeature, CreateSupportTicketFeat);
6278
7736
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketCustomerFeature, UpdateSupportTicketCustomerFeat);
6279
7737
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketCustomerFeature, GetSupportTicketCustomerFeature);
6280
7738
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketsCustomerFeature, GetSupportTicketsCustomerFeature);
7739
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature, CustomerToggleSubscriptionFeat);
6281
7740
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IApproveSupportTicketFeature, ApproveSupportTicketFeat);
6282
7741
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IDeleteSupportTicketFeature, DeleteSupportTicketFeat);
7742
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature, ArchiveSupportTicketFeat);
6283
7743
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRejectSupportTicketFeature, RejectSupportTicketFeat);
6284
7744
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRevertSupportTicketFeature, RevertSupportTicketFeat);
6285
7745
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICompleteSupportTicketFeature, CompleteSupportTicketFeat);
@@ -6291,6 +7751,11 @@ function registerSupportTicketDependencies() {
6291
7751
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketAdminFeature, UpdateSupportTicketAdminFeat);
6292
7752
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketAdminFeature, GetSupportTicketAdminFeature);
6293
7753
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketsAdminFeature, GetSupportTicketsAdminFeature);
7754
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature, GetRequestorsForActiveSupportTicketsFeat);
7755
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature, AddSupportTicketSubscriberFeat);
7756
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature, ListSupportTicketSubscribersFeat);
7757
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature, RemoveSupportTicketSubscriberFeat);
7758
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature, FixSupportTicketUserIdsFeature);
6294
7759
  }
6295
7760
 
6296
7761
  //#endregion
@@ -6571,14 +8036,34 @@ function mapTeamEntityToDto(entity) {
6571
8036
  };
6572
8037
  }
6573
8038
 
8039
+ //#endregion
8040
+ //#region src/slices/team/features/enrich_team.ts
8041
+ /**
8042
+ * Enriches a TeamReadDto with display names from a lookup map.
8043
+ */
8044
+ function enrichTeam(dto, displayMap) {
8045
+ return {
8046
+ ...dto,
8047
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null,
8048
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
8049
+ };
8050
+ }
8051
+ /**
8052
+ * Collects user IDs from a team for display name lookup.
8053
+ */
8054
+ function collectUserIdsFromTeam(team) {
8055
+ return [team.created_by, team.updated_by].filter((id) => Boolean(id));
8056
+ }
8057
+
6574
8058
  //#endregion
6575
8059
  //#region src/slices/team/features/create_team_feat.ts
6576
8060
  let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6577
- constructor(teamRepo, session, create_record_version, createTeamMemberFeature) {
8061
+ constructor(teamRepo, session, create_record_version, createTeamMemberFeature, userDisplayLookup) {
6578
8062
  this.teamRepo = teamRepo;
6579
8063
  this.session = session;
6580
8064
  this.create_record_version = create_record_version;
6581
8065
  this.createTeamMemberFeature = createTeamMemberFeature;
8066
+ this.userDisplayLookup = userDisplayLookup;
6582
8067
  }
6583
8068
  async execute(input) {
6584
8069
  if (await this.teamRepo.findByUniqueName(input.unique_name || "")) throw new Error("Team with this name already exists");
@@ -6633,7 +8118,8 @@ let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6633
8118
  website_address: null,
6634
8119
  time_zone: null
6635
8120
  });
6636
- return mapTeamEntityToDto(teamEntity);
8121
+ const dto = mapTeamEntityToDto(teamEntity);
8122
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6637
8123
  }
6638
8124
  /**
6639
8125
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6647,7 +8133,8 @@ CreateTeamFeatureImpl = __decorate([
6647
8133
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6648
8134
  __decorateParam(1, injectSession()),
6649
8135
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6650
- __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature))
8136
+ __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature)),
8137
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6651
8138
  ], CreateTeamFeatureImpl);
6652
8139
 
6653
8140
  //#endregion
@@ -6685,16 +8172,20 @@ DeleteTeamFeatureImpl = __decorate([
6685
8172
  //#endregion
6686
8173
  //#region src/slices/team/features/read_all_teams_feat.ts
6687
8174
  let ReadAllTeamsFeatureImpl = class ReadAllTeamsFeatureImpl$1 {
6688
- constructor(teamRepo, session, teamMemberRepo) {
8175
+ constructor(teamRepo, session, teamMemberRepo, userDisplayLookup) {
6689
8176
  this.teamRepo = teamRepo;
6690
8177
  this.session = session;
6691
8178
  this.teamMemberRepo = teamMemberRepo;
8179
+ this.userDisplayLookup = userDisplayLookup;
6692
8180
  }
6693
8181
  async execute(filters) {
6694
8182
  const processedFilters = await this.applyConsumerFiltering(filters);
6695
8183
  const result = await this.teamRepo.read_all(processedFilters);
8184
+ const dtos = result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity));
8185
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromTeam))];
8186
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
6696
8187
  return {
6697
- items: result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity)),
8188
+ items: dtos.map((t) => enrichTeam(t, displayMap)),
6698
8189
  pageInfo: result.pageInfo
6699
8190
  };
6700
8191
  }
@@ -6721,30 +8212,38 @@ ReadAllTeamsFeatureImpl = __decorate([
6721
8212
  injectable(),
6722
8213
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6723
8214
  __decorateParam(1, injectSession()),
6724
- __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo))
8215
+ __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
8216
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6725
8217
  ], ReadAllTeamsFeatureImpl);
6726
8218
 
6727
8219
  //#endregion
6728
8220
  //#region src/slices/team/features/read_team_feat.ts
6729
8221
  let ReadTeamFeatureImpl = class ReadTeamFeatureImpl$1 {
6730
- constructor(teamRepo) {
8222
+ constructor(teamRepo, userDisplayLookup) {
6731
8223
  this.teamRepo = teamRepo;
8224
+ this.userDisplayLookup = userDisplayLookup;
6732
8225
  }
6733
8226
  async execute(id) {
6734
8227
  const teamEntity = await this.teamRepo.read(id);
6735
8228
  if (!teamEntity) return null;
6736
- return mapTeamEntityToDto(teamEntity);
8229
+ const dto = mapTeamEntityToDto(teamEntity);
8230
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6737
8231
  }
6738
8232
  };
6739
- ReadTeamFeatureImpl = __decorate([injectable(), __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository))], ReadTeamFeatureImpl);
8233
+ ReadTeamFeatureImpl = __decorate([
8234
+ injectable(),
8235
+ __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
8236
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
8237
+ ], ReadTeamFeatureImpl);
6740
8238
 
6741
8239
  //#endregion
6742
8240
  //#region src/slices/team/features/update_team_feat.ts
6743
8241
  let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6744
- constructor(teamRepo, session, create_record_version) {
8242
+ constructor(teamRepo, session, create_record_version, userDisplayLookup) {
6745
8243
  this.teamRepo = teamRepo;
6746
8244
  this.session = session;
6747
8245
  this.create_record_version = create_record_version;
8246
+ this.userDisplayLookup = userDisplayLookup;
6748
8247
  }
6749
8248
  async execute(input) {
6750
8249
  const existingTeam = await this.teamRepo.read(input.id);
@@ -6796,7 +8295,8 @@ let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6796
8295
  auth_role: this.session.user.user_type,
6797
8296
  auth_username: this.session.user.username
6798
8297
  });
6799
- return mapTeamEntityToDto(teamEntity);
8298
+ const dto = mapTeamEntityToDto(teamEntity);
8299
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6800
8300
  }
6801
8301
  /**
6802
8302
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6809,7 +8309,8 @@ UpdateTeamFeatureImpl = __decorate([
6809
8309
  injectable(),
6810
8310
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6811
8311
  __decorateParam(1, injectSession()),
6812
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8312
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8313
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6813
8314
  ], UpdateTeamFeatureImpl);
6814
8315
 
6815
8316
  //#endregion
@@ -7027,13 +8528,33 @@ let TeamMemberRepo = class TeamMemberRepo$1 {
7027
8528
  };
7028
8529
  TeamMemberRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], TeamMemberRepo);
7029
8530
 
8531
+ //#endregion
8532
+ //#region src/slices/team_member/features/enrich_team_member.ts
8533
+ /**
8534
+ * Enriches a TeamMemberReadDto with display names from a lookup map.
8535
+ */
8536
+ function enrichTeamMember(dto, displayMap) {
8537
+ return {
8538
+ ...dto,
8539
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null,
8540
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
8541
+ };
8542
+ }
8543
+ /**
8544
+ * Collects user IDs from a team member for display name lookup.
8545
+ */
8546
+ function collectUserIdsFromTeamMember(member) {
8547
+ return [member.created_by, member.updated_by].filter((id) => Boolean(id));
8548
+ }
8549
+
7030
8550
  //#endregion
7031
8551
  //#region src/slices/team_member/features/create_team_member_feat.ts
7032
8552
  let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7033
- constructor(session, teamMemberRepo, create_record_version) {
8553
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7034
8554
  this.session = session;
7035
8555
  this.teamMemberRepo = teamMemberRepo;
7036
8556
  this.create_record_version = create_record_version;
8557
+ this.userDisplayLookup = userDisplayLookup;
7037
8558
  }
7038
8559
  async execute(input) {
7039
8560
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7065,14 +8586,16 @@ let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7065
8586
  auth_role: this.session.user.user_type,
7066
8587
  auth_username: this.session.user.username
7067
8588
  });
7068
- return teamMemberCreated;
8589
+ const dto = teamMemberCreated;
8590
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7069
8591
  }
7070
8592
  };
7071
8593
  CreateTeamMemberFeat = __decorate([
7072
8594
  injectable(),
7073
8595
  __decorateParam(0, injectSession()),
7074
8596
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7075
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8597
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8598
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7076
8599
  ], CreateTeamMemberFeat);
7077
8600
 
7078
8601
  //#endregion
@@ -7126,14 +8649,27 @@ DeleteTeamMemberFeat = __decorate([
7126
8649
  //#endregion
7127
8650
  //#region src/slices/team_member/features/get_team_members_feat.ts
7128
8651
  let GetTeamMembersFeat = class GetTeamMembersFeat$1 {
7129
- constructor(teamMemberRepo) {
8652
+ constructor(teamMemberRepo, userDisplayLookup) {
7130
8653
  this.teamMemberRepo = teamMemberRepo;
8654
+ this.userDisplayLookup = userDisplayLookup;
7131
8655
  }
7132
8656
  async execute(filters) {
7133
- return await this.teamMemberRepo.getAll(filters);
8657
+ const result = await this.teamMemberRepo.getAll(filters);
8658
+ const items = result.items;
8659
+ const userIds = [...new Set(items.flatMap(collectUserIdsFromTeamMember))];
8660
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
8661
+ const enrichedItems = items.map((m) => enrichTeamMember(m, displayMap));
8662
+ return {
8663
+ ...result,
8664
+ items: enrichedItems
8665
+ };
7134
8666
  }
7135
8667
  };
7136
- GetTeamMembersFeat = __decorate([injectable(), __decorateParam(0, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo))], GetTeamMembersFeat);
8668
+ GetTeamMembersFeat = __decorate([
8669
+ injectable(),
8670
+ __decorateParam(0, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
8671
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
8672
+ ], GetTeamMembersFeat);
7137
8673
 
7138
8674
  //#endregion
7139
8675
  //#region src/slices/team_member/features/get_user_team_members_feat.ts
@@ -7200,10 +8736,11 @@ GetUserTeamsFeat = __decorate([
7200
8736
  //#endregion
7201
8737
  //#region src/slices/team_member/features/update_team_member_feat.ts
7202
8738
  let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7203
- constructor(session, teamMemberRepo, create_record_version) {
8739
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7204
8740
  this.session = session;
7205
8741
  this.teamMemberRepo = teamMemberRepo;
7206
8742
  this.create_record_version = create_record_version;
8743
+ this.userDisplayLookup = userDisplayLookup;
7207
8744
  }
7208
8745
  async execute(input) {
7209
8746
  const existingTeamMember = await this.teamMemberRepo.getById(input.id);
@@ -7254,14 +8791,16 @@ let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7254
8791
  auth_role: this.session.user.user_type,
7255
8792
  auth_username: this.session.user.username
7256
8793
  });
7257
- return teamMemberUpdated;
8794
+ const dto = teamMemberUpdated;
8795
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7258
8796
  }
7259
8797
  };
7260
8798
  UpdateTeamMemberFeat = __decorate([
7261
8799
  injectable(),
7262
8800
  __decorateParam(0, injectSession()),
7263
8801
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7264
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8802
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8803
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7265
8804
  ], UpdateTeamMemberFeat);
7266
8805
 
7267
8806
  //#endregion
@@ -7325,6 +8864,21 @@ let UserRepo = class UserRepo$1 {
7325
8864
  this.logger.perf(`UserRepo.read_user_by_email: ${queryTime.toFixed(2)}ms for email ${email}`);
7326
8865
  return result[0];
7327
8866
  }
8867
+ async read_users_by_emails(emails) {
8868
+ if (emails.length === 0) return [];
8869
+ const { ...rest } = getTableColumns(user_table);
8870
+ return this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(inArray(user_table.email, emails)));
8871
+ }
8872
+ /**
8873
+ * Case-insensitive email lookup. Use when matching against values that may
8874
+ * differ in casing (e.g. emails stored in other tables).
8875
+ */
8876
+ async read_users_by_emails_case_insensitive(emails) {
8877
+ if (emails.length === 0) return [];
8878
+ const { ...rest } = getTableColumns(user_table);
8879
+ const lowerEmails = [...new Set(emails.map((e) => e.toLowerCase()))];
8880
+ return this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(sql`LOWER(${user_table.email}) IN (${sql.join(lowerEmails.map((e) => sql`${e}`), sql`, `)})`));
8881
+ }
7328
8882
  async read_user_by_username(username) {
7329
8883
  const { ...rest } = getTableColumns(user_table);
7330
8884
  return (await this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(eq(user_table.username, username)).limit(1)))[0];
@@ -7434,6 +8988,26 @@ CreateUser = __decorate([
7434
8988
  __decorateParam(3, inject(TOKENS.LOGGER))
7435
8989
  ], CreateUser);
7436
8990
 
8991
+ //#endregion
8992
+ //#region src/slices/user/features/user_display_lookup_feat.ts
8993
+ let UserDisplayLookupFeat = class UserDisplayLookupFeat$1 {
8994
+ constructor(userRepo) {
8995
+ this.userRepo = userRepo;
8996
+ }
8997
+ async lookupDisplayNames(userIds) {
8998
+ const validIds = [...new Set(userIds)].filter((id) => !!id?.trim()).filter((id) => id.length === 32);
8999
+ if (validIds.length === 0) return /* @__PURE__ */ new Map();
9000
+ const users = await this.userRepo.read_users_by_ids(validIds);
9001
+ const map = /* @__PURE__ */ new Map();
9002
+ for (const u of users) {
9003
+ const display = u.email ?? u.id;
9004
+ map.set(u.id, display);
9005
+ }
9006
+ return map;
9007
+ }
9008
+ };
9009
+ UserDisplayLookupFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS.IUsersRepo))], UserDisplayLookupFeat);
9010
+
7437
9011
  //#endregion
7438
9012
  //#region src/slices/user/features/delete_user.ts
7439
9013
  let DeleteUser = class DeleteUser$1 {
@@ -7511,6 +9085,18 @@ let GetUserFeat = class GetUserFeat$1 {
7511
9085
  };
7512
9086
  GetUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS.IUsersRepo))], GetUserFeat);
7513
9087
 
9088
+ //#endregion
9089
+ //#region src/slices/user/features/get_triage_users_feat.ts
9090
+ let GetTriageUsersFeat = class GetTriageUsersFeat$1 {
9091
+ constructor(supportStaffRepo) {
9092
+ this.supportStaffRepo = supportStaffRepo;
9093
+ }
9094
+ async execute() {
9095
+ return this.supportStaffRepo.readTriageUsers();
9096
+ }
9097
+ };
9098
+ GetTriageUsersFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], GetTriageUsersFeat);
9099
+
7514
9100
  //#endregion
7515
9101
  //#region src/slices/user/features/get_users_for_selection_feat.ts
7516
9102
  let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
@@ -7518,7 +9104,7 @@ let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
7518
9104
  this.repo = repo;
7519
9105
  }
7520
9106
  async execute() {
7521
- return (await this.repo.read_users_by_types(["lead", "consumer"])).map((user) => ({
9107
+ return (await this.repo.read_users_by_types([...USER_TYPES])).map((user) => ({
7522
9108
  id: user.id,
7523
9109
  email: user.email
7524
9110
  }));
@@ -7662,6 +9248,7 @@ UpdateUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS
7662
9248
  //#region src/slices/user/user_container.ts
7663
9249
  function registerUserContainer() {
7664
9250
  container.registerSingleton(USER_TOKENS.IUsersRepo, UserRepo);
9251
+ container.registerSingleton(USER_TOKENS.IUserDisplayLookup, UserDisplayLookupFeat);
7665
9252
  container.registerSingleton(USER_TOKENS.IReadAllUsers, ReadAllUsers);
7666
9253
  container.registerSingleton(USER_TOKENS.ISignUpUser, SignUpUser);
7667
9254
  container.registerSingleton(USER_TOKENS.IDeleteUser, DeleteUser);
@@ -7672,6 +9259,7 @@ function registerUserContainer() {
7672
9259
  container.registerSingleton(USER_TOKENS.IGetUserFeature, GetUserFeat);
7673
9260
  container.registerSingleton(USER_TOKENS.IUpdateUserFeature, UpdateUserFeat);
7674
9261
  container.registerSingleton(USER_TOKENS.IGetUsersForSelectionFeature, GetUsersForSelectionFeat);
9262
+ container.registerSingleton(USER_TOKENS.IGetTriageUsersFeature, GetTriageUsersFeat);
7675
9263
  }
7676
9264
 
7677
9265
  //#endregion
@@ -7691,6 +9279,11 @@ let UserProfileRepo = class UserProfileRepo$1 {
7691
9279
  const { ...rest } = getTableColumns(user_profile_table);
7692
9280
  return (await this.router.queryAll((db) => db.select({ ...rest }).from(user_profile_table).where(eq(user_profile_table.user_id, user_id)).limit(1)))[0];
7693
9281
  }
9282
+ async read_user_profiles_by_user_ids(user_ids) {
9283
+ if (user_ids.length === 0) return [];
9284
+ const { ...rest } = getTableColumns(user_profile_table);
9285
+ return this.router.queryAll((db) => db.select({ ...rest }).from(user_profile_table).where(inArray(user_profile_table.user_id, user_ids)));
9286
+ }
7694
9287
  async update_user_profile(user_profileRecord) {
7695
9288
  user_profileRecord.updated_at = (/* @__PURE__ */ new Date()).toISOString();
7696
9289
  const { created_at, ...rest } = user_profileRecord;
@@ -7699,16 +9292,6 @@ let UserProfileRepo = class UserProfileRepo$1 {
7699
9292
  };
7700
9293
  UserProfileRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], UserProfileRepo);
7701
9294
 
7702
- //#endregion
7703
- //#region src/slices/user_profile/user_profile_interfaces.ts
7704
- const USER_PROFILE_TOKENS = {
7705
- IUserProfilesRepo: Symbol("IUserProfilesRepo"),
7706
- IReadUserProfilesByUser: Symbol("IReadUserProfilesByUser"),
7707
- IReadUserProfile: Symbol("IReadUserProfile"),
7708
- ICreateUserProfile: Symbol("ICreateUserProfile"),
7709
- IUpdateUserProfile: Symbol("IUpdateUserProfile")
7710
- };
7711
-
7712
9295
  //#endregion
7713
9296
  //#region src/slices/user_profile/features/read_user_profile.ts
7714
9297
  let ReadUserProfile = class ReadUserProfile$1 {
@@ -8271,14 +9854,17 @@ function registerCoreContainer() {
8271
9854
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.PrefixRegistry, DisplayIdPrefixRegistry);
8272
9855
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.IDisplayIdPrefixService, DisplayIdPrefixService);
8273
9856
  registerPasswordResetContainer();
9857
+ registerRecordSubscriberDependencies();
8274
9858
  registerRecordVersionContainer();
9859
+ registerSavedFilterContainer();
9860
+ registerSupportStaffDependencies();
8275
9861
  registerUserContainer();
8276
9862
  registerUserProfileContainer();
8277
9863
  registerUserSessionContainer();
8278
9864
  registerCustomerDependencies();
8279
9865
  registerSupportTicketDependencies();
8280
9866
  registerAttachmentContainer();
8281
- registerAppSettingsContainer();
9867
+ /* @__PURE__ */ registerAppSettingsContainer();
8282
9868
  registerNoteContainer();
8283
9869
  registerTeamDependencies();
8284
9870
  registerTeamMemberContainer();
@@ -8315,37 +9901,15 @@ function createRequestContainer(config, additionalRegistrations) {
8315
9901
  //#endregion
8316
9902
  //#region src/slices/app_settings/app-settings-api.server.ts
8317
9903
  var AppSettingsApiServer = class extends RpcTarget {
8318
- constructor(container$1) {
9904
+ constructor(_container) {
8319
9905
  super();
8320
- this.container = container$1;
8321
- }
8322
- async getNotificationEmails() {
8323
- return rpcMethodPartial({
8324
- auth: "admin",
8325
- container: this.container,
8326
- context: "AppSettingsApiServer.getNotificationEmails",
8327
- input: void 0,
8328
- outputSchema: ReadNotificationEmailsSchema,
8329
- execute: async (_, container$1) => {
8330
- return { emails: (await container$1.resolve(APP_SETTINGS_TOKENS.IGetNotificationEmailsFeature).execute()).emails.map((e) => ({ email: e.email })) };
8331
- }
8332
- });
8333
- }
8334
- async updateNotificationEmails(input) {
8335
- return rpcMethod({
8336
- auth: "admin",
8337
- container: this.container,
8338
- context: "AppSettingsApiServer.updateNotificationEmails",
8339
- input,
8340
- inputSchema: UpdateNotificationEmailsSchema,
8341
- outputSchema: UpdateNotificationEmailsSchema,
8342
- execute: async (input$1, container$1) => {
8343
- return await container$1.resolve(APP_SETTINGS_TOKENS.IUpdateNotificationEmailsFeature).execute(input$1);
8344
- }
8345
- });
8346
9906
  }
8347
9907
  };
8348
9908
 
9909
+ //#endregion
9910
+ //#region src/slices/app_settings/app_settings_tokens.ts
9911
+ const APP_SETTINGS_TOKENS = {};
9912
+
8349
9913
  //#endregion
8350
9914
  //#region src/slices/attachment/attachment-api.server.ts
8351
9915
  const CreateFolderInputSchema = z.object({
@@ -8669,12 +10233,211 @@ var RecordVersionApiServer = class extends RpcTarget {
8669
10233
  }
8670
10234
  });
8671
10235
  }
10236
+ async listTrackerActivityVersions(trackerId, filters) {
10237
+ return rpcMethod({
10238
+ auth: "protected",
10239
+ container: this.container,
10240
+ context: "RecordVersionApiServer.listTrackerActivityVersions",
10241
+ input: {
10242
+ tracker_id: trackerId,
10243
+ filters
10244
+ },
10245
+ inputSchema: recordVersionTrackerActivityInputSchema,
10246
+ outputSchema: recordVersionPageBreadcrumbSchema,
10247
+ execute: async ({ tracker_id, filters: f }, container$1) => {
10248
+ return await container$1.resolve(RECORD_VERSION_TOKENS.IReadTrackerActivityVersions).execute(tracker_id, f || {});
10249
+ }
10250
+ });
10251
+ }
10252
+ };
10253
+
10254
+ //#endregion
10255
+ //#region src/slices/support_staff/support-staff-api.server.ts
10256
+ const SupportStaffMemberSchema = z.object({
10257
+ id: z.string(),
10258
+ user_id: z.string(),
10259
+ email: z.string(),
10260
+ created_at: z.string()
10261
+ });
10262
+ var SupportStaffApiServer = class extends RpcTarget {
10263
+ constructor(container$1) {
10264
+ super();
10265
+ this.container = container$1;
10266
+ }
10267
+ async listSupportStaff() {
10268
+ return rpcMethodPartial({
10269
+ auth: "admin",
10270
+ container: this.container,
10271
+ context: "SupportStaffApiServer.listSupportStaff",
10272
+ input: void 0,
10273
+ outputSchema: z.array(SupportStaffMemberSchema),
10274
+ execute: async (_, container$1) => {
10275
+ return container$1.resolve(SUPPORT_STAFF_TOKENS.IListSupportStaffFeature).execute();
10276
+ }
10277
+ });
10278
+ }
10279
+ async addSupportStaff(userId) {
10280
+ return rpcMethod({
10281
+ auth: "admin",
10282
+ container: this.container,
10283
+ context: "SupportStaffApiServer.addSupportStaff",
10284
+ input: userId,
10285
+ inputSchema: z.string(),
10286
+ outputSchema: SupportStaffMemberSchema,
10287
+ execute: async (userId$1, container$1) => {
10288
+ return container$1.resolve(SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature).execute(userId$1);
10289
+ }
10290
+ });
10291
+ }
10292
+ async removeSupportStaff(userId) {
10293
+ return rpcMethod({
10294
+ auth: "admin",
10295
+ container: this.container,
10296
+ context: "SupportStaffApiServer.removeSupportStaff",
10297
+ input: userId,
10298
+ inputSchema: z.string(),
10299
+ outputSchema: z.void(),
10300
+ execute: async (userId$1, container$1) => {
10301
+ await container$1.resolve(SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature).execute(userId$1);
10302
+ }
10303
+ });
10304
+ }
10305
+ };
10306
+
10307
+ //#endregion
10308
+ //#region src/slices/saved_filter/saved-filter-api.server.ts
10309
+ var SavedFilterApiServer = class extends RpcTarget {
10310
+ constructor(container$1) {
10311
+ super();
10312
+ this.container = container$1;
10313
+ }
10314
+ async listSavedFilters(context) {
10315
+ return rpcMethod({
10316
+ auth: "protected",
10317
+ container: this.container,
10318
+ context: "SavedFilterApiServer.listSavedFilters",
10319
+ input: context,
10320
+ inputSchema: z.string(),
10321
+ outputSchema: z.array(SavedFilterReadSchema),
10322
+ execute: async (context$1, container$1) => {
10323
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IListSavedFilters).execute(context$1);
10324
+ }
10325
+ });
10326
+ }
10327
+ async listAllSavedFilters() {
10328
+ return rpcMethod({
10329
+ auth: "protected",
10330
+ container: this.container,
10331
+ context: "SavedFilterApiServer.listAllSavedFilters",
10332
+ input: void 0,
10333
+ inputSchema: z.undefined(),
10334
+ outputSchema: z.array(SavedFilterReadSchema),
10335
+ execute: async (_input, container$1) => {
10336
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IListAllSavedFilters).execute();
10337
+ }
10338
+ });
10339
+ }
10340
+ async createSavedFilter(input) {
10341
+ return rpcMethod({
10342
+ auth: "protected",
10343
+ container: this.container,
10344
+ context: "SavedFilterApiServer.createSavedFilter",
10345
+ input,
10346
+ inputSchema: SavedFilterCreateSchema,
10347
+ outputSchema: SavedFilterReadSchema,
10348
+ execute: async (input$1, container$1) => {
10349
+ return await container$1.resolve(SAVED_FILTER_TOKENS.ICreateSavedFilter).execute(input$1);
10350
+ }
10351
+ });
10352
+ }
10353
+ async updateSavedFilter(input) {
10354
+ return rpcMethod({
10355
+ auth: "protected",
10356
+ container: this.container,
10357
+ context: "SavedFilterApiServer.updateSavedFilter",
10358
+ input,
10359
+ inputSchema: SavedFilterUpdateSchema,
10360
+ outputSchema: SavedFilterReadSchema.nullable(),
10361
+ execute: async (input$1, container$1) => {
10362
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IUpdateSavedFilter).execute(input$1);
10363
+ }
10364
+ });
10365
+ }
10366
+ async deleteSavedFilter(id) {
10367
+ return rpcMethod({
10368
+ auth: "protected",
10369
+ container: this.container,
10370
+ context: "SavedFilterApiServer.deleteSavedFilter",
10371
+ input: id,
10372
+ inputSchema: z.string(),
10373
+ outputSchema: z.boolean(),
10374
+ execute: async (id$1, container$1) => {
10375
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IDeleteSavedFilter).execute(id$1);
10376
+ }
10377
+ });
10378
+ }
10379
+ async listPinnedPresets() {
10380
+ return rpcMethod({
10381
+ auth: "protected",
10382
+ container: this.container,
10383
+ context: "SavedFilterApiServer.listPinnedPresets",
10384
+ input: void 0,
10385
+ inputSchema: z.undefined(),
10386
+ outputSchema: z.array(SavedFilterReadSchema),
10387
+ execute: async (_input, container$1) => {
10388
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IListPinnedPresets).execute();
10389
+ }
10390
+ });
10391
+ }
10392
+ async addPinnedPreset(presetId) {
10393
+ return rpcMethod({
10394
+ auth: "protected",
10395
+ container: this.container,
10396
+ context: "SavedFilterApiServer.addPinnedPreset",
10397
+ input: presetId,
10398
+ inputSchema: z.string(),
10399
+ outputSchema: SavedFilterReadSchema.nullable(),
10400
+ execute: async (presetId$1, container$1) => {
10401
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IAddPinnedPreset).execute(presetId$1);
10402
+ }
10403
+ });
10404
+ }
10405
+ async removePinnedPreset(presetId) {
10406
+ return rpcMethod({
10407
+ auth: "protected",
10408
+ container: this.container,
10409
+ context: "SavedFilterApiServer.removePinnedPreset",
10410
+ input: presetId,
10411
+ inputSchema: z.string(),
10412
+ outputSchema: z.boolean(),
10413
+ execute: async (presetId$1, container$1) => {
10414
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset).execute(presetId$1);
10415
+ }
10416
+ });
10417
+ }
10418
+ async reorderPinnedPresets(presetIds) {
10419
+ return rpcMethod({
10420
+ auth: "protected",
10421
+ container: this.container,
10422
+ context: "SavedFilterApiServer.reorderPinnedPresets",
10423
+ input: presetIds,
10424
+ inputSchema: z.array(z.string()),
10425
+ outputSchema: z.void(),
10426
+ execute: async (presetIds$1, container$1) => {
10427
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets).execute(presetIds$1);
10428
+ }
10429
+ });
10430
+ }
8672
10431
  };
8673
10432
 
8674
10433
  //#endregion
8675
10434
  //#region src/slices/support_ticket/support-ticket-api.server.ts
8676
10435
  const CancelInternalTaskInputSchema = z.object({ id: z.string() });
8677
10436
  const ReactivateInternalTaskInputSchema = z.object({ id: z.string() });
10437
+ const UsersForSelectionSchema$1 = z.array(z.object({
10438
+ id: z.string(),
10439
+ email: z.string()
10440
+ }));
8678
10441
  var SupportTicketApiServer = class extends RpcTarget {
8679
10442
  constructor(container$1) {
8680
10443
  super();
@@ -8732,6 +10495,31 @@ var SupportTicketApiServer = class extends RpcTarget {
8732
10495
  }
8733
10496
  });
8734
10497
  }
10498
+ async toggleSubscription(supportTicketId) {
10499
+ return rpcMethod({
10500
+ auth: "protected",
10501
+ container: this.container,
10502
+ context: "SupportTicketApiServer.toggleSubscription",
10503
+ input: supportTicketId,
10504
+ inputSchema: z.string(),
10505
+ outputSchema: z.object({ subscribed: z.boolean() }),
10506
+ execute: async (id, container$1) => {
10507
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature).execute(id);
10508
+ }
10509
+ });
10510
+ }
10511
+ async getRequestorsForActiveTickets() {
10512
+ return rpcMethodPartial({
10513
+ auth: "protected",
10514
+ container: this.container,
10515
+ context: "SupportTicketApiServer.getRequestorsForActiveTickets",
10516
+ input: void 0,
10517
+ outputSchema: UsersForSelectionSchema$1,
10518
+ execute: async (_, container$1) => {
10519
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature).execute({ excludeInternal: true });
10520
+ }
10521
+ });
10522
+ }
8735
10523
  async staffCreateTicket(input) {
8736
10524
  return rpcMethod({
8737
10525
  auth: "admin",
@@ -8784,6 +10572,18 @@ var SupportTicketApiServer = class extends RpcTarget {
8784
10572
  }
8785
10573
  });
8786
10574
  }
10575
+ async staffGetRequestorsForActiveTickets() {
10576
+ return rpcMethodPartial({
10577
+ auth: "staff",
10578
+ container: this.container,
10579
+ context: "SupportTicketApiServer.staffGetRequestorsForActiveTickets",
10580
+ input: void 0,
10581
+ outputSchema: UsersForSelectionSchema$1,
10582
+ execute: async (_, container$1) => {
10583
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature).execute({ excludeInternal: false });
10584
+ }
10585
+ });
10586
+ }
8787
10587
  async approveTicket(input) {
8788
10588
  return rpcMethod({
8789
10589
  auth: "admin",
@@ -8901,6 +10701,78 @@ var SupportTicketApiServer = class extends RpcTarget {
8901
10701
  }
8902
10702
  });
8903
10703
  }
10704
+ async archiveTicket(input) {
10705
+ return rpcMethod({
10706
+ auth: "admin",
10707
+ container: this.container,
10708
+ context: "SupportTicketApiServer.archiveTicket",
10709
+ input,
10710
+ inputSchema: ArchiveSupportTicketSchema,
10711
+ outputSchema: StaffSupportTicketReadSchema,
10712
+ execute: async (input$1, container$1) => {
10713
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature).execute(input$1.id);
10714
+ }
10715
+ });
10716
+ }
10717
+ async staffListSubscribers(supportTicketId) {
10718
+ return rpcMethod({
10719
+ auth: "admin",
10720
+ container: this.container,
10721
+ context: "SupportTicketApiServer.staffListSubscribers",
10722
+ input: supportTicketId,
10723
+ inputSchema: z.string(),
10724
+ outputSchema: z.array(RecordSubscriberReadSchema),
10725
+ execute: async (id, container$1) => {
10726
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature).execute(id);
10727
+ }
10728
+ });
10729
+ }
10730
+ async staffAddSubscriber(input) {
10731
+ return rpcMethod({
10732
+ auth: "admin",
10733
+ container: this.container,
10734
+ context: "SupportTicketApiServer.staffAddSubscriber",
10735
+ input,
10736
+ inputSchema: SupportTicketSubscriberCreateSchema,
10737
+ outputSchema: RecordSubscriberReadSchema,
10738
+ execute: async (validated, container$1) => {
10739
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature).execute(validated);
10740
+ }
10741
+ });
10742
+ }
10743
+ async staffRemoveSubscriber(input) {
10744
+ return rpcMethod({
10745
+ auth: "admin",
10746
+ container: this.container,
10747
+ context: "SupportTicketApiServer.staffRemoveSubscriber",
10748
+ input,
10749
+ inputSchema: z.object({
10750
+ supportTicketId: z.string(),
10751
+ subscriberId: z.string()
10752
+ }),
10753
+ outputSchema: z.void(),
10754
+ execute: async (validated, container$1) => {
10755
+ await container$1.resolve(SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature).execute(validated.supportTicketId, validated.subscriberId);
10756
+ }
10757
+ });
10758
+ }
10759
+ async staffFixSupportTicketUserIds() {
10760
+ return rpcMethod({
10761
+ auth: "admin",
10762
+ container: this.container,
10763
+ context: "SupportTicketApiServer.staffFixSupportTicketUserIds",
10764
+ input: void 0,
10765
+ inputSchema: z.undefined(),
10766
+ outputSchema: z.object({
10767
+ ticketsScanned: z.number(),
10768
+ ticketsFixed: z.number(),
10769
+ ticketsSkipped: z.number()
10770
+ }),
10771
+ execute: async (_input, container$1) => {
10772
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature).execute();
10773
+ }
10774
+ });
10775
+ }
8904
10776
  };
8905
10777
 
8906
10778
  //#endregion
@@ -9189,6 +11061,18 @@ var UserApiServer = class extends RpcTarget {
9189
11061
  }
9190
11062
  });
9191
11063
  }
11064
+ async getTriageUsers() {
11065
+ return rpcMethodPartial({
11066
+ auth: "admin",
11067
+ container: this.container,
11068
+ context: "UserApiServer.getTriageUsers",
11069
+ input: void 0,
11070
+ outputSchema: UsersForSelectionSchema,
11071
+ execute: async (_, container$1) => {
11072
+ return await container$1.resolve(USER_TOKENS.IGetTriageUsersFeature).execute();
11073
+ }
11074
+ });
11075
+ }
9192
11076
  };
9193
11077
 
9194
11078
  //#endregion
@@ -9380,5 +11264,5 @@ var UserSessionApiServer = class extends RpcTarget {
9380
11264
  };
9381
11265
 
9382
11266
  //#endregion
9383
- export { APP_SETTINGS_TOKENS, ATTACHMENT_FOLDER_TOKENS, ATTACHMENT_TOKENS, AalLevel, AddCreditsFeat, AppSettingsApiServer, AttachmentApiServer, AttachmentFolderRepo, AttachmentRepo, AuthErrorCode, AuthenticationError, BreadcrumbUtils, BusinessError, CREDIT_SERVICE_TOKEN, CREDIT_TRANSACTION_REPO_TOKEN, CUSTOMER_TOKENS, ChangeUserPassword, CodeChallengeMethod, CookieService, CreateTeamFeatureImpl, CreateTeamMemberFeat, CreditService, CreditTransactionRepo, CursorUtils, CustomerApiServer, DISPLAY_ID_PREFIX_TOKENS, DatabaseRouter, DeleteTeamFeatureImpl, DeleteTeamMemberFeat, DirectDatabaseAdapter, DisplayIdPrefixRegistry, DisplayIdPrefixService, EXTENSION_INTERVAL_MS, EmailService, FactorStatus, FactorType, ForgotPassword, GetCreditTransactionsFeat, GetTeamMembersFeat, GetUserTeamMembersFeat, GetUserTeamsFeat, ID_ERRORS, INACTIVITY_TIMEOUT_MS, IdComponentType, InternalServerError, InvalidCredentialsError, InvalidRefreshTokenError, JWT_TOKENS, KeyStatus, KeyType, LOG_LEVEL, Logger, LoginUserSession, MAX_SESSION_LIFETIME_MS, NOTE_TOKENS, NoteApiServer, NoteRepo, OperationConst, PASSWORD_RESET_TOKENS, PaginationUtils, PasswordResetApiServer, PasswordService, PricingPlanInterval, PricingType, RECORD_VERSION_TOKENS, RECORD_VERSION_VALIDATION_TOKENS, ReadAllTeamsFeatureImpl, ReadAllUserSessions, RecordAccessValidatorRegistry, RecordConst, RecordVersionApiServer, RefreshTokenRepo, RefreshTokenSession, RequestStatus, ResetMonthlyBalanceFeat, ResetPassword, RevokeRefreshToken, SUPPORT_TICKET_TOKENS, SessionNotFoundError, SetMonthlyAllocationFeat, SubscriptionStatus, SupportTicketApiServer, SupportTicketRepo, TEAM_MEMBER_TOKENS, TEAM_TOKENS, TOKENS, TeamApiServer, TeamMemberApiServer, TeamMemberRepo, TeamRepositoryImpl, TenantContext, TokenFamilyReusedError, TransactionTypeEnum, USER_PROFILE_TOKENS, USER_SESSION_TOKENS, USER_TOKENS, UniversalIdGenerator, UpdateTeamFeatureImpl, UpdateTeamMemberFeat, UserApiServer, UserNotFoundError, UserProfileApiServer, UserRepo, UserSessionApiServer, ValidationError, applyCookiesToResponse, applyFilter, archiveConditions, attachment_folder_table, attachment_table, buildContainerFactories, checkAuth, combineConditions, createAuthenticatedState, createBackendRegistry, createContainerSetupMiddleware, createDefaultContainerFactories, createDefaultContainerSetupConfig, createDefaultSessionValidationConfig, createExpiredState, createFilterBuilder, createHonoErrorFactory, createIsAuthenticatedMiddleware, createLoggerHelpers, createRequestContainer, createRevokedState, createUnauthenticatedState, credit_transaction_table, customNanoid, custom_long_nanoid, deriveColumnMap, findR2Bucket, formatCreditValue, generateAccessToken, generatePasswordResetToken, generateRefreshToken, generateUserDetailsToken, getAuthenticatedSession, getChangedProperties, getR2BucketBindingName, get_aliased_table_columns, injectSession, isCreditValueEmpty, logger, mfa_secret_table, note_table, oauth_provider_table, record_version_table, refresh_token_table, registerAppSettingsContainer, registerAttachmentContainer, registerAuthenticatedSession, registerCoreContainer, registerCustomerDependencies, registerDisplayIdPrefixesFromMap, registerNoteContainer, registerPasswordResetContainer, registerRecordVersionContainer, registerSupportTicketDependencies, registerTeamDependencies, registerTeamMemberContainer, registerUserContainer, registerUserProfileContainer, registerUserSessionContainer, requireAdmin, requireAuthenticated, requireLeadOrStaff, requireStaff, resolveTeamAccess, rpcMethod, rpcMethodPartial, rpcMethodSimple, searchOrCondition, support_ticket_table, teamCondition, team_member_table, team_table, user_oauth_account_table, user_profile_table, user_table, validateInput, validateOutput, validateSessionFromJWT, verifyToken, withErrorHandling };
11267
+ export { APP_SETTINGS_TOKENS, ATTACHMENT_FOLDER_TOKENS, ATTACHMENT_TOKENS, AalLevel, AddCreditsFeat, AppSettingKey, AppSettingsApiServer, AppSettingsRepo, AttachmentApiServer, AttachmentFolderRepo, AttachmentRepo, AuthErrorCode, AuthenticationError, BreadcrumbUtils, BusinessError, CREDIT_SERVICE_TOKEN, CREDIT_TRANSACTION_REPO_TOKEN, CUSTOMER_TOKENS, ChangeUserPassword, CodeChallengeMethod, CookieService, CreateTeamFeatureImpl, CreateTeamMemberFeat, CreditService, CreditTransactionRepo, CursorUtils, CustomerApiServer, DISPLAY_ID_PREFIX_TOKENS, DatabaseRouter, DeleteTeamFeatureImpl, DeleteTeamMemberFeat, DirectDatabaseAdapter, DisplayIdPrefixRegistry, DisplayIdPrefixService, EXTENSION_INTERVAL_MS, EmailService, FactorStatus, FactorType, ForgotPassword, GetCreditTransactionsFeat, GetTeamMembersFeat, GetUserTeamMembersFeat, GetUserTeamsFeat, ID_ERRORS, INACTIVITY_TIMEOUT_MS, IdComponentType, InternalServerError, InvalidCredentialsError, InvalidRefreshTokenError, JWT_TOKENS, KeyStatus, KeyType, LOG_LEVEL, Logger, LoginUserSession, MAX_PINNED_PRESETS, MAX_SESSION_LIFETIME_MS, NOTE_TOKENS, NoteApiServer, NoteRepo, OperationConst, PASSWORD_RESET_TOKENS, PaginationUtils, PasswordResetApiServer, PasswordService, PricingPlanInterval, PricingType, RECORD_SUBSCRIBER_TOKENS, RECORD_VERSION_TOKENS, RECORD_VERSION_VALIDATION_TOKENS, ReadAllTeamsFeatureImpl, ReadAllUserSessions, RecordAccessValidatorRegistry, RecordConst, RecordSubscriberRepo, RecordVersionApiServer, RefreshTokenRepo, RefreshTokenSession, RequestStatus, ResetMonthlyBalanceFeat, ResetPassword, RevokeRefreshToken, SAVED_FILTER_TOKENS, SUPPORT_STAFF_TOKENS, SUPPORT_TICKET_TOKENS, SavedFilterApiServer, SavedFilterRepository, SessionNotFoundError, SetMonthlyAllocationFeat, SubscriptionStatus, SupportStaffApiServer, SupportStaffRepo, SupportTicketApiServer, SupportTicketRepo, TEAM_MEMBER_TOKENS, TEAM_TOKENS, TOKENS, TeamApiServer, TeamMemberApiServer, TeamMemberRepo, TeamRepositoryImpl, TenantContext, TokenFamilyReusedError, TransactionTypeEnum, USER_PROFILE_TOKENS, USER_SESSION_TOKENS, USER_TOKENS, UniversalIdGenerator, UpdateTeamFeatureImpl, UpdateTeamMemberFeat, UserApiServer, UserDisplayLookupFeat, UserNotFoundError, UserProfileApiServer, UserRepo, UserSessionApiServer, ValidationError, app_settings_table, applyCookiesToResponse, applyFilter, archiveConditions, attachment_folder_table, attachment_table, buildContainerFactories, checkAuth, combineConditions, createAuthenticatedState, createBackendRegistry, createContainerSetupMiddleware, createDefaultContainerFactories, createDefaultContainerSetupConfig, createDefaultSessionValidationConfig, createExpiredState, createFilterBuilder, createHonoErrorFactory, createIsAuthenticatedMiddleware, createLoggerHelpers, createRequestContainer, createRevokedState, createUnauthenticatedState, credit_transaction_table, customNanoid, custom_long_nanoid, deriveColumnMap, findR2Bucket, formatCreditValue, generateAccessToken, generatePasswordResetToken, generateRefreshToken, generateUserDetailsToken, getAuthenticatedSession, getChangedProperties, getR2BucketBindingName, get_aliased_table_columns, injectSession, isCreditValueEmpty, logger, mfa_secret_table, note_table, oauth_provider_table, record_subscriber_table, record_version_table, refresh_token_table, registerAppSettingsContainer, registerAttachmentContainer, registerAuthenticatedSession, registerCoreContainer, registerCustomerDependencies, registerDisplayIdPrefixesFromMap, registerNoteContainer, registerPasswordResetContainer, registerRecordSubscriberDependencies, registerRecordVersionContainer, registerSupportTicketDependencies, registerTeamDependencies, registerTeamMemberContainer, registerUserContainer, registerUserProfileContainer, registerUserSessionContainer, requireAdmin, requireAuthenticated, requireLeadOrStaff, requireStaff, resolveTeamAccess, rpcMethod, rpcMethodPartial, rpcMethodSimple, saved_filter_table, searchOrCondition, support_staff_table, support_ticket_table, teamCondition, team_member_table, team_table, user_oauth_account_table, user_pinned_preset_table, user_profile_table, user_table, validateInput, validateOutput, validateSessionFromJWT, verifyToken, withErrorHandling };
9384
11268
  //# sourceMappingURL=index.mjs.map