@dragonmastery/dragoncore-api 0.0.1 → 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,14 +2,13 @@ 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";
9
9
  import { z } from "zod";
10
10
  import * as jose from "jose";
11
11
  import { HTTPException } from "hono/http-exception";
12
- import { archiveConditions as archiveConditions$1, createBackendRegistry as createBackendRegistry$1, createFilterBuilder as createFilterBuilder$1, deriveColumnMap as deriveColumnMap$1, searchOrCondition as searchOrCondition$1 } from "@dragonmastery/dragoncore-api";
13
12
  import { RpcTarget } from "capnweb";
14
13
 
15
14
  //#region src/di_tokens.ts
@@ -330,6 +329,139 @@ const OperationConst = {
330
329
  INSERT: "insert"
331
330
  };
332
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
+
333
465
  //#endregion
334
466
  //#region src/slices/record_version/db/record_version_table.ts
335
467
  const record_version_table = sqliteTable("record_version", {
@@ -482,18 +614,81 @@ const note_table = sqliteTable("note", {
482
614
  archived_at: text("archived_at"),
483
615
  deleted_by: text("deleted_by"),
484
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()
485
673
  }, (table) => ({
486
- record_id_idx: index("note_record_id_idx").on(table.record_id),
487
- record_type_idx: index("note_record_type_idx").on(table.record_type),
488
- tag_idx: index("note_tag_idx").on(table.tag),
489
- title_idx: index("note_title_idx").on(table.title),
490
- is_internal_idx: index("note_is_internal_idx").on(table.is_internal),
491
- created_at_idx: index("note_created_at_idx").on(table.created_at),
492
- updated_at_idx: index("note_updated_at_idx").on(table.updated_at),
493
- deleted_at_idx: index("note_deleted_at_idx").on(table.deleted_at),
494
- 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)
495
677
  }));
496
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
+
497
692
  //#endregion
498
693
  //#region src/slices/support_ticket/db/support_ticket_table.ts
499
694
  /**
@@ -505,10 +700,9 @@ const support_ticket_table = sqliteTable("support_ticket", {
505
700
  title: text("title").notNull(),
506
701
  description: text("description").notNull(),
507
702
  type: text("type", { enum: SupportTicketTypeEnum }).notNull().default("FEATURE_REQUEST"),
508
- priority: text("priority", { enum: SupportTicketPriorityEnum }).notNull().default("MEDIUM"),
703
+ priority: integer("priority", { mode: "number" }).notNull().default(2),
509
704
  approval_status: text("approval_status", { enum: SupportTicketApprovalEnum }).notNull().default("PENDING"),
510
- requester_email: text("requester_email").notNull(),
511
- requester_name: text("requester_name").notNull(),
705
+ assigned_to: text("assigned_to"),
512
706
  credit_value: text("credit_value"),
513
707
  delivered_value: text("delivered_value"),
514
708
  dev_lifecycle: text("dev_lifecycle", { enum: SupportTicketDevLifecycleEnum }),
@@ -532,8 +726,7 @@ const support_ticket_table = sqliteTable("support_ticket", {
532
726
  priorityIdx: index("support_ticket_priority_idx").on(table.priority),
533
727
  approvalStatusIdx: index("support_ticket_approval_status_idx").on(table.approval_status),
534
728
  devLifecycleIdx: index("support_ticket_dev_lifecycle_idx").on(table.dev_lifecycle),
535
- requesterEmailIdx: index("support_ticket_requester_email_idx").on(table.requester_email),
536
- requesterNameIdx: index("support_ticket_requester_name_idx").on(table.requester_name),
729
+ assignedToIdx: index("support_ticket_assigned_to_idx").on(table.assigned_to),
537
730
  creditValueIdx: index("support_ticket_credit_value_idx").on(table.credit_value),
538
731
  deliveredValueIdx: index("support_ticket_delivered_value_idx").on(table.delivered_value),
539
732
  createdAtIdx: index("support_ticket_created_at_idx").on(table.created_at),
@@ -660,6 +853,7 @@ const user_profile_table = sqliteTable("user_profile", {
660
853
  last_name: text("last_name", { length: 255 }),
661
854
  additional_name: text("additional_name", { length: 255 }),
662
855
  avatar_url: text("avatar_url"),
856
+ notification_email: text("notification_email"),
663
857
  gender: text("gender", { length: 255 }),
664
858
  bio: text("bio"),
665
859
  birthday: text("birthday"),
@@ -1352,6 +1546,14 @@ function resolveTeamAccess(userTeamIds, teamFilter) {
1352
1546
  };
1353
1547
  return { denied: true };
1354
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
+ }
1355
1557
  return {
1356
1558
  denied: false,
1357
1559
  effectiveTeamIds: userTeamIds
@@ -1617,6 +1819,11 @@ var CursorUtils = class {
1617
1819
  * Get items AFTER this cursor position in the sorted order
1618
1820
  */
1619
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
+ }
1620
1827
  const { sortColumn, processedValue } = this.processSortColumn(column, sortValue);
1621
1828
  if (isDesc) return [or(lt(sortColumn, processedValue), and(eq(sortColumn, processedValue), lt(table.id, id)))];
1622
1829
  else return [or(gt(sortColumn, processedValue), and(eq(sortColumn, processedValue), gt(table.id, id)))];
@@ -1740,7 +1947,7 @@ var PaginationUtils = class {
1740
1947
  responseState = BreadcrumbUtils.navigateNext(state, nextPageCursor);
1741
1948
  responseState.currentPageIndex = state.currentPageIndex;
1742
1949
  }
1743
- 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;
1744
1951
  return {
1745
1952
  items,
1746
1953
  pageInfo: {
@@ -2426,6 +2633,63 @@ async function verifyToken(token, secret) {
2426
2633
  }
2427
2634
  }
2428
2635
 
2636
+ //#endregion
2637
+ //#region src/middleware/session_validation.ts
2638
+ /**
2639
+ * Validates session from JWT token in Authorization header
2640
+ * Returns the appropriate SessionState based on token validation
2641
+ *
2642
+ * @param authHeader - Authorization header value (e.g., "Bearer <token>")
2643
+ * @param config - Configuration for session validation
2644
+ * @returns Promise resolving to SessionState
2645
+ */
2646
+ async function validateSessionFromJWT(authHeader, config) {
2647
+ if (!authHeader?.startsWith("Bearer ")) return config.createUnauthenticatedState();
2648
+ try {
2649
+ const accessToken = authHeader.replace("Bearer ", "");
2650
+ const payload = await config.verifyToken(accessToken, config.env.ACCESS_JWT_SECRET);
2651
+ if (payload.exp < Math.floor(Date.now() / 1e3)) return config.createExpiredState();
2652
+ if (payload.type === "access" && payload.exp > Math.floor(Date.now() / 1e3)) {
2653
+ const globalRevokeTimestamp = config.getGlobalRevokeTimestamp?.(config.env);
2654
+ if (config.shouldRevokeTokens?.(config.env) && globalRevokeTimestamp && payload.iat < parseInt(globalRevokeTimestamp)) {
2655
+ config.logger?.warn("Token revoked by global timestamp", {
2656
+ issued_at: payload.iat,
2657
+ revoke_threshold: globalRevokeTimestamp
2658
+ });
2659
+ return config.createRevokedState();
2660
+ }
2661
+ const headers = config.getRequestHeaders?.() || {};
2662
+ const minimalSession = {
2663
+ id: payload.jti,
2664
+ created_at: (/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString(),
2665
+ expires_at: (/* @__PURE__ */ new Date(payload.exp * 1e3)).toISOString(),
2666
+ status: "active",
2667
+ user_agent: headers.userAgent || null,
2668
+ ip_address: headers.ipAddress || null,
2669
+ user: {
2670
+ userId: payload.sub,
2671
+ username: payload.username,
2672
+ email: payload.email,
2673
+ email_verified: payload.email_verified,
2674
+ user_type: payload.user_type,
2675
+ first_name: null,
2676
+ last_name: null,
2677
+ avatar_url: null,
2678
+ subscriptions: []
2679
+ }
2680
+ };
2681
+ return config.createAuthenticatedState(minimalSession);
2682
+ }
2683
+ return config.createUnauthenticatedState();
2684
+ } catch (error) {
2685
+ config.logger?.error("Error validating session", {
2686
+ error: error instanceof Error ? error.message : String(error),
2687
+ stack: error instanceof Error ? error.stack : void 0
2688
+ });
2689
+ return config.createUnauthenticatedState();
2690
+ }
2691
+ }
2692
+
2429
2693
  //#endregion
2430
2694
  //#region src/middleware/container_setup_helpers.ts
2431
2695
  /**
@@ -2521,9 +2785,7 @@ function createDefaultContainerSetupConfig(config) {
2521
2785
  return {
2522
2786
  createRequestContainer: config.createRequestContainer,
2523
2787
  validateSession: async (authHeader) => {
2524
- const sessionConfig = createDefaultSessionValidationConfig(config.env, config.ctx, getLogger, perfLog, config.sessionValidationOptions);
2525
- const { validateSessionFromJWT: validateSessionFromJWT$1 } = await import("./session_validation-DYUZWJFy.mjs");
2526
- return validateSessionFromJWT$1(authHeader, sessionConfig);
2788
+ return validateSessionFromJWT(authHeader, createDefaultSessionValidationConfig(config.env, config.ctx, getLogger, perfLog, config.sessionValidationOptions));
2527
2789
  },
2528
2790
  buildFactories: () => {
2529
2791
  return buildContainerFactories({
@@ -2672,239 +2934,9 @@ function createIsAuthenticatedMiddleware(options) {
2672
2934
  };
2673
2935
  }
2674
2936
 
2675
- //#endregion
2676
- //#region src/middleware/session_validation.ts
2677
- /**
2678
- * Validates session from JWT token in Authorization header
2679
- * Returns the appropriate SessionState based on token validation
2680
- *
2681
- * @param authHeader - Authorization header value (e.g., "Bearer <token>")
2682
- * @param config - Configuration for session validation
2683
- * @returns Promise resolving to SessionState
2684
- */
2685
- async function validateSessionFromJWT(authHeader, config) {
2686
- if (!authHeader?.startsWith("Bearer ")) return config.createUnauthenticatedState();
2687
- try {
2688
- const accessToken = authHeader.replace("Bearer ", "");
2689
- const payload = await config.verifyToken(accessToken, config.env.ACCESS_JWT_SECRET);
2690
- if (payload.exp < Math.floor(Date.now() / 1e3)) return config.createExpiredState();
2691
- if (payload.type === "access" && payload.exp > Math.floor(Date.now() / 1e3)) {
2692
- const globalRevokeTimestamp = config.getGlobalRevokeTimestamp?.(config.env);
2693
- if (config.shouldRevokeTokens?.(config.env) && globalRevokeTimestamp && payload.iat < parseInt(globalRevokeTimestamp)) {
2694
- config.logger?.warn("Token revoked by global timestamp", {
2695
- issued_at: payload.iat,
2696
- revoke_threshold: globalRevokeTimestamp
2697
- });
2698
- return config.createRevokedState();
2699
- }
2700
- const headers = config.getRequestHeaders?.() || {};
2701
- const minimalSession = {
2702
- id: payload.jti,
2703
- created_at: (/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString(),
2704
- expires_at: (/* @__PURE__ */ new Date(payload.exp * 1e3)).toISOString(),
2705
- status: "active",
2706
- user_agent: headers.userAgent || null,
2707
- ip_address: headers.ipAddress || null,
2708
- user: {
2709
- userId: payload.sub,
2710
- username: payload.username,
2711
- email: payload.email,
2712
- email_verified: payload.email_verified,
2713
- user_type: payload.user_type,
2714
- first_name: null,
2715
- last_name: null,
2716
- avatar_url: null,
2717
- subscriptions: []
2718
- }
2719
- };
2720
- return config.createAuthenticatedState(minimalSession);
2721
- }
2722
- return config.createUnauthenticatedState();
2723
- } catch (error) {
2724
- config.logger?.error("Error validating session", {
2725
- error: error instanceof Error ? error.message : String(error),
2726
- stack: error instanceof Error ? error.stack : void 0
2727
- });
2728
- return config.createUnauthenticatedState();
2729
- }
2730
- }
2731
-
2732
- //#endregion
2733
- //#region src/db/schemas/app_setting/app_settings_table.ts
2734
- /**
2735
- * Application settings table for storing global configuration
2736
- * that doesn't need to be sharded.
2737
- *
2738
- * This includes default rates and other application-wide settings.
2739
- */
2740
- const app_settings_table = sqliteTable("app_settings", {
2741
- key: text("key").primaryKey(),
2742
- value: text("value", { mode: "json" }).$type().notNull(),
2743
- created_at: text("created_at").notNull(),
2744
- created_by: text("created_by").notNull(),
2745
- updated_at: text("updated_at").notNull(),
2746
- updated_by: text("updated_by")
2747
- }, (table) => ({
2748
- created_at_idx: index("app_settings_created_at_idx").on(table.created_at),
2749
- updated_at_idx: index("app_settings_updated_at_idx").on(table.updated_at)
2750
- }));
2751
- /**
2752
- * Known setting keys for type safety
2753
- */
2754
- const AppSettingKey = {
2755
- GLOBAL_TAX_SETTINGS: "global_tax_settings",
2756
- DEFAULT_PRICING_LEVEL: "default_pricing_level",
2757
- TERMS_OF_SERVICE: "terms_of_service",
2758
- PRIVACY_POLICY: "privacy_policy",
2759
- MAX_SUPPORT_TICKET_ITEMS: "max_support_ticket_items",
2760
- CUSTOMER_MONTHLY_BALANCE: "customer_monthly_balance",
2761
- CUSTOMER_ROLLOVER_BALANCE: "customer_rollover_balance",
2762
- CUSTOMER_MONTHLY_ALLOCATION: "customer_monthly_allocation",
2763
- SUPPORT_TICKET_NOTIFICATION_EMAILS: "support_ticket_notification_emails"
2764
- };
2765
-
2766
- //#endregion
2767
- //#region src/db/schemas/app_setting/app_settings_repo.ts
2768
- let AppSettingsRepo = class AppSettingsRepo$1 {
2769
- constructor(appSettingsDb) {
2770
- this.appSettingsDb = appSettingsDb;
2771
- }
2772
- /**
2773
- * Get a setting value by key
2774
- * @param key Setting key
2775
- * @returns The setting value or undefined if not found
2776
- */
2777
- async readSetting(key) {
2778
- const [setting] = await this.appSettingsDb.select().from(app_settings_table).where(eq(app_settings_table.key, key)).limit(1);
2779
- return setting;
2780
- }
2781
- /**
2782
- * Save a setting value
2783
- * @param key Setting key
2784
- * @param value Setting value
2785
- * @param user User making the change
2786
- */
2787
- async createSetting(key, value, user) {
2788
- const now = (/* @__PURE__ */ new Date()).toISOString();
2789
- await this.appSettingsDb.insert(app_settings_table).values({
2790
- key,
2791
- value,
2792
- created_at: now,
2793
- created_by: user.email,
2794
- updated_at: now,
2795
- updated_by: user.email
2796
- });
2797
- }
2798
- async updateSetting(key, value, user) {
2799
- const now = (/* @__PURE__ */ new Date()).toISOString();
2800
- const [updated] = await this.appSettingsDb.update(app_settings_table).set({
2801
- value,
2802
- updated_at: now,
2803
- updated_by: user.email
2804
- }).where(eq(app_settings_table.key, key)).returning();
2805
- if (!updated) {
2806
- const [inserted] = await this.appSettingsDb.insert(app_settings_table).values({
2807
- key,
2808
- value,
2809
- created_at: now,
2810
- created_by: user.email,
2811
- updated_at: now,
2812
- updated_by: user.email
2813
- }).returning();
2814
- return inserted?.value;
2815
- }
2816
- return updated.value;
2817
- }
2818
- /**
2819
- * Delete a setting
2820
- * @param key Setting key
2821
- * @returns True if the setting was deleted, false if it didn't exist
2822
- */
2823
- async deleteSetting(key) {
2824
- return (await this.appSettingsDb.delete(app_settings_table).where(eq(app_settings_table.key, key)).returning()).length > 0;
2825
- }
2826
- /**
2827
- * Get all settings
2828
- * @returns All settings as a map of key to value
2829
- */
2830
- async getAllSettings() {
2831
- const settings = await this.appSettingsDb.select().from(app_settings_table);
2832
- const settingsMap = /* @__PURE__ */ new Map();
2833
- for (const setting of settings) settingsMap.set(setting.key, setting.value);
2834
- return settingsMap;
2835
- }
2836
- async readMultipleSettings(keys) {
2837
- const settings = await this.appSettingsDb.select().from(app_settings_table).where(inArray(app_settings_table.key, keys));
2838
- const settingsMap = /* @__PURE__ */ new Map();
2839
- for (const setting of settings) settingsMap.set(setting.key, setting.value);
2840
- return settingsMap;
2841
- }
2842
- async getNextSequentialId(recordType, userId) {
2843
- const counterKey = `${recordType}:counter`;
2844
- const now = (/* @__PURE__ */ new Date()).toISOString();
2845
- const [result] = await this.appSettingsDb.insert(app_settings_table).values({
2846
- key: counterKey,
2847
- value: 1,
2848
- created_at: now,
2849
- created_by: userId,
2850
- updated_at: now,
2851
- updated_by: userId
2852
- }).onConflictDoUpdate({
2853
- target: app_settings_table.key,
2854
- set: {
2855
- value: sql`json(json_extract(${app_settings_table.value}, '$') + 1)`,
2856
- updated_at: now,
2857
- updated_by: userId
2858
- }
2859
- }).returning({ value: app_settings_table.value });
2860
- return String(result.value);
2861
- }
2862
- };
2863
- AppSettingsRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IAppSettingsDb))], AppSettingsRepo);
2864
-
2865
- //#endregion
2866
- //#region src/slices/app_settings/app_settings_tokens.ts
2867
- const APP_SETTINGS_TOKENS = {
2868
- IGetNotificationEmailsFeature: Symbol("IGetNotificationEmailsFeature"),
2869
- IUpdateNotificationEmailsFeature: Symbol("IUpdateNotificationEmailsFeature")
2870
- };
2871
-
2872
- //#endregion
2873
- //#region src/slices/app_settings/features/get_notification_emails_feat.ts
2874
- let GetNotificationEmailsFeat = class GetNotificationEmailsFeat$1 {
2875
- constructor(appSettingsRepo) {
2876
- this.appSettingsRepo = appSettingsRepo;
2877
- }
2878
- async execute() {
2879
- return { emails: (await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS))?.value || [] };
2880
- }
2881
- };
2882
- GetNotificationEmailsFeat = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IAppSettingsRepo))], GetNotificationEmailsFeat);
2883
-
2884
- //#endregion
2885
- //#region src/slices/app_settings/features/update_notification_emails_feat.ts
2886
- let UpdateNotificationEmailsFeat = class UpdateNotificationEmailsFeat$1 {
2887
- constructor(appSettingsRepo, session) {
2888
- this.appSettingsRepo = appSettingsRepo;
2889
- this.session = session;
2890
- }
2891
- async execute(input) {
2892
- await this.appSettingsRepo.updateSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS, input.emails.map((email) => email.email), this.session.user);
2893
- return input;
2894
- }
2895
- };
2896
- UpdateNotificationEmailsFeat = __decorate([
2897
- injectable(),
2898
- __decorateParam(0, inject(TOKENS.IAppSettingsRepo)),
2899
- __decorateParam(1, injectSession())
2900
- ], UpdateNotificationEmailsFeat);
2901
-
2902
2937
  //#endregion
