@dragonmastery/dragoncore-api 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  };
@@ -3963,28 +4075,34 @@ let NoteRepo = class NoteRepo$1 {
3963
4075
  NoteRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], NoteRepo);
3964
4076
 
3965
4077
  //#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")
4078
+ //#region src/slices/user/user_interfaces.ts
4079
+ const USER_TOKENS = {
4080
+ IUserDisplayLookup: Symbol("IUserDisplayLookup"),
4081
+ IUsersRepo: Symbol("IUsersRepo"),
4082
+ ISignUpUser: Symbol("ISignUpUser"),
4083
+ IReadAllUsers: Symbol("IReadAllUsers"),
4084
+ IReadUser: Symbol("IReadUser"),
4085
+ ICreateUser: Symbol("ICreateUser"),
4086
+ IDeleteUser: Symbol("IDeleteUser"),
4087
+ IReadConsumers: Symbol("IReadConsumers"),
4088
+ IGetAllUsersFeature: "IGetAllUsersFeature",
4089
+ IGetUserFeature: "IGetUserFeature",
4090
+ IUpdateUserFeature: "IUpdateUserFeature",
4091
+ IGetUsersForSelectionFeature: "IGetUsersForSelectionFeature",
4092
+ IGetTriageUsersFeature: "IGetTriageUsersFeature"
3977
4093
  };
3978
4094
 
3979
4095
  //#endregion
3980
4096
  //#region src/slices/note/features/create_note_feat.ts
3981
4097
  let CreateNoteFeat = class CreateNoteFeat$1 {
3982
- constructor(session, noteRepo, businessLogicRouter, create_record_version, supportTicketRepo) {
4098
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup, supportTicketRepo, notificationService) {
3983
4099
  this.session = session;
3984
4100
  this.noteRepo = noteRepo;
3985
4101
  this.businessLogicRouter = businessLogicRouter;
3986
4102
  this.create_record_version = create_record_version;
4103
+ this.userDisplayLookup = userDisplayLookup;
3987
4104
  this.supportTicketRepo = supportTicketRepo;
4105
+ this.notificationService = notificationService;
3988
4106
  }
3989
4107
  async execute(input) {
3990
4108
  const processor = this.businessLogicRouter.getCreateProcessor(input.record_type);
@@ -4014,7 +4132,13 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4014
4132
  auth_role: this.session.user.user_type,
4015
4133
  auth_username: this.session.user.username
4016
4134
  });
4017
- return noteCreated;
4135
+ await this.notifySupportTicketSubscribers(processedInput.record_type, processedInput.record_id, noteCreated.is_internal, noteCreated.body ?? "");
4136
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames([noteCreated.created_by]);
4137
+ return {
4138
+ ...noteCreated,
4139
+ created_by_display_name: displayMap.get(noteCreated.created_by) ?? noteCreated.created_by,
4140
+ updated_by_display_name: displayMap.get(noteCreated.updated_by ?? "") ?? noteCreated.updated_by ?? null
4141
+ };
4018
4142
  }
4019
4143
  /**
4020
4144
  * Bumps parent record's updated_at timestamp when note is created
@@ -4036,6 +4160,21 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4036
4160
  default: break;
4037
4161
  }
4038
4162
  }
4163
+ /**
4164
+ * Notify support ticket subscribers when a note is added (best-effort)
4165
+ */
4166
+ async notifySupportTicketSubscribers(recordType, recordId, isInternal, noteBody) {
4167
+ if (recordType !== RecordConst.SUPPORT_TICKET || !this.supportTicketRepo || !this.notificationService) return;
4168
+ try {
4169
+ const ticket = await this.supportTicketRepo.read(recordId);
4170
+ if (!ticket) return;
4171
+ const eventType = isInternal ? "NEW_INTERNAL_NOTE" : "NEW_CUSTOMER_NOTE";
4172
+ await this.notificationService.notifyFollowers(recordId, ticket, eventType, {
4173
+ noteBody: noteBody || void 0,
4174
+ actorUserId: this.session.user.userId
4175
+ });
4176
+ } catch {}
4177
+ }
4039
4178
  };
4040
4179
  CreateNoteFeat = __decorate([
4041
4180
  injectable(),
@@ -4043,7 +4182,9 @@ CreateNoteFeat = __decorate([
4043
4182
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4044
4183
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4045
4184
  __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4046
- __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))
4185
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup)),
4186
+ __decorateParam(5, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
4187
+ __decorateParam(6, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService))
4047
4188
  ], CreateNoteFeat);
4048
4189
 
4049
4190
  //#endregion
@@ -4087,10 +4228,19 @@ DeleteNoteFeat = __decorate([
4087
4228
 
4088
4229
  //#endregion
4089
4230
  //#region src/slices/note/features/get_notes_feat.ts
4231
+ function collectUserIdsFromNotes(notes) {
4232
+ const ids = [];
4233
+ for (const n of notes) {
4234
+ if (n.created_by) ids.push(n.created_by);
4235
+ if (n.updated_by) ids.push(n.updated_by);
4236
+ }
4237
+ return ids;
4238
+ }
4090
4239
  let GetNotesFeat = class GetNotesFeat$1 {
4091
- constructor(noteRepo, businessLogicRouter) {
4240
+ constructor(noteRepo, businessLogicRouter, userDisplayLookup) {
4092
4241
  this.noteRepo = noteRepo;
4093
4242
  this.businessLogicRouter = businessLogicRouter;
4243
+ this.userDisplayLookup = userDisplayLookup;
4094
4244
  }
4095
4245
  async execute(filters) {
4096
4246
  let processedFilters = filters;
@@ -4101,23 +4251,36 @@ let GetNotesFeat = class GetNotesFeat$1 {
4101
4251
  if (processor) processedFilters = await processor.process(filters);
4102
4252
  }
4103
4253
  }
4104
- return await this.noteRepo.read_all(processedFilters);
4254
+ const result = await this.noteRepo.read_all(processedFilters);
4255
+ const userIds = collectUserIdsFromNotes(result.items);
4256
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4257
+ const enrichedItems = result.items.map((n) => ({
4258
+ ...n,
4259
+ created_by_display_name: n.created_by ? displayMap.get(n.created_by) ?? n.created_by : null,
4260
+ updated_by_display_name: n.updated_by ? displayMap.get(n.updated_by) ?? n.updated_by : null
4261
+ }));
4262
+ return {
4263
+ ...result,
4264
+ items: enrichedItems
4265
+ };
4105
4266
  }
4106
4267
  };
4107
4268
  GetNotesFeat = __decorate([
4108
4269
  injectable(),
4109
4270
  __decorateParam(0, inject(NOTE_TOKENS.INoteRepo)),
4110
- __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter))
4271
+ __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4272
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup))
4111
4273
  ], GetNotesFeat);
4112
4274
 
4113
4275
  //#endregion
4114
4276
  //#region src/slices/note/features/update_note_feat.ts
4115
4277
  let UpdateNoteFeat = class UpdateNoteFeat$1 {
4116
- constructor(session, noteRepo, businessLogicRouter, create_record_version) {
4278
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup) {
4117
4279
  this.session = session;
4118
4280
  this.noteRepo = noteRepo;
4119
4281
  this.businessLogicRouter = businessLogicRouter;
4120
4282
  this.create_record_version = create_record_version;
4283
+ this.userDisplayLookup = userDisplayLookup;
4121
4284
  }
4122
4285
  async execute(input) {
4123
4286
  const existingNote = await this.noteRepo.read(input.id);
@@ -4145,7 +4308,13 @@ let UpdateNoteFeat = class UpdateNoteFeat$1 {
4145
4308
  auth_role: this.session.user.user_type,
4146
4309
  auth_username: this.session.user.username
4147
4310
  });
4148
- return noteUpdated;
4311
+ const userIds = [noteUpdated.created_by, noteUpdated.updated_by].filter(Boolean);
4312
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4313
+ return {
4314
+ ...noteUpdated,
4315
+ created_by_display_name: noteUpdated.created_by ? displayMap.get(noteUpdated.created_by) ?? noteUpdated.created_by : null,
4316
+ updated_by_display_name: noteUpdated.updated_by ? displayMap.get(noteUpdated.updated_by) ?? noteUpdated.updated_by : null
4317
+ };
4149
4318
  }
4150
4319
  };
4151
4320
  UpdateNoteFeat = __decorate([
@@ -4153,7 +4322,8 @@ UpdateNoteFeat = __decorate([
4153
4322
  __decorateParam(0, injectSession()),
4154
4323
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4155
4324
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4156
- __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
4325
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4326
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
4157
4327
  ], UpdateNoteFeat);
4158
4328
 
4159
4329
  //#endregion
@@ -4200,22 +4370,6 @@ let PasswordResetTokenVerifier = class PasswordResetTokenVerifier$1 {
4200
4370
  };
4201
4371
  PasswordResetTokenVerifier = __decorate([injectable()], PasswordResetTokenVerifier);
4202
4372
 
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
4373
  //#endregion
4220
4374
  //#region src/slices/password_reset/features/change_password.ts
4221
4375
  let ChangeUserPassword = class ChangeUserPassword$1 {
@@ -4360,6 +4514,51 @@ function registerPasswordResetContainer() {
4360
4514
  container.registerSingleton(PASSWORD_RESET_TOKENS.IChangeUserPassword, ChangeUserPassword);
4361
4515
  }
4362
4516
 
4517
+ //#endregion
4518
+ //#region src/slices/record_subscriber/db/record_subscriber_repo.ts
4519
+ let RecordSubscriberRepo = class RecordSubscriberRepo$1 {
4520
+ constructor(router) {
4521
+ this.router = router;
4522
+ }
4523
+ async create(entity) {
4524
+ const id = await this.router.generateId(RecordConst.RECORD_SUBSCRIBER);
4525
+ const [result] = await this.router.queryLatest((db) => db.insert(record_subscriber_table).values({
4526
+ id,
4527
+ ...entity
4528
+ }).returning());
4529
+ return result;
4530
+ }
4531
+ async ensureSubscriber(entity) {
4532
+ const existing = await this.readByRecordAndUser(entity.record_type, entity.record_id, entity.user_id);
4533
+ if (existing) return existing;
4534
+ return this.create(entity);
4535
+ }
4536
+ async readByRecordId(recordType, recordId) {
4537
+ 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))));
4538
+ }
4539
+ async readByRecordAndUser(recordType, recordId, userId) {
4540
+ 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];
4541
+ }
4542
+ async delete(id) {
4543
+ return (await this.router.queryLatest((db) => db.delete(record_subscriber_table).where(eq(record_subscriber_table.id, id)).returning())).length > 0;
4544
+ }
4545
+ async updateSubscribedEvents(id, subscribedEvents) {
4546
+ const [result] = await this.router.queryLatest((db) => db.update(record_subscriber_table).set({ subscribed_events: subscribedEvents }).where(eq(record_subscriber_table.id, id)).returning());
4547
+ return result;
4548
+ }
4549
+ };
4550
+ RecordSubscriberRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], RecordSubscriberRepo);
4551
+
4552
+ //#endregion
4553
+ //#region src/slices/record_subscriber/record_subscriber_tokens.ts
4554
+ const RECORD_SUBSCRIBER_TOKENS = { IRecordSubscriberRepo: "IRecordSubscriberRepo" };
4555
+
4556
+ //#endregion
4557
+ //#region src/slices/record_subscriber/record_subscriber_registration.ts
4558
+ function registerRecordSubscriberDependencies() {
4559
+ container.registerSingleton(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo, RecordSubscriberRepo);
4560
+ }
4561
+
4363
4562
  //#endregion
4364
4563
  //#region src/slices/team/team_interfaces.ts
4365
4564
  const TEAM_TOKENS = {
@@ -4407,10 +4606,11 @@ var RecordAccessValidatorRegistry = class {
4407
4606
  }
4408
4607
  };
