@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/LICENSE +2 -2
- package/dist/index.d.mts +1474 -626
- package/dist/index.mjs +2545 -680
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/dist/session_validation-DYUZWJFy.mjs +0 -3
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,
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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:
|
|
703
|
+
priority: integer("priority", { mode: "number" }).notNull().default(2),
|
|
509
704
|
approval_status: text("approval_status", { enum: SupportTicketApprovalEnum }).notNull().default("PENDING"),
|
|
510
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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(
|
|
3106
|
-
__decorateParam(4, inject(
|
|
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(
|
|
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,
|
|
3186
|
-
__decorateParam(1, inject(
|
|
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
|
|
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
|
|
3318
|
-
const buildFieldFilters$3 = createFilterBuilder
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
3654
|
-
if (
|
|
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
|
|
3698
|
-
if (existingNote.record_type !== "support_ticket") throw new
|
|
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
|
|
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
|
|
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(
|
|
3707
|
-
if (
|
|
3708
|
-
|
|
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(
|
|
3711
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3782
|
-
if (existingNote.record_type !== "support_ticket") throw new
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3797
|
-
if (existingNote.created_by !== user.userId) throw new
|
|
3798
|
-
if (existingNote.is_internal) throw new
|
|
3799
|
-
if (input.is_internal !== void 0 && input.is_internal !== existingNote.is_internal) throw new
|
|
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
|
|
3871
|
-
const buildFieldFilters$2 = createFilterBuilder
|
|
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
|
|
3989
|
+
const archive = archiveConditions(filters, note_table.archived_at);
|
|
3881
3990
|
const fields = buildFieldFilters$2(filters).conditions;
|
|
3882
|
-
const search = searchOrCondition
|
|
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/
|
|
3970
|
-
const
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
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))
|
|
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
|
-
|
|
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,
|
|
4523
|
-
const
|
|
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
|
|
4659
|
-
records
|
|
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),
|
|
4663
|
-
old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record),
|
|
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
|
|
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([
|
|
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
|
-
|
|
4722
|
-
const
|
|
4723
|
-
records
|
|
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),
|
|
4727
|
-
old_record: this.filterFieldsByRecordType(JSON.stringify(record.old_record),
|
|
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
|
|
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,
|
|
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: "
|
|
5615
|
+
type: "number",
|
|
4827
5616
|
filterable: true,
|
|
4828
|
-
searchable:
|
|
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
|
-
|
|
4846
|
-
column: support_ticket_table.
|
|
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:
|
|
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
|
|
4994
|
-
const buildFieldFilters$1 = createFilterBuilder
|
|
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
|
|
5805
|
+
const archive = archiveConditions(filters, support_ticket_table.archived_at);
|
|
5035
5806
|
const fields = buildFieldFilters$1(filters).conditions;
|
|
5036
|
-
const search = searchOrCondition
|
|
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 === "
|
|
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:
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).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
|
-
|
|
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
|
-
|
|
5269
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
5331
|
-
|
|
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
|
-
*
|
|
6381
|
+
* Auto-add requester (created_by) as follower
|
|
5335
6382
|
*/
|
|
5336
|
-
async
|
|
6383
|
+
async addRequesterAsFollower(ticket) {
|
|
5337
6384
|
try {
|
|
5338
|
-
const
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
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
|
|
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(
|
|
5390
|
-
__decorateParam(3, inject(
|
|
5391
|
-
__decorateParam(4, inject(
|
|
5392
|
-
__decorateParam(5, inject(
|
|
5393
|
-
__decorateParam(6, inject(TOKENS.
|
|
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
|
-
|
|
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(
|
|
5427
|
-
__decorateParam(3, inject(
|
|
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:
|
|
6540
|
+
items: dtos.map((dto) => enrichCustomerTicket(dto, displayMap)),
|
|
5447
6541
|
pageInfo: result.pageInfo
|
|
5448
6542
|
};
|
|
5449
6543
|
}
|
|
5450
6544
|
};
|
|
5451
|
-
GetSupportTicketsCustomerFeature = __decorate([
|
|
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
|
|
5467
|
-
if (support_ticket.approval_status !== "PENDING") throw new
|
|
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
|
-
|
|
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(
|
|
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
|
|
5525
|
-
if (support_ticket.approval_status !== "PENDING") throw new
|
|
5526
|
-
|
|
5527
|
-
if (
|
|
5528
|
-
const
|
|
5529
|
-
|
|
5530
|
-
|
|
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
|
|
5556
|
-
|
|
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(
|
|
5563
|
-
__decorateParam(2, inject(
|
|
5564
|
-
__decorateParam(3,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
|
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)
|
|
5825
|
-
|
|
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
|
-
*
|
|
7108
|
+
* Auto-add requester (created_by) as follower
|
|
5829
7109
|
*/
|
|
5830
|
-
async
|
|
7110
|
+
async addRequesterAsFollower(ticket) {
|
|
5831
7111
|
try {
|
|
5832
|
-
const
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
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
|
|
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(
|
|
5884
|
-
__decorateParam(3, inject(
|
|
5885
|
-
__decorateParam(4, inject(
|
|
5886
|
-
__decorateParam(5, inject(
|
|
5887
|
-
__decorateParam(6, inject(
|
|
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
|
-
|
|
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:
|
|
7331
|
+
items: dtos.map((t) => enrichStaffTicket(t, displayMap)),
|
|
5974
7332
|
pageInfo: result.pageInfo
|
|
5975
7333
|
};
|
|
5976
7334
|
}
|
|
5977
7335
|
};
|
|
5978
|
-
GetSupportTicketsAdminFeature = __decorate([
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
6098
|
-
__decorateParam(2,
|
|
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
|
-
|
|
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
|
|
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
|
|
6181
|
-
if (currentApproval !== "PENDING") throw new
|
|
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
|
|
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
|
|
6196
|
-
if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new
|
|
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
|
|
6200
|
-
if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) throw new
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
8213
|
+
const dto = mapTeamEntityToDto(teamEntity);
|
|
8214
|
+
return enrichTeam(dto, await this.userDisplayLookup.lookupDisplayNames(collectUserIdsFromTeam(dto)));
|
|
6740
8215
|
}
|
|
6741
8216
|
};
|
|
6742
|
-
ReadTeamFeatureImpl = __decorate([
|
|
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
|
-
|
|
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
|
|
6932
|
-
const buildFieldFilters = createFilterBuilder
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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([
|
|
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
|
-
|
|
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([
|
|
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(
|
|
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
|