2903
2938
  //#region src/slices/app_settings/app_settings_container.ts
2904
- function registerAppSettingsContainer() {
2905
- container.registerSingleton(APP_SETTINGS_TOKENS.IGetNotificationEmailsFeature, GetNotificationEmailsFeat);
2906
- container.registerSingleton(APP_SETTINGS_TOKENS.IUpdateNotificationEmailsFeature, UpdateNotificationEmailsFeat);
2907
- }
2939
+ function registerAppSettingsContainer() {}
2908
2940
 
2909
2941
  //#endregion
2910
2942
  //#region src/slices/attachment/attachment_interfaces.ts
@@ -3040,17 +3072,74 @@ let AttachmentRepo = class AttachmentRepo$1 {
3040
3072
  };
3041
3073
  AttachmentRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], AttachmentRepo);
3042
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
+
3043
3127
  //#endregion
3044
3128
  //#region src/slices/attachment/features/create_attachment.ts
3045
3129
  let CreateAttachment = class CreateAttachment$1 {
3046
- 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) {
3047
3131
  this.session = session;
3048
3132
  this.attachment_repo = attachment_repo;
3049
3133
  this.attachment_folder_repo = attachment_folder_repo;
3134
+ this.support_ticket_repo = support_ticket_repo;
3135
+ this.create_record_version = create_record_version;
3050
3136
  this.env = env;
3051
3137
  this.logger = logger$1;
3052
3138
  }
3053
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
+ }
3054
3143
  this.logger.debug("[CreateAttachment] Starting file upload");
3055
3144
  if (!attachment.file) throw new Error("File is required");
3056
3145
  const envWithOptional = this.env;
@@ -3093,6 +3182,17 @@ let CreateAttachment = class CreateAttachment$1 {
3093
3182
  this.logger.debug("[CreateAttachment] Failed to upload file to R2");
3094
3183
  throw new Error("Failed to upload file to storage");
3095
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
+ });
3096
3196
  this.logger.debug("[CreateAttachment] Attachment created successfully");
3097
3197
  return new_attachment;
3098
3198
  }
@@ -3102,19 +3202,25 @@ CreateAttachment = __decorate([
3102
3202
  __decorateParam(0, injectSession()),
3103
3203
  __decorateParam(1, inject(ATTACHMENT_TOKENS.IAttachmentRepo)),
3104
3204
  __decorateParam(2, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)),
3105
- __decorateParam(3, inject(TOKENS.ENV)),
3106
- __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))
3107
3209
  ], CreateAttachment);
3108
3210
 
3109
3211
  //#endregion
3110
3212
  //#region src/slices/attachment/features/create_attachment_folder.ts
3111
3213
  let CreateAttachmentFolder = class CreateAttachmentFolder$1 {
3112
- constructor(session, attachment_folder_repo, logger$1) {
3214
+ constructor(session, attachment_folder_repo, support_ticket_repo, logger$1) {
3113
3215
  this.session = session;
3114
3216
  this.attachment_folder_repo = attachment_folder_repo;
3217
+ this.support_ticket_repo = support_ticket_repo;
3115
3218
  this.logger = logger$1;
3116
3219
  }
3117
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
+ }
3118
3224
  this.logger.debug("[CreateAttachmentFolder] Starting folder creation");
3119
3225
  if (!input.folder_name) throw new Error("Folder name is required");
3120
3226
  const sanitized_name = sanitizeFileName(input.folder_name);
@@ -3162,28 +3268,52 @@ CreateAttachmentFolder = __decorate([
3162
3268
  injectable(),
3163
3269
  __decorateParam(0, injectSession()),
3164
3270
  __decorateParam(1, inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)),
3165
- __decorateParam(2, inject(TOKENS.LOGGER))
3271
+ __decorateParam(2, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
3272
+ __decorateParam(3, inject(TOKENS.LOGGER))
3166
3273
  ], CreateAttachmentFolder);
3167
3274
 
3168
3275
  //#endregion
3169
3276
  //#region src/slices/attachment/features/delete_attachment.ts
3170
3277
  let DeleteAttachment = class DeleteAttachment$1 {
3171
- constructor(attachment_repo, attachment_folder_repo) {
3278
+ constructor(session, attachment_repo, attachment_folder_repo, support_ticket_repo, create_record_version) {
3279
+ this.session = session;
3172
3280
  this.attachment_repo = attachment_repo;
3173
3281
  this.attachment_folder_repo = attachment_folder_repo;
3282
+ this.support_ticket_repo = support_ticket_repo;
3283
+ this.create_record_version = create_record_version;
3174
3284
  }
3175
3285
  async execute(id) {
3176
3286
  const attachment = await this.attachment_repo.read_attachment(id);
3177
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
+ }
3178
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
+ }
3179
3306
  if (deleted && attachment.folder_id) await this.attachment_folder_repo.recompute_file_count(attachment.folder_id);
3180
3307
  return deleted;
3181
3308
  }
3182
3309
  };
3183
3310
  DeleteAttachment = __decorate([
3184
3311
  injectable(),
3185
- __decorateParam(0, inject(ATTACHMENT_TOKENS.IAttachmentRepo)),
3186
- __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))
3187
3317
  ], DeleteAttachment);
3188
3318
 
3189
3319
  //#endregion