4409
4608
  let ValidateRecordAccess = class ValidateRecordAccess$1 {
4410
- constructor(session, teamRepo, validatorRegistry) {
4609
+ constructor(session, teamRepo, validatorRegistry, logger$1) {
4411
4610
  this.session = session;
4412
4611
  this.teamRepo = teamRepo;
4413
4612
  this.validatorRegistry = validatorRegistry;
4613
+ this.logger = logger$1;
4414
4614
  }
4415
4615
  async execute(record_id, record_type) {
4416
4616
  const user = this.session.user;
@@ -4423,25 +4623,68 @@ let ValidateRecordAccess = class ValidateRecordAccess$1 {
4423
4623
  case "team":
4424
4624
  await this.validateTeamAccess(record_id, user.user_type);
4425
4625
  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.`);
4626
+ default: if (user.user_type !== "lead" && user.user_type !== "staff" && user.user_type !== "super_admin") {
4627
+ this.logger.warn("[ValidateRecordAccess] Access denied", {
4628
+ record_id,
4629
+ record_type,
4630
+ user_id: user.userId,
4631
+ user_type: user.user_type,
4632
+ reason: "Record type requires lead/staff/admin; no custom validator registered"
4633
+ });
4634
+ throw new BusinessError("Access denied: You do not have permission to view this record.");
4635
+ }
4427
4636
  }
4428
4637
  }
4638
+ async filterAllowedRecordTypes(record_id, record_types) {
4639
+ const allowed = (await Promise.all(record_types.map(async (rt) => {
4640
+ try {
4641
+ await this.execute(record_id, rt);
4642
+ return {
4643
+ rt,
4644
+ allowed: true
4645
+ };
4646
+ } catch {
4647
+ return {
4648
+ rt,
4649
+ allowed: false
4650
+ };
4651
+ }
4652
+ }))).filter((r) => r.allowed).map((r) => r.rt);
4653
+ if (allowed.length === 0) {
4654
+ this.logger.warn("[ValidateRecordAccess] Access denied - no requested record types allowed", {
4655
+ record_id,
4656
+ requested_record_types: record_types,
4657
+ user_id: this.session.user.userId,
4658
+ user_type: this.session.user.user_type
4659
+ });
4660
+ throw new BusinessError("Access denied: You do not have permission to access any of the requested record types.");
4661
+ }
4662
+ return allowed;
4663
+ }
4429
4664
  /**
4430
4665
  * Validates access to a team record
4431
4666
  * Lead, staff, and admin have full access
4432
4667
  * Consumers cannot access team records
4433
4668
  */
4434
4669
  async validateTeamAccess(record_id, user_type) {
4435
- if (!await this.teamRepo.read(record_id)) throw new Error("Team not found");
4670
+ if (!await this.teamRepo.read(record_id)) {
4671
+ this.logger.warn("[ValidateRecordAccess] Team not found", { record_id });
4672
+ throw new BusinessError("Team not found");
4673
+ }
4436
4674
  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");
4675
+ this.logger.warn("[ValidateRecordAccess] Access denied - team requires lead/staff", {
4676
+ record_id,
4677
+ user_type
4678
+ });
4679
+ throw new BusinessError("Access denied: Team records require lead or staff permissions");
4438
4680
  }
4439
4681
  };
4440
4682
  ValidateRecordAccess = __decorate([
4441
4683
  injectable(),
4442
4684
  __decorateParam(0, injectSession()),
4443
4685
  __decorateParam(1, inject(TEAM_TOKENS.ITeamRepository)),
4444
- __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry))
4686
+ __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry)),
4687
+ __decorateParam(3, inject(TOKENS.LOGGER))
4445
4688
  ], ValidateRecordAccess);
4446
4689
 
4447
4690
  //#endregion
@@ -4515,9 +4758,13 @@ let RecordVersionRepo = class RecordVersionRepo$1 {
4515
4758
  /**
4516
4759
  * NEW: Breadcrumb pagination for record versions
4517
4760
  * Use this for new implementations requiring bidirectional navigation
4761
+ * Supports single record_type or multiple record_types (merged results)
4518
4762
  */
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)];
4763
+ async read_all_record_versions_paginated(record_id, record_type_or_types, filters) {
4764
+ const record_types = Array.isArray(record_type_or_types) ? record_type_or_types : [record_type_or_types];
4765
+ const recordTypeCondition = record_types.length === 1 ? eq(record_version_table.record_type, record_types[0]) : inArray(record_version_table.record_type, record_types);
4766
+ const record_ids = filters?.record_ids;
4767
+ 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
4768
  if (filters?.start_date) baseConditions.push(gte(record_version_table.recorded_at, filters.start_date));
4522
4769
  if (filters?.end_date) baseConditions.push(lte(record_version_table.recorded_at, filters.end_date));
4523
4770
  return PaginationUtils.findAllPaginated({
@@ -4647,19 +4894,49 @@ ReadAllRecordVersionsByRecord = __decorate([
4647
4894
 
4648
4895
  //#endregion
4649
4896
  //#region src/slices/record_version/features/read_all_record_versions_by_record_paginated_customer_feat.ts
4897
+ const USER_ID_FIELDS$1 = new Set([
4898
+ "assigned_to",
4899
+ "created_by",
4900
+ "updated_by",
4901
+ "archived_by",
4902
+ "deleted_by"
4903
+ ]);
4904
+ function collectUserIdsFromRecordVersions$1(items) {
4905
+ const ids = /* @__PURE__ */ new Set();
4906
+ for (const item of items) for (const raw of [item.record, item.old_record]) {
4907
+ if (!raw || typeof raw !== "string") continue;
4908
+ try {
4909
+ const rec = JSON.parse(raw);
4910
+ for (const [key, val] of Object.entries(rec)) if (USER_ID_FIELDS$1.has(key) && typeof val === "string" && val.trim()) ids.add(val);
4911
+ } catch {}
4912
+ }
4913
+ return [...ids];
4914
+ }
4650
4915
  let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersionsByRecordPaginatedCustomer$1 {
4651
- constructor(record_version_repo) {
4916
+ constructor(record_version_repo, userDisplayLookup) {
4652
4917
  this.record_version_repo = record_version_repo;
4918
+ this.userDisplayLookup = userDisplayLookup;
4653
4919
  }
4654
4920
  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) => {
4921
+ const record_types = filters?.record_types ?? [record_type];
4922
+ const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_types, filters);
4923
+ const mappedRecords = records.items.map((record) => {
4924
+ const recordType = record.record_type;
4657
4925
  return {
4658
4926
  ...record,
4659
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4660
- old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), record_type)
4927
+ record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),
4928
+ old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), recordType)
4661
4929
  };
4662
4930
  });
4931
+ records.items = mappedRecords;
4932
+ const userIds = collectUserIdsFromRecordVersions$1(mappedRecords);
4933
+ if (userIds.length > 0) {
4934
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
4935
+ return {
4936
+ ...records,
4937
+ user_display_map: Object.fromEntries(displayMap)
4938
+ };
4939
+ }
4663
4940
  return records;
4664
4941
  }
4665
4942
  /**
@@ -4669,7 +4946,9 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4669
4946
  filterFieldsByRecordType(recordData, recordType) {
4670
4947
  if (!recordData) return recordData;
4671
4948
  switch (recordType) {
4672
- case "support_ticket": return this.filterSupportTicketFields(recordData);
4949
+ case RecordConst$1.SUPPORT_TICKET: return this.filterSupportTicketFields(recordData);
4950
+ case RecordConst$1.SUPPORT_TICKET_ACTIVITY: return recordData;
4951
+ case RecordConst$1.TRACKER_ACTIVITY: return recordData;
4673
4952
  default: return recordData;
4674
4953
  }
4675
4954
  }
@@ -4704,26 +4983,60 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4704
4983
  }
4705
4984
  }
4706
4985
  };
4707
- ReadAllRecordVersionsByRecordPaginatedCustomer = __decorate([injectable(), __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo))], ReadAllRecordVersionsByRecordPaginatedCustomer);
4986
+ ReadAllRecordVersionsByRecordPaginatedCustomer = __decorate([
4987
+ injectable(),
4988
+ __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)),
4989
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
4990
+ ], ReadAllRecordVersionsByRecordPaginatedCustomer);
4708
4991
 
4709
4992
  //#endregion
4710
4993
  //#region src/slices/record_version/features/read_all_record_versions_by_record_paginated_feat.ts
4994
+ const USER_ID_FIELDS = new Set([
4995
+ "assigned_to",
4996
+ "created_by",
4997
+ "updated_by",
4998
+ "archived_by",
4999
+ "deleted_by"
5000
+ ]);
5001
+ function collectUserIdsFromRecordVersions(items) {
5002
+ const ids = /* @__PURE__ */ new Set();
5003
+ for (const item of items) for (const raw of [item.record, item.old_record]) {
5004
+ if (!raw || typeof raw !== "string") continue;
5005
+ try {
5006
+ const rec = JSON.parse(raw);
5007
+ for (const [key, val] of Object.entries(rec)) if (USER_ID_FIELDS.has(key) && typeof val === "string" && val.trim()) ids.add(val);
5008
+ } catch {}
5009
+ }
5010
+ return [...ids];
5011
+ }
4711
5012
  let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecordPaginated$1 {
4712
- constructor(record_version_repo, validateRecordAccess, session) {
5013
+ constructor(record_version_repo, validateRecordAccess, userDisplayLookup, session) {
4713
5014
  this.record_version_repo = record_version_repo;
4714
5015
  this.validateRecordAccess = validateRecordAccess;
5016
+ this.userDisplayLookup = userDisplayLookup;
4715
5017
  this.session = session;
4716
5018
  }
4717
5019
  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) => {
5020
+ const requested_types = filters?.record_types ?? [record_type];
5021
+ const allowed_record_types = await this.validateRecordAccess.filterAllowedRecordTypes(record_id, requested_types);
5022
+ const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, allowed_record_types, filters);
5023
+ const mappedRecords = records.items.map((record) => {
5024
+ const recordType = record.record_type;
4721
5025
  return {
4722
5026
  ...record,
4723
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4724
- old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), record_type)
5027
+ record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),
5028
+ old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record), recordType)
4725
5029
  };
4726
5030
  });
5031
+ records.items = mappedRecords;
5032
+ const userIds = collectUserIdsFromRecordVersions(mappedRecords);
5033
+ if (userIds.length > 0) {
5034
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5035
+ return {
5036
+ ...records,
5037
+ user_display_map: Object.fromEntries(displayMap)
5038
+ };
5039
+ }
4727
5040
  return records;
4728
5041
  }
4729
5042
  /**
@@ -4734,7 +5047,9 @@ let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecord
4734
5047
  if (!recordData) return recordData;
4735
5048
  if (this.session.user.user_type === "staff" || this.session.user.user_type === "super_admin") return recordData;
4736
5049
  switch (recordType) {
4737
- case "support_ticket": return this.filterSupportTicketFields(recordData);
5050
+ case RecordConst$1.SUPPORT_TICKET: return this.filterSupportTicketFields(recordData);
5051
+ case RecordConst$1.SUPPORT_TICKET_ACTIVITY: return recordData;
5052
+ case RecordConst$1.TRACKER_ACTIVITY: return recordData;
4738
5053
  default: return recordData;
4739
5054
  }
4740
5055
  }
@@ -4773,7 +5088,8 @@ ReadAllRecordVersionsByRecordPaginated = __decorate([
4773
5088
  injectable(),
4774
5089
  __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)),
4775
5090
  __decorateParam(1, inject(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess)),
4776
- __decorateParam(2, injectSession())
5091
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
5092
+ __decorateParam(3, injectSession())
4777
5093
  ], ReadAllRecordVersionsByRecordPaginated);
4778
5094
 
4779
5095
  //#endregion
@@ -4808,6 +5124,482 @@ function registerRecordVersionContainer() {
4808
5124
  container.registerSingleton(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess, ValidateRecordAccess);
4809
5125
  }
4810
5126
 
5127
+ //#endregion
5128
+ //#region src/slices/saved_filter/saved_filter_interfaces.ts
5129
+ const SAVED_FILTER_TOKENS = {
5130
+ ISavedFilterRepo: Symbol("ISavedFilterRepo"),
5131
+ IListSavedFilters: Symbol("IListSavedFilters"),
5132
+ IListAllSavedFilters: Symbol("IListAllSavedFilters"),
5133
+ ICreateSavedFilter: Symbol("ICreateSavedFilter"),
5134
+ IUpdateSavedFilter: Symbol("IUpdateSavedFilter"),
5135
+ IDeleteSavedFilter: Symbol("IDeleteSavedFilter")
5136
+ };
5137
+
5138
+ //#endregion
5139
+ //#region src/slices/saved_filter/db/saved_filter_mapper.ts
5140
+ function mapSavedFilterEntityToDto(entity) {
5141
+ let filters;
5142
+ try {
5143
+ filters = JSON.parse(entity.filters);
5144
+ } catch {
5145
+ filters = {};
5146
+ }
5147
+ return {
5148
+ id: entity.id,
5149
+ user_id: entity.user_id,
5150
+ name: entity.name,
5151
+ context: entity.context,
5152
+ route_path: entity.route_path,
5153
+ filters,
5154
+ sort_by: entity.sort_by ?? void 0,
5155
+ sort_direction: entity.sort_direction ?? void 0,
5156
+ created_at: entity.created_at,
5157
+ updated_at: entity.updated_at
5158
+ };
5159
+ }
5160
+
5161
+ //#endregion
5162
+ //#region src/slices/saved_filter/features/create_saved_filter_feat.ts
5163
+ let CreateSavedFilterFeature = class CreateSavedFilterFeature$1 {
5164
+ constructor(session, repo) {
5165
+ this.session = session;
5166
+ this.repo = repo;
5167
+ }
5168
+ async execute(input) {
5169
+ const userId = this.session.user.userId;
5170
+ 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.`);
5171
+ return mapSavedFilterEntityToDto(await this.repo.create({
5172
+ user_id: userId,
5173
+ name: input.name,
5174
+ context: input.context,
5175
+ route_path: input.route_path,
5176
+ filters: JSON.stringify(input.filters),
5177
+ sort_by: input.sort_by ?? null,
5178
+ sort_direction: input.sort_direction ?? null
5179
+ }));
5180
+ }
5181
+ };
5182
+ CreateSavedFilterFeature = __decorate([
5183
+ injectable(),
5184
+ __decorateParam(0, injectSession()),
5185
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5186
+ ], CreateSavedFilterFeature);
5187
+
5188
+ //#endregion
5189
+ //#region src/slices/saved_filter/user_pinned_preset_interfaces.ts
5190
+ const USER_PINNED_PRESET_TOKENS = {
5191
+ IUserPinnedPresetRepo: Symbol("IUserPinnedPresetRepo"),
5192
+ IListPinnedPresets: Symbol("IListPinnedPresets"),
5193
+ IAddPinnedPreset: Symbol("IAddPinnedPreset"),
5194
+ IRemovePinnedPreset: Symbol("IRemovePinnedPreset"),
5195
+ IReorderPinnedPresets: Symbol("IRearrangePinnedPresets")
5196
+ };
5197
+
5198
+ //#endregion
5199
+ //#region src/slices/saved_filter/features/delete_saved_filter_feat.ts
5200
+ let DeleteSavedFilterFeature = class DeleteSavedFilterFeature$1 {
5201
+ constructor(session, repo, pinnedRepo) {
5202
+ this.session = session;
5203
+ this.repo = repo;
5204
+ this.pinnedRepo = pinnedRepo;
5205
+ }
5206
+ async execute(id) {
5207
+ const existing = await this.repo.read(id);
5208
+ if (!existing) return false;
5209
+ if (existing.user_id !== this.session.user.userId) throw new BusinessError("You can only delete your own saved filters");
5210
+ await this.pinnedRepo.deleteByPresetId(id);
5211
+ return await this.repo.delete(id);
5212
+ }
5213
+ };
5214
+ DeleteSavedFilterFeature = __decorate([
5215
+ injectable(),
5216
+ __decorateParam(0, injectSession()),
5217
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)),
5218
+ __decorateParam(2, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5219
+ ], DeleteSavedFilterFeature);
5220
+
5221
+ //#endregion
5222
+ //#region src/slices/saved_filter/features/list_saved_filters_feat.ts
5223
+ let ListSavedFiltersFeature = class ListSavedFiltersFeature$1 {
5224
+ constructor(session, repo) {
5225
+ this.session = session;
5226
+ this.repo = repo;
5227
+ }
5228
+ async execute(context) {
5229
+ const userId = this.session.user.userId;
5230
+ return (await this.repo.listByUserAndContext(userId, context)).map(mapSavedFilterEntityToDto);
5231
+ }
5232
+ };
5233
+ ListSavedFiltersFeature = __decorate([
5234
+ injectable(),
5235
+ __decorateParam(0, injectSession()),
5236
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5237
+ ], ListSavedFiltersFeature);
5238
+
5239
+ //#endregion
5240
+ //#region src/slices/saved_filter/features/update_saved_filter_feat.ts
5241
+ let UpdateSavedFilterFeature = class UpdateSavedFilterFeature$1 {
5242
+ constructor(session, repo) {
5243
+ this.session = session;
5244
+ this.repo = repo;
5245
+ }
5246
+ async execute(input) {
5247
+ const existing = await this.repo.read(input.id);
5248
+ if (!existing) return null;
5249
+ if (existing.user_id !== this.session.user.userId) throw new BusinessError("You can only update your own saved filters");
5250
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5251
+ return mapSavedFilterEntityToDto(await this.repo.update({
5252
+ id: input.id,
5253
+ ...input.name !== void 0 && { name: input.name },
5254
+ ...input.route_path !== void 0 && { route_path: input.route_path },
5255
+ ...input.filters !== void 0 && { filters: JSON.stringify(input.filters) },
5256
+ ...input.sort_by !== void 0 && { sort_by: input.sort_by },
5257
+ ...input.sort_direction !== void 0 && { sort_direction: input.sort_direction },
5258
+ updated_at: now
5259
+ }));
5260
+ }
5261
+ };
5262
+ UpdateSavedFilterFeature = __decorate([
5263
+ injectable(),
5264
+ __decorateParam(0, injectSession()),
5265
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5266
+ ], UpdateSavedFilterFeature);
5267
+
5268
+ //#endregion
5269
+ //#region src/slices/saved_filter/features/add_pinned_preset_feat.ts
5270
+ let AddPinnedPresetFeature = class AddPinnedPresetFeature$1 {
5271
+ constructor(session, pinnedRepo, savedFilterRepo) {
5272
+ this.session = session;
5273
+ this.pinnedRepo = pinnedRepo;
5274
+ this.savedFilterRepo = savedFilterRepo;
5275
+ }
5276
+ async execute(presetId) {
5277
+ const userId = this.session.user.userId;
5278
+ const preset = await this.savedFilterRepo.read(presetId);
5279
+ if (!preset) return null;
5280
+ if (preset.user_id !== userId) throw new BusinessError("You can only pin your own presets");
5281
+ if (await this.pinnedRepo.findByUserAndPreset(userId, presetId)) return mapSavedFilterEntityToDto(preset);
5282
+ const pins = await this.pinnedRepo.listByUser(userId);
5283
+ if (pins.length >= MAX_PINNED_PRESETS) throw new BusinessError(`You can pin at most ${MAX_PINNED_PRESETS} presets. Unpin one to add another.`);
5284
+ const position = pins.length;
5285
+ await this.pinnedRepo.create(userId, presetId, position);
5286
+ return mapSavedFilterEntityToDto(preset);
5287
+ }
5288
+ };
5289
+ AddPinnedPresetFeature = __decorate([
5290
+ injectable(),
5291
+ __decorateParam(0, injectSession()),
5292
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)),
5293
+ __decorateParam(2, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5294
+ ], AddPinnedPresetFeature);
5295
+
5296
+ //#endregion
5297
+ //#region src/slices/saved_filter/features/list_all_saved_filters_feat.ts
5298
+ let ListAllSavedFiltersFeature = class ListAllSavedFiltersFeature$1 {
5299
+ constructor(session, repo) {
5300
+ this.session = session;
5301
+ this.repo = repo;
5302
+ }
5303
+ async execute() {
5304
+ const userId = this.session.user.userId;
5305
+ return (await this.repo.listByUser(userId)).map(mapSavedFilterEntityToDto);
5306
+ }
5307
+ };
5308
+ ListAllSavedFiltersFeature = __decorate([
5309
+ injectable(),
5310
+ __decorateParam(0, injectSession()),
5311
+ __decorateParam(1, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5312
+ ], ListAllSavedFiltersFeature);
5313
+
5314
+ //#endregion
5315
+ //#region src/slices/saved_filter/features/list_pinned_presets_feat.ts
5316
+ let ListPinnedPresetsFeature = class ListPinnedPresetsFeature$1 {
5317
+ constructor(session, pinnedRepo, savedFilterRepo) {
5318
+ this.session = session;
5319
+ this.pinnedRepo = pinnedRepo;
5320
+ this.savedFilterRepo = savedFilterRepo;
5321
+ }
5322
+ async execute() {
5323
+ const userId = this.session.user.userId;
5324
+ const pins = await this.pinnedRepo.listByUser(userId);
5325
+ if (pins.length === 0) return [];
5326
+ const presetIds = pins.map((p) => p.preset_id);
5327
+ const presets = await this.savedFilterRepo.readByIds(presetIds);
5328
+ const presetMap = new Map(presets.map((p) => [p.id, p]));
5329
+ const result = [];
5330
+ for (const pin of pins) {
5331
+ const preset = presetMap.get(pin.preset_id);
5332
+ if (preset && preset.user_id === userId) result.push(mapSavedFilterEntityToDto(preset));
5333
+ }
5334
+ return result;
5335
+ }
5336
+ };
5337
+ ListPinnedPresetsFeature = __decorate([
5338
+ injectable(),
5339
+ __decorateParam(0, injectSession()),
5340
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)),
5341
+ __decorateParam(2, inject(SAVED_FILTER_TOKENS.ISavedFilterRepo))
5342
+ ], ListPinnedPresetsFeature);
5343
+
5344
+ //#endregion
5345
+ //#region src/slices/saved_filter/features/remove_pinned_preset_feat.ts
5346
+ let RemovePinnedPresetFeature = class RemovePinnedPresetFeature$1 {
5347
+ constructor(session, pinnedRepo) {
5348
+ this.session = session;
5349
+ this.pinnedRepo = pinnedRepo;
5350
+ }
5351
+ async execute(presetId) {
5352
+ const userId = this.session.user.userId;
5353
+ const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);
5354
+ if (!pin) return false;
5355
+ return await this.pinnedRepo.delete(pin.id);
5356
+ }
5357
+ };
5358
+ RemovePinnedPresetFeature = __decorate([
5359
+ injectable(),
5360
+ __decorateParam(0, injectSession()),
5361
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5362
+ ], RemovePinnedPresetFeature);
5363
+
5364
+ //#endregion
5365
+ //#region src/slices/saved_filter/features/reorder_pinned_presets_feat.ts
5366
+ let ReorderPinnedPresetsFeature = class ReorderPinnedPresetsFeature$1 {
5367
+ constructor(session, pinnedRepo) {
5368
+ this.session = session;
5369
+ this.pinnedRepo = pinnedRepo;
5370
+ }
5371
+ async execute(presetIds) {
5372
+ const userId = this.session.user.userId;
5373
+ for (let i = 0; i < presetIds.length; i++) {
5374
+ const presetId = presetIds[i];
5375
+ const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);
5376
+ if (pin) await this.pinnedRepo.updatePosition(pin.id, i);
5377
+ }
5378
+ }
5379
+ };
5380
+ ReorderPinnedPresetsFeature = __decorate([
5381
+ injectable(),
5382
+ __decorateParam(0, injectSession()),
5383
+ __decorateParam(1, inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo))
5384
+ ], ReorderPinnedPresetsFeature);
5385
+
5386
+ //#endregion
5387
+ //#region src/slices/saved_filter/db/saved_filter_repo.ts
5388
+ let SavedFilterRepository = class SavedFilterRepository$1 {
5389
+ constructor(router) {
5390
+ this.router = router;
5391
+ }
5392
+ async create(entity) {
5393
+ const id = await this.router.generateId(RecordConst.SAVED_FILTER);
5394
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5395
+ const [result] = await this.router.queryLatest((db) => db.insert(saved_filter_table).values({
5396
+ id,
5397
+ user_id: entity.user_id,
5398
+ name: entity.name,
5399
+ context: entity.context,
5400
+ route_path: entity.route_path,
5401
+ filters: entity.filters,
5402
+ sort_by: entity.sort_by ?? null,
5403
+ sort_direction: entity.sort_direction ?? null,
5404
+ created_at: now,
5405
+ updated_at: now
5406
+ }).returning());
5407
+ return result;
5408
+ }
5409
+ async read(id) {
5410
+ return (await this.router.queryById(id, (db) => db.select().from(saved_filter_table).where(eq(saved_filter_table.id, id)).limit(1)))[0] ?? null;
5411
+ }
5412
+ async readByIds(ids) {
5413
+ if (ids.length === 0) return [];
5414
+ return this.router.queryByIds(ids, (db, idsForShard) => db.select().from(saved_filter_table).where(inArray(saved_filter_table.id, idsForShard)));
5415
+ }
5416
+ async listByUser(userId) {
5417
+ return await this.router.queryAll((db) => db.select().from(saved_filter_table).where(eq(saved_filter_table.user_id, userId)));
5418
+ }
5419
+ async listByUserAndContext(userId, context) {
5420
+ 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))));
5421
+ }
5422
+ async update(entity) {
5423
+ const updated = (await this.router.queryById(entity.id, (db) => db.update(saved_filter_table).set({
5424
+ ...entity.name !== void 0 && { name: entity.name },
5425
+ ...entity.route_path !== void 0 && { route_path: entity.route_path },
5426
+ ...entity.filters !== void 0 && { filters: entity.filters },
5427
+ ...entity.sort_by !== void 0 && { sort_by: entity.sort_by },
5428
+ ...entity.sort_direction !== void 0 && { sort_direction: entity.sort_direction },
5429
+ updated_at: entity.updated_at
5430
+ }).where(eq(saved_filter_table.id, entity.id)).returning()))[0];
5431
+ if (!updated) throw new Error("Saved filter not found or update failed");
5432
+ return updated;
5433
+ }
5434
+ async delete(id) {
5435
+ 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;
5436
+ }
5437
+ };
5438
+ SavedFilterRepository = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SavedFilterRepository);
5439
+
5440
+ //#endregion
5441
+ //#region src/slices/saved_filter/db/user_pinned_preset_repo.ts
5442
+ let UserPinnedPresetRepository = class UserPinnedPresetRepository$1 {
5443
+ constructor(router) {
5444
+ this.router = router;
5445
+ }
5446
+ async create(userId, presetId, position) {
5447
+ const id = await this.router.generateId(RecordConst.USER_PINNED_PRESET);
5448
+ const [result] = await this.router.queryLatest((db) => db.insert(user_pinned_preset_table).values({
5449
+ id,
5450
+ user_id: userId,
5451
+ preset_id: presetId,
5452
+ position
5453
+ }).returning());
5454
+ return result;
5455
+ }
5456
+ async listByUser(userId) {
5457
+ 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);
5458
+ }
5459
+ async findByUserAndPreset(userId, presetId) {
5460
+ 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;
5461
+ }
5462
+ async updatePosition(id, position) {
5463
+ 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 }));
5464
+ }
5465
+ async delete(id) {
5466
+ 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;
5467
+ }
5468
+ async deleteByPresetId(presetId) {
5469
+ const pins = await this.router.queryAll((db) => db.select().from(user_pinned_preset_table).where(eq(user_pinned_preset_table.preset_id, presetId)));
5470
+ let deleted = 0;
5471
+ for (const pin of pins) if (await this.delete(pin.id)) deleted++;
5472
+ return deleted;
5473
+ }
5474
+ };
5475
+ UserPinnedPresetRepository = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], UserPinnedPresetRepository);
5476
+
5477
+ //#endregion
5478
+ //#region src/slices/saved_filter/saved_filter_container.ts
5479
+ function registerSavedFilterContainer() {
5480
+ container.registerSingleton(SAVED_FILTER_TOKENS.ISavedFilterRepo, SavedFilterRepository);
5481
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo, UserPinnedPresetRepository);
5482
+ container.registerSingleton(SAVED_FILTER_TOKENS.IListSavedFilters, ListSavedFiltersFeature);
5483
+ container.registerSingleton(SAVED_FILTER_TOKENS.IListAllSavedFilters, ListAllSavedFiltersFeature);
5484
+ container.registerSingleton(SAVED_FILTER_TOKENS.ICreateSavedFilter, CreateSavedFilterFeature);
5485
+ container.registerSingleton(SAVED_FILTER_TOKENS.IUpdateSavedFilter, UpdateSavedFilterFeature);
5486
+ container.registerSingleton(SAVED_FILTER_TOKENS.IDeleteSavedFilter, DeleteSavedFilterFeature);
5487
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IListPinnedPresets, ListPinnedPresetsFeature);
5488
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IAddPinnedPreset, AddPinnedPresetFeature);
5489
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset, RemovePinnedPresetFeature);
5490
+ container.registerSingleton(USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets, ReorderPinnedPresetsFeature);
5491
+ }
5492
+
5493
+ //#endregion
5494
+ //#region src/slices/support_staff/db/support_staff_repo.ts
5495
+ let SupportStaffRepo = class SupportStaffRepo$1 {
5496
+ constructor(router) {
5497
+ this.router = router;
5498
+ }
5499
+ async readAll() {
5500
+ return this.router.queryAll((db) => db.select().from(support_staff_table).orderBy(support_staff_table.user_id));
5501
+ }
5502
+ async readSupportStaffMembers() {
5503
+ return this.router.queryAll((db) => db.select({
5504
+ id: support_staff_table.id,
5505
+ user_id: support_staff_table.user_id,
5506
+ email: user_table.email,
5507
+ created_at: support_staff_table.created_at
5508
+ }).from(support_staff_table).innerJoin(user_table, eq(support_staff_table.user_id, user_table.id)).orderBy(support_staff_table.user_id));
5509
+ }
5510
+ async readTriageUsers() {
5511
+ return this.router.queryAll((db) => db.select({
5512
+ id: user_table.id,
5513
+ email: user_table.email
5514
+ }).from(support_staff_table).innerJoin(user_table, eq(support_staff_table.user_id, user_table.id)).orderBy(support_staff_table.user_id));
5515
+ }
5516
+ async add(userId, createdBy) {
5517
+ const id = await this.router.generateId(RecordConst.SUPPORT_STAFF);
5518
+ const entity = {
5519
+ user_id: userId,
5520
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
5521
+ created_by: createdBy
5522
+ };
5523
+ const [result] = await this.router.queryLatest((db) => db.insert(support_staff_table).values({
5524
+ id,
5525
+ ...entity
5526
+ }).returning());
5527
+ return result;
5528
+ }
5529
+ async remove(userId) {
5530
+ 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;
5531
+ }
5532
+ };
5533
+ SupportStaffRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportStaffRepo);
5534
+
5535
+ //#endregion
5536
+ //#region src/slices/support_staff/support_staff_tokens.ts
5537
+ const SUPPORT_STAFF_TOKENS = {
5538
+ ISupportStaffRepo: Symbol("ISupportStaffRepo"),
5539
+ IListSupportStaffFeature: Symbol("IListSupportStaffFeature"),
5540
+ IAddSupportStaffFeature: Symbol("IAddSupportStaffFeature"),
5541
+ IRemoveSupportStaffFeature: Symbol("IRemoveSupportStaffFeature")
5542
+ };
5543
+
5544
+ //#endregion
5545
+ //#region src/slices/support_staff/features/add_support_staff_feat.ts
5546
+ let AddSupportStaffFeat = class AddSupportStaffFeat$1 {
5547
+ constructor(supportStaffRepo, userRepo, session) {
5548
+ this.supportStaffRepo = supportStaffRepo;
5549
+ this.userRepo = userRepo;
5550
+ this.session = session;
5551
+ }
5552
+ async execute(userId) {
5553
+ const user = await this.userRepo.read_user(userId);
5554
+ if (!user) throw new Error("User not found");
5555
+ if (!["staff", "super_admin"].includes(user.user_type)) throw new BusinessError("Only staff and super_admin users can be added to support staff");
5556
+ if ((await this.supportStaffRepo.readAll()).some((s) => s.user_id === userId)) throw new Error("User is already support staff");
5557
+ const created = await this.supportStaffRepo.add(userId, this.session.user.userId);
5558
+ const member = (await this.supportStaffRepo.readSupportStaffMembers()).find((m) => m.id === created.id);
5559
+ if (!member) throw new Error("Failed to fetch created support staff member");
5560
+ return member;
5561
+ }
5562
+ };
5563
+ AddSupportStaffFeat = __decorate([
5564
+ injectable(),
5565
+ __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
5566
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo)),
5567
+ __decorateParam(2, inject(TOKENS.AUTHENTICATED_SESSION))
5568
+ ], AddSupportStaffFeat);
5569
+
5570
+ //#endregion
5571
+ //#region src/slices/support_staff/features/list_support_staff_feat.ts
5572
+ let ListSupportStaffFeat = class ListSupportStaffFeat$1 {
5573
+ constructor(supportStaffRepo) {
5574
+ this.supportStaffRepo = supportStaffRepo;
5575
+ }
5576
+ async execute() {
5577
+ return this.supportStaffRepo.readSupportStaffMembers();
5578
+ }
5579
+ };
5580
+ ListSupportStaffFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], ListSupportStaffFeat);
5581
+
5582
+ //#endregion
5583
+ //#region src/slices/support_staff/features/remove_support_staff_feat.ts
5584
+ let RemoveSupportStaffFeat = class RemoveSupportStaffFeat$1 {
5585
+ constructor(supportStaffRepo) {
5586
+ this.supportStaffRepo = supportStaffRepo;
5587
+ }
5588
+ async execute(userId) {
5589
+ if (!await this.supportStaffRepo.remove(userId)) throw new Error("Support staff member not found");
5590
+ }
5591
+ };
5592
+ RemoveSupportStaffFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], RemoveSupportStaffFeat);
5593
+
5594
+ //#endregion
5595
+ //#region src/slices/support_staff/support_staff_registration.ts
5596
+ function registerSupportStaffDependencies() {
5597
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.ISupportStaffRepo, SupportStaffRepo);
5598
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IListSupportStaffFeature, ListSupportStaffFeat);
5599
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature, AddSupportStaffFeat);
5600
+ container.registerSingleton(SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature, RemoveSupportStaffFeat);
5601
+ }
5602
+
4811
5603
  //#endregion