@@ -3269,7 +3399,7 @@ const credit_transaction_table = sqliteTable("credit_transaction", {
3269
3399
 
3270
3400
  //#endregion
3271
3401
  //#region src/slices/customer/db/credit_transaction_query_config.ts
3272
- const creditTransactionFields = createBackendRegistry$1({
3402
+ const creditTransactionFields = createBackendRegistry({
3273
3403
  type: {
3274
3404
  type: "string",
3275
3405
  filterable: true,
@@ -3314,8 +3444,8 @@ const creditTransactionFields = createBackendRegistry$1({
3314
3444
  created_at: credit_transaction_table.created_at,
3315
3445
  created_by: credit_transaction_table.created_by
3316
3446
  });
3317
- const creditTransactionColumnMap = deriveColumnMap$1(creditTransactionFields);
3318
- const buildFieldFilters$3 = createFilterBuilder$1({
3447
+ const creditTransactionColumnMap = deriveColumnMap(creditTransactionFields);
3448
+ const buildFieldFilters$3 = createFilterBuilder({
3319
3449
  fieldRegistry: creditTransactionFields,
3320
3450
  processedSeparately: []
3321
3451
  });
@@ -3324,7 +3454,7 @@ const buildFieldFilters$3 = createFilterBuilder$1({
3324
3454
  */
3325
3455
  function buildCreditTransactionQuery(filters) {
3326
3456
  const fields = buildFieldFilters$3(filters).conditions;
3327
- const search = searchOrCondition$1(filters?.search?.query, creditTransactionFields, filters?.search?.searchableFields);
3457
+ const search = searchOrCondition(filters?.search?.query, creditTransactionFields, filters?.search?.searchableFields);
3328
3458
  return {
3329
3459
  conditions: [...fields, ...search ? [search] : []],
3330
3460
  skipQuery: false
@@ -3477,7 +3607,7 @@ let CreditService = class CreditService$1 {
3477
3607
  type: "DEDUCTION",
3478
3608
  balance_after: CreditMath.toString(newMonthlyBalance + newRolloverBalance),
3479
3609
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3480
- created_by: this.systemUser.email
3610
+ created_by: this.systemUser.userId
3481
3611
  };
3482
3612
  await this.transactionRepo.createCreditTransaction(transaction);
3483
3613
  return true;
@@ -3492,7 +3622,7 @@ let CreditService = class CreditService$1 {
3492
3622
  type: "REFUND",
3493
3623
  balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),
3494
3624
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3495
- created_by: this.systemUser.email
3625
+ created_by: this.systemUser.userId
3496
3626
  };
3497
3627
  await this.transactionRepo.createCreditTransaction(transaction);
3498
3628
  }
@@ -3530,7 +3660,7 @@ let CreditService = class CreditService$1 {
3530
3660
  type: "PURCHASE_ONETIME",
3531
3661
  balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),
3532
3662
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3533
- created_by: this.systemUser.email
3663
+ created_by: this.systemUser.userId
3534
3664
  };
3535
3665
  await this.transactionRepo.createCreditTransaction(transaction);
3536
3666
  }
@@ -3603,34 +3733,6 @@ BusinessLogicRouter = __decorate([
3603
3733
  __decorateParam(3, inject("DeleteNoteSupportTicketProcessor"))
3604
3734
  ], BusinessLogicRouter);
3605
3735
 
3606
- //#endregion
3607
- //#region src/slices/support_ticket/support_ticket_tokens.ts
3608
- /**
3609
- * Tokens for dependency injection
3610
- * Organized by customer vs staff features
3611
- */
3612
- const SUPPORT_TICKET_TOKENS = {
3613
- ISupportTicketRepo: "ISupportTicketRepo",
3614
- ICreateSupportTicketFeature: "ICreateSupportTicketFeature",
3615
- IUpdateSupportTicketCustomerFeature: "IUpdateSupportTicketCustomerFeature",
3616
- IGetSupportTicketCustomerFeature: "IGetSupportTicketCustomerFeature",
3617
- IGetSupportTicketsCustomerFeature: "IGetSupportTicketsCustomerFeature",
3618
- IApproveSupportTicketFeature: "IApproveSupportTicketFeature",
3619
- IRejectSupportTicketFeature: "IRejectSupportTicketFeature",
3620
- IRevertSupportTicketFeature: "IRevertSupportTicketFeature",
3621
- ICompleteSupportTicketFeature: "ICompleteSupportTicketFeature",
3622
- IConvertToInternalFeature: "IConvertToInternalFeature",
3623
- IConvertToCustomerFeature: "IConvertToCustomerFeature",
3624
- IDeleteSupportTicketFeature: "IDeleteSupportTicketFeature",
3625
- ICancelInternalTaskFeature: "ICancelInternalTaskFeature",
3626
- IReactivateInternalTaskFeature: "IReactivateInternalTaskFeature",
3627
- ICreateSupportTicketAdminFeature: "ICreateSupportTicketAdminFeature",
3628
- IUpdateSupportTicketAdminFeature: "IUpdateSupportTicketAdminFeature",
3629
- IGetSupportTicketAdminFeature: "IGetSupportTicketAdminFeature",
3630
- IGetSupportTicketsAdminFeature: "IGetSupportTicketsAdminFeature",
3631
- IGetSupportTicketNotesFeature: "IGetSupportTicketNotesFeature"
3632
- };
3633
-
3634
3736
  //#endregion
3635
3737
  //#region src/slices/note/business_logic/support_ticket/create_note_support_ticket_processor.ts
3636
3738
  let CreateNoteSupportTicketProcessor = class CreateNoteSupportTicketProcessor$1 {
@@ -3641,18 +3743,18 @@ let CreateNoteSupportTicketProcessor = class CreateNoteSupportTicketProcessor$1
3641
3743
  async process(input) {
3642
3744
  const supportTicket = await this.supportTicketRepo.read(input.record_id);
3643
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.");
3644
3747
  const user = this.session.user;
3645
3748
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(input, supportTicket);
3646
- else if (user.user_type === "consumer") return await this.processConsumerRequest(input, supportTicket);
3647
- 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");
3648
3751
  }
3649
3752
  async processStaffRequest(input, _supportTicket) {
3650
3753
  return input;
3651
3754
  }
3652
3755
  async processConsumerRequest(input, supportTicket) {
3653
- const user = this.session.user;
3654
- if (supportTicket.created_by !== user.userId) throw new Error("You do not have access to this support ticket");
3655
- 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");
3656
3758
  return {
3657
3759
  ...input,
3658
3760
  is_internal: false
@@ -3677,7 +3779,7 @@ const NOTE_TOKENS = {
3677
3779
  IUpdateNoteFeature: "IUpdateNoteFeature",
3678
3780
  IGetNotesFeature: "IGetNotesFeature",
3679
3781
  IDeleteNoteFeature: "IDeleteNoteFeature",
3680
- IBusinessLogicRouter: "IBusinessLogicRouter",
3782
+ IBusinessLogicRouter: "Note.IBusinessLogicRouter",
3681
3783
  CreateNoteSupportTicketProcessor: "CreateNoteSupportTicketProcessor",
3682
3784
  UpdateNoteSupportTicketProcessor: "UpdateNoteSupportTicketProcessor",
3683
3785
  GetNotesSupportTicketProcessor: "GetNotesSupportTicketProcessor",
@@ -3694,21 +3796,27 @@ let DeleteNoteSupportTicketProcessor = class DeleteNoteSupportTicketProcessor$1
3694
3796
  }
3695
3797
  async process(id) {
3696
3798
  const existingNote = await this.noteRepo.read(id);
3697
- if (!existingNote) throw new Error("Note not found");
3698
- 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");
3699
3801
  const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);
3700
- if (!supportTicket) throw new Error("Support ticket not found");
3802
+ if (!supportTicket) throw new BusinessError("Support ticket not found");
3701
3803
  const user = this.session.user;
3702
3804
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(id, existingNote, supportTicket);
3703
- else if (user.user_type === "consumer") return await this.processConsumerRequest(id, existingNote, supportTicket);
3704
- 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");
3705
3807
  }
3706
- async processStaffRequest(_id, _existingNote, supportTicket) {
3707
- if (supportTicket.dev_lifecycle === "DEPLOYED" || supportTicket.approval_status === "REJECTED") throw new Error("Cannot delete notes on completed or cancelled support tickets");
3708
- 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;
3709
3813
  }
3710
- async processConsumerRequest(_id, _existingNote, _supportTicket) {
3711
- 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;
3712
3820
  }
3713
3821
  };
3714
3822
  DeleteNoteSupportTicketProcessor = __decorate([
@@ -3732,8 +3840,8 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3732
3840
  const supportTicket = await this.supportTicketRepo.read(recordIdValue);
3733
3841
  if (!supportTicket) throw new Error("Support ticket not found");
3734
3842
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(filters, supportTicket);
3735
- else if (user.user_type === "consumer") return await this.processConsumerRequest(filters, supportTicket);
3736
- 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");
3737
3845
  }
3738
3846
  return await this.applyVisibilityRules(filters);
3739
3847
  }
@@ -3741,8 +3849,7 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3741
3849
  return filters;
3742
3850
  }
3743
3851
  async processConsumerRequest(filters, supportTicket) {
3744
- const user = this.session.user;
3745
- 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");
3746
3853
  return {
3747
3854
  ...filters,
3748
3855
  is_internal: {
@@ -3752,7 +3859,8 @@ let GetNotesSupportTicketProcessor = class GetNotesSupportTicketProcessor$1 {
3752
3859
  };
3753
3860
  }
3754
3861
  async applyVisibilityRules(filters) {
3755
- 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 {
3756
3864
  ...filters,
3757
3865
  is_internal: {
3758
3866
  operator: OPERATORS.EQUALS,
@@ -3778,25 +3886,26 @@ let UpdateNoteSupportTicketProcessor = class UpdateNoteSupportTicketProcessor$1
3778
3886
  }
3779
3887
  async process(input) {
3780
3888
  const existingNote = await this.noteRepo.read(input.id);
3781
- if (!existingNote) throw new Error("Note not found");
3782
- 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");
3783
3891
  const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);
3784
- 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.");
3785
3894
  const user = this.session.user;
3786
3895
  if (user.user_type === "staff" || user.user_type === "super_admin") return await this.processStaffRequest(input, existingNote, supportTicket);
3787
- else if (user.user_type === "consumer") return await this.processConsumerRequest(input, existingNote, supportTicket);
3788
- 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");
3789
3898
  }
3790
3899
  async processStaffRequest(input, existingNote, _supportTicket) {
3791
- 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");
3792
3901
  return input;
3793
3902
  }
3794
3903
  async processConsumerRequest(input, existingNote, supportTicket) {
3795
3904
  const user = this.session.user;
3796
- if (supportTicket.created_by !== user.userId) throw new Error("You do not have access to this support ticket");
3797
- if (existingNote.created_by !== user.userId) throw new Error("You can only update notes you created");
3798
- if (existingNote.is_internal) throw new Error("Consumers cannot update internal notes");
3799
- 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");
3800
3909
  return input;
3801
3910
  }
3802
3911
  };
@@ -3867,8 +3976,8 @@ const noteFields = {
3867
3976
  sortable: false
3868
3977
  }
3869
3978
  };
3870
- const noteColumnMap = deriveColumnMap$1(noteFields);
3871
- const buildFieldFilters$2 = createFilterBuilder$1({
3979
+ const noteColumnMap = deriveColumnMap(noteFields);
3980
+ const buildFieldFilters$2 = createFilterBuilder({
3872
3981
  fieldRegistry: noteFields,
3873
3982
  processedSeparately: ["archived_at"]
3874
3983
  });
@@ -3877,9 +3986,9 @@ const buildFieldFilters$2 = createFilterBuilder$1({
3877
3986
  */
3878
3987
  function buildNoteQuery(filters) {
3879
3988
  const softDelete = isNull(note_table.deleted_at);
3880
- const archive = archiveConditions$1(filters, note_table.archived_at);
3989
+ const archive = archiveConditions(filters, note_table.archived_at);
3881
3990
  const fields = buildFieldFilters$2(filters).conditions;
3882
- const search = searchOrCondition$1(filters?.search?.query, noteFields, filters?.search?.searchableFields);
3991
+ const search = searchOrCondition(filters?.search?.query, noteFields, filters?.search?.searchableFields);
3883
3992
  return {
3884
3993
  conditions: [
3885
3994
  softDelete,
@@ -3966,28 +4075,34 @@ let NoteRepo = class NoteRepo$1 {
3966
4075
  NoteRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], NoteRepo);
3967
4076
 
3968
4077
  //#endregion
3969
- //#region src/slices/record_version/record_version_interfaces.ts
3970
- const RECORD_VERSION_TOKENS = {
3971
- IRecordVersionsRepo: Symbol("IRecordVersionsRepo"),
3972
- IReadAllRecordVersionsByRecord: Symbol("IReadAllRecordVersionsByRecord"),
3973
- IReadAllRecordVersionsByRecordPaginated: Symbol("IReadAllRecordVersionsByRecordPaginated"),
3974
- IReadAllRecordVersionsByRecordPaginatedCustomer: Symbol("IReadAllRecordVersionsByRecordPaginatedCustomer"),
3975
- IReadRecordVersion: Symbol("IReadRecordVersion"),
3976
- ICreateRecordVersion: Symbol("ICreateRecordVersion"),
3977
- ICreateRecordVersionsBatch: Symbol("ICreateRecordVersionsBatch"),
3978
- IUpdateRecordVersion: Symbol("IUpdateRecordVersion"),
3979
- 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"
3980
4093
  };
3981
4094
 
3982
4095
  //#endregion
3983
4096
  //#region src/slices/note/features/create_note_feat.ts
3984
4097
  let CreateNoteFeat = class CreateNoteFeat$1 {
3985
- constructor(session, noteRepo, businessLogicRouter, create_record_version, supportTicketRepo) {
4098
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup, supportTicketRepo, notificationService) {
3986
4099
  this.session = session;
3987
4100
  this.noteRepo = noteRepo;
3988
4101
  this.businessLogicRouter = businessLogicRouter;
3989
4102
  this.create_record_version = create_record_version;
4103
+ this.userDisplayLookup = userDisplayLookup;
3990
4104
  this.supportTicketRepo = supportTicketRepo;
4105
+ this.notificationService = notificationService;
3991
4106
  }
3992
4107
  async execute(input) {
3993
4108
  const processor = this.businessLogicRouter.getCreateProcessor(input.record_type);
@@ -4017,7 +4132,13 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4017
4132
  auth_role: this.session.user.user_type,
4018
4133
  auth_username: this.session.user.username
4019
4134
  });
4020
- 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
+ };
4021
4142
  }
4022
4143
  /**
4023
4144
  * Bumps parent record's updated_at timestamp when note is created
@@ -4039,6 +4160,21 @@ let CreateNoteFeat = class CreateNoteFeat$1 {
4039
4160
  default: break;
4040
4161
  }
4041
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
+ }
4042
4178
  };
4043
4179
  CreateNoteFeat = __decorate([
4044
4180
  injectable(),
@@ -4046,7 +4182,9 @@ CreateNoteFeat = __decorate([
4046
4182
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4047
4183
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4048
4184
  __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4049
- __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))
4050
4188
  ], CreateNoteFeat);
4051
4189
 
4052
4190
  //#endregion
@@ -4090,10 +4228,19 @@ DeleteNoteFeat = __decorate([
4090
4228
 
4091
4229
  //#endregion
4092
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
+ }
4093
4239
  let GetNotesFeat = class GetNotesFeat$1 {
4094
- constructor(noteRepo, businessLogicRouter) {
4240
+ constructor(noteRepo, businessLogicRouter, userDisplayLookup) {
4095
4241
  this.noteRepo = noteRepo;
4096
4242
  this.businessLogicRouter = businessLogicRouter;
4243
+ this.userDisplayLookup = userDisplayLookup;
4097
4244
  }
4098
4245
  async execute(filters) {
4099
4246
  let processedFilters = filters;
@@ -4104,23 +4251,36 @@ let GetNotesFeat = class GetNotesFeat$1 {
4104
4251
  if (processor) processedFilters = await processor.process(filters);
4105
4252
  }
4106
4253
  }
4107
- 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
+ };
4108
4266
  }
4109
4267
  };
4110
4268
  GetNotesFeat = __decorate([
4111
4269
  injectable(),
4112
4270
  __decorateParam(0, inject(NOTE_TOKENS.INoteRepo)),
4113
- __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter))
4271
+ __decorateParam(1, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4272
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup))
4114
4273
  ], GetNotesFeat);
4115
4274
 
4116
4275
  //#endregion
4117
4276
  //#region src/slices/note/features/update_note_feat.ts
4118
4277
  let UpdateNoteFeat = class UpdateNoteFeat$1 {
4119
- constructor(session, noteRepo, businessLogicRouter, create_record_version) {
4278
+ constructor(session, noteRepo, businessLogicRouter, create_record_version, userDisplayLookup) {
4120
4279
  this.session = session;
4121
4280
  this.noteRepo = noteRepo;
4122
4281
  this.businessLogicRouter = businessLogicRouter;
4123
4282
  this.create_record_version = create_record_version;
4283
+ this.userDisplayLookup = userDisplayLookup;
4124
4284
  }
4125
4285
  async execute(input) {
4126
4286
  const existingNote = await this.noteRepo.read(input.id);
@@ -4148,7 +4308,13 @@ let UpdateNoteFeat = class UpdateNoteFeat$1 {
4148
4308
  auth_role: this.session.user.user_type,
4149
4309
  auth_username: this.session.user.username
4150
4310
  });
4151
- 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
+ };
4152
4318
  }
4153
4319
  };
4154
4320
  UpdateNoteFeat = __decorate([
@@ -4156,7 +4322,8 @@ UpdateNoteFeat = __decorate([
4156
4322
  __decorateParam(0, injectSession()),
4157
4323
  __decorateParam(1, inject(NOTE_TOKENS.INoteRepo)),
4158
4324
  __decorateParam(2, inject(NOTE_TOKENS.IBusinessLogicRouter)),
4159
- __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
4325
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
4326
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
4160
4327
  ], UpdateNoteFeat);
4161
4328
 
4162
4329
  //#endregion
@@ -4203,22 +4370,6 @@ let PasswordResetTokenVerifier = class PasswordResetTokenVerifier$1 {
4203
4370
  };
4204
4371
  PasswordResetTokenVerifier = __decorate([injectable()], PasswordResetTokenVerifier);
4205
4372
 
4206
- //#endregion
4207
- //#region src/slices/user/user_interfaces.ts
4208
- const USER_TOKENS = {
4209
- IUsersRepo: Symbol("IUsersRepo"),
4210
- ISignUpUser: Symbol("ISignUpUser"),
4211
- IReadAllUsers: Symbol("IReadAllUsers"),
4212
- IReadUser: Symbol("IReadUser"),
4213
- ICreateUser: Symbol("ICreateUser"),
4214
- IDeleteUser: Symbol("IDeleteUser"),
4215
- IReadConsumers: Symbol("IReadConsumers"),
4216
- IGetAllUsersFeature: "IGetAllUsersFeature",
4217
- IGetUserFeature: "IGetUserFeature",
4218
- IUpdateUserFeature: "IUpdateUserFeature",
4219
- IGetUsersForSelectionFeature: "IGetUsersForSelectionFeature"
4220
- };
4221
-
4222
4373
  //#endregion
4223
4374
  //#region src/slices/password_reset/features/change_password.ts
4224
4375
  let ChangeUserPassword = class ChangeUserPassword$1 {
@@ -4363,6 +4514,51 @@ function registerPasswordResetContainer() {
4363
4514
  container.registerSingleton(PASSWORD_RESET_TOKENS.IChangeUserPassword, ChangeUserPassword);
4364
4515
  }
4365
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
+
4366
4562
  //#endregion
4367
4563
  //#region src/slices/team/team_interfaces.ts
4368
4564
  const TEAM_TOKENS = {
@@ -4410,10 +4606,11 @@ var RecordAccessValidatorRegistry = class {
4410
4606
  }
4411
4607
  };
4412
4608
  let ValidateRecordAccess = class ValidateRecordAccess$1 {
4413
- constructor(session, teamRepo, validatorRegistry) {
4609
+ constructor(session, teamRepo, validatorRegistry, logger$1) {
4414
4610
  this.session = session;
4415
4611
  this.teamRepo = teamRepo;
4416
4612
  this.validatorRegistry = validatorRegistry;
4613
+ this.logger = logger$1;
4417
4614
  }
4418
4615
  async execute(record_id, record_type) {
4419
4616
  const user = this.session.user;
@@ -4426,25 +4623,68 @@ let ValidateRecordAccess = class ValidateRecordAccess$1 {
4426
4623
  case "team":
4427
4624
  await this.validateTeamAccess(record_id, user.user_type);
4428
4625
  break;
4429
- 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
+ }
4430
4636
  }
4431
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
+ }
4432
4664
  /**
4433
4665
  * Validates access to a team record
4434
4666
  * Lead, staff, and admin have full access
4435
4667
  * Consumers cannot access team records
4436
4668
  */
4437
4669
  async validateTeamAccess(record_id, user_type) {
4438
- 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
+ }
4439
4674
  if (user_type === "lead" || user_type === "staff" || user_type === "super_admin") return;
4440
- 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");
4441
4680
  }
4442
4681
  };
4443
4682
  ValidateRecordAccess = __decorate([
4444
4683
  injectable(),
4445
4684
  __decorateParam(0, injectSession()),
4446
4685
  __decorateParam(1, inject(TEAM_TOKENS.ITeamRepository)),
4447
- __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry))
4686
+ __decorateParam(2, inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry)),
4687
+ __decorateParam(3, inject(TOKENS.LOGGER))
4448
4688
  ], ValidateRecordAccess);
4449
4689
 
4450
4690
  //#endregion
@@ -4518,9 +4758,13 @@ let RecordVersionRepo = class RecordVersionRepo$1 {
4518
4758
  /**
4519
4759
  * NEW: Breadcrumb pagination for record versions
4520
4760
  * Use this for new implementations requiring bidirectional navigation
4761
+ * Supports single record_type or multiple record_types (merged results)
4521
4762
  */
4522
- async read_all_record_versions_paginated(record_id, record_type, filters) {
4523
- 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)];
4524
4768
  if (filters?.start_date) baseConditions.push(gte(record_version_table.recorded_at, filters.start_date));
4525
4769
  if (filters?.end_date) baseConditions.push(lte(record_version_table.recorded_at, filters.end_date));
4526
4770
  return PaginationUtils.findAllPaginated({
@@ -4650,19 +4894,49 @@ ReadAllRecordVersionsByRecord = __decorate([
4650
4894
 
4651
4895
  //#endregion
4652
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
+ }
4653
4915
  let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersionsByRecordPaginatedCustomer$1 {
4654
- constructor(record_version_repo) {
4916
+ constructor(record_version_repo, userDisplayLookup) {
4655
4917
  this.record_version_repo = record_version_repo;
4918
+ this.userDisplayLookup = userDisplayLookup;
4656
4919
  }
4657
4920
  async execute(record_id, record_type, filters) {
4658
- const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_type, filters);
4659
- 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;
4660
4925
  return {
4661
4926
  ...record,
4662
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4663
- 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)
4664
4929
  };
4665
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
+ }
4666
4940
  return records;
4667
4941
  }
4668
4942
  /**
@@ -4672,7 +4946,9 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4672
4946
  filterFieldsByRecordType(recordData, recordType) {
4673
4947
  if (!recordData) return recordData;
4674
4948
  switch (recordType) {
4675
- 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;
4676
4952
  default: return recordData;
4677
4953
  }
4678
4954
  }
@@ -4707,26 +4983,60 @@ let ReadAllRecordVersionsByRecordPaginatedCustomer = class ReadAllRecordVersions
4707
4983
  }
4708
4984
  }
4709
4985
  };
4710
- 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);
4711
4991
 
4712
4992
  //#endregion
4713
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
+ }
4714
5012
  let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecordPaginated$1 {
4715
- constructor(record_version_repo, validateRecordAccess, session) {
5013
+ constructor(record_version_repo, validateRecordAccess, userDisplayLookup, session) {
4716
5014
  this.record_version_repo = record_version_repo;
4717
5015
  this.validateRecordAccess = validateRecordAccess;
5016
+ this.userDisplayLookup = userDisplayLookup;
4718
5017
  this.session = session;
4719
5018
  }
4720
5019
  async execute(record_id, record_type, filters) {
4721
- await this.validateRecordAccess.execute(record_id, record_type);
4722
- const records = await this.record_version_repo.read_all_record_versions_paginated(record_id, record_type, filters);
4723
- 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;
4724
5025
  return {
4725
5026
  ...record,
4726
- record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),
4727
- 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)
4728
5029
  };
4729
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
+ }
4730
5040
  return records;
4731
5041
  }
4732
5042
  /**
@@ -4737,7 +5047,9 @@ let ReadAllRecordVersionsByRecordPaginated = class ReadAllRecordVersionsByRecord
4737
5047
  if (!recordData) return recordData;
4738
5048
  if (this.session.user.user_type === "staff" || this.session.user.user_type === "super_admin") return recordData;
4739
5049
  switch (recordType) {
4740
- 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;
4741
5053
  default: return recordData;
4742
5054
  }
4743
5055
  }
@@ -4776,7 +5088,8 @@ ReadAllRecordVersionsByRecordPaginated = __decorate([
4776
5088
  injectable(),
4777
5089
  __decorateParam(0, inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)),
4778
5090
  __decorateParam(1, inject(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess)),
4779
- __decorateParam(2, injectSession())
5091
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
5092
+ __decorateParam(3, injectSession())
4780
5093
  ], ReadAllRecordVersionsByRecordPaginated);
4781
5094
 
4782
5095
  //#endregion
@@ -4811,6 +5124,482 @@ function registerRecordVersionContainer() {
4811
5124
  container.registerSingleton(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess, ValidateRecordAccess);
4812
5125
  }
4813
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
+
4814
5603
  //#endregion
4815
5604
  //#region src/slices/support_ticket/db/support_ticket_query_config.ts
4816
5605
  const supportTicketFields = {
@@ -4823,9 +5612,9 @@ const supportTicketFields = {
4823
5612
  },
4824
5613
  priority: {
4825
5614
  column: support_ticket_table.priority,
4826
- type: "string",
5615
+ type: "number",
4827
5616
  filterable: true,
4828
- searchable: true,
5617
+ searchable: false,
4829
5618
  sortable: true
4830
5619
  },
4831
5620
  approval_status: {
@@ -4842,18 +5631,11 @@ const supportTicketFields = {
4842
5631
  searchable: true,
4843
5632
  sortable: true
4844
5633
  },
4845
- requester_email: {
4846
- column: support_ticket_table.requester_email,
4847
- type: "string",
4848
- filterable: true,
4849
- searchable: true,
4850
- sortable: false
4851
- },
4852
- requester_name: {
4853
- column: support_ticket_table.requester_name,
5634
+ created_by: {
5635
+ column: support_ticket_table.created_by,
4854
5636
  type: "string",
4855
5637
  filterable: true,
4856
- searchable: true,
5638
+ searchable: false,
4857
5639
  sortable: false
4858
5640
  },
4859
5641
  title: {
@@ -4912,20 +5694,6 @@ const supportTicketFields = {
4912
5694
  searchable: false,
4913
5695
  sortable: false
4914
5696
  },
4915
- requesterName: {
4916
- column: support_ticket_table.requester_name,
4917
- type: "string",
4918
- filterable: false,
4919
- searchable: true,
4920
- sortable: false
4921
- },
4922
- requesterEmail: {
4923
- column: support_ticket_table.requester_email,
4924
- type: "string",
4925
- filterable: false,
4926
- searchable: true,
4927
- sortable: false
4928
- },
4929
5697
  approvalStatus: {
4930
5698
  column: support_ticket_table.approval_status,
4931
5699
  type: "string",
@@ -4990,13 +5758,14 @@ const supportTicketFields = {
4990
5758
  sortable: false
4991
5759
  }
4992
5760
  };
4993
- const supportTicketColumnMap = deriveColumnMap$1(supportTicketFields);
4994
- const buildFieldFilters$1 = createFilterBuilder$1({
5761
+ const supportTicketColumnMap = deriveColumnMap(supportTicketFields);
5762
+ const buildFieldFilters$1 = createFilterBuilder({
4995
5763
  fieldRegistry: supportTicketFields,
4996
5764
  processedSeparately: [
4997
5765
  "archived_at",
4998
5766
  "status",
4999
- "is_locked"
5767
+ "is_locked",
5768
+ "dev_lifecycle"
5000
5769
  ]
5001
5770
  });
5002
5771
  /**
@@ -5010,6 +5779,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5010
5779
  case "CANCELLED": return "REJECTED";
5011
5780
  case "FOLLOWUP": return;
5012
5781
  case "IN_PROGRESS": return;
5782
+ case "VERIFICATION": return;
5013
5783
  case "COMPLETED": return;
5014
5784
  default: throw new Error(`Invalid support ticket status: ${status}`);
5015
5785
  }
@@ -5020,6 +5790,7 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5020
5790
  case "CANCELLED": return;
5021
5791
  case "FOLLOWUP": return;
5022
5792
  case "IN_PROGRESS": return;
5793
+ case "VERIFICATION": return "VERIFICATION";
5023
5794
  case "COMPLETED": return "DEPLOYED";
5024
5795
  default: throw new Error(`Invalid support ticket status: ${status}`);
5025
5796
  }
@@ -5031,9 +5802,9 @@ function mapSupportTicketStatusToApprovalStatus(statuses) {
5031
5802
  */
5032
5803
  function buildSupportTicketQuery(filters) {
5033
5804
  const softDelete = isNull(support_ticket_table.deleted_at);
5034
- const archive = archiveConditions$1(filters, support_ticket_table.archived_at);
5805
+ const archive = archiveConditions(filters, support_ticket_table.archived_at);
5035
5806
  const fields = buildFieldFilters$1(filters).conditions;
5036
- const search = searchOrCondition$1(filters?.search?.query, supportTicketFields, filters?.search?.searchableFields);
5807
+ const search = searchOrCondition(filters?.search?.query, supportTicketFields, filters?.search?.searchableFields);
5037
5808
  const conditions = [
5038
5809
  softDelete,
5039
5810
  ...archive,
@@ -5063,7 +5834,21 @@ function buildSupportTicketQuery(filters) {
5063
5834
  } else if (statusValue === "IN_PROGRESS") {
5064
5835
  conditions.push(eq(support_ticket_table.approval_status, "APPROVED"));
5065
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")));
5066
- } 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);
5067
5852
  }
5068
5853
  }
5069
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"));
@@ -5105,7 +5890,7 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5105
5890
  const now = (/* @__PURE__ */ new Date()).toISOString();
5106
5891
  const [result] = await this.router.queryById(support_ticket.id, (db) => db.update(support_ticket_table).set({
5107
5892
  ...support_ticket,
5108
- updated_at: support_ticket.updated_at || now
5893
+ updated_at: now
5109
5894
  }).where(eq(support_ticket_table.id, support_ticket.id)).returning());
5110
5895
  return result;
5111
5896
  }
@@ -5160,6 +5945,26 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5160
5945
  return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);
5161
5946
  }
5162
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
+ /**
5163
5968
  * Soft delete a support ticket by ID
5164
5969
  */
5165
5970
  async soft_delete(id, deleted_by) {
@@ -5172,7 +5977,237 @@ let SupportTicketRepo = class SupportTicketRepo$1 {
5172
5977
  return result[0];
5173
5978
  }
5174
5979
  };
5175
- 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);
5176
6211
 
5177
6212
  //#endregion
5178
6213
  //#region src/slices/support_ticket/mappers/support_ticket_mapper.ts
@@ -5191,6 +6226,7 @@ function computeStatus(approvalStatus, devLifecycle) {
5191
6226
  case "TESTING":
5192
6227
  case "STAGING":
5193
6228
  case "PO_APPROVAL": return "IN_PROGRESS";
6229
+ case "VERIFICATION": return "VERIFICATION";
5194
6230
  case "DEPLOYED": return "COMPLETED";
5195
6231
  default: return "FOLLOWUP";
5196
6232
  }
@@ -5224,11 +6260,10 @@ function toCustomerSupportTicketReadDto(entity) {
5224
6260
  title: entity.title,
5225
6261
  description: entity.description,
5226
6262
  type: entity.type,
5227
- priority: entity.priority,
6263
+ priority: supportTicketNumberToPriority(entity.priority),
5228
6264
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5229
6265
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5230
- requester_name: entity.requester_name,
5231
- requester_email: entity.requester_email,
6266
+ created_by_display_name: null,
5232
6267
  credit_value: entity.credit_value,
5233
6268
  start_at: startAt,
5234
6269
  target_at: targetAt,
@@ -5237,7 +6272,8 @@ function toCustomerSupportTicketReadDto(entity) {
5237
6272
  created_at: createdAt,
5238
6273
  created_by: entity.created_by,
5239
6274
  updated_at: updatedAt,
5240
- updated_by: entity.updated_by
6275
+ updated_by: entity.updated_by,
6276
+ archived_at: entity.archived_at ?? null
5241
6277
  };
5242
6278
  }
5243
6279
  /**
@@ -5260,13 +6296,13 @@ function toStaffSupportTicketReadDto(entity) {
5260
6296
  title: entity.title,
5261
6297
  description: entity.description,
5262
6298
  type: entity.type,
5263
- priority: entity.priority,
6299
+ priority: supportTicketNumberToPriority(entity.priority),
5264
6300
  status: computeStatus(entity.approval_status, entity.dev_lifecycle),
5265
6301
  approval_status: entity.approval_status,
5266
6302
  is_locked: !!entity.approval_status && entity.approval_status !== "PENDING",
5267
6303
  can_delete: canDelete,
5268
- requester_name: entity.requester_name,
5269
- requester_email: entity.requester_email,
6304
+ created_by_display_name: null,
6305
+ assigned_to: entity.assigned_to ?? null,
5270
6306
  dev_lifecycle: entity.dev_lifecycle || "PENDING",
5271
6307
  credit_value: entity.credit_value,
5272
6308
  delivered_value: entity.delivered_value,
@@ -5277,7 +6313,9 @@ function toStaffSupportTicketReadDto(entity) {
5277
6313
  created_at: createdAt,
5278
6314
  created_by: entity.created_by,
5279
6315
  updated_at: updatedAt,
5280
- 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
5281
6319
  };
5282
6320
  }
5283
6321
  function dateIsValid(date) {
@@ -5289,13 +6327,14 @@ function dateIsValid(date) {
5289
6327
  //#endregion
5290
6328
  //#region src/slices/support_ticket/features/customer/create_support_ticket_feat.ts
5291
6329
  let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5292
- 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) {
5293
6331
  this.session = session;
5294
6332
  this.support_ticketRepo = support_ticketRepo;
6333
+ this.subscriberRepo = subscriberRepo;
6334
+ this.notificationService = notificationService;
6335
+ this.triageService = triageService;
5295
6336
  this.create_record_version = create_record_version;
5296
6337
  this.appSettingsRepo = appSettingsRepo;
5297
- this.emailService = emailService;
5298
- this.env = env;
5299
6338
  this.logger = logger$1;
5300
6339
  }
5301
6340
  /**
@@ -5306,9 +6345,8 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5306
6345
  const userId = this.session.user.userId;
5307
6346
  const frEntity = {
5308
6347
  ...input,
6348
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5309
6349
  description: input.description ?? "",
5310
- requester_email: this.session.user.email,
5311
- requester_name: this.session.user.username,
5312
6350
  created_at: now,
5313
6351
  created_by: userId,
5314
6352
  updated_at: now,
@@ -5316,7 +6354,11 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5316
6354
  };
5317
6355
  const support_ticketCreated = await this.support_ticketRepo.create(frEntity);
5318
6356
  support_ticketCreated.display_id = await this.appSettingsRepo.getNextSequentialId(RecordConst.SUPPORT_TICKET, this.session.user.userId);
5319
- 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);
5320
6362
  await this.create_record_version.execute({
5321
6363
  record_id: support_ticketCreated.id,
5322
6364
  operation: OperationConst.INSERT,
@@ -5327,58 +6369,30 @@ let CreateSupportTicketFeat = class CreateSupportTicketFeat$1 {
5327
6369
  auth_role: this.session.user.user_type,
5328
6370
  auth_username: this.session.user.username
5329
6371
  });
5330
- await this.sendNotificationEmails(support_ticketCreated);
5331
- 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
+ };
5332
6379
  }
5333
6380
  /**
5334
- * Send email notifications to staff members
6381
+ * Auto-add requester (created_by) as follower
5335
6382
  */
5336
- async sendNotificationEmails(ticket) {
6383
+ async addRequesterAsFollower(ticket) {
5337
6384
  try {
5338
- const emailsSetting = await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS);
5339
- if (!emailsSetting?.value) {
5340
- this.logger.debug("No notification emails configured for support tickets");
5341
- return;
5342
- }
5343
- const emails = emailsSetting.value;
5344
- if (!Array.isArray(emails) || emails.length === 0) {
5345
- this.logger.debug("No valid notification emails found");
5346
- return;
5347
- }
5348
- const ticketUrl = `${this.env.WEBSITE_URL}/support-tickets/${ticket.id}`;
5349
- const subject = `New Support Ticket Created: ${ticket.title}`;
5350
- const bodyText = `
5351
- A new support ticket has been created:
5352
-
5353
- Ticket #${ticket.display_id || ticket.id}
5354
- Title: ${ticket.title}
5355
- Type: ${ticket.type}
5356
- Priority: ${ticket.priority}
5357
- Requester: ${ticket.requester_name} (${ticket.requester_email})
5358
-
5359
- Description:
5360
- ${ticket.description}
5361
-
5362
- View ticket: ${ticketUrl}
5363
- `.trim();
5364
- const bodyHtml = `
5365
- <h2>New Support Ticket Created</h2>
5366
- <p>A new support ticket has been created:</p>
5367
- <ul>
5368
- <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
5369
- <li><strong>Title:</strong> ${ticket.title}</li>
5370
- <li><strong>Type:</strong> ${ticket.type}</li>
5371
- <li><strong>Priority:</strong> ${ticket.priority}</li>
5372
- <li><strong>Requester:</strong> ${ticket.requester_name} (${ticket.requester_email})</li>
5373
- </ul>
5374
- <p><strong>Description:</strong></p>
5375
- <p>${ticket.description}</p>
5376
- <p><a href="${ticketUrl}">View Ticket</a></p>
5377
- `.trim();
5378
- await this.emailService.sendBulkEmail(emails, subject, bodyText, bodyHtml);
5379
- 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
+ });
5380
6394
  } catch (error) {
5381
- this.logger.error("Failed to send support ticket notification emails", { error });
6395
+ this.logger.error("Failed to add requester as follower", { error });
5382
6396
  }
5383
6397
  }
5384
6398
  };
@@ -5386,19 +6400,87 @@ CreateSupportTicketFeat = __decorate([
5386
6400
  injectable(),
5387
6401
  __decorateParam(0, injectSession()),
5388
6402
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5389
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5390
- __decorateParam(3, inject(TOKENS.IAppSettingsRepo)),
5391
- __decorateParam(4, inject(TOKENS.IEmailService)),
5392
- __decorateParam(5, inject(TOKENS.ENV)),
5393
- __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))
5394
6409
  ], CreateSupportTicketFeat);
5395
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
+
5396
6465
  //#endregion
5397
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
+ }
5398
6478
  let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5399