4812
5604
  //#region src/slices/support_ticket/db/support_ticket_query_config.ts
4813
5605
  const supportTicketFields = {
@@ -4820,9 +5612,9 @@ const supportTicketFields = {
4820
5612
  },
4821
5613
  priority: {
4822
5614
  column: support_ticket_table.priority,
4823
- type: "string",
5615
+ type: "number",
4824
5616
  filterable: true,
4825
- searchable: true,
5617
+ searchable: false,
4826
5618
  sortable: true
4827
5619
  },
4828
5620
  approval_status: {
@@ -4839,18 +5631,11 @@ const supportTicketFields = {
4839
5631
  searchable: true,
4840
5632
  sortable: true
4841
5633
  },
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,
5634
+ created_by: {
5635
+ column: support_ticket_table.created_by,
4851
5636
  type: "string",
4852
5637
  filterable: true,
4853
- searchable: true,
5638
+ searchable: false,
4854
5639
  sortable: false
4855
5640
  },
4856
5641
  title: {
@@ -4909,20 +5694,6 @@ const supportTicketFields = {
4909
5694
  searchable: false,
4910
5695
  sortable: false
4911
5696
  },
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
5697
  approvalStatus: {
4927
5698
  column: support_ticket_table.approval_status,
4928
5699
  type: "string",
@@ -4993,7 +5764,8 @@ const buildFieldFilters$1 = createFilterBuilder({
4993
5764
  processedSeparately: [
4994
5765
  "archived_at",
4995
5766
  "status",
4996
- "is_locked"
5767
+ "is_locked",
5768
+ "dev_lifecycle"
4997
5769
  ]
4998
5770
  });
4999
5771
  /**
@@ -5007,6 +5779,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5007
5779
  case "CANCELLED": return "REJECTED";
5008
5780
  case "FOLLOWUP": return;
5009
5781
  case "IN_PROGRESS": return;
5782
+ case "VERIFICATION": return;
5010
5783
  case "COMPLETED": return;
5011
5784
  default: throw new Error(`Invalid support ticket status: ${status}`);
5012
5785
  }
@@ -5017,6 +5790,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5017
5790
  case "CANCELLED": return;
5018
5791
  case "FOLLOWUP": return;
5019
5792
  case "IN_PROGRESS": return;
5793
+ case "VERIFICATION": return "VERIFICATION";
5020
5794
  case "COMPLETED": return "DEPLOYED";
5021
5795
  default: throw new Error(`Invalid support ticket status: ${status}`);
5022
5796
  }
@@ -5060,7 +5834,21 @@ function buildSupportTicketQuery(filters) {
5060
5834
  } else if (statusValue === "IN_PROGRESS") {
5061
5835
  conditions.push(eq(support_ticket_table.approval_status, "APPROVED"));
5062
5836
  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"));
5837
+ } else if (statusValue === "VERIFICATION") conditions.push(eq(support_ticket_table.dev_lifecycle, "VERIFICATION"));
5838
+ else if (statusValue === "COMPLETED") conditions.push(eq(support_ticket_table.dev_lifecycle, "DEPLOYED"));
5839
+ }
5840
+ }
5841
+ if (filters?.dev_lifecycle) {
5842
+ const dl = filters.dev_lifecycle;
5843
+ const col = support_ticket_table.dev_lifecycle;
5844
+ const isPending = (v) => v === "PENDING";
5845
+ if (dl.value !== void 0 && isPending(dl.value) && dl.operator === OPERATORS.EQUALS) conditions.push(isNull(col));
5846
+ else if (dl.values?.some(isPending) && dl.operator === OPERATORS.IS_ONE_OF) {
5847
+ const others = dl.values.filter((v) => !isPending(v));
5848
+ conditions.push(others.length ? or(isNull(col), inArray(col, others)) : isNull(col));
5849
+ } else {
5850
+ const cond = applyFilter(col, dl, "string");
5851
+ if (cond) conditions.push(cond);
5064
5852
  }
5065
5853
  }
5066
5854
  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 +5890,7 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5102
5890
  const now = (/* @__PURE__ */ new Date()).toISOString();
5103
5891
  const [result] = await this.router.queryById(support_ticket.id, (db) => db.update(support_ticket_table).set({
5104
5892
  ...support_ticket,
5105
- updated_at: support_ticket.updated_at || now
5893
+ updated_at: now
5106
5894
  }).where(eq(support_ticket_table.id, support_ticket.id)).returning());
5107
5895
  return result;
5108
5896
  }
@@ -5157,6 +5945,26 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5157
5945
  return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);
5158
5946
  }
5159
5947
  /**
5948
+ * Get distinct requestor (created_by) user IDs for active support tickets.
5949
+ * Active = not archived (archived_at IS NULL).
5950
+ * @param excludeInternal - when true, exclude internal tickets (for customer view)
5951
+ * Queries all shards.
5952
+ */
5953
+ async read_distinct_requestors_for_active_tickets(options) {
5954
+ const conditions = [isNull(support_ticket_table.deleted_at), isNull(support_ticket_table.archived_at)];
5955
+ if (options?.excludeInternal) conditions.push(ne(support_ticket_table.approval_status, "INTERNAL"));
5956
+ const rows = await this.router.queryAll((db) => db.selectDistinct({ created_by: support_ticket_table.created_by }).from(support_ticket_table).where(and(...conditions)));
5957
+ return [...new Set(rows.map((r) => r.created_by).filter(Boolean))];
5958
+ }
5959
+ /**
5960
+ * Find support tickets with invalid user IDs (e.g. emails instead of 32-char universal IDs).
5961
+ * Queries all shards.
5962
+ */
5963
+ async findWithInvalidUserIds() {
5964
+ 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)`);
5965
+ return this.router.queryAll((db) => db.select().from(support_ticket_table).where(and(isNull(support_ticket_table.deleted_at), invalidLengthCondition)));
5966
+ }
5967
+ /**
5160
5968
  * Soft delete a support ticket by ID
5161
5969
  */
5162
5970
  async soft_delete(id, deleted_by) {
@@ -5169,7 +5977,237 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5169
5977
  return result[0];
5170
5978
  }
5171
5979
  };
5172
- SupportTicketRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportTicketRepo);
5980
+ SupportTicketRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], SupportTicketRepo);
5981
+
5982
+ //#endregion
5983
+ //#region src/slices/user_profile/user_profile_interfaces.ts
5984
+ const USER_PROFILE_TOKENS = {
5985
+ IUserProfilesRepo: Symbol("IUserProfilesRepo"),
5986
+ IReadUserProfilesByUser: Symbol("IReadUserProfilesByUser"),
5987
+ IReadUserProfile: Symbol("IReadUserProfile"),
5988
+ ICreateUserProfile: Symbol("ICreateUserProfile"),
5989
+ IUpdateUserProfile: Symbol("IUpdateUserProfile")
5990
+ };
5991
+
5992
+ //#endregion
5993
+ //#region src/slices/support_ticket/services/support_ticket_notification_service.ts
5994
+ const STAFF_USER_TYPES = ["staff", "super_admin"];
5995
+ /** Escape HTML for safe inclusion in email body. */
5996
+ function escapeHtmlForEmail(s) {
5997
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\n/g, "<br>");
5998
+ }
5999
+ let SupportTicketNotificationService = class SupportTicketNotificationService$1 {
6000
+ constructor(subscriberRepo, userRepo, userProfileRepo, emailService, env, logger$1) {
6001
+ this.subscriberRepo = subscriberRepo;
6002
+ this.userRepo = userRepo;
6003
+ this.userProfileRepo = userProfileRepo;
6004
+ this.emailService = emailService;
6005
+ this.env = env;
6006
+ this.logger = logger$1;
6007
+ }
6008
+ async notifyAssignee(ticket, eventType, context) {
6009
+ try {
6010
+ const assigneeId = ticket.assigned_to;
6011
+ if (!assigneeId) return;
6012
+ if (context?.actorUserId && assigneeId === context.actorUserId) return;
6013
+ const [email] = await this.getNotificationEmailsForFollowers([assigneeId], context?.actorUserId);
6014
+ if (!email) return;
6015
+ const ticketUrl = `${this.env.WEBSITE_URL}/staff/support/${ticket.id}`;
6016
+ const { subject, bodyText, bodyHtml } = this.buildAssigneeEmailContent(ticket, eventType, ticketUrl, context);
6017
+ await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);
6018
+ this.logger.info(`Sent support ticket ${eventType} notification to assignee`);
6019
+ } catch (error) {
6020
+ this.logger.error("Failed to send support ticket assignee notification", {
6021
+ error,
6022
+ supportTicketId: ticket.id,
6023
+ eventType
6024
+ });
6025
+ }
6026
+ }
6027
+ async notifyFollowers(supportTicketId, ticket, eventType, context) {
6028
+ try {
6029
+ await this.ensureAssigneeIsSubscriber(ticket);
6030
+ const followers = await this.subscriberRepo.readByRecordId(RecordConst$1.SUPPORT_TICKET, supportTicketId);
6031
+ if (followers.length === 0) return;
6032
+ const filteredFollowers = eventType === "NEW_INTERNAL_NOTE" ? await this.filterStaffFollowersOnly(followers) : followers;
6033
+ if (filteredFollowers.length === 0) return;
6034
+ const subscribed = filteredFollowers.filter((f) => {
6035
+ const events = f.subscribed_events;
6036
+ if (!events || events.length === 0) return true;
6037
+ return events.includes(eventType);
6038
+ });
6039
+ if (subscribed.length === 0) return;
6040
+ const assigneeExcluded = eventType === "TICKET_CREATED" || eventType === "TICKET_ASSIGNED" ? subscribed.filter((f) => f.user_id !== ticket.assigned_to) : subscribed;
6041
+ const recipients = await this.getNotificationRecipientsForFollowers(assigneeExcluded.map((f) => f.user_id), context?.actorUserId);
6042
+ if (recipients.length === 0) return;
6043
+ const baseUrl = this.env.WEBSITE_URL;
6044
+ for (const { email, userType } of recipients) {
6045
+ const ticketUrl = STAFF_USER_TYPES.includes(userType) ? `${baseUrl}/staff/support/${ticket.id}` : `${baseUrl}/support/${ticket.id}`;
6046
+ const { subject, bodyText, bodyHtml } = this.buildEmailContent(ticket, eventType, ticketUrl, context);
6047
+ await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);
6048
+ }
6049
+ this.logger.info(`Sent support ticket ${eventType} notification to ${recipients.length} followers`);
6050
+ } catch (error) {
6051
+ this.logger.error("Failed to send support ticket follower notifications", {
6052
+ error,
6053
+ supportTicketId,
6054
+ eventType
6055
+ });
6056
+ }
6057
+ }
6058
+ /**
6059
+ * Ensures the assigned staff member is a subscriber so they receive the same
6060
+ * notifications as other followers (notes, approvals, etc.). Idempotent.
6061
+ */
6062
+ async ensureAssigneeIsSubscriber(ticket) {
6063
+ const assigneeId = ticket.assigned_to;
6064
+ if (!assigneeId) return;
6065
+ try {
6066
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6067
+ await this.subscriberRepo.ensureSubscriber({
6068
+ record_type: RecordConst$1.SUPPORT_TICKET,
6069
+ record_id: ticket.id,
6070
+ user_id: assigneeId,
6071
+ subscribed_events: null,
6072
+ created_at: now,
6073
+ created_by: assigneeId
6074
+ });
6075
+ } catch (error) {
6076
+ this.logger.error("Failed to ensure assignee is subscriber", {
6077
+ error,
6078
+ supportTicketId: ticket.id,
6079
+ assigneeId
6080
+ });
6081
+ }
6082
+ }
6083
+ async filterStaffFollowersOnly(followers) {
6084
+ if (followers.length === 0) return [];
6085
+ const userIds = followers.map((f) => f.user_id);
6086
+ const users = await this.userRepo.read_users_by_ids(userIds);
6087
+ const staffUserIds = new Set(users.filter((u) => STAFF_USER_TYPES.includes(u.user_type)).map((u) => u.id));
6088
+ return followers.filter((f) => staffUserIds.has(f.user_id));
6089
+ }
6090
+ async getNotificationEmailsForFollowers(userIds, excludeUserId) {
6091
+ return (await this.getNotificationRecipientsForFollowers(userIds, excludeUserId)).map((r) => r.email);
6092
+ }
6093
+ async getNotificationRecipientsForFollowers(userIds, excludeUserId) {
6094
+ const filteredIds = excludeUserId ? userIds.filter((id) => id !== excludeUserId) : userIds;
6095
+ if (filteredIds.length === 0) return [];
6096
+ const [users, profiles] = await Promise.all([this.userRepo.read_users_by_ids(filteredIds), this.userProfileRepo.read_user_profiles_by_user_ids(filteredIds)]);
6097
+ const profileByUserId = new Map(profiles.map((p) => [p.user_id, p]));
6098
+ const seen = /* @__PURE__ */ new Set();
6099
+ const recipients = [];
6100
+ for (const user of users) {
6101
+ const email = profileByUserId.get(user.id)?.notification_email ?? user.email;
6102
+ if (!email || seen.has(email)) continue;
6103
+ seen.add(email);
6104
+ recipients.push({
6105
+ email,
6106
+ userType: user.user_type ?? DEFAULT_USER_TYPE
6107
+ });
6108
+ }
6109
+ return recipients;
6110
+ }
6111
+ buildEmailContent(ticket, eventType, ticketUrl, context) {
6112
+ const eventLabel = eventType.replace(/_/g, " ").toLowerCase();
6113
+ const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;
6114
+ const details = [
6115
+ `Ticket #: ${ticket.display_id || ticket.id}`,
6116
+ `Title: ${ticket.title}`,
6117
+ `Priority: ${supportTicketNumberToPriority(ticket.priority)}`
6118
+ ];
6119
+ if (context?.noteBody) details.push(`\nNote: ${context.noteBody}`);
6120
+ if (eventType === "TICKET_CREATED" && ticket.description?.trim()) details.push(`\nDescription:\n${ticket.description}`);
6121
+ return {
6122
+ subject,
6123
+ bodyText: `A support ticket you're following has been updated.\n\n${details.join("\n")}\n\nView ticket: ${ticketUrl}`,
6124
+ bodyHtml: `
6125
+ <h2>Support Ticket Update</h2>
6126
+ <p>A support ticket you're following has been updated (${eventLabel}).</p>
6127
+ <ul>
6128
+ <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
6129
+ <li><strong>Title:</strong> ${ticket.title}</li>
6130
+ <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>
6131
+ </ul>
6132
+ ${eventType === "TICKET_CREATED" && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ""}
6133
+ ${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ""}
6134
+ <p><a href="${ticketUrl}">View Ticket</a></p>
6135
+ `.trim()
6136
+ };
6137
+ }
6138
+ buildAssigneeEmailContent(ticket, eventType, ticketUrl, context) {
6139
+ const eventLabel = eventType.replace(/_/g, " ").toLowerCase();
6140
+ const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;
6141
+ const isAssigned = eventType === "TICKET_ASSIGNED";
6142
+ const intro = isAssigned ? "You have been assigned to a support ticket." : "A new support ticket has been assigned to you.";
6143
+ const details = [
6144
+ `Ticket #: ${ticket.display_id || ticket.id}`,
6145
+ `Title: ${ticket.title}`,
6146
+ `Priority: ${supportTicketNumberToPriority(ticket.priority)}`
6147
+ ];
6148
+ if (context?.noteBody) details.push(`\nNote: ${context.noteBody}`);
6149
+ if (!isAssigned && ticket.description?.trim()) details.push(`\nDescription:\n${ticket.description}`);
6150
+ return {
6151
+ subject,
6152
+ bodyText: `${intro}\n\n${details.join("\n")}\n\nView ticket: ${ticketUrl}`,
6153
+ bodyHtml: `
6154
+ <h2>Support Ticket ${isAssigned ? "Assigned" : "Created"}</h2>
6155
+ <p>${intro}</p>
6156
+ <ul>
6157
+ <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
6158
+ <li><strong>Title:</strong> ${ticket.title}</li>
6159
+ <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>
6160
+ </ul>
6161
+ ${!isAssigned && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ""}
6162
+ ${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ""}
6163
+ <p><a href="${ticketUrl}">View Ticket</a></p>
6164
+ `.trim()
6165
+ };
6166
+ }
6167
+ };
6168
+ SupportTicketNotificationService = __decorate([
6169
+ injectable(),
6170
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6171
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo)),
6172
+ __decorateParam(2, inject(USER_PROFILE_TOKENS.IUserProfilesRepo)),
6173
+ __decorateParam(3, inject(TOKENS.IEmailService)),
6174
+ __decorateParam(4, inject(TOKENS.ENV)),
6175
+ __decorateParam(5, inject(TOKENS.LOGGER))
6176
+ ], SupportTicketNotificationService);
6177
+
6178
+ //#endregion
6179
+ //#region src/slices/support_ticket/services/support_ticket_triage_service.ts
6180
+ let SupportTicketTriageService = class SupportTicketTriageService$1 {
6181
+ constructor(supportStaffRepo, appSettingsRepo, logger$1) {
6182
+ this.supportStaffRepo = supportStaffRepo;
6183
+ this.appSettingsRepo = appSettingsRepo;
6184
+ this.logger = logger$1;
6185
+ }
6186
+ async getNextAssignee(updatedBy) {
6187
+ try {
6188
+ const triageUsers = await this.supportStaffRepo.readTriageUsers();
6189
+ if (triageUsers.length === 0) {
6190
+ this.logger.debug("No triage users found; skipping assignee");
6191
+ return null;
6192
+ }
6193
+ const ids = triageUsers.map((u) => u.id);
6194
+ const lastId = (await this.appSettingsRepo.readSetting(AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE))?.value;
6195
+ const lastIndex = lastId ? ids.indexOf(lastId) : -1;
6196
+ const nextUserId = ids[lastIndex < ids.length - 1 ? lastIndex + 1 : 0];
6197
+ await this.appSettingsRepo.updateSetting(AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE, nextUserId, updatedBy);
6198
+ return nextUserId;
6199
+ } catch (error) {
6200
+ this.logger.error("Failed to get next triage assignee", { error });
6201
+ return null;
6202
+ }
6203
+ }
6204
+ };
6205
+ SupportTicketTriageService = __decorate([
6206
+ injectable(),
6207
+ __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
6208
+ __decorateParam(1, inject(TOKENS.IAppSettingsRepo)),
6209
+ __decorateParam(2, inject(TOKENS.LOGGER))
6210
+ ], SupportTicketTriageService);
5173
6211
 
5174
6212
  //#endregion
5175
6213
  //#region src/slices/support_ticket/mappers/support_ticket_mapper.ts
@@ -5188,6 +6226,7 @@ function computeStatus(approvalStatus, devLifecycle) {
5188
6226
  case "TESTING":
5189
6227
  case "STAGING":
5190
6228
  case "PO_APPROVAL": return "IN_PROGRESS";
6229
+ case "VERIFICATION": return "VERIFICATION";
5191
6230
  case "DEPLOYED": return "COMPLETED";
5192
6231
  default: return "FOLLOWUP";
5193
6232
  }
@@ -5221,11 +6260,10 @@ function toCustomerSupportTicketReadDto(entity) {
5221
6260
  title: entity.title,
5222
6261
  description: entity.description,
5223
6262
  type: entity.type,
5224
- priority: entity.priority,
6263
+ priority: supportTicketNumberToPriority(entity.priority),
5225
6264
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5226
6265
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5227
- requester_name: entity.requester_name,
5228
- requester_email: entity.requester_email,
6266
+ created_by_display_name: null,
5229
6267
  credit_value: entity.credit_value,
5230
6268
  start_at: startAt,
5231
6269
  target_at: targetAt,
@@ -5234,7 +6272,8 @@ function toCustomerSupportTicketReadDto(entity) {
5234
6272
  created_at: createdAt,
5235
6273
  created_by: entity.created_by,
5236
6274
  updated_at: updatedAt,
5237
- updated_by: entity.updated_by
6275
+ updated_by: entity.updated_by,
6276
+ archived_at: entity.archived_at ?? null
5238
6277
  };
5239
6278
  }
5240
6279
  /**
@@ -5257,13 +6296,13 @@ function toStaffSupportTicketReadDto(entity) {
5257
6296
  title: entity.title,
5258
6297
  description: entity.description,
5259
6298
  type: entity.type,
5260
- priority: entity.priority,
6299
+ priority: supportTicketNumberToPriority(entity.priority),
5261
6300
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5262
6301
  approval_status: entity.approval_status,
5263
6302
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5264
6303
  can_delete: canDelete,
5265
- requester_name: entity.requester_name,
5266
- requester_email: entity.requester_email,
6304
+ created_by_display_name: null,
6305
+ assigned_to: entity.assigned_to ?? null,
5267
6306
  dev_lifecycle: entity.dev_lifecycle || "PENDING",
5268
6307
  credit_value: entity.credit_value,
5269
6308
  delivered_value: entity.delivered_value,
@@ -5274,7 +6313,9 @@ function toStaffSupportTicketReadDto(entity) {
5274
6313
  created_at: createdAt,
5275
6314
  created_by: entity.created_by,
5276
6315
  updated_at: updatedAt,
5277
- updated_by: entity.updated_by
6316
+ updated_by: entity.updated_by,
6317
+ archived_at: entity.archived_at ?? null,
6318
+ archived_by: entity.archived_by ?? null
5278
6319
  };
5279
6320
  }
5280
6321
  function dateIsValid(date) {
@@ -5286,13 +6327,14 @@ function dateIsValid(date) {
5286
6327
  //#endregion
5287
6328
  //#region src/slices/support_ticket/features/customer/create_support_ticket_feat.ts
5288
6329
  let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5289
- constructor(session, support_ticketRepo, create_record_version, appSettingsRepo, emailService, env, logger$1) {
6330
+ constructor(session, support_ticketRepo, subscriberRepo, notificationService, triageService, create_record_version, appSettingsRepo, logger$1) {
5290
6331
  this.session = session;
5291
6332
  this.support_ticketRepo = support_ticketRepo;
6333
+ this.subscriberRepo = subscriberRepo;
6334
+ this.notificationService = notificationService;
6335
+ this.triageService = triageService;
5292
6336
  this.create_record_version = create_record_version;
5293
6337
  this.appSettingsRepo = appSettingsRepo;
5294
- this.emailService = emailService;
5295
- this.env = env;
5296
6338
  this.logger = logger$1;
5297
6339
  }
5298
6340
  /**
@@ -5303,9 +6345,8 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5303
6345
  const userId = this.session.user.userId;
5304
6346
  const frEntity = {
5305
6347
  ...input,
6348
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5306
6349
  description: input.description ?? "",
5307
- requester_email: this.session.user.email,
5308
- requester_name: this.session.user.username,
5309
6350
  created_at: now,
5310
6351
  created_by: userId,
5311
6352
  updated_at: now,
@@ -5313,7 +6354,11 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5313
6354
  };
5314
6355
  const support_ticketCreated = await this.support_ticketRepo.create(frEntity);
5315
6356
  support_ticketCreated.display_id = await this.appSettingsRepo.getNextSequentialId(RecordConst.SUPPORT_TICKET, this.session.user.userId);
5316
- await this.support_ticketRepo.update(support_ticketCreated);
6357
+ const assigneeId = await this.triageService.getNextAssignee(this.session.user);
6358
+ if (assigneeId) {
6359
+ support_ticketCreated.assigned_to = assigneeId;
6360
+ await this.support_ticketRepo.update(support_ticketCreated);
6361
+ } else await this.support_ticketRepo.update(support_ticketCreated);
5317
6362
  await this.create_record_version.execute({
5318
6363
  record_id: support_ticketCreated.id,
5319
6364
  operation: OperationConst.INSERT,
@@ -5324,58 +6369,30 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5324
6369
  auth_role: this.session.user.user_type,
5325
6370
  auth_username: this.session.user.username
5326
6371
  });
5327
- await this.sendNotificationEmails(support_ticketCreated);
5328
- return toCustomerSupportTicketReadDto(support_ticketCreated);
6372
+ await this.addRequesterAsFollower(support_ticketCreated);
6373
+ await this.notificationService.notifyAssignee(support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
6374
+ await this.notificationService.notifyFollowers(support_ticketCreated.id, support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
6375
+ return {
6376
+ ...toCustomerSupportTicketReadDto(support_ticketCreated),
6377
+ created_by_display_name: this.session.user.email
6378
+ };
5329
6379
  }
5330
6380
  /**
5331
- * Send email notifications to staff members
6381
+ * Auto-add requester (created_by) as follower
5332
6382
  */