- constructor(session, support_ticketRepo, appSettingsRepo, logger$1) {
6479
+ constructor(session, support_ticketRepo, subscriberRepo, userDisplayLookup, appSettingsRepo, logger$1) {
5400
6480
  this.session = session;
5401
6481
  this.support_ticketRepo = support_ticketRepo;
6482
+ this.subscriberRepo = subscriberRepo;
6483
+ this.userDisplayLookup = userDisplayLookup;
5402
6484
  this.appSettingsRepo = appSettingsRepo;
5403
6485
  this.logger = logger$1;
5404
6486
  }
@@ -5416,22 +6498,31 @@ let GetSupportTicketCustomerFeature = class GetSupportTicketCustomerFeature$1 {
5416
6498
  updated_by: this.session.user.userId
5417
6499
  });
5418
6500
  }
5419
- 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
+ };
5420
6508
  }
5421
6509
  };
5422
6510
  GetSupportTicketCustomerFeature = __decorate([
5423
6511
  injectable(),
5424
6512
  __decorateParam(0, injectSession()),
5425
6513
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5426
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
5427
- __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))
5428
6518
  ], GetSupportTicketCustomerFeature);
5429
6519
 
5430
6520
  //#endregion
5431
6521
  //#region src/slices/support_ticket/features/customer/get_support_tickets_customer_feat.ts