5333
- async sendNotificationEmails(ticket) {
6383
+ async addRequesterAsFollower(ticket) {
5334
6384
  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`);
6385
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6386
+ await this.subscriberRepo.ensureSubscriber({
6387
+ record_type: RecordConst.SUPPORT_TICKET,
6388
+ record_id: ticket.id,
6389
+ user_id: ticket.created_by,
6390
+ subscribed_events: null,
6391
+ created_at: now,
6392
+ created_by: ticket.created_by
6393
+ });
5377
6394
  } catch (error) {
5378
- this.logger.error("Failed to send support ticket notification emails", { error });
6395
+ this.logger.error("Failed to add requester as follower", { error });
5379
6396
  }
5380
6397
  }
5381
6398
  };
@@ -5383,19 +6400,87 @@ CreateSupportTicketFeat = __decorate([
5383
6400
  injectable(),
5384
6401
  __decorateParam(0, injectSession()),
5385
6402
  __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))
6403
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6404
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
6405
+ __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)),
6406
+ __decorateParam(5, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6407
+ __decorateParam(6, inject(TOKENS.IAppSettingsRepo)),
6408
+ __decorateParam(7, inject(TOKENS.LOGGER))
5391
6409
  ], CreateSupportTicketFeat);
5392
6410
 
6411
+ //#endregion
6412
+ //#region src/slices/support_ticket/features/customer/customer_toggle_subscription_feat.ts
6413
+ let CustomerToggleSubscriptionFeat = class CustomerToggleSubscriptionFeat$1 {
6414
+ constructor(session, subscriberRepo, supportTicketRepo) {
6415
+ this.session = session;
6416
+ this.subscriberRepo = subscriberRepo;
6417
+ this.supportTicketRepo = supportTicketRepo;
6418
+ }
6419
+ async execute(supportTicketId) {
6420
+ const ticket = await this.supportTicketRepo.read(supportTicketId);
6421
+ if (!ticket) throw new BusinessError("Support ticket not found");
6422
+ if (ticket.approval_status === "INTERNAL") throw new BusinessError("Support ticket not found");
6423
+ const subscription = await this.subscriberRepo.readByRecordAndUser(RecordConst.SUPPORT_TICKET, supportTicketId, this.session.user.userId);
6424
+ if (subscription) {
6425
+ if (!await this.subscriberRepo.delete(subscription.id)) throw new BusinessError("Failed to unsubscribe");
6426
+ return { subscribed: false };
6427
+ }
6428
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6429
+ const userId = this.session.user.userId;
6430
+ await this.subscriberRepo.ensureSubscriber({
6431
+ record_type: RecordConst.SUPPORT_TICKET,
6432
+ record_id: supportTicketId,
6433
+ user_id: userId,
6434
+ subscribed_events: null,
6435
+ created_at: now,
6436
+ created_by: userId
6437
+ });
6438
+ return { subscribed: true };
6439
+ }
6440
+ };
6441
+ CustomerToggleSubscriptionFeat = __decorate([
6442
+ injectable(),
6443
+ __decorateParam(0, injectSession()),
6444
+ __decorateParam(1, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6445
+ __decorateParam(2, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))
6446
+ ], CustomerToggleSubscriptionFeat);
6447
+
6448
+ //#endregion
6449
+ //#region src/slices/support_ticket/features/customer/enrich_customer_ticket.ts
6450
+ /**
6451
+ * Enriches a CustomerSupportTicketReadDto with requester info from created_by lookup.
6452
+ * Requester name/email are derived from created_by (user display = email).
6453
+ */
6454
+ function enrichCustomerTicket(dto, displayMap) {
6455
+ const createdByDisplay = dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null;
6456
+ return {
6457
+ ...dto,
6458
+ created_by_display_name: createdByDisplay ?? dto.created_by ?? "—"
6459
+ };
6460
+ }
6461
+ function collectUserIdsFromCustomerTicket(ticket) {
6462
+ return [ticket.created_by].filter((id) => Boolean(id));
6463
+ }
6464
+
5393
6465
  //#endregion
5394
6466
  //#region src/slices/support_ticket/features/customer/get_support_ticket_customer_feat.ts
6467
+ function toRecordSubscriberReadDto$2(e) {
6468
+ return {
6469
+ id: e.id,
6470
+ record_type: e.record_type,
6471
+ record_id: e.record_id,
6472
+ user_id: e.user_id,
6473
+ subscribed_events: e.subscribed_events ?? null,
6474
+ created_at: e.created_at,
6475
+ created_by: e.created_by
6476
+ };
6477
+ }
5395
6478
  let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5396
- constructor(session, support_ticketRepo, appSettingsRepo, logger$1) {
6479
+ constructor(session, support_ticketRepo, subscriberRepo, userDisplayLookup, appSettingsRepo, logger$1) {
5397
6480
  this.session = session;
5398
6481
  this.support_ticketRepo = support_ticketRepo;
6482
+ this.subscriberRepo = subscriberRepo;
6483
+ this.userDisplayLookup = userDisplayLookup;
5399
6484
  this.appSettingsRepo = appSettingsRepo;
5400
6485
  this.logger = logger$1;
5401
6486
  }
@@ -5413,22 +6498,31 @@ let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5413
6498
  updated_by: this.session.user.userId
5414
6499
  });
5415
6500
  }
5416
- return toCustomerSupportTicketReadDto(support_ticket);
6501
+ const base = toCustomerSupportTicketReadDto(support_ticket);
6502
+ const enriched = enrichCustomerTicket(base, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromCustomerTicket(base)));
6503
+ const subscription = await this.subscriberRepo.readByRecordAndUser(RecordConst.SUPPORT_TICKET, id, this.session.user.userId);
6504
+ return {
6505
+ ...enriched,
6506
+ my_subscription: subscription ? toRecordSubscriberReadDto$2(subscription) : null
6507
+ };
5417
6508
  }
5418
6509
  };
5419
6510
  GetSupportTicketCustomerFeature = __decorate([
5420
6511
  injectable(),
5421
6512
  __decorateParam(0, injectSession()),
5422
6513
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5423
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
5424
- __decorateParam(3, inject(TOKENS.LOGGER))
6514
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6515
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup)),
6516
+ __decorateParam(4, inject(TOKENS.IAppSettingsRepo)),
6517
+ __decorateParam(5, inject(TOKENS.LOGGER))
5425
6518
  ], GetSupportTicketCustomerFeature);
5426
6519
 
5427
6520
  //#endregion
5428
6521
  //#region src/slices/support_ticket/features/customer/get_support_tickets_customer_feat.ts
5429
6522
  let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1 {
5430
- constructor(support_ticketRepo) {
6523
+ constructor(support_ticketRepo, userDisplayLookup) {
5431
6524
  this.support_ticketRepo = support_ticketRepo;
6525
+ this.userDisplayLookup = userDisplayLookup;
5432
6526
  }
5433
6527
  async execute(filters) {
5434
6528
  const customerFilters = {
@@ -5439,20 +6533,28 @@ let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1
5439
6533
  }
5440
6534
  };
5441
6535
  const result = await this.support_ticketRepo.read_all(customerFilters);
6536
+ const dtos = result.items.map(toCustomerSupportTicketReadDto);
6537
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromCustomerTicket))];
6538
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5442
6539
  return {
5443
- items: result.items.map(toCustomerSupportTicketReadDto),
6540
+ items: dtos.map((dto) => enrichCustomerTicket(dto, displayMap)),
5444
6541
  pageInfo: result.pageInfo
5445
6542
  };
5446
6543
  }
5447
6544
  };
5448
- GetSupportTicketsCustomerFeature = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))], GetSupportTicketsCustomerFeature);
6545
+ GetSupportTicketsCustomerFeature = __decorate([
6546
+ injectable(),
6547
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6548
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
6549
+ ], GetSupportTicketsCustomerFeature);
5449
6550
 
5450
6551
  //#endregion
5451
6552
  //#region src/slices/support_ticket/features/customer/update_support_ticket_customer_feat.ts
5452
6553
  let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5453
- constructor(session, support_ticketRepo, create_record_version) {
6554
+ constructor(session, support_ticketRepo, userDisplayLookup, create_record_version) {
5454
6555
  this.session = session;
5455
6556
  this.support_ticketRepo = support_ticketRepo;
6557
+ this.userDisplayLookup = userDisplayLookup;
5456
6558
  this.create_record_version = create_record_version;
5457
6559
  }
5458
6560
  /**
@@ -5460,8 +6562,9 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5460
6562
  */
5461
6563
  async execute(input) {
5462
6564
  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");
6565
+ if (!support_ticket) throw new BusinessError("SupportTicket not found");
6566
+ if (support_ticket.approval_status !== "PENDING") throw new BusinessError("SupportTicket is locked - cannot be edited");
6567
+ if (support_ticket.archived_at) throw new BusinessError("Cannot edit an archived support ticket.");
5465
6568
  const frEntity = {
5466
6569
  ...support_ticket,
5467
6570
  title: input.title,
@@ -5483,24 +6586,102 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5483
6586
  auth_role: this.session.user.user_type,
5484
6587
  auth_username: this.session.user.username
5485
6588
  });
5486
- return toCustomerSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
6589
+ const base = toCustomerSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
6590
+ return enrichCustomerTicket(base, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromCustomerTicket(base)));
5487
6591
  }
5488
6592
  };
5489
6593
  UpdateSupportTicketCustomerFeat = __decorate([
5490
6594
  injectable(),
5491
6595
  __decorateParam(0, injectSession()),
5492
6596
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5493
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
6597
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
6598
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5494
6599
  ], UpdateSupportTicketCustomerFeat);
5495
6600
 
6601
+ //#endregion
6602
+ //#region src/slices/support_ticket/features/staff/add_support_ticket_subscriber_feat.ts
6603
+ function toRecordSubscriberReadDto$1(e) {
6604
+ return {
6605
+ id: e.id,
6606
+ record_type: e.record_type,
6607
+ record_id: e.record_id,
6608
+ user_id: e.user_id,
6609
+ subscribed_events: e.subscribed_events ?? null,
6610
+ created_at: e.created_at,
6611
+ created_by: e.created_by
6612
+ };
6613
+ }
6614
+ let AddSupportTicketSubscriberFeat = class AddSupportTicketSubscriberFeat$1 {
6615
+ constructor(subscriberRepo, supportTicketRepo, session, userDisplayLookup) {
6616
+ this.subscriberRepo = subscriberRepo;
6617
+ this.supportTicketRepo = supportTicketRepo;
6618
+ this.session = session;
6619
+ this.userDisplayLookup = userDisplayLookup;
6620
+ }
6621
+ async execute(input) {
6622
+ if (!await this.supportTicketRepo.read(input.support_ticket_id)) throw new BusinessError("Support ticket not found");
6623
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6624
+ const dto = toRecordSubscriberReadDto$1(await this.subscriberRepo.ensureSubscriber({
6625
+ record_type: RecordConst.SUPPORT_TICKET,
6626
+ record_id: input.support_ticket_id,
6627
+ user_id: input.user_id,
6628
+ subscribed_events: input.subscribed_events ?? null,
6629
+ created_at: now,
6630
+ created_by: this.session.user.userId
6631
+ }));
6632
+ const userIds = [dto.user_id, dto.created_by].filter(Boolean);
6633
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
6634
+ return {
6635
+ ...dto,
6636
+ user_id_display_name: dto.user_id ? displayMap.get(dto.user_id) ?? dto.user_id : null,
6637
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null
6638
+ };
6639
+ }
6640
+ };
6641
+ AddSupportTicketSubscriberFeat = __decorate([
6642
+ injectable(),
6643
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
6644
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6645
+ __decorateParam(2, injectSession()),
6646
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6647
+ ], AddSupportTicketSubscriberFeat);
6648
+
6649
+ //#endregion
6650
+ //#region src/slices/support_ticket/features/staff/enrich_staff_ticket.ts
6651
+ /**
6652
+ * Enriches a StaffSupportTicketReadDto with display names from a lookup map.
6653
+ * Used by all staff features that return a ticket.
6654
+ */
6655
+ function enrichStaffTicket(dto, displayMap) {
6656
+ const createdByDisplay = dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null;
6657
+ return {
6658
+ ...dto,
6659
+ assigned_to_display_name: dto.assigned_to ? displayMap.get(dto.assigned_to) ?? dto.assigned_to : null,
6660
+ created_by_display_name: createdByDisplay ?? dto.created_by ?? "—",
6661
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
6662
+ };
6663
+ }
6664
+ /**
6665
+ * Collects user IDs from a staff ticket for display name lookup.
6666
+ */
6667
+ function collectUserIdsFromStaffTicket(ticket) {
6668
+ return [
6669
+ ticket.assigned_to,
6670
+ ticket.created_by,
6671
+ ticket.updated_by
6672
+ ].filter((id) => Boolean(id));
6673
+ }
6674
+
5496
6675
  //#endregion
5497
6676
  //#region src/slices/support_ticket/features/staff/approve_support_ticket_feat.ts
5498
6677
  let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5499
- constructor(repo, creditService, create_record_version, session) {
6678
+ constructor(repo, notificationService, creditService, create_record_version, session, userDisplayLookup) {
5500
6679
  this.repo = repo;
6680
+ this.notificationService = notificationService;
5501
6681
  this.creditService = creditService;
5502
6682
  this.create_record_version = create_record_version;
5503
6683
  this.session = session;
6684
+ this.userDisplayLookup = userDisplayLookup;
5504
6685
  }
5505
6686
  /**
5506
6687
  * Approves a support_ticket item, deducts credits, and locks it.
@@ -5516,15 +6697,18 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5516
6697
  * @returns The updated support_ticket with full staff view.
5517
6698
  */
5518
6699
  async execute(args) {
5519
- const { id } = args;
6700
+ const { id, credit_value: creditValueFromArgs } = args;
5520
6701
  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.");
6702
+ if (!support_ticket) throw new BusinessError(`Support Ticket with ID ${id} not found`);
6703
+ if (support_ticket.approval_status !== "PENDING") throw new BusinessError("Support Ticket must be in PENDING status to be approved.");
6704
+ const creditAmount = parseFloat(creditValueFromArgs);
6705
+ if (isNaN(creditAmount) || creditAmount < 0) throw new BusinessError("Invalid credit value amount.");
6706
+ const ticketToUpdate = {
6707
+ ...support_ticket,
6708
+ credit_value: creditValueFromArgs
6709
+ };
6710
+ if (support_ticket.credit_value?.trim() !== creditValueFromArgs) await this.repo.update(ticketToUpdate);
6711
+ 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
6712
  const lockedAt = (/* @__PURE__ */ new Date()).toISOString();
5529
6713
  const changed_props = getChangedProperties(support_ticket, {
5530
6714
  ...support_ticket,
@@ -5549,25 +6733,101 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5549
6733
  dev_lifecycle: "BACKLOG",
5550
6734
  locked_approval_at: lockedAt
5551
6735
  });
5552
- if (!updatedSupportTicket) throw new Error("Failed to approve support_ticket.");
5553
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6736
+ if (!updatedSupportTicket) throw new BusinessError("Failed to approve support_ticket.");
6737
+ await this.notificationService.notifyFollowers(id, updatedSupportTicket, "APPROVAL_STATUS_CHANGED", { actorUserId: this.session.user.userId });
6738
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6739
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5554
6740
  }
5555
6741
  };
5556
6742
  ApproveSupportTicketFeat = __decorate([
5557
6743
  injectable(),
5558
6744
  __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())
6745
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
6746
+ __decorateParam(2, inject(CREDIT_SERVICE_TOKEN)),
6747
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6748
+ __decorateParam(4, injectSession()),
6749
+ __decorateParam(5, inject(USER_TOKENS.IUserDisplayLookup))
5562
6750
  ], ApproveSupportTicketFeat);
5563
6751
 
6752
+ //#endregion
6753
+ //#region src/slices/support_ticket/features/staff/archive_support_ticket_feat.ts
6754
+ let ArchiveSupportTicketFeat = class ArchiveSupportTicketFeat$1 {
6755
+ constructor(repo, create_record_version, session, userDisplayLookup) {
6756
+ this.repo = repo;
6757
+ this.create_record_version = create_record_version;
6758
+ this.session = session;
6759
+ this.userDisplayLookup = userDisplayLookup;
6760
+ }
6761
+ async execute(id) {
6762
+ const support_ticket = await this.repo.read(id);
6763
+ if (!support_ticket) throw new Error(`Support ticket with id ${id} not found`);
6764
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6765
+ const userId = this.session.user.userId;
6766
+ if (support_ticket.archived_at) {
6767
+ await this.create_record_version.execute({
6768
+ record_id: id,
6769
+ operation: OperationConst.UPDATE,
6770
+ recorded_at: now,
6771
+ record_type: RecordConst.SUPPORT_TICKET,
6772
+ record: {
6773
+ archived_at: null,
6774
+ archived_by: null
6775
+ },
6776
+ old_record: support_ticket,
6777
+ auth_uid: userId,
6778
+ auth_role: this.session.user.user_type,
6779
+ auth_username: this.session.user.username
6780
+ });
6781
+ const updated$1 = await this.repo.update({
6782
+ ...support_ticket,
6783
+ archived_at: null,
6784
+ archived_by: null
6785
+ });
6786
+ if (!updated$1) throw new Error("Failed to unarchive support ticket.");
6787
+ const dto$1 = toStaffSupportTicketReadDto(updated$1);
6788
+ return enrichStaffTicket(dto$1, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto$1)));
6789
+ }
6790
+ 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.");
6791
+ await this.create_record_version.execute({
6792
+ record_id: id,
6793
+ operation: OperationConst.UPDATE,
6794
+ recorded_at: now,
6795
+ record_type: RecordConst.SUPPORT_TICKET,
6796
+ record: {
6797
+ archived_at: now,
6798
+ archived_by: userId
6799
+ },
6800
+ old_record: support_ticket,
6801
+ auth_uid: userId,
6802
+ auth_role: this.session.user.user_type,
6803
+ auth_username: this.session.user.username
6804
+ });
6805
+ const updated = await this.repo.update({
6806
+ ...support_ticket,
6807
+ archived_at: now,
6808
+ archived_by: userId
6809
+ });
6810
+ if (!updated) throw new Error("Failed to archive support ticket.");
6811
+ const dto = toStaffSupportTicketReadDto(updated);
6812
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6813
+ }
6814
+ };
6815
+ ArchiveSupportTicketFeat = __decorate([
6816
+ injectable(),
6817
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6818
+ __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6819
+ __decorateParam(2, injectSession()),
6820
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6821
+ ], ArchiveSupportTicketFeat);
6822
+
5564
6823
  //#endregion
5565
6824
  //#region src/slices/support_ticket/features/staff/cancel_internal_task_feat.ts
5566
6825
  let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5567
- constructor(repo, create_record_version, session) {
6826
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5568
6827
  this.repo = repo;
5569
6828
  this.create_record_version = create_record_version;
5570
6829
  this.session = session;
6830
+ this.userDisplayLookup = userDisplayLookup;
5571
6831
  }
5572
6832
  /**
5573
6833
  * Cancels an internal task by setting dev_lifecycle to CANCELLED.
@@ -5612,23 +6872,26 @@ let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5612
6872
  completed_at: completedAt
5613
6873
  });
5614
6874
  if (!updatedSupportTicket) throw new Error("Failed to cancel internal task.");
5615
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6875
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6876
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5616
6877
  }
5617
6878
  };
5618
6879
  CancelInternalTaskFeat = __decorate([
5619
6880
  injectable(),
5620
6881
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5621
6882
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5622
- __decorateParam(2, injectSession())
6883
+ __decorateParam(2, injectSession()),
6884
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5623
6885
  ], CancelInternalTaskFeat);
5624
6886
 
5625
6887
  //#endregion
5626
6888
  //#region src/slices/support_ticket/features/staff/complete_support_ticket_feat.ts
5627
6889
  let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5628
- constructor(repo, create_record_version, session) {
6890
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5629
6891
  this.repo = repo;
5630
6892
  this.create_record_version = create_record_version;
5631
6893
  this.session = session;
6894
+ this.userDisplayLookup = userDisplayLookup;
5632
6895
  }
5633
6896
  /**
5634
6897
  * Completes a support_ticket item by setting it to DEPLOYED with delivered value.
@@ -5674,23 +6937,26 @@ let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5674
6937
  completed_at: completedAt
5675
6938
  });
5676
6939
  if (!updatedSupportTicket) throw new Error("Failed to complete support_ticket.");
5677
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6940
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6941
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5678
6942
  }
5679
6943
  };
5680
6944
  CompleteSupportTicketFeat = __decorate([
5681
6945
  injectable(),
5682
6946
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5683
6947
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5684
- __decorateParam(2, injectSession())
6948
+ __decorateParam(2, injectSession()),
6949
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5685
6950
  ], CompleteSupportTicketFeat);
5686
6951
 
5687
6952
  //#endregion
5688
6953
  //#region src/slices/support_ticket/features/staff/convert_to_customer_feat.ts
5689
6954
  let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5690
- constructor(session, support_ticketRepo, create_record_version) {
6955
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5691
6956
  this.session = session;
5692
6957
  this.support_ticketRepo = support_ticketRepo;
5693
6958
  this.create_record_version = create_record_version;
6959
+ this.userDisplayLookup = userDisplayLookup;
5694
6960
  }
5695
6961
  async execute(support_ticket_id) {
5696
6962
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5701,7 +6967,7 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5701
6967
  approval_status: "PENDING",
5702
6968
  dev_lifecycle: null,
5703
6969
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5704
- updated_by: this.session.user.email
6970
+ updated_by: this.session.user.userId
5705
6971
  };
5706
6972
  const updated = await this.support_ticketRepo.update(updateEntity);
5707
6973
  await this.create_record_version.execute({
@@ -5715,23 +6981,26 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5715
6981
  auth_role: this.session.user.user_type,
5716
6982
  auth_username: this.session.user.username
5717
6983
  });
5718
- return toStaffSupportTicketReadDto(updated);
6984
+ const dto = toStaffSupportTicketReadDto(updated);
6985
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5719
6986
  }
5720
6987
  };
5721
6988
  ConvertToCustomerFeat = __decorate([
5722
6989
  injectable(),
5723
6990
  __decorateParam(0, injectSession()),
5724
6991
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5725
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
6992
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6993
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5726
6994
  ], ConvertToCustomerFeat);
5727
6995
 
5728
6996
  //#endregion
5729
6997
  //#region src/slices/support_ticket/features/staff/convert_to_internal_feat.ts
5730
6998
  let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5731
- constructor(session, support_ticketRepo, create_record_version) {
6999
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5732
7000
  this.session = session;
5733
7001
  this.support_ticketRepo = support_ticketRepo;
5734
7002
  this.create_record_version = create_record_version;
7003
+ this.userDisplayLookup = userDisplayLookup;
5735
7004
  }
5736
7005
  async execute(support_ticket_id) {
5737
7006
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5743,7 +7012,7 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5743
7012
  credit_value: null,
5744
7013
  dev_lifecycle: "BACKLOG",
5745
7014
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5746
- updated_by: this.session.user.email
7015
+ updated_by: this.session.user.userId
5747
7016
  };
5748
7017
  const updated = await this.support_ticketRepo.update(updateEntity);
5749
7018
  await this.create_record_version.execute({
@@ -5757,26 +7026,31 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5757
7026
  auth_role: this.session.user.user_type,
5758
7027
  auth_username: this.session.user.username
5759
7028
  });
5760
- return toStaffSupportTicketReadDto(updated);
7029
+ const dto = toStaffSupportTicketReadDto(updated);
7030
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5761
7031
  }
5762
7032
  };
5763
7033
  ConvertToInternalFeat = __decorate([
5764
7034
  injectable(),
5765
7035
  __decorateParam(0, injectSession()),
5766
7036
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5767
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7037
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7038
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5768
7039
  ], ConvertToInternalFeat);
5769
7040
 
5770
7041
  //#endregion
5771
7042
  //#region src/slices/support_ticket/features/staff/create_support_ticket_admin_feat.ts
5772
7043
  let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5773
- constructor(session, support_ticketRepo, create_record_version, appSettingsRepo, emailService, env, logger$1) {
7044
+ constructor(session, support_ticketRepo, subscriberRepo, notificationService, triageService, supportStaffRepo, userDisplayLookup, create_record_version, appSettingsRepo, logger$1) {
5774
7045
  this.session = session;
5775
7046
  this.support_ticketRepo = support_ticketRepo;
7047
+ this.subscriberRepo = subscriberRepo;
7048
+ this.notificationService = notificationService;
7049
+ this.triageService = triageService;
7050
+ this.supportStaffRepo = supportStaffRepo;
7051
+ this.userDisplayLookup = userDisplayLookup;
5776
7052
  this.create_record_version = create_record_version;
5777
7053
  this.appSettingsRepo = appSettingsRepo;
5778
- this.emailService = emailService;
5779
- this.env = env;
5780
7054
  this.logger = logger$1;
5781
7055
  }
5782
7056
  /**
@@ -5789,10 +7063,8 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5789
7063
  title: input.title || "",
5790
7064
  description: input.description || "",
5791
7065
  type: input.type || "FEATURE_REQUEST",
5792
- priority: input.priority || "MEDIUM",
7066
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5793
7067
  approval_status: input.is_internal ? "INTERNAL" : "PENDING",
5794
- requester_email: this.session.user.email,
5795
- requester_name: this.session.user.username,
5796
7068
  dev_lifecycle: input.dev_lifecycle || null,
5797
7069
  credit_value: input.is_internal ? null : formatCreditValue(input.credit_value),
5798
7070
  delivered_value: input.delivered_value || null,
@@ -5807,6 +7079,12 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5807
7079
  };
5808
7080
  const support_ticketCreated = await this.support_ticketRepo.create(support_ticketEntity);
5809
7081
  support_ticketCreated.display_id = await this.appSettingsRepo.getNextSequentialId(RecordConst.SUPPORT_TICKET, this.session.user.userId);
7082
+ let assigneeId = null;
7083
+ if (input.assigned_to?.trim()) {
7084
+ if (!(await this.supportStaffRepo.readTriageUsers()).some((u) => u.id === input.assigned_to)) throw new BusinessError("Selected assignee is not a support staff member");
7085
+ assigneeId = input.assigned_to;
7086
+ } else assigneeId = await this.triageService.getNextAssignee(this.session.user);
7087
+ if (assigneeId) support_ticketCreated.assigned_to = assigneeId;
5810
7088
  await this.support_ticketRepo.update(support_ticketCreated);
5811
7089
  await this.create_record_version.execute({
5812
7090
  record_id: support_ticketCreated.id,
@@ -5818,58 +7096,30 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5818
7096
  auth_role: this.session.user.user_type,
5819
7097
  auth_username: this.session.user.username
5820
7098
  });
5821
- if (!input.is_internal) await this.sendNotificationEmails(support_ticketCreated);
5822
- return toStaffSupportTicketReadDto(support_ticketCreated);
7099
+ if (!input.is_internal) {
7100
+ await this.addRequesterAsFollower(support_ticketCreated);
7101
+ await this.notificationService.notifyAssignee(support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
7102
+ await this.notificationService.notifyFollowers(support_ticketCreated.id, support_ticketCreated, "TICKET_CREATED", { actorUserId: userId });
7103
+ }
7104
+ const dto = toStaffSupportTicketReadDto(support_ticketCreated);
7105
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5823
7106
  }
5824
7107
  /**
5825
- * Send email notifications to staff members
7108
+ * Auto-add requester (created_by) as follower
5826
7109
  */
5827
- async sendNotificationEmails(ticket) {
7110
+ async addRequesterAsFollower(ticket) {
5828
7111
  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`);
7112
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7113
+ await this.subscriberRepo.ensureSubscriber({
7114
+ record_type: RecordConst.SUPPORT_TICKET,
7115
+ record_id: ticket.id,
7116
+ user_id: ticket.created_by,
7117
+ subscribed_events: null,
7118
+ created_at: now,
7119
+ created_by: ticket.created_by
7120
+ });
5871
7121
  } catch (error) {
5872
- this.logger.error("Failed to send support ticket notification emails", { error });
7122
+ this.logger.error("Failed to add requester as follower", { error });
5873
7123
  }