5432
6522
  let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1 {
5433
- constructor(support_ticketRepo) {
6523
+ constructor(support_ticketRepo, userDisplayLookup) {
5434
6524
  this.support_ticketRepo = support_ticketRepo;
6525
+ this.userDisplayLookup = userDisplayLookup;
5435
6526
  }
5436
6527
  async execute(filters) {
5437
6528
  const customerFilters = {
@@ -5442,20 +6533,28 @@ let GetSupportTicketsCustomerFeature = class GetSupportTicketsCustomerFeature$1
5442
6533
  }
5443
6534
  };
5444
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);
5445
6539
  return {
5446
- items: result.items.map(toCustomerSupportTicketReadDto),
6540
+ items: dtos.map((dto) => enrichCustomerTicket(dto, displayMap)),
5447
6541
  pageInfo: result.pageInfo
5448
6542
  };
5449
6543
  }
5450
6544
  };
5451
- 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);
5452
6550
 
5453
6551
  //#endregion
5454
6552
  //#region src/slices/support_ticket/features/customer/update_support_ticket_customer_feat.ts
5455
6553
  let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5456
- constructor(session, support_ticketRepo, create_record_version) {
6554
+ constructor(session, support_ticketRepo, userDisplayLookup, create_record_version) {
5457
6555
  this.session = session;
5458
6556
  this.support_ticketRepo = support_ticketRepo;
6557
+ this.userDisplayLookup = userDisplayLookup;
5459
6558
  this.create_record_version = create_record_version;
5460
6559
  }
5461
6560
  /**
@@ -5463,8 +6562,9 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5463
6562
  */
5464
6563
  async execute(input) {
5465
6564
  const support_ticket = await this.support_ticketRepo.read(input.id);
5466
- if (!support_ticket) throw new Error("SupportTicket not found");
5467
- 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.");
5468
6568
  const frEntity = {
5469
6569
  ...support_ticket,
5470
6570
  title: input.title,
@@ -5486,24 +6586,102 @@ let UpdateSupportTicketCustomerFeat = class UpdateSupportTicketCustomerFeat$1 {
5486
6586
  auth_role: this.session.user.user_type,
5487
6587
  auth_username: this.session.user.username
5488
6588
  });
5489
- 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)));
5490
6591
  }
5491
6592
  };
5492
6593
  UpdateSupportTicketCustomerFeat = __decorate([
5493
6594
  injectable(),
5494
6595
  __decorateParam(0, injectSession()),
5495
6596
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5496
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
6597
+ __decorateParam(2, inject(USER_TOKENS.IUserDisplayLookup)),
6598
+ __decorateParam(3, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5497
6599
  ], UpdateSupportTicketCustomerFeat);
5498
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
+
5499
6675
  //#endregion
5500
6676
  //#region src/slices/support_ticket/features/staff/approve_support_ticket_feat.ts
5501
6677
  let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5502
- constructor(repo, creditService, create_record_version, session) {
6678
+ constructor(repo, notificationService, creditService, create_record_version, session, userDisplayLookup) {
5503
6679
  this.repo = repo;
6680
+ this.notificationService = notificationService;
5504
6681
  this.creditService = creditService;
5505
6682
  this.create_record_version = create_record_version;
5506
6683
  this.session = session;
6684
+ this.userDisplayLookup = userDisplayLookup;
5507
6685
  }
5508
6686
  /**
5509
6687
  * Approves a support_ticket item, deducts credits, and locks it.
@@ -5519,15 +6697,18 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5519
6697
  * @returns The updated support_ticket with full staff view.
5520
6698
  */
5521
6699
  async execute(args) {
5522
- const { id } = args;
6700
+ const { id, credit_value: creditValueFromArgs } = args;
5523
6701
  const support_ticket = await this.repo.read(id);
5524
- if (!support_ticket) throw new Error(`Support Ticket with ID ${id} not found`);
5525
- if (support_ticket.approval_status !== "PENDING") throw new Error("Support Ticket must be in PENDING status to be approved.");
5526
- if (!support_ticket.credit_value) throw new Error("Support Ticket must have an estimated credit cost to be approved.");
5527
- 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.");
5528
- const creditAmount = parseFloat(support_ticket.credit_value);
5529
- if (isNaN(creditAmount)) throw new Error("Invalid credit value amount.");
5530
- 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.");
5531
6712
  const lockedAt = (/* @__PURE__ */ new Date()).toISOString();
5532
6713
  const changed_props = getChangedProperties(support_ticket, {
5533
6714
  ...support_ticket,
@@ -5552,25 +6733,101 @@ let ApproveSupportTicketFeat = class ApproveSupportTicketFeat$1 {
5552
6733
  dev_lifecycle: "BACKLOG",
5553
6734
  locked_approval_at: lockedAt
5554
6735
  });
5555
- if (!updatedSupportTicket) throw new Error("Failed to approve support_ticket.");
5556
- 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)));
5557
6740
  }
5558
6741
  };
5559
6742
  ApproveSupportTicketFeat = __decorate([
5560
6743
  injectable(),
5561
6744
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5562
- __decorateParam(1, inject(CREDIT_SERVICE_TOKEN)),
5563
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5564
- __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))
5565
6750
  ], ApproveSupportTicketFeat);
5566
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
+
5567
6823
  //#endregion
5568
6824
  //#region src/slices/support_ticket/features/staff/cancel_internal_task_feat.ts
5569
6825
  let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5570
- constructor(repo, create_record_version, session) {
6826
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5571
6827
  this.repo = repo;
5572
6828
  this.create_record_version = create_record_version;
5573
6829
  this.session = session;
6830
+ this.userDisplayLookup = userDisplayLookup;
5574
6831
  }