5874
7124
  }
5875
7125
  };
@@ -5877,11 +7127,14 @@ CreateSupportTicketAdminFeat = __decorate([
5877
7127
  injectable(),
5878
7128
  __decorateParam(0, injectSession()),
5879
7129
  __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))
7130
+ __decorateParam(2, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
7131
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7132
+ __decorateParam(4, inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)),
7133
+ __decorateParam(5, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)),
7134
+ __decorateParam(6, inject(USER_TOKENS.IUserDisplayLookup)),
7135
+ __decorateParam(7, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7136
+ __decorateParam(8, inject(TOKENS.IAppSettingsRepo)),
7137
+ __decorateParam(9, inject(TOKENS.LOGGER))
5885
7138
  ], CreateSupportTicketAdminFeat);
5886
7139
 
5887
7140
  //#endregion
@@ -5928,13 +7181,93 @@ DeleteSupportTicketFeat = __decorate([
5928
7181
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5929
7182
  ], DeleteSupportTicketFeat);
5930
7183
 
7184
+ //#endregion
7185
+ //#region src/slices/support_ticket/features/staff/fix_support_ticket_user_ids_feat.ts
7186
+ const UNIVERSAL_ID_LENGTH = 32;
7187
+ let FixSupportTicketUserIdsFeature = class FixSupportTicketUserIdsFeature$1 {
7188
+ constructor(supportTicketRepo, userRepo) {
7189
+ this.supportTicketRepo = supportTicketRepo;
7190
+ this.userRepo = userRepo;
7191
+ }
7192
+ async execute() {
7193
+ const tickets = await this.supportTicketRepo.findWithInvalidUserIds();
7194
+ if (tickets.length === 0) return {
7195
+ ticketsScanned: 0,
7196
+ ticketsFixed: 0,
7197
+ ticketsSkipped: 0
7198
+ };
7199
+ const emailToUserId = await this.buildEmailToUserIdMap(tickets);
7200
+ let ticketsFixed = 0;
7201
+ let ticketsSkipped = 0;
7202
+ for (const ticket of tickets) {
7203
+ const updates = this.buildUpdates(ticket, emailToUserId);
7204
+ if (Object.keys(updates).length === 0) {
7205
+ ticketsSkipped++;
7206
+ continue;
7207
+ }
7208
+ const updated = {
7209
+ ...ticket,
7210
+ ...updates
7211
+ };
7212
+ await this.supportTicketRepo.update(updated);
7213
+ ticketsFixed++;
7214
+ }
7215
+ return {
7216
+ ticketsScanned: tickets.length,
7217
+ ticketsFixed,
7218
+ ticketsSkipped
7219
+ };
7220
+ }
7221
+ async buildEmailToUserIdMap(tickets) {
7222
+ const fieldsToFix = [
7223
+ "created_by",
7224
+ "updated_by",
7225
+ "assigned_to",
7226
+ "archived_by",
7227
+ "deleted_by"
7228
+ ];
7229
+ const emails = /* @__PURE__ */ new Set();
7230
+ for (const ticket of tickets) for (const field of fieldsToFix) {
7231
+ const value = ticket[field];
7232
+ if (value != null && value.length !== UNIVERSAL_ID_LENGTH) emails.add(value);
7233
+ }
7234
+ if (emails.size === 0) return /* @__PURE__ */ new Map();
7235
+ const users = await this.userRepo.read_users_by_emails_case_insensitive([...emails]);
7236
+ const map = /* @__PURE__ */ new Map();
7237
+ for (const u of users) map.set(u.email.toLowerCase(), u.id);
7238
+ return map;
7239
+ }
7240
+ buildUpdates(ticket, emailToUserId) {
7241
+ const updates = {};
7242
+ for (const field of [
7243
+ "created_by",
7244
+ "updated_by",
7245
+ "assigned_to",
7246
+ "archived_by",
7247
+ "deleted_by"
7248
+ ]) {
7249
+ const value = ticket[field];
7250
+ if (value == null || value.length === UNIVERSAL_ID_LENGTH) continue;
7251
+ const userId = emailToUserId.get(value.toLowerCase());
7252
+ if (userId) updates[field] = userId;
7253
+ }
7254
+ return updates;
7255
+ }
7256
+ };
7257
+ FixSupportTicketUserIdsFeature = __decorate([
7258
+ injectable(),
7259
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7260
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo))
7261
+ ], FixSupportTicketUserIdsFeature);
7262
+
5931
7263
  //#endregion
5932
7264
  //#region src/slices/support_ticket/features/staff/get_support_ticket_admin_feat.ts
5933
7265
  let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5934
- constructor(session, support_ticketRepo, appSettingsRepo) {
7266
+ constructor(session, support_ticketRepo, appSettingsRepo, userDisplayLookup) {
5935
7267
  this.session = session;
5936
7268
  this.support_ticketRepo = support_ticketRepo;
5937
7269
  this.appSettingsRepo = appSettingsRepo;
7270
+ this.userDisplayLookup = userDisplayLookup;
5938
7271
  }
5939
7272
  async execute(id) {
5940
7273
  let support_ticket = await this.support_ticketRepo.read(id);
@@ -5948,39 +7281,110 @@ let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5948
7281
  updated_by: this.session.user.userId
5949
7282
  });
5950
7283
  }
5951
- return toStaffSupportTicketReadDto(support_ticket);
7284
+ const dto = toStaffSupportTicketReadDto(support_ticket);
7285
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5952
7286
  }
5953
7287
  };
5954
7288
  GetSupportTicketAdminFeature = __decorate([
5955
7289
  injectable(),
5956
7290
  __decorateParam(0, injectSession()),
5957
7291
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5958
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo))
7292
+ __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
7293
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5959
7294
  ], GetSupportTicketAdminFeature);
5960
7295
 
7296
+ //#endregion
7297
+ //#region src/slices/support_ticket/features/staff/get_requestors_for_active_support_tickets_feat.ts
7298
+ let GetRequestorsForActiveSupportTicketsFeat = class GetRequestorsForActiveSupportTicketsFeat$1 {
7299
+ constructor(supportTicketRepo, userRepo) {
7300
+ this.supportTicketRepo = supportTicketRepo;
7301
+ this.userRepo = userRepo;
7302
+ }
7303
+ async execute(options) {
7304
+ const requestorIds = await this.supportTicketRepo.read_distinct_requestors_for_active_tickets(options);
7305
+ if (requestorIds.length === 0) return [];
7306
+ return (await this.userRepo.read_users_by_ids(requestorIds)).map((u) => ({
7307
+ id: u.id,
7308
+ email: u.email
7309
+ }));
7310
+ }
7311
+ };
7312
+ GetRequestorsForActiveSupportTicketsFeat = __decorate([
7313
+ injectable(),
7314
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7315
+ __decorateParam(1, inject(USER_TOKENS.IUsersRepo))
7316
+ ], GetRequestorsForActiveSupportTicketsFeat);
7317
+
5961
7318
  //#endregion
5962
7319
  //#region src/slices/support_ticket/features/staff/get_support_tickets_admin_feat.ts
5963
7320
  let GetSupportTicketsAdminFeature = class GetSupportTicketsAdminFeature$1 {
5964
- constructor(support_ticketRepo) {
7321
+ constructor(support_ticketRepo, userDisplayLookup) {
5965
7322
  this.support_ticketRepo = support_ticketRepo;
7323
+ this.userDisplayLookup = userDisplayLookup;
5966
7324
  }
5967
7325
  async execute(filters) {
5968
7326
  const result = await this.support_ticketRepo.read_all(filters);
7327
+ const dtos = result.items.map(toStaffSupportTicketReadDto);
7328
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromStaffTicket))];
7329
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
5969
7330
  return {
5970
- items: result.items.map(toStaffSupportTicketReadDto),
7331
+ items: dtos.map((t) => enrichStaffTicket(t, displayMap)),
5971
7332
  pageInfo: result.pageInfo
5972
7333
  };
5973
7334
  }
5974
7335
  };
5975
- GetSupportTicketsAdminFeature = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo))], GetSupportTicketsAdminFeature);
7336
+ GetSupportTicketsAdminFeature = __decorate([
7337
+ injectable(),
7338
+ __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7339
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
7340
+ ], GetSupportTicketsAdminFeature);
7341
+
7342
+ //#endregion
7343
+ //#region src/slices/support_ticket/features/staff/list_support_ticket_subscribers_feat.ts
7344
+ function toRecordSubscriberReadDto(e) {
7345
+ return {
7346
+ id: e.id,
7347
+ record_type: e.record_type,
7348
+ record_id: e.record_id,
7349
+ user_id: e.user_id,
7350
+ subscribed_events: e.subscribed_events ?? null,
7351
+ created_at: e.created_at,
7352
+ created_by: e.created_by
7353
+ };
7354
+ }
7355
+ let ListSupportTicketSubscribersFeat = class ListSupportTicketSubscribersFeat$1 {
7356
+ constructor(subscriberRepo, supportTicketRepo, userDisplayLookup) {
7357
+ this.subscriberRepo = subscriberRepo;
7358
+ this.supportTicketRepo = supportTicketRepo;
7359
+ this.userDisplayLookup = userDisplayLookup;
7360
+ }
7361
+ async execute(supportTicketId) {
7362
+ if (!await this.supportTicketRepo.read(supportTicketId)) throw new BusinessError("Support ticket not found");
7363
+ const dtos = (await this.subscriberRepo.readByRecordId(RecordConst.SUPPORT_TICKET, supportTicketId)).map(toRecordSubscriberReadDto);
7364
+ const userIds = [...new Set(dtos.flatMap((s) => [s.user_id, s.created_by].filter(Boolean)))];
7365
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
7366
+ return dtos.map((s) => ({
7367
+ ...s,
7368
+ user_id_display_name: s.user_id ? displayMap.get(s.user_id) ?? s.user_id : null,
7369
+ created_by_display_name: s.created_by ? displayMap.get(s.created_by) ?? s.created_by : null
7370
+ }));
7371
+ }
7372
+ };
7373
+ ListSupportTicketSubscribersFeat = __decorate([
7374
+ injectable(),
7375
+ __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)),
7376
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
7377
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup))
7378
+ ], ListSupportTicketSubscribersFeat);
5976
7379
 
5977
7380
  //#endregion
5978
7381
  //#region src/slices/support_ticket/features/staff/reactivate_internal_task_feat.ts
5979
7382
  let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
5980
- constructor(repo, create_record_version, session) {
7383
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5981
7384
  this.repo = repo;
5982
7385
  this.create_record_version = create_record_version;
5983
7386
  this.session = session;
7387
+ this.userDisplayLookup = userDisplayLookup;
5984
7388
  }
5985
7389
  /**
5986
7390
  * Reactivates a terminal internal task.
@@ -6028,23 +7432,27 @@ let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
6028
7432
  completed_at: null
6029
7433
  });
6030
7434
  if (!updatedSupportTicket) throw new Error("Failed to reactivate internal task.");
6031
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7435
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7436
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6032
7437
  }
6033
7438
  };
6034
7439
  ReactivateInternalTaskFeat = __decorate([
6035
7440
  injectable(),
6036
7441
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6037
7442
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6038
- __decorateParam(2, injectSession())
7443
+ __decorateParam(2, injectSession()),
7444
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6039
7445
  ], ReactivateInternalTaskFeat);
6040
7446
 
6041
7447
  //#endregion
6042
7448
  //#region src/slices/support_ticket/features/staff/reject_support_ticket_feat.ts
6043
7449
  let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6044
- constructor(repo, create_record_version, session) {
7450
+ constructor(repo, notificationService, create_record_version, session, userDisplayLookup) {
6045
7451
  this.repo = repo;
7452
+ this.notificationService = notificationService;
6046
7453
  this.create_record_version = create_record_version;
6047
7454
  this.session = session;
7455
+ this.userDisplayLookup = userDisplayLookup;
6048
7456
  }
6049
7457
  /**
6050
7458
  * Rejects a support_ticket item and locks it.
@@ -6085,24 +7493,41 @@ let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6085
7493
  locked_approval_at: lockedAt
6086
7494
  });
6087
7495
  if (!updatedSupportTicket) throw new Error("Failed to reject support_ticket.");
6088
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7496
+ await this.notificationService.notifyFollowers(id, updatedSupportTicket, "APPROVAL_STATUS_CHANGED", { actorUserId: this.session.user.userId });
7497
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7498
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6089
7499
  }
6090
7500
  };
6091
7501
  RejectSupportTicketFeat = __decorate([
6092
7502
  injectable(),
6093
7503
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6094
- __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6095
- __decorateParam(2, injectSession())
7504
+ __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7505
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7506
+ __decorateParam(3, injectSession()),
7507
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6096
7508
  ], RejectSupportTicketFeat);
6097
7509
 
7510
+ //#endregion
7511
+ //#region src/slices/support_ticket/features/staff/remove_support_ticket_subscriber_feat.ts
7512
+ let RemoveSupportTicketSubscriberFeat = class RemoveSupportTicketSubscriberFeat$1 {
7513
+ constructor(subscriberRepo) {
7514
+ this.subscriberRepo = subscriberRepo;
7515
+ }
7516
+ async execute(supportTicketId, subscriberId) {
7517
+ if (!await this.subscriberRepo.delete(subscriberId)) throw new BusinessError("Subscriber not found or already removed");
7518
+ }
7519
+ };
7520
+ RemoveSupportTicketSubscriberFeat = __decorate([injectable(), __decorateParam(0, inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo))], RemoveSupportTicketSubscriberFeat);
7521
+
6098
7522
  //#endregion
6099
7523
  //#region src/slices/support_ticket/features/staff/revert_support_ticket_feat.ts
6100
7524
  let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6101
- constructor(repo, creditService, create_record_version, session) {
7525
+ constructor(repo, creditService, create_record_version, session, userDisplayLookup) {
6102
7526
  this.repo = repo;
6103
7527
  this.creditService = creditService;
6104
7528
  this.create_record_version = create_record_version;
6105
7529
  this.session = session;
7530
+ this.userDisplayLookup = userDisplayLookup;
6106
7531
  }
6107
7532
  /**
6108
7533
  * Reverts a support_ticket item back to PENDING status and refunds credits.
@@ -6151,7 +7576,8 @@ let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6151
7576
  locked_approval_at: null
6152
7577
  });
6153
7578
  if (!updatedSupportTicket) throw new Error("Failed to revert support_ticket.");
6154
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7579
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7580
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6155
7581
  }
6156
7582
  };
6157
7583
  RevertSupportTicketFeat = __decorate([
@@ -6159,7 +7585,8 @@ RevertSupportTicketFeat = __decorate([
6159
7585
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6160
7586
  __decorateParam(1, inject(CREDIT_SERVICE_TOKEN)),
6161
7587
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6162
- __decorateParam(3, injectSession())
7588
+ __decorateParam(3, injectSession()),
7589
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6163
7590
  ], RevertSupportTicketFeat);
6164
7591
 
6165
7592
  //#endregion
@@ -6168,17 +7595,18 @@ RevertSupportTicketFeat = __decorate([
6168
7595
  * Validate business rules for staff updates based on permissions matrix
6169
7596
  */
6170
7597
  const validateBusinessRules = (input, existing) => {
7598
+ if (existing.archived_at) throw new BusinessError("Cannot edit an archived support ticket.");
6171
7599
  const currentApproval = existing.approval_status;
6172
7600
  const currentDevLifecycle = existing.dev_lifecycle;
6173
7601
  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");
7602
+ if (!currentApproval || !["APPROVED", "INTERNAL"].includes(currentApproval)) throw new BusinessError("Can only set dev lifecycle when support_ticket is APPROVED or INTERNAL");
6175
7603
  }
6176
7604
  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");
7605
+ if (currentApproval === "INTERNAL") throw new BusinessError("Internal support_ticket cannot have credit values");
7606
+ if (currentApproval !== "PENDING") throw new BusinessError("Can only edit credit value when support_ticket is PENDING");
6179
7607
  }
6180
7608
  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");
7609
+ if (currentDevLifecycle !== "DEPLOYED") throw new BusinessError("Can only set delivered value when dev lifecycle is DEPLOYED");
6182
7610
  }
6183
7611
  const lockedDevStages = [
6184
7612
  "DEVELOPMENT",
@@ -6186,21 +7614,22 @@ const validateBusinessRules = (input, existing) => {
6186
7614
  "TESTING",
6187
7615
  "STAGING",
6188
7616
  "PO_APPROVAL",
7617
+ "VERIFICATION",
6189
7618
  "DEPLOYED"
6190
7619
  ];
6191
7620
  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)");
7621
+ if (currentApproval === "REJECTED") throw new BusinessError("Cannot edit start date when support_ticket is REJECTED");
7622
+ if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new BusinessError("Cannot edit start date once development has started (DEVELOPMENT or higher)");
6194
7623
  }
6195
7624
  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)");
7625
+ if (currentApproval === "REJECTED") throw new BusinessError("Cannot edit target date when support_ticket is REJECTED");
7626
+ if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new BusinessError("Cannot edit target date once development has started (DEVELOPMENT or higher)");
6198
7627
  }
6199
7628
  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");
7629
+ 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
7630
  }
6202
7631
  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");
7632
+ 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
7633
  }
6205
7634
  };
6206
7635
 
@@ -6213,24 +7642,26 @@ function shouldUpdateCreditsSetAt(oldValue, newValue) {
6213
7642
  return isCreditValueEmpty(oldValue) !== isCreditValueEmpty(newValue);
6214
7643
  }
6215
7644
  let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6216