5575
6832
  /**
5576
6833
  * Cancels an internal task by setting dev_lifecycle to CANCELLED.
@@ -5615,23 +6872,26 @@ let CancelInternalTaskFeat = class CancelInternalTaskFeat$1 {
5615
6872
  completed_at: completedAt
5616
6873
  });
5617
6874
  if (!updatedSupportTicket) throw new Error("Failed to cancel internal task.");
5618
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6875
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6876
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5619
6877
  }
5620
6878
  };
5621
6879
  CancelInternalTaskFeat = __decorate([
5622
6880
  injectable(),
5623
6881
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5624
6882
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5625
- __decorateParam(2, injectSession())
6883
+ __decorateParam(2, injectSession()),
6884
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5626
6885
  ], CancelInternalTaskFeat);
5627
6886
 
5628
6887
  //#endregion
5629
6888
  //#region src/slices/support_ticket/features/staff/complete_support_ticket_feat.ts
5630
6889
  let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5631
- constructor(repo, create_record_version, session) {
6890
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5632
6891
  this.repo = repo;
5633
6892
  this.create_record_version = create_record_version;
5634
6893
  this.session = session;
6894
+ this.userDisplayLookup = userDisplayLookup;
5635
6895
  }
5636
6896
  /**
5637
6897
  * Completes a support_ticket item by setting it to DEPLOYED with delivered value.
@@ -5677,23 +6937,26 @@ let CompleteSupportTicketFeat = class CompleteSupportTicketFeat$1 {
5677
6937
  completed_at: completedAt
5678
6938
  });
5679
6939
  if (!updatedSupportTicket) throw new Error("Failed to complete support_ticket.");
5680
- return toStaffSupportTicketReadDto(updatedSupportTicket);
6940
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
6941
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5681
6942
  }
5682
6943
  };
5683
6944
  CompleteSupportTicketFeat = __decorate([
5684
6945
  injectable(),
5685
6946
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5686
6947
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5687
- __decorateParam(2, injectSession())
6948
+ __decorateParam(2, injectSession()),
6949
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5688
6950
  ], CompleteSupportTicketFeat);
5689
6951
 
5690
6952
  //#endregion
5691
6953
  //#region src/slices/support_ticket/features/staff/convert_to_customer_feat.ts
5692
6954
  let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5693
- constructor(session, support_ticketRepo, create_record_version) {
6955
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5694
6956
  this.session = session;
5695
6957
  this.support_ticketRepo = support_ticketRepo;
5696
6958
  this.create_record_version = create_record_version;
6959
+ this.userDisplayLookup = userDisplayLookup;
5697
6960
  }
5698
6961
  async execute(support_ticket_id) {
5699
6962
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5704,7 +6967,7 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5704
6967
  approval_status: "PENDING",
5705
6968
  dev_lifecycle: null,
5706
6969
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5707
- updated_by: this.session.user.email
6970
+ updated_by: this.session.user.userId
5708
6971
  };
5709
6972
  const updated = await this.support_ticketRepo.update(updateEntity);
5710
6973
  await this.create_record_version.execute({
@@ -5718,23 +6981,26 @@ let ConvertToCustomerFeat = class ConvertToCustomerFeat$1 {
5718
6981
  auth_role: this.session.user.user_type,
5719
6982
  auth_username: this.session.user.username
5720
6983
  });
5721
- return toStaffSupportTicketReadDto(updated);
6984
+ const dto = toStaffSupportTicketReadDto(updated);
6985
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5722
6986
  }
5723
6987
  };
5724
6988
  ConvertToCustomerFeat = __decorate([
5725
6989
  injectable(),
5726
6990
  __decorateParam(0, injectSession()),
5727
6991
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5728
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
6992
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6993
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5729
6994
  ], ConvertToCustomerFeat);
5730
6995
 
5731
6996
  //#endregion
5732
6997
  //#region src/slices/support_ticket/features/staff/convert_to_internal_feat.ts
5733
6998
  let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5734
- constructor(session, support_ticketRepo, create_record_version) {
6999
+ constructor(session, support_ticketRepo, create_record_version, userDisplayLookup) {
5735
7000
  this.session = session;
5736
7001
  this.support_ticketRepo = support_ticketRepo;
5737
7002
  this.create_record_version = create_record_version;
7003
+ this.userDisplayLookup = userDisplayLookup;
5738
7004
  }
5739
7005
  async execute(support_ticket_id) {
5740
7006
  const existing = await this.support_ticketRepo.read(support_ticket_id);
@@ -5746,7 +7012,7 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5746
7012
  credit_value: null,
5747
7013
  dev_lifecycle: "BACKLOG",
5748
7014
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
5749
- updated_by: this.session.user.email
7015
+ updated_by: this.session.user.userId
5750
7016
  };
5751
7017
  const updated = await this.support_ticketRepo.update(updateEntity);
5752
7018
  await this.create_record_version.execute({
@@ -5760,26 +7026,31 @@ let ConvertToInternalFeat = class ConvertToInternalFeat$1 {
5760
7026
  auth_role: this.session.user.user_type,
5761
7027
  auth_username: this.session.user.username
5762
7028
  });
5763
- return toStaffSupportTicketReadDto(updated);
7029
+ const dto = toStaffSupportTicketReadDto(updated);
7030
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5764
7031
  }
5765
7032
  };
5766
7033
  ConvertToInternalFeat = __decorate([
5767
7034
  injectable(),
5768
7035
  __decorateParam(0, injectSession()),
5769
7036
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5770
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
7037
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
7038
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5771
7039
  ], ConvertToInternalFeat);
5772
7040
 
5773
7041
  //#endregion
5774
7042
  //#region src/slices/support_ticket/features/staff/create_support_ticket_admin_feat.ts
5775
7043
  let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5776
- 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) {
5777
7045
  this.session = session;
5778
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;
5779
7052
  this.create_record_version = create_record_version;
5780
7053
  this.appSettingsRepo = appSettingsRepo;
5781
- this.emailService = emailService;
5782
- this.env = env;
5783
7054
  this.logger = logger$1;
5784
7055
  }
5785
7056
  /**
@@ -5792,10 +7063,8 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5792
7063
  title: input.title || "",
5793
7064
  description: input.description || "",
5794
7065
  type: input.type || "FEATURE_REQUEST",
5795
- priority: input.priority || "MEDIUM",
7066
+ priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,
5796
7067
  approval_status: input.is_internal ? "INTERNAL" : "PENDING",
5797
- requester_email: this.session.user.email,
5798
- requester_name: this.session.user.username,
5799
7068
  dev_lifecycle: input.dev_lifecycle || null,
5800
7069
  credit_value: input.is_internal ? null : formatCreditValue(input.credit_value),
5801
7070
  delivered_value: input.delivered_value || null,
@@ -5810,6 +7079,12 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5810
7079
  };
5811
7080
  const support_ticketCreated = await this.support_ticketRepo.create(support_ticketEntity);
5812
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;
5813
7088
  await this.support_ticketRepo.update(support_ticketCreated);
5814
7089
  await this.create_record_version.execute({
5815
7090
  record_id: support_ticketCreated.id,
@@ -5821,58 +7096,30 @@ let CreateSupportTicketAdminFeat = class CreateSupportTicketAdminFeat$1 {
5821
7096
  auth_role: this.session.user.user_type,
5822
7097
  auth_username: this.session.user.username
5823
7098
  });
5824
- if (!input.is_internal) await this.sendNotificationEmails(support_ticketCreated);
5825
- 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)));
5826
7106
  }
5827
7107
  /**
5828
- * Send email notifications to staff members
7108
+ * Auto-add requester (created_by) as follower
5829
7109
  */
5830
- async sendNotificationEmails(ticket) {
7110
+ async addRequesterAsFollower(ticket) {
5831
7111
  try {
5832
- const emailsSetting = await this.appSettingsRepo.readSetting(AppSettingKey.SUPPORT_TICKET_NOTIFICATION_EMAILS);
5833
- if (!emailsSetting?.value) {
5834
- this.logger.debug("No notification emails configured for support tickets");
5835
- return;
5836
- }
5837
- const emails = emailsSetting.value;
5838
- if (!Array.isArray(emails) || emails.length === 0) {
5839
- this.logger.debug("No valid notification emails found");
5840
- return;
5841
- }
5842
- const ticketUrl = `${this.env.WEBSITE_URL}/support/${ticket.id}`;
5843
- const subject = `New Support Ticket Created: ${ticket.title}`;
5844
- const bodyText = `
5845
- A new support ticket has been created:
5846
-
5847
- Ticket #${ticket.display_id || ticket.id}
5848
- Title: ${ticket.title}
5849
- Type: ${ticket.type}
5850
- Priority: ${ticket.priority}
5851
- Requester: ${ticket.requester_name} (${ticket.requester_email})
5852
-
5853
- Description:
5854
- ${ticket.description}
5855
-
5856
- View ticket: ${ticketUrl}
5857
- `.trim();
5858
- const bodyHtml = `
5859
- <h2>New Support Ticket Created</h2>
5860
- <p>A new support ticket has been created:</p>
5861
- <ul>
5862
- <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>
5863
- <li><strong>Title:</strong> ${ticket.title}</li>
5864
- <li><strong>Type:</strong> ${ticket.type}</li>
5865
- <li><strong>Priority:</strong> ${ticket.priority}</li>
5866
- <li><strong>Requester:</strong> ${ticket.requester_name} (${ticket.requester_email})</li>
5867
- </ul>
5868
- <p><strong>Description:</strong></p>
5869
- <p>${ticket.description}</p>
5870
- <p><a href="${ticketUrl}">View Ticket</a></p>
5871
- `.trim();
5872
- await this.emailService.sendBulkEmail(emails, subject, bodyText, bodyHtml);
5873
- 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
+ });
5874
7121
  } catch (error) {
5875
- this.logger.error("Failed to send support ticket notification emails", { error });
7122
+ this.logger.error("Failed to add requester as follower", { error });
5876
7123
  }
5877
7124
  }
5878
7125
  };
@@ -5880,11 +7127,14 @@ CreateSupportTicketAdminFeat = __decorate([
5880
7127
  injectable(),
5881
7128
  __decorateParam(0, injectSession()),
5882
7129
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5883
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
5884
- __decorateParam(3, inject(TOKENS.IAppSettingsRepo)),
5885
- __decorateParam(4, inject(TOKENS.IEmailService)),
5886
- __decorateParam(5, inject(TOKENS.ENV)),
5887
- __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))
5888
7138
  ], CreateSupportTicketAdminFeat);
5889
7139
 
5890
7140
  //#endregion
@@ -5931,13 +7181,93 @@ DeleteSupportTicketFeat = __decorate([
5931
7181
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
5932
7182
  ], DeleteSupportTicketFeat);
5933
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
+
5934
7263
  //#endregion
5935
7264
  //#region src/slices/support_ticket/features/staff/get_support_ticket_admin_feat.ts
5936
7265
  let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5937
- constructor(session, support_ticketRepo, appSettingsRepo) {
7266
+ constructor(session, support_ticketRepo, appSettingsRepo, userDisplayLookup) {
5938
7267
  this.session = session;
5939
7268
  this.support_ticketRepo = support_ticketRepo;
5940
7269
  this.appSettingsRepo = appSettingsRepo;
7270
+ this.userDisplayLookup = userDisplayLookup;
5941
7271
  }
5942
7272
  async execute(id) {
5943
7273
  let support_ticket = await this.support_ticketRepo.read(id);
@@ -5951,39 +7281,110 @@ let GetSupportTicketAdminFeature = class GetSupportTicketAdminFeature$1 {
5951
7281
  updated_by: this.session.user.userId
5952
7282
  });
5953
7283
  }
5954
- return toStaffSupportTicketReadDto(support_ticket);
7284
+ const dto = toStaffSupportTicketReadDto(support_ticket);
7285
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
5955
7286
  }
5956
7287
  };
5957
7288
  GetSupportTicketAdminFeature = __decorate([
5958
7289
  injectable(),
5959
7290
  __decorateParam(0, injectSession()),
5960
7291
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
5961
- __decorateParam(2, inject(TOKENS.IAppSettingsRepo))
7292
+ __decorateParam(2, inject(TOKENS.IAppSettingsRepo)),
7293
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
5962
7294
  ], GetSupportTicketAdminFeature);
5963
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
+
5964
7318
  //#endregion
5965
7319
  //#region src/slices/support_ticket/features/staff/get_support_tickets_admin_feat.ts
5966
7320
  let GetSupportTicketsAdminFeature = class GetSupportTicketsAdminFeature$1 {
5967
- constructor(support_ticketRepo) {
7321
+ constructor(support_ticketRepo, userDisplayLookup) {
5968
7322
  this.support_ticketRepo = support_ticketRepo;
7323
+ this.userDisplayLookup = userDisplayLookup;
5969
7324
  }
5970
7325
  async execute(filters) {
5971
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);
5972
7330
  return {
5973
- items: result.items.map(toStaffSupportTicketReadDto),
7331
+ items: dtos.map((t) => enrichStaffTicket(t, displayMap)),
5974
7332
  pageInfo: result.pageInfo
5975
7333
  };
5976
7334
  }
5977
7335
  };
5978
- 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);
5979
7379
 
5980
7380
  //#endregion
5981
7381
  //#region src/slices/support_ticket/features/staff/reactivate_internal_task_feat.ts
5982
7382
  let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
5983
- constructor(repo, create_record_version, session) {
7383
+ constructor(repo, create_record_version, session, userDisplayLookup) {
5984
7384
  this.repo = repo;
5985
7385
  this.create_record_version = create_record_version;
5986
7386
  this.session = session;
7387
+ this.userDisplayLookup = userDisplayLookup;
5987
7388
  }
5988
7389
  /**
5989
7390
  * Reactivates a terminal internal task.
@@ -6031,23 +7432,27 @@ let ReactivateInternalTaskFeat = class ReactivateInternalTaskFeat$1 {
6031
7432
  completed_at: null
6032
7433
  });
6033
7434
  if (!updatedSupportTicket) throw new Error("Failed to reactivate internal task.");
6034
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7435
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7436
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6035
7437
  }
6036
7438
  };
6037
7439
  ReactivateInternalTaskFeat = __decorate([
6038
7440
  injectable(),
6039
7441
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6040
7442
  __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6041
- __decorateParam(2, injectSession())
7443
+ __decorateParam(2, injectSession()),
7444
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6042
7445
  ], ReactivateInternalTaskFeat);
6043
7446
 
6044
7447
  //#endregion
6045
7448
  //#region src/slices/support_ticket/features/staff/reject_support_ticket_feat.ts
6046
7449
  let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6047
- constructor(repo, create_record_version, session) {
7450
+ constructor(repo, notificationService, create_record_version, session, userDisplayLookup) {
6048
7451
  this.repo = repo;
7452
+ this.notificationService = notificationService;
6049
7453
  this.create_record_version = create_record_version;
6050
7454
  this.session = session;
7455
+ this.userDisplayLookup = userDisplayLookup;
6051
7456
  }
6052
7457
  /**
6053
7458
  * Rejects a support_ticket item and locks it.
@@ -6088,24 +7493,41 @@ let RejectSupportTicketFeat = class RejectSupportTicketFeat$1 {
6088
7493
  locked_approval_at: lockedAt
6089
7494
  });
6090
7495
  if (!updatedSupportTicket) throw new Error("Failed to reject support_ticket.");
6091
- 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)));
6092
7499
  }
6093
7500
  };
6094
7501
  RejectSupportTicketFeat = __decorate([
6095
7502
  injectable(),
6096
7503
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6097
- __decorateParam(1, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6098
- __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))
6099
7508
  ], RejectSupportTicketFeat);
6100
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
+
6101
7522
  //#endregion
6102
7523
  //#region src/slices/support_ticket/features/staff/revert_support_ticket_feat.ts
6103
7524
  let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6104
- constructor(repo, creditService, create_record_version, session) {
7525
+ constructor(repo, creditService, create_record_version, session, userDisplayLookup) {
6105
7526
  this.repo = repo;
6106
7527
  this.creditService = creditService;
6107
7528
  this.create_record_version = create_record_version;
6108
7529
  this.session = session;
7530
+ this.userDisplayLookup = userDisplayLookup;
6109
7531
  }
6110
7532
  /**
6111
7533
  * Reverts a support_ticket item back to PENDING status and refunds credits.
@@ -6154,7 +7576,8 @@ let RevertSupportTicketFeat = class RevertSupportTicketFeat$1 {
6154
7576
  locked_approval_at: null
6155
7577
  });
6156
7578
  if (!updatedSupportTicket) throw new Error("Failed to revert support_ticket.");
6157
- return toStaffSupportTicketReadDto(updatedSupportTicket);
7579
+ const dto = toStaffSupportTicketReadDto(updatedSupportTicket);
7580
+ return enrichStaffTicket(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromStaffTicket(dto)));
6158
7581
  }
6159
7582
  };
6160
7583
  RevertSupportTicketFeat = __decorate([
@@ -6162,7 +7585,8 @@ RevertSupportTicketFeat = __decorate([
6162
7585
  __decorateParam(0, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6163
7586
  __decorateParam(1, inject(CREDIT_SERVICE_TOKEN)),
6164
7587
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6165
- __decorateParam(3, injectSession())
7588
+ __decorateParam(3, injectSession()),
7589
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6166
7590
  ], RevertSupportTicketFeat);
6167
7591
 
6168
7592
  //#endregion
@@ -6171,17 +7595,18 @@ RevertSupportTicketFeat = __decorate([
6171
7595
  * Validate business rules for staff updates based on permissions matrix
6172
7596
  */
6173
7597
  const validateBusinessRules = (input, existing) => {
7598
+ if (existing.archived_at) throw new BusinessError("Cannot edit an archived support ticket.");
6174
7599
  const currentApproval = existing.approval_status;
6175
7600
  const currentDevLifecycle = existing.dev_lifecycle;
6176
7601
  if (input.dev_lifecycle !== void 0 && input.dev_lifecycle !== currentDevLifecycle) {
6177
- 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");
6178
7603
  }
6179
7604
  if (input.credit_value !== void 0 && input.credit_value !== existing.credit_value) {
6180
- if (currentApproval === "INTERNAL") throw new Error("Internal support_ticket cannot have credit values");
6181
- 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");
6182
7607
  }
6183
7608
  if (input.delivered_value !== void 0 && input.delivered_value !== existing.delivered_value) {
6184
- 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");
6185
7610
  }
6186
7611
  const lockedDevStages = [
6187
7612
  "DEVELOPMENT",
@@ -6189,21 +7614,22 @@ const validateBusinessRules = (input, existing) => {
6189
7614
  "TESTING",
6190
7615
  "STAGING",
6191
7616
  "PO_APPROVAL",
7617
+ "VERIFICATION",
6192
7618
  "DEPLOYED"
6193
7619
  ];
6194
7620
  if (input.start_at !== void 0 && input.start_at !== existing.start_at) {
6195
- if (currentApproval === "REJECTED") throw new Error("Cannot edit start date when support_ticket is REJECTED");
6196
- 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)");
6197
7623
  }
6198
7624
  if (input.target_at !== void 0 && input.target_at !== existing.target_at) {
6199
- if (currentApproval === "REJECTED") throw new Error("Cannot edit target date when support_ticket is REJECTED");
6200
- 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)");
6201
7627
  }
6202
7628
  if (input.completed_at !== void 0 && input.completed_at !== existing.completed_at) {
6203
- 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");
6204
7630
  }
6205
7631
  if (currentApproval === "REJECTED") {
6206
- 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");
6207
7633
  }
6208
7634
  };
6209
7635
 
@@ -6216,24 +7642,26 @@ function shouldUpdateCreditsSetAt(oldValue, newValue) {
6216
7642
  return isCreditValueEmpty(oldValue) !== isCreditValueEmpty(newValue);
6217
7643
  }
6218
7644
  let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6219
- constructor(session, support_ticketRepo, create_record_version) {
7645
+ constructor(session, support_ticketRepo, create_record_version, notificationService, userDisplayLookup) {
6220
7646
  this.session = session;
6221
7647
  this.support_ticketRepo = support_ticketRepo;
6222
7648
  this.create_record_version = create_record_version;
7649
+ this.notificationService = notificationService;
7650
+ this.userDisplayLookup = userDisplayLookup;
6223
7651
  }
6224
7652
  /**
6225
7653
  * Update support_ticket - comprehensive update for staff
6226
7654
  */
6227
7655
  async execute(input) {
6228
7656
  const support_ticket = await this.support_ticketRepo.read(input.id);
6229
- if (!support_ticket) throw new Error("SupportTicket not found");
7657
+ if (!support_ticket) throw new BusinessError("SupportTicket not found");
6230
7658
  validateBusinessRules(input, support_ticket);
6231
7659
  const isoTime = (/* @__PURE__ */ new Date()).toISOString();
6232
7660
  const frEntity = {
6233
7661
  ...support_ticket,
6234
7662
  id: input.id,
6235
7663
  updated_at: isoTime,
6236
- updated_by: this.session.user.email
7664
+ updated_by: this.session.user.userId
6237
7665
  };
6238
7666
  if (input.title !== void 0 && input.title !== null) frEntity.title = input.title;
6239
7667
  if (input.description !== void 0 && input.description !== null) frEntity.description = input.description;
@@ -6248,6 +7676,7 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6248
7676
  if (input.start_at !== void 0) frEntity.start_at = input.start_at ?? void 0;
6249
7677
  if (input.target_at !== void 0) frEntity.target_at = input.target_at ?? void 0;
6250
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;
6251
7680
  const changed_props = getChangedProperties(support_ticket, frEntity);
6252
7681
  if (Object.keys(changed_props).length > 0) await this.create_record_version.execute({
6253
7682
  record_id: frEntity.id,
@@ -6260,14 +7689,22 @@ let UpdateSupportTicketAdminFeat = class UpdateSupportTicketAdminFeat$1 {
6260
7689
  auth_role: this.session.user.user_type,
6261
7690
  auth_username: this.session.user.username
6262
7691
  });
6263
- 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)));
6264
7699
  }
6265
7700
  };
6266
7701
  UpdateSupportTicketAdminFeat = __decorate([
6267
7702
  injectable(),
6268
7703
  __decorateParam(0, injectSession()),
6269
7704
  __decorateParam(1, inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)),
6270
- __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))
6271
7708
  ], UpdateSupportTicketAdminFeat);
6272
7709
 
6273
7710
  //#endregion
@@ -6277,12 +7714,16 @@ UpdateSupportTicketAdminFeat = __decorate([
6277
7714
  */
6278
7715
  function registerSupportTicketDependencies() {
6279
7716
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketRepo, SupportTicketRepo);
7717
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService, SupportTicketNotificationService);
7718
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService, SupportTicketTriageService);
6280
7719
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICreateSupportTicketFeature, CreateSupportTicketFeat);
6281
7720
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketCustomerFeature, UpdateSupportTicketCustomerFeat);
6282
7721
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketCustomerFeature, GetSupportTicketCustomerFeature);
6283
7722
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketsCustomerFeature, GetSupportTicketsCustomerFeature);
7723
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature, CustomerToggleSubscriptionFeat);
6284
7724
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IApproveSupportTicketFeature, ApproveSupportTicketFeat);
6285
7725
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IDeleteSupportTicketFeature, DeleteSupportTicketFeat);
7726
+ container.registerSingleton(SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature, ArchiveSupportTicketFeat);
6286
7727
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRejectSupportTicketFeature, RejectSupportTicketFeat);
6287
7728
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IRevertSupportTicketFeature, RevertSupportTicketFeat);
6288
7729
  container.registerSingleton(SUPPORT_TICKET_TOKENS.ICompleteSupportTicketFeature, CompleteSupportTicketFeat);
@@ -6294,6 +7735,11 @@ function registerSupportTicketDependencies() {
6294
7735
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IUpdateSupportTicketAdminFeature, UpdateSupportTicketAdminFeat);
6295
7736
  container.registerSingleton(SUPPORT_TICKET_TOKENS.IGetSupportTicketAdminFeature, GetSupportTicketAdminFeature);
6296
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);
6297
7743
  }
6298
7744
 
6299
7745
  //#endregion
@@ -6574,14 +8020,34 @@ function mapTeamEntityToDto(entity) {
6574
8020
  };
6575
8021
  }
6576
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
+
6577
8042
  //#endregion
6578
8043
  //#region src/slices/team/features/create_team_feat.ts
6579
8044
  let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6580
- constructor(teamRepo, session, create_record_version, createTeamMemberFeature) {
8045
+ constructor(teamRepo, session, create_record_version, createTeamMemberFeature, userDisplayLookup) {
6581
8046
  this.teamRepo = teamRepo;
6582
8047
  this.session = session;
6583
8048
  this.create_record_version = create_record_version;
6584
8049
  this.createTeamMemberFeature = createTeamMemberFeature;
8050
+ this.userDisplayLookup = userDisplayLookup;
6585
8051
  }
6586
8052
  async execute(input) {
6587
8053
  if (await this.teamRepo.findByUniqueName(input.unique_name || "")) throw new Error("Team with this name already exists");
@@ -6636,7 +8102,8 @@ let CreateTeamFeatureImpl = class CreateTeamFeatureImpl$1 {
6636
8102
  website_address: null,
6637
8103
  time_zone: null
6638
8104
  });
6639
- return mapTeamEntityToDto(teamEntity);
8105
+ const dto = mapTeamEntityToDto(teamEntity);
8106
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6640
8107
  }
6641
8108
  /**
6642
8109
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6650,7 +8117,8 @@ CreateTeamFeatureImpl = __decorate([
6650
8117
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6651
8118
  __decorateParam(1, injectSession()),
6652
8119
  __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
6653
- __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature))
8120
+ __decorateParam(3, inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature)),
8121
+ __decorateParam(4, inject(USER_TOKENS.IUserDisplayLookup))
6654
8122
  ], CreateTeamFeatureImpl);
6655
8123
 
6656
8124
  //#endregion
@@ -6688,16 +8156,20 @@ DeleteTeamFeatureImpl = __decorate([
6688
8156
  //#endregion
6689
8157
  //#region src/slices/team/features/read_all_teams_feat.ts
6690
8158
  let ReadAllTeamsFeatureImpl = class ReadAllTeamsFeatureImpl$1 {
6691
- constructor(teamRepo, session, teamMemberRepo) {
8159
+ constructor(teamRepo, session, teamMemberRepo, userDisplayLookup) {
6692
8160
  this.teamRepo = teamRepo;
6693
8161
  this.session = session;
6694
8162
  this.teamMemberRepo = teamMemberRepo;
8163
+ this.userDisplayLookup = userDisplayLookup;
6695
8164
  }
6696
8165
  async execute(filters) {
6697
8166
  const processedFilters = await this.applyConsumerFiltering(filters);
6698
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);
6699
8171
  return {
6700
- items: result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity)),
8172
+ items: dtos.map((t) => enrichTeam(t, displayMap)),
6701
8173
  pageInfo: result.pageInfo
6702
8174
  };
6703
8175
  }
@@ -6724,30 +8196,38 @@ ReadAllTeamsFeatureImpl = __decorate([
6724
8196
  injectable(),
6725
8197
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6726
8198
  __decorateParam(1, injectSession()),
6727
- __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo))
8199
+ __decorateParam(2, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
8200
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6728
8201
  ], ReadAllTeamsFeatureImpl);
6729
8202
 
6730
8203
  //#endregion
6731
8204
  //#region src/slices/team/features/read_team_feat.ts
6732
8205
  let ReadTeamFeatureImpl = class ReadTeamFeatureImpl$1 {
6733
- constructor(teamRepo) {
8206
+ constructor(teamRepo, userDisplayLookup) {
6734
8207
  this.teamRepo = teamRepo;
8208
+ this.userDisplayLookup = userDisplayLookup;
6735
8209
  }
6736
8210
  async execute(id) {
6737
8211
  const teamEntity = await this.teamRepo.read(id);
6738
8212
  if (!teamEntity) return null;
6739
- return mapTeamEntityToDto(teamEntity);
8213
+ const dto = mapTeamEntityToDto(teamEntity);
8214
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6740
8215
  }
6741
8216
  };
6742
- 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);
6743
8222
 
6744
8223
  //#endregion
6745
8224
  //#region src/slices/team/features/update_team_feat.ts
6746
8225
  let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6747
- constructor(teamRepo, session, create_record_version) {
8226
+ constructor(teamRepo, session, create_record_version, userDisplayLookup) {
6748
8227
  this.teamRepo = teamRepo;
6749
8228
  this.session = session;
6750
8229
  this.create_record_version = create_record_version;
8230
+ this.userDisplayLookup = userDisplayLookup;
6751
8231
  }
6752
8232
  async execute(input) {
6753
8233
  const existingTeam = await this.teamRepo.read(input.id);
@@ -6799,7 +8279,8 @@ let UpdateTeamFeatureImpl = class UpdateTeamFeatureImpl$1 {
6799
8279
  auth_role: this.session.user.user_type,
6800
8280
  auth_username: this.session.user.username
6801
8281
  });
6802
- return mapTeamEntityToDto(teamEntity);
8282
+ const dto = mapTeamEntityToDto(teamEntity);
8283
+ return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
6803
8284
  }
6804
8285
  /**
6805
8286
  * Compute path from unique name (e.g., "My Team" -> "my-team")
@@ -6812,7 +8293,8 @@ UpdateTeamFeatureImpl = __decorate([
6812
8293
  injectable(),
6813
8294
  __decorateParam(0, inject(TEAM_TOKENS.ITeamRepository)),
6814
8295
  __decorateParam(1, injectSession()),
6815
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8296
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8297
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
6816
8298
  ], UpdateTeamFeatureImpl);
6817
8299
 
6818
8300
  //#endregion
@@ -6928,15 +8410,15 @@ const teamMemberFields = {
6928
8410
  sortable: true
6929
8411
  }
6930
8412
  };
6931
- const teamMemberColumnMap = deriveColumnMap$1(teamMemberFields);
6932
- const buildFieldFilters = createFilterBuilder$1({ fieldRegistry: teamMemberFields });
8413
+ const teamMemberColumnMap = deriveColumnMap(teamMemberFields);
8414
+ const buildFieldFilters = createFilterBuilder({ fieldRegistry: teamMemberFields });
6933
8415
  /**
6934
8416
  * Build team member query conditions from filters
6935
8417
  */