- constructor(session, support_ticketRepo, create_record_version) {
7645
+ constructor(session, support_ticketRepo, create_record_version, notificationService, userDisplayLookup) {
6217
7646
  this.session = session;
6218
7647
  this.support_ticketRepo = support_ticketRepo;
6219
7648
  this.create_record_version = create_record_version;
7649
+ this.notificationService = notificationService;
7650
+ this.userDisplayLookup = userDisplayLookup;
6220
7651
  }
6221
7652
  /**
6222
7653
  * Update support_ticket - comprehensive update for staff
6223
7654
  */
6224
7655
  async execute(input) {
6225
7656
  const support_ticket = await this.support_ticketRepo.read(input.id);
6226
- if (!support_ticket) throw new Error("SupportTicket not found");
7657
+ if (!support_ticket) throw new BusinessError("SupportTicket not found");
6227
7658
  validateBusinessRules(input, support_ticket);
6228
7659
  const isoTime = (/* @__PURE__ */ new Date()).toISOString();
6229
7660
  const frEntity = {
6230
7661
  ...support_ticket,
6231
7662
  id: input.id,
6232
7663
  updated_at: isoTime,
6233
- updated_by: this.session.user.email
7664
+ updated_by: this.session.user.userId
6234
7665
  };
6235
7666
  if (input.title !== void 0 && input.title !== null) frEntity.title = input.title;
6236
7667
  if (input.description !== void 0 && input.description !== null) frEntity.description = input.description;
@@ -6245,6 +7676,7 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6245
7676
  if (input.start_at !== void 0) frEntity.start_at = input.start_at ?? void 0;
6246
7677
  if (input.target_at !== void 0) frEntity.target_at = input.target_at ?? void 0;
6247
7678
  if (input.completed_at !== void 0) frEntity.completed_at = input.completed_at ?? void 0;
7679
+ if (input.assigned_to !== void 0) frEntity.assigned_to = input.assigned_to?.trim() || null;
6248
7680
  const changed_props = getChangedProperties(support_ticket, frEntity);
6249
7681
  if (Object.keys(changed_props).length > 0) await this.create_record_version.execute({
6250
7682
  record_id: frEntity.id,
@@ -6257,14 +7689,22 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6257
7689
  auth_role: this.session.user.user_type,
6258
7690
  auth_username: this.session.user.username
6259
7691
  });
6260
- return toStaffSupportTicketReadDto(await this.support_ticketRepo.update(frEntity));
7692
+ const support_ticketUpdated = await this.support_ticketRepo.update(frEntity);
7693
+ if (input.assigned_to !== void 0 && support_ticket.assigned_to !== support_ticketUpdated.assigned_to && support_ticketUpdated.assigned_to) {
7694
+ await this.notificationService.notifyAssignee(support_ticketUpdated, "TICKET_ASSIGNED", { actorUserId: this.session.user.userId });
7695
+ await this.notificationService.notifyFollowers(support_ticketUpdated.id, support_ticketUpdated, "TICKET_ASSIGNED", { actorUserId: this.session.user.userId });
7696
+ }
7697
+ const dto = toStaffSupportTicketReadDto(support_ticketUpdated);
7698
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6261
7699
  }
6262
7700
  };
6263
7701
  UpdateSupportTicketAdminFeat = __decorate([
6264
7702
  injectable(),
6265
7703
  __decorateParam(0, injectSession()),
6266
7704
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6267
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7705
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7706
+ __decorateParam(3, inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)),
7707
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6268
7708
  ], UpdateSupportTicketAdminFeat);
6269
7709
 
6270
7710
  //#endregion
@@ -6274,12 +7714,16 @@ UpdateSupportTicketAdminFeat = __decorate([
6274
7714
  */
6275
7715
  function registerSupportTicketDependencies() {
6276
7716
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketRepo, SupportTicketRepo);
7717
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService, SupportTicketNotificationService);
7718
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService, SupportTicketTriageService);
6277
7719
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICreateSupportTicketFeature, CreateSupportTicketFeat);
6278
7720
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketCustomerFeature, UpdateSupportTicketCustomerFeat);
6279
7721
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketCustomerFeature, GetSupportTicketCustomerFeature);
6280
7722
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketsCustomerFeature, GetSupportTicketsCustomerFeature);
7723
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature, CustomerToggleSubscriptionFeat);
6281
7724
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IApproveSupportTicketFeature, ApproveSupportTicketFeat);
6282
7725
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IDeleteSupportTicketFeature, DeleteSupportTicketFeat);
7726
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature, ArchiveSupportTicketFeat);
6283
7727
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRejectSupportTicketFeature, RejectSupportTicketFeat);
6284
7728
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRevertSupportTicketFeature, RevertSupportTicketFeat);
6285
7729
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICompleteSupportTicketFeature, CompleteSupportTicketFeat);
@@ -6291,6 +7735,11 @@ function registerSupportTicketDependencies() {
6291
7735
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketAdminFeature, UpdateSupportTicketAdminFeat);
6292
7736
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketAdminFeature, GetSupportTicketAdminFeature);
6293
7737
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketsAdminFeature, GetSupportTicketsAdminFeature);
7738
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature, GetRequestorsForActiveSupportTicketsFeat);
7739
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature, AddSupportTicketSubscriberFeat);
7740
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature, ListSupportTicketSubscribersFeat);
7741
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature, RemoveSupportTicketSubscriberFeat);
7742
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature, FixSupportTicketUserIdsFeature);
6294
7743
  }
6295
7744
 
6296
7745
  //#endregion
@@ -6571,14 +8020,34 @@ function mapTeamEntityToDto(entity) {
6571
8020
  };
6572
8021
  }
6573
8022
 
8023
+ //#endregion
8024
+ //#region src/slices/team/features/enrich_team.ts
8025
+ /**
8026
+ * Enriches a TeamReadDto with display names from a lookup map.
8027
+ */
8028
+ function enrichTeam(dto, displayMap) {
8029
+ return {
8030
+ ...dto,
8031
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null,
8032
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
8033
+ };
8034
+ }
8035
+ /**
8036
+ * Collects user IDs from a team for display name lookup.
8037
+ */
8038
+ function collectUserIdsFromTeam(team) {
8039
+ return [team.created_by, team.updated_by].filter((id) => Boolean(id));
8040
+ }
8041
+
6574
8042
  //#endregion
6575
8043
  //#region src/slices/team/features/create_team_feat.ts
6576
8044
  let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6577
- constructor(teamRepo, session, create_record_version, createTeamMemberFeature) {
8045
+ constructor(teamRepo, session, create_record_version, createTeamMemberFeature, userDisplayLookup) {
6578
8046
  this.teamRepo = teamRepo;
6579
8047
  this.session = session;
6580
8048
  this.create_record_version = create_record_version;
6581
8049
  this.createTeamMemberFeature = createTeamMemberFeature;
8050
+ this.userDisplayLookup = userDisplayLookup;
6582
8051
  }
6583
8052
  async execute(input) {
6584
8053
  if (await this.teamRepo.findByUniqueName(input.unique_name || "")) throw new Error("Team with this name already exists");
@@ -6633,7 +8102,8 @@ let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6633
8102
  website_address: null,
6634
8103
  time_zone: null
6635
8104
  });
6636
- return mapTeamEntityToDto(teamEntity);
8105
+ const dto = mapTeamEntityToDto(teamEntity);
8106
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6637
8107
  }
6638
8108
  /**
6639
8109
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6647,7 +8117,8 @@ CreateTeamFeatureImpl = __decorate([
6647
8117
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6648
8118
  __decorateParam(1, injectSession()),
6649
8119
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6650
- __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature))
8120
+ __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature)),
8121
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6651
8122
  ], CreateTeamFeatureImpl);
6652
8123
 
6653
8124
  //#endregion
@@ -6685,16 +8156,20 @@ DeleteTeamFeatureImpl = __decorate([
6685
8156
  //#endregion
6686
8157
  //#region src/slices/team/features/read_all_teams_feat.ts
6687
8158
  let ReadAllTeamsFeatureImpl = class ReadAllTeamsFeatureImpl$1 {
6688
- constructor(teamRepo, session, teamMemberRepo) {
8159
+ constructor(teamRepo, session, teamMemberRepo, userDisplayLookup) {
6689
8160
  this.teamRepo = teamRepo;
6690
8161
  this.session = session;
6691
8162
  this.teamMemberRepo = teamMemberRepo;
8163
+ this.userDisplayLookup = userDisplayLookup;
6692
8164
  }
6693
8165
  async execute(filters) {
6694
8166
  const processedFilters = await this.applyConsumerFiltering(filters);
6695
8167
  const result = await this.teamRepo.read_all(processedFilters);
8168
+ const dtos = result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity));
8169
+ const userIds = [...new Set(dtos.flatMap(collectUserIdsFromTeam))];
8170
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
6696
8171
  return {
6697
- items: result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity)),
8172
+ items: dtos.map((t) => enrichTeam(t, displayMap)),
6698
8173
  pageInfo: result.pageInfo
6699
8174
  };
6700
8175
  }
@@ -6721,30 +8196,38 @@ ReadAllTeamsFeatureImpl = __decorate([
6721
8196
  injectable(),
6722
8197
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6723
8198
  __decorateParam(1, injectSession()),
6724
- __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo))
8199
+ __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
8200
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6725
8201
  ], ReadAllTeamsFeatureImpl);
6726
8202
 
6727
8203
  //#endregion
6728
8204
  //#region src/slices/team/features/read_team_feat.ts
6729
8205
  let ReadTeamFeatureImpl = class ReadTeamFeatureImpl$1 {
6730
- constructor(teamRepo) {
8206
+ constructor(teamRepo, userDisplayLookup) {
6731
8207
  this.teamRepo = teamRepo;
8208
+ this.userDisplayLookup = userDisplayLookup;
6732
8209
  }
6733
8210
  async execute(id) {
6734
8211
  const teamEntity = await this.teamRepo.read(id);
6735
8212
  if (!teamEntity) return null;
6736
- return mapTeamEntityToDto(teamEntity);
8213
+ const dto = mapTeamEntityToDto(teamEntity);
8214
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6737
8215
  }
6738
8216
  };
6739
- ReadTeamFeatureImpl = __decorate([injectable(), __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository))], ReadTeamFeatureImpl);
8217
+ ReadTeamFeatureImpl = __decorate([
8218
+ injectable(),
8219
+ __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
8220
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
8221
+ ], ReadTeamFeatureImpl);
6740
8222
 
6741
8223
  //#endregion
6742
8224
  //#region src/slices/team/features/update_team_feat.ts
6743
8225
  let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6744
- constructor(teamRepo, session, create_record_version) {
8226
+ constructor(teamRepo, session, create_record_version, userDisplayLookup) {
6745
8227
  this.teamRepo = teamRepo;
6746
8228
  this.session = session;
6747
8229
  this.create_record_version = create_record_version;
8230
+ this.userDisplayLookup = userDisplayLookup;
6748
8231
  }
6749
8232
  async execute(input) {
6750
8233
  const existingTeam = await this.teamRepo.read(input.id);
@@ -6796,7 +8279,8 @@ let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6796
8279
  auth_role: this.session.user.user_type,
6797
8280
  auth_username: this.session.user.username
6798
8281
  });
6799
- return mapTeamEntityToDto(teamEntity);
8282
+ const dto = mapTeamEntityToDto(teamEntity);
8283
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6800
8284
  }
6801
8285
  /**
6802
8286
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6809,7 +8293,8 @@ UpdateTeamFeatureImpl = __decorate([
6809
8293
  injectable(),
6810
8294
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6811
8295
  __decorateParam(1, injectSession()),
6812
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8296
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8297
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6813
8298
  ], UpdateTeamFeatureImpl);
6814
8299
 
6815
8300
  //#endregion
@@ -7027,13 +8512,33 @@ let TeamMemberRepo = class TeamMemberRepo$1 {
7027
8512
  };
7028
8513
  TeamMemberRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], TeamMemberRepo);
7029
8514
 
8515
+ //#endregion
8516
+ //#region src/slices/team_member/features/enrich_team_member.ts
8517
+ /**
8518
+ * Enriches a TeamMemberReadDto with display names from a lookup map.
8519
+ */
8520
+ function enrichTeamMember(dto, displayMap) {
8521
+ return {
8522
+ ...dto,
8523
+ created_by_display_name: dto.created_by ? displayMap.get(dto.created_by) ?? dto.created_by : null,
8524
+ updated_by_display_name: dto.updated_by ? displayMap.get(dto.updated_by) ?? dto.updated_by : null
8525
+ };
8526
+ }
8527
+ /**
8528
+ * Collects user IDs from a team member for display name lookup.
8529
+ */
8530
+ function collectUserIdsFromTeamMember(member) {
8531
+ return [member.created_by, member.updated_by].filter((id) => Boolean(id));
8532
+ }
8533
+
7030
8534
  //#endregion
7031
8535
  //#region src/slices/team_member/features/create_team_member_feat.ts
7032
8536
  let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7033
- constructor(session, teamMemberRepo, create_record_version) {
8537
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7034
8538
  this.session = session;
7035
8539
  this.teamMemberRepo = teamMemberRepo;
7036
8540
  this.create_record_version = create_record_version;
8541
+ this.userDisplayLookup = userDisplayLookup;
7037
8542
  }
7038
8543
  async execute(input) {
7039
8544
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7065,14 +8570,16 @@ let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7065
8570
  auth_role: this.session.user.user_type,
7066
8571
  auth_username: this.session.user.username
7067
8572
  });
7068
- return teamMemberCreated;
8573
+ const dto = teamMemberCreated;
8574
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7069
8575
  }
7070
8576
  };
7071
8577
  CreateTeamMemberFeat = __decorate([
7072
8578
  injectable(),
7073
8579
  __decorateParam(0, injectSession()),
7074
8580
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7075
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8581
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8582
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7076
8583
  ], CreateTeamMemberFeat);
7077
8584
 
7078
8585
  //#endregion