6936
8418
  function buildTeamMemberQuery(filters) {
6937
8419
  const softDelete = isNull(team_member_table.deleted_at);
6938
8420
  const fields = buildFieldFilters(filters).conditions;
6939
- const search = searchOrCondition$1(filters?.search?.query, teamMemberFields, filters?.search?.searchableFields);
8421
+ const search = searchOrCondition(filters?.search?.query, teamMemberFields, filters?.search?.searchableFields);
6940
8422
  return {
6941
8423
  conditions: [
6942
8424
  softDelete,
@@ -7030,13 +8512,33 @@ let TeamMemberRepo = class TeamMemberRepo$1 {
7030
8512
  };
7031
8513
  TeamMemberRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], TeamMemberRepo);
7032
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
+
7033
8534
  //#endregion
7034
8535
  //#region src/slices/team_member/features/create_team_member_feat.ts
7035
8536
  let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7036
- constructor(session, teamMemberRepo, create_record_version) {
8537
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7037
8538
  this.session = session;
7038
8539
  this.teamMemberRepo = teamMemberRepo;
7039
8540
  this.create_record_version = create_record_version;
8541
+ this.userDisplayLookup = userDisplayLookup;
7040
8542
  }
7041
8543
  async execute(input) {
7042
8544
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7068,14 +8570,16 @@ let CreateTeamMemberFeat = class CreateTeamMemberFeat$1 {
7068
8570
  auth_role: this.session.user.user_type,
7069
8571
  auth_username: this.session.user.username
7070
8572
  });
7071
- return teamMemberCreated;
8573
+ const dto = teamMemberCreated;
8574
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7072
8575
  }
7073
8576
  };
7074
8577
  CreateTeamMemberFeat = __decorate([
7075
8578
  injectable(),
7076
8579
  __decorateParam(0, injectSession()),
7077
8580
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7078
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8581
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8582
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7079
8583
  ], CreateTeamMemberFeat);
7080
8584
 
7081
8585
  //#endregion
@@ -7129,14 +8633,27 @@ DeleteTeamMemberFeat = __decorate([
7129
8633
  //#endregion
7130
8634
  //#region src/slices/team_member/features/get_team_members_feat.ts
7131
8635
  let GetTeamMembersFeat = class GetTeamMembersFeat$1 {
7132
- constructor(teamMemberRepo) {
8636
+ constructor(teamMemberRepo, userDisplayLookup) {
7133
8637
  this.teamMemberRepo = teamMemberRepo;
8638
+ this.userDisplayLookup = userDisplayLookup;
7134
8639
  }
7135
8640
  async execute(filters) {
7136
- 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
+ };
7137
8650
  }
7138
8651
  };
7139
- 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);
7140
8657
 
7141
8658
  //#endregion
7142
8659
  //#region src/slices/team_member/features/get_user_team_members_feat.ts
@@ -7203,10 +8720,11 @@ GetUserTeamsFeat = __decorate([
7203
8720
  //#endregion
7204
8721
  //#region src/slices/team_member/features/update_team_member_feat.ts
7205
8722
  let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7206
- constructor(session, teamMemberRepo, create_record_version) {
8723
+ constructor(session, teamMemberRepo, create_record_version, userDisplayLookup) {
7207
8724
  this.session = session;
7208
8725
  this.teamMemberRepo = teamMemberRepo;
7209
8726
  this.create_record_version = create_record_version;
8727
+ this.userDisplayLookup = userDisplayLookup;
7210
8728
  }
7211
8729
  async execute(input) {
7212
8730
  const existingTeamMember = await this.teamMemberRepo.getById(input.id);
@@ -7257,14 +8775,16 @@ let UpdateTeamMemberFeat = class UpdateTeamMemberFeat$1 {
7257
8775
  auth_role: this.session.user.user_type,
7258
8776
  auth_username: this.session.user.username
7259
8777
  });
7260
- return teamMemberUpdated;
8778
+ const dto = teamMemberUpdated;
8779
+ return enrichTeamMember(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeamMember(dto)));
7261
8780
  }
7262
8781
  };
7263
8782
  UpdateTeamMemberFeat = __decorate([
7264
8783
  injectable(),
7265
8784
  __decorateParam(0, injectSession()),
7266
8785
  __decorateParam(1, inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)),
7267
- __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion))
8786
+ __decorateParam(2, inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)),
8787
+ __decorateParam(3, inject(USER_TOKENS.IUserDisplayLookup))
7268
8788
  ], UpdateTeamMemberFeat);
7269
8789
 
7270
8790
  //#endregion
@@ -7328,6 +8848,21 @@ let UserRepo = class UserRepo$1 {
7328
8848
  this.logger.perf(`UserRepo.read_user_by_email: ${queryTime.toFixed(2)}ms for email ${email}`);
7329
8849
  return result[0];
7330
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
+ }
7331
8866
  async read_user_by_username(username) {
7332
8867
  const { ...rest } = getTableColumns(user_table);
7333
8868
  return (await this.router.queryAll((db) => db.select({ ...rest }).from(user_table).where(eq(user_table.username, username)).limit(1)))[0];
@@ -7437,6 +8972,26 @@ CreateUser = __decorate([
7437
8972
  __decorateParam(3, inject(TOKENS.LOGGER))
7438
8973
  ], CreateUser);
7439
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
+
7440
8995
  //#endregion
7441
8996
  //#region src/slices/user/features/delete_user.ts
7442
8997
  let DeleteUser = class DeleteUser$1 {
@@ -7514,6 +9069,18 @@ let GetUserFeat = class GetUserFeat$1 {
7514
9069
  };
7515
9070
  GetUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS.IUsersRepo))], GetUserFeat);
7516
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
+
7517
9084
  //#endregion
7518
9085
  //#region src/slices/user/features/get_users_for_selection_feat.ts
7519
9086
  let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
@@ -7521,7 +9088,7 @@ let GetUsersForSelectionFeat = class GetUsersForSelectionFeat$1 {
7521
9088
  this.repo = repo;
7522
9089
  }
7523
9090
  async execute() {
7524
- 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) => ({
7525
9092
  id: user.id,
7526
9093
  email: user.email
7527
9094
  }));
@@ -7665,6 +9232,7 @@ UpdateUserFeat = __decorate([injectable(), __decorateParam(0, inject(USER_TOKENS
7665
9232
  //#region src/slices/user/user_container.ts
7666
9233
  function registerUserContainer() {
7667
9234
  container.registerSingleton(USER_TOKENS.IUsersRepo, UserRepo);
9235
+ container.registerSingleton(USER_TOKENS.IUserDisplayLookup, UserDisplayLookupFeat);
7668
9236
  container.registerSingleton(USER_TOKENS.IReadAllUsers, ReadAllUsers);
7669
9237
  container.registerSingleton(USER_TOKENS.ISignUpUser, SignUpUser);
7670
9238
  container.registerSingleton(USER_TOKENS.IDeleteUser, DeleteUser);
@@ -7675,6 +9243,7 @@ function registerUserContainer() {
7675
9243
  container.registerSingleton(USER_TOKENS.IGetUserFeature, GetUserFeat);
7676
9244
  container.registerSingleton(USER_TOKENS.IUpdateUserFeature, UpdateUserFeat);
7677
9245
  container.registerSingleton(USER_TOKENS.IGetUsersForSelectionFeature, GetUsersForSelectionFeat);
9246
+ container.registerSingleton(USER_TOKENS.IGetTriageUsersFeature, GetTriageUsersFeat);
7678
9247
  }
7679
9248
 
7680
9249
  //#endregion
@@ -7694,6 +9263,11 @@ let UserProfileRepo = class UserProfileRepo$1 {
7694
9263
  const { ...rest } = getTableColumns(user_profile_table);
7695
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];
7696
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
+ }
7697
9271
  async update_user_profile(user_profileRecord) {
7698
9272
  user_profileRecord.updated_at = (/* @__PURE__ */ new Date()).toISOString();
7699
9273
  const { created_at, ...rest } = user_profileRecord;
@@ -7702,16 +9276,6 @@ let UserProfileRepo = class UserProfileRepo$1 {
7702
9276
  };
7703
9277
  UserProfileRepo = __decorate([injectable(), __decorateParam(0, inject(TOKENS.IDatabaseRouter))], UserProfileRepo);
7704
9278
 
7705
- //#endregion
7706
- //#region src/slices/user_profile/user_profile_interfaces.ts
7707
- const USER_PROFILE_TOKENS = {
7708
- IUserProfilesRepo: Symbol("IUserProfilesRepo"),
7709
- IReadUserProfilesByUser: Symbol("IReadUserProfilesByUser"),
7710
- IReadUserProfile: Symbol("IReadUserProfile"),
7711
- ICreateUserProfile: Symbol("ICreateUserProfile"),
7712
- IUpdateUserProfile: Symbol("IUpdateUserProfile")
7713
- };
7714
-
7715
9279
  //#endregion
7716
9280
  //#region src/slices/user_profile/features/read_user_profile.ts
7717
9281
  let ReadUserProfile = class ReadUserProfile$1 {
@@ -8274,14 +9838,17 @@ function registerCoreContainer() {
8274
9838
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.PrefixRegistry, DisplayIdPrefixRegistry);
8275
9839
  container.registerSingleton(DISPLAY_ID_PREFIX_TOKENS.IDisplayIdPrefixService, DisplayIdPrefixService);
8276
9840
  registerPasswordResetContainer();
9841
+ registerRecordSubscriberDependencies();
8277
9842
  registerRecordVersionContainer();
9843
+ registerSavedFilterContainer();
9844
+ registerSupportStaffDependencies();
8278
9845
  registerUserContainer();
8279
9846
  registerUserProfileContainer();
8280
9847
  registerUserSessionContainer();
8281
9848
  registerCustomerDependencies();
8282
9849
  registerSupportTicketDependencies();
8283
9850
  registerAttachmentContainer();
8284
- registerAppSettingsContainer();
9851
+ /* @__PURE__ */ registerAppSettingsContainer();
8285
9852
  registerNoteContainer();
8286
9853
  registerTeamDependencies();
8287
9854
  registerTeamMemberContainer();
@@ -8318,37 +9885,15 @@ function createRequestContainer(config, additionalRegistrations) {
8318
9885
  //#endregion
8319
9886
  //#region src/slices/app_settings/app-settings-api.server.ts
8320
9887
  var AppSettingsApiServer = class extends RpcTarget {
8321
- constructor(container$1) {
9888
+ constructor(_container) {
8322
9889
  super();
8323
- this.container = container$1;
8324
- }
8325
- async getNotificationEmails() {
8326
- return rpcMethodPartial({
8327
- auth: "admin",
8328
- container: this.container,
8329
- context: "AppSettingsApiServer.getNotificationEmails",
8330
- input: void 0,
8331
- outputSchema: ReadNotificationEmailsSchema,
8332
- execute: async (_, container$1) => {
8333
- return { emails: (await container$1.resolve(APP_SETTINGS_TOKENS.IGetNotificationEmailsFeature).execute()).emails.map((e) => ({ email: e.email })) };
8334
- }
8335
- });
8336
- }
8337
- async updateNotificationEmails(input) {
8338
- return rpcMethod({
8339
- auth: "admin",
8340
- container: this.container,
8341
- context: "AppSettingsApiServer.updateNotificationEmails",
8342
- input,
8343
- inputSchema: UpdateNotificationEmailsSchema,
8344
- outputSchema: UpdateNotificationEmailsSchema,
8345
- execute: async (input$1, container$1) => {
8346
- return await container$1.resolve(APP_SETTINGS_TOKENS.IUpdateNotificationEmailsFeature).execute(input$1);
8347
- }
8348
- });
8349
9890
  }
8350
9891
  };
8351
9892
 
9893
+ //#endregion
9894
+ //#region src/slices/app_settings/app_settings_tokens.ts
9895
+ const APP_SETTINGS_TOKENS = {};
9896
+
8352
9897
  //#endregion
8353
9898
  //#region src/slices/attachment/attachment-api.server.ts
8354
9899
  const CreateFolderInputSchema = z.object({
@@ -8672,12 +10217,211 @@ var RecordVersionApiServer = class extends RpcTarget {
8672
10217
  }
8673
10218
  });
8674
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
+ }
8675
10415
  };
8676
10416
 
8677
10417
  //#endregion
8678
10418
  //#region src/slices/support_ticket/support-ticket-api.server.ts
8679
10419
  const CancelInternalTaskInputSchema = z.object({ id: z.string() });
8680
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
+ }));
8681
10425
  var SupportTicketApiServer = class extends RpcTarget {
8682
10426
  constructor(container$1) {
8683
10427
  super();
@@ -8735,6 +10479,31 @@ var SupportTicketApiServer = class extends RpcTarget {
8735
10479
  }
8736
10480
  });
8737
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
+ }
8738
10507
  async staffCreateTicket(input) {
8739
10508
  return rpcMethod({
8740
10509
  auth: "admin",
@@ -8787,6 +10556,18 @@ var SupportTicketApiServer = class extends RpcTarget {
8787
10556
  }
8788
10557
  });
8789
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
+ }
8790
10571
  async approveTicket(input) {
8791
10572
  return rpcMethod({
8792
10573
  auth: "admin",
@@ -8904,6 +10685,78 @@ var SupportTicketApiServer = class extends RpcTarget {
8904
10685
  }
8905
10686
  });
8906
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
+ }
8907
10760
  };
8908
10761
 
8909
10762
  //#endregion
@@ -9192,6 +11045,18 @@ var UserApiServer = class extends RpcTarget {
9192
11045
  }
9193
11046
  });
9194
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
+ }
9195
11060
  };
9196
11061
 
9197
11062
  //#endregion
@@ -9383,5 +11248,5 @@ var UserSessionApiServer = class extends RpcTarget {
9383
11248
  };
9384
11249
 
9385
11250
  //#endregion
9386
- 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 };
9387
11252
  //# sourceMappingURL=index.mjs.map