@@ -7126,14 +8633,27 @@ DeleteTeamMemberFeat = __decorate([
7126
8633
  //#endregion
7127
8634
  //#region src/slices/team_member/features/get_team_members_feat.ts
7128
8635
  let GetTeamMembersFeat = class GetTeamMembersFeat$1 {
7129
- constructor(teamMemberRepo) {
8636
+ constructor(teamMemberRepo, userDisplayLookup) {
7130
8637
  this.teamMemberRepo = teamMemberRepo;
8638
+ this.userDisplayLookup = userDisplayLookup;
7131
8639
  }
7132
8640
  async execute(filters) {
7133
- return await this.teamMemberRepo.getAll(filters);
8641
+ const result = await this.teamMemberRepo.getAll(filters);
8642
+ const items = result.items;
8643
+ const userIds = [...new Set(items.flatMap(collectUserIdsFromTeamMember))];
8644
+ const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);
8645
+ const enrichedItems = items.map((m) => enrichTeamMember(m, displayMap));
8646
+ return {
8647
+ ...result,
8648
+ items: enrichedItems
8649
+ };
7134
8650
  }
7135
8651
  };
7136
- GetTeamMembersFeat = __decorate([injectable(), __decorateParam(0, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo))], GetTeamMembersFeat);
8652
+ GetTeamMembersFeat = __decorate([
8653
+ injectable(),
8654
+ __decorateParam(0, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
8655
+ __decorateParam(1, inject(USER_TOKENS.IUserDisplayLookup))
8656
+ ], GetTeamMembersFeat);
7137
8657
 
7138
8658
  //#endregion
7139
8659
  //#region src/slices/team_member/features/get_user_team_members_feat.ts
@@ -7200,10 +8720,11 @@ GetUserTeamsFeat = __decorate([
7200
8720
  //#endregion
7201
8721
  //#region src/slices/team_member/features/update_team_member_feat.ts
7202
8722
  let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7203
- constructor(session, teamMemberRepo, create_record_version) {
8723
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7204
8724
  this.session = session;
7205
8725
  this.teamMemberRepo = teamMemberRepo;
7206
8726
  this.create_record_version = create_record_version;
8727
+ this.userDisplayLookup = userDisplayLookup;
7207
8728
  }
7208
8729
  async execute(input) {
7209
8730
  const existingTeamMember = await this.teamMemberRepo.getById(input.id);
@@ -7254,14 +8775,16 @@ let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7254
8775
  auth_role: this.session.user.user_type,
7255
8776
  auth_username: this.session.user.username
7256
8777
  });
7257
- return teamMemberUpdated;
8778
+ const dto = teamMemberUpdated;
8779
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7258
8780
  }
7259
8781
  };
7260
8782
  UpdateTeamMemberFeat = __decorate([
7261
8783
  injectable(),
7262
8784
  __decorateParam(0, injectSession()),
7263
8785
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7264
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8786
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8787
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7265
8788
  ], UpdateTeamMemberFeat);
7266
8789
 
7267
8790
  //#endregion
@@ -7325,6 +8848,21 @@ let UserRepo = class UserRepo$1 {
7325
8848
  this.logger.perf(`UserRepo.read_user_by_email: ${queryTime.toFixed(2)}ms for email ${email}`);
7326
8849
  return result[0];
7327
8850
  }
8851
+ async read_users_by_emails(emails) {
8852
+ if (emails.length === 0) return [];
8853
+ const { ...rest } = getTableColumns(user_table);
8854
+ return this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(inArray(user_table.email, emails)));
8855
+ }
8856
+ /**
8857
+ * Case-insensitive email lookup. Use when matching against values that may
8858
+ * differ in casing (e.g. emails stored in other tables).
8859
+ */
8860
+ async read_users_by_emails_case_insensitive(emails) {
8861
+ if (emails.length === 0) return [];
8862
+ const { ...rest } = getTableColumns(user_table);
8863
+ const lowerEmails = [...new Set(emails.map((e) => e.toLowerCase()))];
8864
+ 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`, `)})`));
8865
+ }
7328
8866
  async read_user_by_username(username) {
7329
8867
  const { ...rest } = getTableColumns(user_table);
7330
8868
  return (await this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(eq(user_table.username, username)).limit(1)))[0];
@@ -7434,6 +8972,26 @@ CreateUser = __decorate([
7434
8972
  __decorateParam(3, inject(TOKENS.LOGGER))
7435
8973
  ], CreateUser);
7436
8974
 
8975
+ //#endregion
8976
+ //#region src/slices/user/features/user_display_lookup_feat.ts
8977
+ let UserDisplayLookupFeat = class UserDisplayLookupFeat$1 {
8978
+ constructor(userRepo) {
8979
+ this.userRepo = userRepo;
8980
+ }
8981
+ async lookupDisplayNames(userIds) {
8982
+ const validIds = [...new Set(userIds)].filter((id) => !!id?.trim()).filter((id) => id.length === 32);
8983
+ if (validIds.length === 0) return /* @__PURE__ */ new Map();
8984
+ const users = await this.userRepo.read_users_by_ids(validIds);
8985
+ const map = /* @__PURE__ */ new Map();
8986
+ for (const u of users) {
8987
+ const display = u.email ?? u.id;
8988
+ map.set(u.id, display);
8989
+ }
8990
+ return map;
8991
+ }
8992
+ };
8993
+ UserDisplayLookupFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS.IUsersRepo))], UserDisplayLookupFeat);
8994
+
7437
8995
  //#endregion
7438
8996
  //#region src/slices/user/features/delete_user.ts
7439
8997
  let DeleteUser = class DeleteUser$1 {
@@ -7511,6 +9069,18 @@ let GetUserFeat = class GetUserFeat$1 {
7511
9069
  };
7512
9070
  GetUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS.IUsersRepo))], GetUserFeat);
7513
9071
 
9072
+ //#endregion
9073
+ //#region src/slices/user/features/get_triage_users_feat.ts
9074
+ let GetTriageUsersFeat = class GetTriageUsersFeat$1 {
9075
+ constructor(supportStaffRepo) {
9076
+ this.supportStaffRepo = supportStaffRepo;
9077
+ }
9078
+ async execute() {
9079
+ return this.supportStaffRepo.readTriageUsers();
9080
+ }
9081
+ };
9082
+ GetTriageUsersFeat = __decorate([injectable(), __decorateParam(0, inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo))], GetTriageUsersFeat);
9083
+
7514
9084
  //#endregion
7515
9085
  //#region src/slices/user/features/get_users_for_selection_feat.ts
7516
9086
  let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
@@ -7518,7 +9088,7 @@ let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
7518
9088
  this.repo = repo;
7519
9089
  }
7520
9090
  async execute() {
7521
- return (await this.repo.read_users_by_types(["lead", "consumer"])).map((user) => ({
9091
+ return (await this.repo.read_users_by_types([...USER_TYPES])).map((user) => ({
7522
9092
  id: user.id,
7523
9093
  email: user.email
7524
9094
  }));
@@ -7662,6 +9232,7 @@ UpdateUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS
7662
9232
  //#region src/slices/user/user_container.ts
7663
9233
  function registerUserContainer() {
7664
9234
  container.registerSingleton(USER_TOKENS.IUsersRepo, UserRepo);
9235
+ container.registerSingleton(USER_TOKENS.IUserDisplayLookup, UserDisplayLookupFeat);
7665
9236
  container.registerSingleton(USER_TOKENS.IReadAllUsers, ReadAllUsers);
7666
9237
  container.registerSingleton(USER_TOKENS.ISignUpUser, SignUpUser);
7667
9238
  container.registerSingleton(USER_TOKENS.IDeleteUser, DeleteUser);
@@ -7672,6 +9243,7 @@ function registerUserContainer() {
7672
9243
  container.registerSingleton(USER_TOKENS.IGetUserFeature, GetUserFeat);
7673
9244
  container.registerSingleton(USER_TOKENS.IUpdateUserFeature, UpdateUserFeat);
7674
9245
  container.registerSingleton(USER_TOKENS.IGetUsersForSelectionFeature, GetUsersForSelectionFeat);
9246
+ container.registerSingleton(USER_TOKENS.IGetTriageUsersFeature, GetTriageUsersFeat);
7675
9247
  }
7676
9248
 
7677
9249
  //#endregion
@@ -7691,6 +9263,11 @@ let UserProfileRepo = class UserProfileRepo$1 {
7691
9263
  const { ...rest } = getTableColumns(user_profile_table);
7692
9264
  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
9265
  }
9266
+ async read_user_profiles_by_user_ids(user_ids) {
9267
+ if (user_ids.length === 0) return [];
9268
+ const { ...rest } = getTableColumns(user_profile_table);
9269
+ return this.router.queryAll((db) => db.select({ ...rest }).from(user_profile_table).where(inArray(user_profile_table.user_id, user_ids)));
9270
+ }
7694
9271
  async update_user_profile(user_profileRecord) {
7695
9272
  user_profileRecord.updated_at = (/* @__PURE__ */ new Date()).toISOString();
7696
9273
  const { created_at, ...rest } = user_profileRecord;
@@ -7699,16 +9276,6 @@ let UserProfileRepo = class UserProfileRepo$1 {
7699
9276
  };
7700
9277
  UserProfileRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], UserProfileRepo);
7701
9278
 
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
9279
  //#endregion
7713
9280
  //#region src/slices/user_profile/features/read_user_profile.ts
7714
9281
  let ReadUserProfile = class ReadUserProfile$1 {
@@ -8271,14 +9838,17 @@ function registerCoreContainer() {
8271
9838
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.PrefixRegistry, DisplayIdPrefixRegistry);
8272
9839
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.IDisplayIdPrefixService, DisplayIdPrefixService);
8273
9840
  registerPasswordResetContainer();
9841
+ registerRecordSubscriberDependencies();
8274
9842
  registerRecordVersionContainer();
9843
+ registerSavedFilterContainer();
9844
+ registerSupportStaffDependencies();
8275
9845
  registerUserContainer();
8276
9846
  registerUserProfileContainer();
8277
9847
  registerUserSessionContainer();
8278
9848
  registerCustomerDependencies();
8279
9849
  registerSupportTicketDependencies();
8280
9850
  registerAttachmentContainer();
8281
- registerAppSettingsContainer();
9851
+ /* @__PURE__ */ registerAppSettingsContainer();
8282
9852
  registerNoteContainer();
8283
9853
  registerTeamDependencies();
8284
9854
  registerTeamMemberContainer();
@@ -8315,37 +9885,15 @@ function createRequestContainer(config, additionalRegistrations) {
8315
9885
  //#endregion
8316
9886
  //#region src/slices/app_settings/app-settings-api.server.ts
8317
9887
  var AppSettingsApiServer = class extends RpcTarget {
8318
- constructor(container$1) {
9888
+ constructor(_container) {
8319
9889
  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
9890
  }
8347
9891
  };
8348
9892
 
9893
+ //#endregion
9894
+ //#region src/slices/app_settings/app_settings_tokens.ts
9895
+ const APP_SETTINGS_TOKENS = {};
9896
+
8349
9897
  //#endregion
8350
9898
  //#region src/slices/attachment/attachment-api.server.ts
8351
9899
  const CreateFolderInputSchema = z.object({
@@ -8669,12 +10217,211 @@ var RecordVersionApiServer = class extends RpcTarget {
8669
10217
  }
8670
10218
  });
8671
10219
  }
10220
+ async listTrackerActivityVersions(trackerId, filters) {
10221
+ return rpcMethod({
10222
+ auth: "protected",
10223
+ container: this.container,
10224
+ context: "RecordVersionApiServer.listTrackerActivityVersions",
10225
+ input: {
10226
+ tracker_id: trackerId,
10227
+ filters
10228
+ },
10229
+ inputSchema: recordVersionTrackerActivityInputSchema,
10230
+ outputSchema: recordVersionPageBreadcrumbSchema,
10231
+ execute: async ({ tracker_id, filters: f }, container$1) => {
10232
+ return await container$1.resolve(RECORD_VERSION_TOKENS.IReadTrackerActivityVersions).execute(tracker_id, f || {});
10233
+ }
10234
+ });
10235
+ }
10236
+ };
10237
+
10238
+ //#endregion
10239
+ //#region src/slices/support_staff/support-staff-api.server.ts
10240
+ const SupportStaffMemberSchema = z.object({
10241
+ id: z.string(),
10242
+ user_id: z.string(),
10243
+ email: z.string(),
10244
+ created_at: z.string()
10245
+ });
10246
+ var SupportStaffApiServer = class extends RpcTarget {
10247
+ constructor(container$1) {
10248
+ super();
10249
+ this.container = container$1;
10250
+ }
10251
+ async listSupportStaff() {
10252
+ return rpcMethodPartial({
10253
+ auth: "admin",
10254
+ container: this.container,
10255
+ context: "SupportStaffApiServer.listSupportStaff",
10256
+ input: void 0,
10257
+ outputSchema: z.array(SupportStaffMemberSchema),
10258
+ execute: async (_, container$1) => {
10259
+ return container$1.resolve(SUPPORT_STAFF_TOKENS.IListSupportStaffFeature).execute();
10260
+ }
10261
+ });
10262
+ }
10263
+ async addSupportStaff(userId) {
10264
+ return rpcMethod({
10265
+ auth: "admin",
10266
+ container: this.container,
10267
+ context: "SupportStaffApiServer.addSupportStaff",
10268
+ input: userId,
10269
+ inputSchema: z.string(),
10270
+ outputSchema: SupportStaffMemberSchema,
10271
+ execute: async (userId$1, container$1) => {
10272
+ return container$1.resolve(SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature).execute(userId$1);
10273
+ }
10274
+ });
10275
+ }
10276
+ async removeSupportStaff(userId) {
10277
+ return rpcMethod({
10278
+ auth: "admin",
10279
+ container: this.container,
10280
+ context: "SupportStaffApiServer.removeSupportStaff",
10281
+ input: userId,
10282
+ inputSchema: z.string(),
10283
+ outputSchema: z.void(),
10284
+ execute: async (userId$1, container$1) => {
10285
+ await container$1.resolve(SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature).execute(userId$1);
10286
+ }
10287
+ });
10288
+ }
10289
+ };
10290
+
10291
+ //#endregion
10292
+ //#region src/slices/saved_filter/saved-filter-api.server.ts
10293
+ var SavedFilterApiServer = class extends RpcTarget {
10294
+ constructor(container$1) {
10295
+ super();
10296
+ this.container = container$1;
10297
+ }
10298
+ async listSavedFilters(context) {
10299
+ return rpcMethod({
10300
+ auth: "protected",
10301
+ container: this.container,
10302
+ context: "SavedFilterApiServer.listSavedFilters",
10303
+ input: context,
10304
+ inputSchema: z.string(),
10305
+ outputSchema: z.array(SavedFilterReadSchema),
10306
+ execute: async (context$1, container$1) => {
10307
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IListSavedFilters).execute(context$1);
10308
+ }
10309
+ });
10310
+ }
10311
+ async listAllSavedFilters() {
10312
+ return rpcMethod({
10313
+ auth: "protected",
10314
+ container: this.container,
10315
+ context: "SavedFilterApiServer.listAllSavedFilters",
10316
+ input: void 0,
10317
+ inputSchema: z.undefined(),
10318
+ outputSchema: z.array(SavedFilterReadSchema),
10319
+ execute: async (_input, container$1) => {
10320
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IListAllSavedFilters).execute();
10321
+ }
10322
+ });
10323
+ }
10324
+ async createSavedFilter(input) {
10325
+ return rpcMethod({
10326
+ auth: "protected",
10327
+ container: this.container,
10328
+ context: "SavedFilterApiServer.createSavedFilter",
10329
+ input,
10330
+ inputSchema: SavedFilterCreateSchema,
10331
+ outputSchema: SavedFilterReadSchema,
10332
+ execute: async (input$1, container$1) => {
10333
+ return await container$1.resolve(SAVED_FILTER_TOKENS.ICreateSavedFilter).execute(input$1);
10334
+ }
10335
+ });
10336
+ }
10337
+ async updateSavedFilter(input) {
10338
+ return rpcMethod({
10339
+ auth: "protected",
10340
+ container: this.container,
10341
+ context: "SavedFilterApiServer.updateSavedFilter",
10342
+ input,
10343
+ inputSchema: SavedFilterUpdateSchema,
10344
+ outputSchema: SavedFilterReadSchema.nullable(),
10345
+ execute: async (input$1, container$1) => {
10346
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IUpdateSavedFilter).execute(input$1);
10347
+ }
10348
+ });
10349
+ }
10350
+ async deleteSavedFilter(id) {
10351
+ return rpcMethod({
10352
+ auth: "protected",
10353
+ container: this.container,
10354
+ context: "SavedFilterApiServer.deleteSavedFilter",
10355
+ input: id,
10356
+ inputSchema: z.string(),
10357
+ outputSchema: z.boolean(),
10358
+ execute: async (id$1, container$1) => {
10359
+ return await container$1.resolve(SAVED_FILTER_TOKENS.IDeleteSavedFilter).execute(id$1);
10360
+ }
10361
+ });
10362
+ }
10363
+ async listPinnedPresets() {
10364
+ return rpcMethod({
10365
+ auth: "protected",
10366
+ container: this.container,
10367
+ context: "SavedFilterApiServer.listPinnedPresets",
10368
+ input: void 0,
10369
+ inputSchema: z.undefined(),
10370
+ outputSchema: z.array(SavedFilterReadSchema),
10371
+ execute: async (_input, container$1) => {
10372
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IListPinnedPresets).execute();
10373
+ }
10374
+ });
10375
+ }
10376
+ async addPinnedPreset(presetId) {
10377
+ return rpcMethod({
10378
+ auth: "protected",
10379
+ container: this.container,
10380
+ context: "SavedFilterApiServer.addPinnedPreset",
10381
+ input: presetId,
10382
+ inputSchema: z.string(),
10383
+ outputSchema: SavedFilterReadSchema.nullable(),
10384
+ execute: async (presetId$1, container$1) => {
10385
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IAddPinnedPreset).execute(presetId$1);
10386
+ }
10387
+ });
10388
+ }
10389
+ async removePinnedPreset(presetId) {
10390
+ return rpcMethod({
10391
+ auth: "protected",
10392
+ container: this.container,
10393
+ context: "SavedFilterApiServer.removePinnedPreset",
10394
+ input: presetId,
10395
+ inputSchema: z.string(),
10396
+ outputSchema: z.boolean(),
10397
+ execute: async (presetId$1, container$1) => {
10398
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset).execute(presetId$1);
10399
+ }
10400
+ });
10401
+ }
10402
+ async reorderPinnedPresets(presetIds) {
10403
+ return rpcMethod({
10404
+ auth: "protected",
10405
+ container: this.container,
10406
+ context: "SavedFilterApiServer.reorderPinnedPresets",
10407
+ input: presetIds,
10408
+ inputSchema: z.array(z.string()),
10409
+ outputSchema: z.void(),
10410
+ execute: async (presetIds$1, container$1) => {
10411
+ return await container$1.resolve(USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets).execute(presetIds$1);
10412
+ }
10413
+ });
10414
+ }
8672
10415
  };
8673
10416
 
8674
10417
  //#endregion
8675
10418
  //#region src/slices/support_ticket/support-ticket-api.server.ts
8676
10419
  const CancelInternalTaskInputSchema = z.object({ id: z.string() });
8677
10420
  const ReactivateInternalTaskInputSchema = z.object({ id: z.string() });
10421
+ const UsersForSelectionSchema$1 = z.array(z.object({
10422
+ id: z.string(),
10423
+ email: z.string()
10424
+ }));
8678
10425
  var SupportTicketApiServer = class extends RpcTarget {
8679
10426
  constructor(container$1) {
8680
10427
  super();
@@ -8732,6 +10479,31 @@ var SupportTicketApiServer = class extends RpcTarget {
8732
10479
  }
8733
10480
  });
8734
10481
  }
10482
+ async toggleSubscription(supportTicketId) {
10483
+ return rpcMethod({
10484
+ auth: "protected",
10485
+ container: this.container,
10486
+ context: "SupportTicketApiServer.toggleSubscription",
10487
+ input: supportTicketId,
10488
+ inputSchema: z.string(),
10489
+ outputSchema: z.object({ subscribed: z.boolean() }),
10490
+ execute: async (id, container$1) => {
10491
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature).execute(id);
10492
+ }
10493
+ });
10494
+ }
10495
+ async getRequestorsForActiveTickets() {
10496
+ return rpcMethodPartial({
10497
+ auth: "protected",
10498
+ container: this.container,
10499
+ context: "SupportTicketApiServer.getRequestorsForActiveTickets",
10500
+ input: void 0,
10501
+ outputSchema: UsersForSelectionSchema$1,
10502
+ execute: async (_, container$1) => {
10503
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature).execute({ excludeInternal: true });
10504
+ }
10505
+ });
10506
+ }
8735
10507
  async staffCreateTicket(input) {
8736
10508
  return rpcMethod({
8737
10509
  auth: "admin",
@@ -8784,6 +10556,18 @@ var SupportTicketApiServer = class extends RpcTarget {
8784
10556
  }
8785
10557
  });
8786
10558
  }
10559
+ async staffGetRequestorsForActiveTickets() {
10560
+ return rpcMethodPartial({
10561
+ auth: "staff",
10562
+ container: this.container,
10563
+ context: "SupportTicketApiServer.staffGetRequestorsForActiveTickets",
10564
+ input: void 0,
10565
+ outputSchema: UsersForSelectionSchema$1,
10566
+ execute: async (_, container$1) => {
10567
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature).execute({ excludeInternal: false });
10568
+ }
10569
+ });
10570
+ }
8787
10571
  async approveTicket(input) {
8788
10572
  return rpcMethod({
8789
10573
  auth: "admin",
@@ -8901,6 +10685,78 @@ var SupportTicketApiServer = class extends RpcTarget {
8901
10685
  }
8902
10686
  });
8903
10687
  }
10688
+ async archiveTicket(input) {
10689
+ return rpcMethod({
10690
+ auth: "admin",
10691
+ container: this.container,
10692
+ context: "SupportTicketApiServer.archiveTicket",
10693
+ input,
10694
+ inputSchema: ArchiveSupportTicketSchema,
10695
+ outputSchema: StaffSupportTicketReadSchema,
10696
+ execute: async (input$1, container$1) => {
10697
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature).execute(input$1.id);
10698
+ }
10699
+ });
10700
+ }
10701
+ async staffListSubscribers(supportTicketId) {
10702
+ return rpcMethod({
10703
+ auth: "admin",
10704
+ container: this.container,
10705
+ context: "SupportTicketApiServer.staffListSubscribers",
10706
+ input: supportTicketId,
10707
+ inputSchema: z.string(),
10708
+ outputSchema: z.array(RecordSubscriberReadSchema),
10709
+ execute: async (id, container$1) => {
10710
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature).execute(id);
10711
+ }
10712
+ });
10713
+ }
10714
+ async staffAddSubscriber(input) {
10715
+ return rpcMethod({
10716
+ auth: "admin",
10717
+ container: this.container,
10718
+ context: "SupportTicketApiServer.staffAddSubscriber",
10719
+ input,
10720
+ inputSchema: SupportTicketSubscriberCreateSchema,
10721
+ outputSchema: RecordSubscriberReadSchema,
10722
+ execute: async (validated, container$1) => {
10723
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature).execute(validated);
10724
+ }
10725
+ });
10726
+ }
10727
+ async staffRemoveSubscriber(input) {
10728
+ return rpcMethod({
10729
+ auth: "admin",
10730
+ container: this.container,
10731
+ context: "SupportTicketApiServer.staffRemoveSubscriber",
10732
+ input,
10733
+ inputSchema: z.object({
10734
+ supportTicketId: z.string(),
10735
+ subscriberId: z.string()
10736
+ }),
10737
+ outputSchema: z.void(),
10738
+ execute: async (validated, container$1) => {
10739
+ await container$1.resolve(SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature).execute(validated.supportTicketId, validated.subscriberId);
10740
+ }
10741
+ });
10742
+ }
10743
+ async staffFixSupportTicketUserIds() {
10744
+ return rpcMethod({
10745
+ auth: "admin",
10746
+ container: this.container,
10747
+ context: "SupportTicketApiServer.staffFixSupportTicketUserIds",
10748
+ input: void 0,
10749
+ inputSchema: z.undefined(),
10750
+ outputSchema: z.object({
10751
+ ticketsScanned: z.number(),
10752
+ ticketsFixed: z.number(),
10753
+ ticketsSkipped: z.number()
10754
+ }),
10755
+ execute: async (_input, container$1) => {
10756
+ return await container$1.resolve(SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature).execute();
10757
+ }
10758
+ });
10759
+ }
8904
10760
  };
8905
10761
 
8906
10762
  //#endregion
@@ -9189,6 +11045,18 @@ var UserApiServer = class extends RpcTarget {
9189
11045
  }
9190
11046
  });
9191
11047
  }
11048
+ async getTriageUsers() {
11049
+ return rpcMethodPartial({
11050
+ auth: "admin",
11051
+ container: this.container,
11052
+ context: "UserApiServer.getTriageUsers",
11053
+ input: void 0,
11054
+ outputSchema: UsersForSelectionSchema,
11055
+ execute: async (_, container$1) => {
11056
+ return await container$1.resolve(USER_TOKENS.IGetTriageUsersFeature).execute();
11057
+ }
11058
+ });
11059
+ }
9192
11060
  };
9193
11061
 
9194
11062
  //#endregion
@@ -9380,5 +11248,5 @@ var UserSessionApiServer = class extends RpcTarget {
9380
11248
  };
9381
11249
 
9382
11250
  //#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 };
11251
+ 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
11252
  //# sourceMappingURL=index.mjs.map