@dragonmastery/dragoncore-api 0.0.8 → 0.0.9

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":["DatabaseRouter","env: Env","idGenerator: IUniversalIdGenerator","logger: Logger | null","latestDate: Date | null","latestShard: string | null","date: Date | undefined","bindingTenantId: string","parsedBinding: ParsedBinding","AppSettingsRepo","appSettingsDb: DrizzleD1Database<Record<string, never>>","CookieService","DisplayIdPrefixService","prefixRegistry: DisplayIdPrefixRegistry","EmailService","env: Env","logger: Logger","registry: FieldRegistry","columnMap","columnMap: Record<string, SortColumn>","andConds: SQL[]","orConds: SQL[]","conditions: SQL[]","cond: SQL | undefined","conditions: SQL[]","result: AnyObject","columnMap","orderBy: SQL","db: DrizzleD1Database<Record<string, never>>","columnMap","pageItems: Entity[]","PasswordService","workerHashPassword","workerVerifyPassword","TenantContext","preloadPromises: Promise<void>[]","container","serialized: Record<string, any>","container","container","instances: Record<symbol, any>","factoriesMap: Record<symbol, (container: DependencyContainer) => any>","container","logger","payload: AccessTokenPayload","payload: RefreshTokenPayload","payload: UserDetailsTokenPayload","payload: PasswordResetTokenPayload","minimalSession: UserAppSession","requestLogger: Logger | null","DatabaseRouterClass","logger","env","factories: ContainerFactories<TEnv>","recordTypesValue: string[]","cookieHeaders: string[]","serializedCookie: string","container","logger","AttachmentFolderRepo","router: DatabaseRouter","AttachmentRepo","router: DatabaseRouter","folders: any[]","CreateAttachment","session: UserAppSession","attachment_repo: IAttachmentRepo","attachment_folder_repo: IAttachmentFolderRepo","support_ticket_repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","env: Env","logger: Logger","RecordConst","attachment_entity: CreateAttachmentEntity","CreateAttachmentFolder","session: UserAppSession","attachment_folder_repo: IAttachmentFolderRepo","support_ticket_repo: ISupportTicketRepo","logger: Logger","RecordConst","folder_entity: CreateAttachmentFolderEntity","folder_dto: AttachmentFolderReadDto","DeleteAttachment","session: UserAppSession","attachment_repo: IAttachmentRepo","attachment_folder_repo: IAttachmentFolderRepo","support_ticket_repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","RecordConst","ReadAllAttachmentsByRecord","attachment_repo: IAttachmentRepo","ReadAttachment","attachment_repo: IAttachmentRepo","creditTransactionFields: FieldRegistry","buildFieldFilters","CreditTransactionRepo","router: DatabaseRouter","AddCreditsFeat","creditService: ICreditService","GetCreditTransactionsFeat","repo: ICreditTransactionRepo","ResetMonthlyBalanceFeat","creditService: ICreditService","SetMonthlyAllocationFeat","creditService: ICreditService","integer","CreditService","appSettingsRepo: IAppSettingsRepo","session: SessionState","transactionRepo: ICreditTransactionRepo","transaction: Omit<InsertCreditTransactionEntity, 'id'>","BusinessLogicRouter","createSupportTicketProcessor: CreateNoteSupportTicketProcessor","updateSupportTicketProcessor: UpdateNoteSupportTicketProcessor","getSupportTicketProcessor: GetNotesSupportTicketProcessor","deleteSupportTicketProcessor: DeleteNoteSupportTicketProcessor","RecordConst","CreateNoteSupportTicketProcessor","session: UserAppSession","supportTicketRepo: ISupportTicketRepo","DeleteNoteSupportTicketProcessor","session: UserAppSession","supportTicketRepo: ISupportTicketRepo","noteRepo: INoteRepo","GetNotesSupportTicketProcessor","session: UserAppSession","supportTicketRepo: ISupportTicketRepo","UpdateNoteSupportTicketProcessor","session: UserAppSession","supportTicketRepo: ISupportTicketRepo","noteRepo: INoteRepo","noteFields: FieldRegistry","buildFieldFilters","NoteRepo","router: DatabaseRouter","conditions: SQL[]","CreateNoteFeat","session: UserAppSession","noteRepo: INoteRepo","businessLogicRouter: IBusinessLogicRouter","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","supportTicketRepo?: ISupportTicketRepo","notificationService?: ISupportTicketNotificationService","entity: InsertNoteEntity","DeleteNoteFeat","session: UserAppSession","noteRepo: INoteRepo","businessLogicRouter: IBusinessLogicRouter","create_record_version: ICreateRecordVersion","ids: string[]","GetNotesFeat","noteRepo: INoteRepo","businessLogicRouter: IBusinessLogicRouter","userDisplayLookup: IUserDisplayLookup","enrichedItems: NoteReadDto[]","UpdateNoteFeat","session: UserAppSession","noteRepo: INoteRepo","businessLogicRouter: IBusinessLogicRouter","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","update_entity: UpdateNoteEntity","PasswordResetTokenGenerator","PasswordResetTokenVerifier","ChangeUserPassword","user_repo: IUserRepo","create_record_version: ICreateRecordVersion","session: UserAppSession","passwordService: IPasswordService","logger: Logger","record_version: CreateRecordVersionDto","ForgotPassword","env: Env","user_repo: IUserRepo","tokenGenerator: IPasswordResetTokenGenerator","emailService: IEmailService","ResetPassword","env: Env","user_repo: IUserRepo","create_record_version: ICreateRecordVersion","passwordService: IPasswordService","tokenVerifier: IPasswordResetTokenVerifier","logger: Logger","record_version: CreateRecordVersionDto","RecordSubscriberRepo","router: DatabaseRouter","ValidateRecordAccess","session: UserAppSession","teamRepo: TeamRepository","validatorRegistry: RecordAccessValidatorRegistry","logger: Logger","columnMap: Record<string, SortColumn>","RecordVersionRepo","router: DatabaseRouter","logger: Logger","index","recordTypeCondition: SQL","baseConditions: SQL[]","CreateRecordVersion","record_version_repo: IRecordVersionRepo","CreateRecordVersionsBatch","record_version_repo: IRecordVersionRepo","DeleteRecordVersion","record_version_repo: IRecordVersionRepo","session: UserAppSession","val","ReadAllRecordVersionsByRecord","record_version_repo: IRecordVersionRepo","session: UserAppSession","USER_ID_FIELDS","collectUserIdsFromRecordVersions","ReadAllRecordVersionsByRecordPaginatedCustomer","record_version_repo: IRecordVersionRepo","userDisplayLookup: IUserDisplayLookup","RecordConst","ReadAllRecordVersionsByRecordPaginated","record_version_repo: IRecordVersionRepo","validateRecordAccess: IValidateRecordAccess","userDisplayLookup: IUserDisplayLookup","session: UserAppSession","RecordConst","ReadRecordVersion","record_version_repo: IRecordVersionRepo","filters: Record<string, string | string[]>","CreateSavedFilterFeature","session: UserAppSession","repo: ISavedFilterRepo","DeleteSavedFilterFeature","session: UserAppSession","repo: ISavedFilterRepo","pinnedRepo: IUserPinnedPresetRepo","ListSavedFiltersFeature","session: UserAppSession","repo: ISavedFilterRepo","UpdateSavedFilterFeature","session: UserAppSession","repo: ISavedFilterRepo","AddPinnedPresetFeature","session: UserAppSession","pinnedRepo: IUserPinnedPresetRepo","savedFilterRepo: ISavedFilterRepo","ListAllSavedFiltersFeature","session: UserAppSession","repo: ISavedFilterRepo","ListPinnedPresetsFeature","session: UserAppSession","pinnedRepo: IUserPinnedPresetRepo","savedFilterRepo: ISavedFilterRepo","result: SavedFilterReadDto[]","RemovePinnedPresetFeature","session: UserAppSession","pinnedRepo: IUserPinnedPresetRepo","ReorderPinnedPresetsFeature","session: UserAppSession","pinnedRepo: IUserPinnedPresetRepo","SavedFilterRepository","router: DatabaseRouter","UserPinnedPresetRepository","router: DatabaseRouter","SupportStaffRepo","router: DatabaseRouter","entity: InsertSupportStaffEntity","AddSupportStaffFeat","supportStaffRepo: ISupportStaffRepo","userRepo: IUserRepo","session: UserAppSession","ListSupportStaffFeat","supportStaffRepo: ISupportStaffRepo","RemoveSupportStaffFeat","supportStaffRepo: ISupportStaffRepo","supportTicketFields: FieldRegistry","buildFieldFilters","conditions: SQL[]","SupportTicketRepo","router: DatabaseRouter","SupportTicketNotificationService","subscriberRepo: IRecordSubscriberRepo","userRepo: IUserRepo","userProfileRepo: IUserProfileRepo","emailService: IEmailService","env: Env","logger: Logger","RecordConst","recipients: Array<{ email: string; userType: string }>","SupportTicketTriageService","supportStaffRepo: ISupportStaffRepo","appSettingsRepo: IAppSettingsRepo","logger: Logger","CreateSupportTicketFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","subscriberRepo: IRecordSubscriberRepo","notificationService: ISupportTicketNotificationService","triageService: ISupportTicketTriageService","create_record_version: ICreateRecordVersion","appSettingsRepo: IAppSettingsRepo","logger: Logger","frEntity: InsertSupportTicketEntity","CustomerToggleSubscriptionFeat","session: UserAppSession","subscriberRepo: IRecordSubscriberRepo","supportTicketRepo: ISupportTicketRepo","toRecordSubscriberReadDto","GetSupportTicketCustomerFeature","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","subscriberRepo: IRecordSubscriberRepo","userDisplayLookup: IUserDisplayLookup","appSettingsRepo: IAppSettingsRepo","logger: Logger","GetSupportTicketsCustomerFeature","support_ticketRepo: ISupportTicketRepo","userDisplayLookup: IUserDisplayLookup","customerFilters: StaffSupportTicketFiltersDto","UpdateSupportTicketCustomerFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","userDisplayLookup: IUserDisplayLookup","create_record_version: ICreateRecordVersion","frEntity: UpdateSupportTicketEntity","toRecordSubscriberReadDto","AddSupportTicketSubscriberFeat","subscriberRepo: IRecordSubscriberRepo","supportTicketRepo: ISupportTicketRepo","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","ApproveSupportTicketFeat","repo: ISupportTicketRepo","notificationService: ISupportTicketNotificationService","creditService: ICreditService","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","ArchiveSupportTicketFeat","repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","updated","dto","CancelInternalTaskFeat","repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","CompleteSupportTicketFeat","repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","ConvertToCustomerFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","updateEntity: UpdateSupportTicketEntity","ConvertToInternalFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","updateEntity: UpdateSupportTicketEntity","CreateSupportTicketAdminFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","subscriberRepo: IRecordSubscriberRepo","notificationService: ISupportTicketNotificationService","triageService: ISupportTicketTriageService","supportStaffRepo: ISupportStaffRepo","userDisplayLookup: IUserDisplayLookup","create_record_version: ICreateRecordVersion","appSettingsRepo: IAppSettingsRepo","logger: Logger","support_ticketEntity: InsertSupportTicketEntity","assigneeId: string | null","DeleteSupportTicketFeat","repo: ISupportTicketRepo","session: UserAppSession","create_record_version: ICreateRecordVersion","record_version: CreateRecordVersionDto","FixSupportTicketUserIdsFeature","supportTicketRepo: ISupportTicketRepo","userRepo: IUserRepo","updated: UpdateSupportTicketEntity","updates: Partial<ReadSupportTicketEntity>","GetSupportTicketAdminFeature","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","appSettingsRepo: IAppSettingsRepo","userDisplayLookup: IUserDisplayLookup","GetRequestorsForActiveSupportTicketsFeat","supportTicketRepo: ISupportTicketRepo","userRepo: IUserRepo","GetSupportTicketsAdminFeature","support_ticketRepo: ISupportTicketRepo","userDisplayLookup: IUserDisplayLookup","ListSupportTicketSubscribersFeat","subscriberRepo: IRecordSubscriberRepo","supportTicketRepo: ISupportTicketRepo","userDisplayLookup: IUserDisplayLookup","ReactivateInternalTaskFeat","repo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","RejectSupportTicketFeat","repo: ISupportTicketRepo","notificationService: ISupportTicketNotificationService","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","RemoveSupportTicketSubscriberFeat","subscriberRepo: IRecordSubscriberRepo","RevertSupportTicketFeat","repo: ISupportTicketRepo","creditService: ICreditService","create_record_version: ICreateRecordVersion","session: UserAppSession","userDisplayLookup: IUserDisplayLookup","UpdateSupportTicketAdminFeat","session: UserAppSession","support_ticketRepo: ISupportTicketRepo","create_record_version: ICreateRecordVersion","notificationService: ISupportTicketNotificationService","userDisplayLookup: IUserDisplayLookup","frEntity: UpdateSupportTicketEntity","columnMap: Record<string, SortColumn>","TeamRepositoryImpl","db: DatabaseRouter","CreateTeamFeatureImpl","teamRepo: TeamRepository","session: UserAppSession","create_record_version: ICreateRecordVersion","createTeamMemberFeature: ICreateTeamMemberFeature","userDisplayLookup: IUserDisplayLookup","DeleteTeamFeatureImpl","teamRepo: TeamRepository","session: UserAppSession","create_record_version: ICreateRecordVersion","ReadAllTeamsFeatureImpl","teamRepo: TeamRepository","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","userDisplayLookup: IUserDisplayLookup","ReadTeamFeatureImpl","teamRepo: TeamRepository","userDisplayLookup: IUserDisplayLookup","UpdateTeamFeatureImpl","teamRepo: TeamRepository","session: UserAppSession","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","updateData: Omit<UpdateTeamEntity, 'id'>","teamMemberFields: FieldRegistry","TeamMemberRepo","router: DatabaseRouter","CreateTeamMemberFeat","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","entity: InsertTeamMemberEntity","DeleteTeamMemberFeat","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","create_record_version: ICreateRecordVersion","GetTeamMembersFeat","teamMemberRepo: ITeamMemberRepo","userDisplayLookup: IUserDisplayLookup","GetUserTeamMembersFeat","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","teamMembers: ReadTeamMemberEntity[]","GetUserTeamsFeat","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","teamRepo: TeamRepository","UpdateTeamMemberFeat","session: UserAppSession","teamMemberRepo: ITeamMemberRepo","create_record_version: ICreateRecordVersion","userDisplayLookup: IUserDisplayLookup","updateData: UpdateTeamMemberEntity","UserRepo","router: DatabaseRouter","logger: Logger","RecordConst","CreateUser","user_repo: IUserRepo","create_record_version: ICreateRecordVersion","passwordService: IPasswordService","logger: Logger","user_entity: CreateUserEntity","record_version: CreateRecordVersionDto","created_dto: CreateUserDtoOutput","UserDisplayLookupFeat","userRepo: IUserRepo","DeleteUser","user_repo: IUserRepo","session: UserAppSession","create_record_version: ICreateRecordVersion","val","record_version: CreateRecordVersionDto","GetAllUsersFeat","repo: IUserRepo","GetUserFeat","repo: IUserRepo","GetTriageUsersFeat","supportStaffRepo: ISupportStaffRepo","GetUsersForSelectionFeat","repo: IUserRepo","ReadAllUsers","user_repo: IUserRepo","session: UserAppSession","val","ReadConsumers","user_repo: IUserRepo","ReadUser","user_repo: IUserRepo","SignUpUser","user_repo: IUserRepo","create_record_version: ICreateRecordVersion","passwordService: IPasswordService","logger: Logger","user_entity: CreateUserEntity","record_version: CreateRecordVersionDto","created_dto: SignUpUserOutputDto","UpdateUserFeat","repo: IUserRepo","UserProfileRepo","router: DatabaseRouter","ReadUserProfile","user_profile_repo: IUserProfileRepo","session: UserAppSession","UpdateUserProfile","user_profile_repo: IUserProfileRepo","session: UserAppSession","create_record_version: ICreateRecordVersion","user_profile_entity: UpdateUserProfileEntity","record_version: CreateRecordVersionDto","RefreshTokenRepo","router: DatabaseRouter","LoginUserSession","refreshTokenRepo: IRefreshTokenRepo","userRepo: IUserRepo","createRecordVersion: ICreateRecordVersion","request: Request","env: Env","passwordService: IPasswordService","accessTokenGenerator: IAccessTokenGenerator","refreshTokenGenerator: IRefreshTokenGenerator","userDetailsTokenGenerator: IUserDetailsTokenGenerator","val","refreshTokenEntity: CreateRefreshTokenEntity","recordVersion: CreateRecordVersionDto","frontendSession: FrontendSessionDto","ReadAllUserSessions","refreshTokenRepo: IRefreshTokenRepo","session: UserAppSession","RefreshTokenSession","refreshTokenRepo: IRefreshTokenRepo","env: Env","logger: Logger","tokenVerifier: IRefreshTokenVerifier","accessTokenGenerator: IAccessTokenGenerator","refreshTokenGenerator: IRefreshTokenGenerator","userDetailsTokenGenerator: IUserDetailsTokenGenerator","newRefreshTokenRecord: CreateRefreshTokenEntity","FrontendSessionDto: FrontendSessionDto","RevokeRefreshToken","refreshTokenRepo: IRefreshTokenRepo","env: Env","tokenVerifier: IRefreshTokenVerifier","logger: Logger","AccessTokenGenerator","RefreshTokenGenerator","RefreshTokenVerifier","UserDetailsTokenGenerator","container","container: DependencyContainer","container","input","id","container: DependencyContainer","container","input","container: DependencyContainer","container","input","id","filters","container: DependencyContainer","container","input","email","container: DependencyContainer","container","id","filters","container: DependencyContainer","container","userId","container: DependencyContainer","container","context","input","id","presetId","presetIds","UsersForSelectionSchema","container: DependencyContainer","container","input","id","container: DependencyContainer","container","id","input","archive","container: DependencyContainer","container","input","id","container: DependencyContainer","container","id","input","container: DependencyContainer","container","userId","input","cookieOptions: CookieOptions & { prefix?: 'secure' }","container: DependencyContainer","container","input"],"sources":["../src/di_tokens.ts","../src/db/database_router.ts","../src/db/db_utils.ts","../src/db/dbTypes.ts","../src/db/schemas/app_setting/app_settings_table.ts","../src/db/schemas/app_setting/app_settings_repo.ts","../src/slices/record_version/db/record_version_table.ts","../src/db/schema/auth/mfa_secret_table.ts","../src/db/schema/auth/oauth_provider_table.ts","../src/db/schema/auth/user_oauth_account_table.ts","../src/slices/attachment/db/attachment_folder_table.ts","../src/slices/attachment/db/attachment_table.ts","../src/slices/note/db/note_table.ts","../src/slices/saved_filter/db/saved_filter_table.ts","../src/slices/saved_filter/db/user_pinned_preset_table.ts","../src/slices/record_subscriber/db/record_subscriber_table.ts","../src/slices/support_staff/db/support_staff_table.ts","../src/slices/support_ticket/db/support_ticket_table.ts","../src/slices/team/db/team_table.ts","../src/slices/team_member/db/team_member_table.ts","../src/slices/user/db/user_table.ts","../src/slices/user_profile/db/user_profile_table.ts","../src/slices/user_session/db/refresh_token_table.ts","../src/db/session_state.ts","../src/utils/creditValueFormatter.ts","../src/utils/logger.ts","../src/lib/cookie_service.ts","../src/lib/display_id_prefix_tokens.ts","../src/lib/display_id_prefix_service.ts","../src/lib/email_service.ts","../src/lib/filters/filter-builder.ts","../src/lib/filters/archive.ts","../src/lib/filters/team-access.ts","../src/lib/filters/search.ts","../src/lib/filters/condition-combiner.ts","../src/lib/getChangedProperties.ts","../src/lib/pagination/breadcrumb-utils.ts","../src/lib/pagination/cursor-utils.ts","../src/lib/pagination/direct-database-adapter.ts","../src/lib/pagination/pagination-utils.ts","../src/lib/password_verifier.ts","../src/lib/r2_bucket_finder.ts","../src/lib/tenant_context.ts","../src/lib/universal_id_generator.ts","../src/decorators/inject_session.ts","../src/middleware/rpc_mid.ts","../src/middleware/auth-checks.ts","../src/middleware/container_factory_builder.ts","../src/slices/user_session/jwt/jwt_utils.ts","../src/middleware/session_validation.ts","../src/middleware/container_setup_helpers.ts","../src/middleware/container_setup_mid.ts","../src/middleware/cookie_response_mid.ts","../src/middleware/hono_helpers.ts","../src/middleware/is_authenticated_mid.ts","../src/slices/app_settings/app_settings_container.ts","../src/slices/attachment/attachment_interfaces.ts","../src/slices/attachment/db/attachment_folder_repo.ts","../src/slices/attachment/db/attachment_repo.ts","../src/slices/record_version/record_version_interfaces.ts","../src/slices/support_ticket/support_ticket_tokens.ts","../src/slices/attachment/features/create_attachment.ts","../src/slices/attachment/features/create_attachment_folder.ts","../src/slices/attachment/features/delete_attachment.ts","../src/slices/attachment/features/read_all_attachments_by_record_feat.ts","../src/slices/attachment/features/read_attachment.ts","../src/slices/attachment/attachment_container.ts","../src/slices/customer/customer_tokens.ts","../src/slices/customer/db/credit_transaction_table.ts","../src/slices/customer/db/credit_transaction_query_config.ts","../src/slices/customer/db/credit_transaction_repo.ts","../src/slices/customer/services/credit_service.ts","../src/slices/customer/features/add_credits_feat.ts","../src/slices/customer/features/get_credit_transactions_feat.ts","../src/slices/customer/features/reset_monthly_balance_feat.ts","../src/slices/customer/features/set_monthly_allocation_feat.ts","../src/slices/customer/services/credit_service_impl.ts","../src/slices/customer/customer_registration.ts","../src/slices/note/business_logic/business_logic_router.ts","../src/slices/note/business_logic/support_ticket/create_note_support_ticket_processor.ts","../src/slices/note/note_tokens.ts","../src/slices/note/business_logic/support_ticket/delete_note_support_ticket_processor.ts","../src/slices/note/business_logic/support_ticket/get_notes_support_ticket_processor.ts","../src/slices/note/business_logic/support_ticket/update_note_support_ticket_processor.ts","../src/slices/note/db/note_query_config.ts","../src/slices/note/db/note_repo.ts","../src/slices/user/user_interfaces.ts","../src/slices/note/features/create_note_feat.ts","../src/slices/note/features/delete_note_feat.ts","../src/slices/note/features/get_notes_feat.ts","../src/slices/note/features/update_note_feat.ts","../src/slices/note/note_container.ts","../src/slices/user_session/jwt/jwt_interfaces.ts","../src/slices/user_session/jwt/password_reset_token_generator.ts","../src/slices/user_session/jwt/password_reset_token_verifier.ts","../src/slices/password_reset/features/change_password.ts","../src/slices/password_reset/features/forgot_password.ts","../src/slices/password_reset/features/reset_password.ts","../src/slices/password_reset/password_reset_interfaces.ts","../src/slices/password_reset/password_reset_container.ts","../src/slices/record_subscriber/db/record_subscriber_repo.ts","../src/slices/record_subscriber/record_subscriber_tokens.ts","../src/slices/record_subscriber/record_subscriber_registration.ts","../src/slices/team/team_interfaces.ts","../src/slices/record_version/business_logic/record_version_validation_tokens.ts","../src/slices/record_version/business_logic/validate_record_access.ts","../src/slices/record_version/db/record_version_repo.ts","../src/slices/record_version/features/create_record_version.ts","../src/slices/record_version/features/create_record_versions_batch.ts","../src/slices/record_version/features/delete_record_version.ts","../src/slices/record_version/features/read_all_record_versions_by_record_feat.ts","../src/slices/record_version/features/read_all_record_versions_by_record_paginated_customer_feat.ts","../src/slices/record_version/features/read_all_record_versions_by_record_paginated_feat.ts","../src/slices/record_version/features/read_record_version.ts","../src/slices/record_version/record_version_container.ts","../src/slices/saved_filter/saved_filter_interfaces.ts","../src/slices/saved_filter/db/saved_filter_mapper.ts","../src/slices/saved_filter/features/create_saved_filter_feat.ts","../src/slices/saved_filter/user_pinned_preset_interfaces.ts","../src/slices/saved_filter/features/delete_saved_filter_feat.ts","../src/slices/saved_filter/features/list_saved_filters_feat.ts","../src/slices/saved_filter/features/update_saved_filter_feat.ts","../src/slices/saved_filter/features/add_pinned_preset_feat.ts","../src/slices/saved_filter/features/list_all_saved_filters_feat.ts","../src/slices/saved_filter/features/list_pinned_presets_feat.ts","../src/slices/saved_filter/features/remove_pinned_preset_feat.ts","../src/slices/saved_filter/features/reorder_pinned_presets_feat.ts","../src/slices/saved_filter/db/saved_filter_repo.ts","../src/slices/saved_filter/db/user_pinned_preset_repo.ts","../src/slices/saved_filter/saved_filter_container.ts","../src/slices/support_staff/db/support_staff_repo.ts","../src/slices/support_staff/support_staff_tokens.ts","../src/slices/support_staff/features/add_support_staff_feat.ts","../src/slices/support_staff/features/list_support_staff_feat.ts","../src/slices/support_staff/features/remove_support_staff_feat.ts","../src/slices/support_staff/support_staff_registration.ts","../src/slices/support_ticket/db/support_ticket_query_config.ts","../src/slices/support_ticket/db/support_ticket_repo.ts","../src/slices/user_profile/user_profile_interfaces.ts","../src/slices/support_ticket/services/support_ticket_notification_service.ts","../src/slices/support_ticket/services/support_ticket_triage_service.ts","../src/slices/support_ticket/mappers/support_ticket_mapper.ts","../src/slices/support_ticket/features/customer/create_support_ticket_feat.ts","../src/slices/support_ticket/features/customer/customer_toggle_subscription_feat.ts","../src/slices/support_ticket/features/customer/enrich_customer_ticket.ts","../src/slices/support_ticket/features/customer/get_support_ticket_customer_feat.ts","../src/slices/support_ticket/features/customer/get_support_tickets_customer_feat.ts","../src/slices/support_ticket/features/customer/update_support_ticket_customer_feat.ts","../src/slices/support_ticket/features/staff/add_support_ticket_subscriber_feat.ts","../src/slices/support_ticket/features/staff/enrich_staff_ticket.ts","../src/slices/support_ticket/features/staff/approve_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/archive_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/cancel_internal_task_feat.ts","../src/slices/support_ticket/features/staff/complete_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/convert_to_customer_feat.ts","../src/slices/support_ticket/features/staff/convert_to_internal_feat.ts","../src/slices/support_ticket/features/staff/create_support_ticket_admin_feat.ts","../src/slices/support_ticket/features/staff/delete_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/fix_support_ticket_user_ids_feat.ts","../src/slices/support_ticket/features/staff/get_support_ticket_admin_feat.ts","../src/slices/support_ticket/features/staff/get_requestors_for_active_support_tickets_feat.ts","../src/slices/support_ticket/features/staff/get_support_tickets_admin_feat.ts","../src/slices/support_ticket/features/staff/list_support_ticket_subscribers_feat.ts","../src/slices/support_ticket/features/staff/reactivate_internal_task_feat.ts","../src/slices/support_ticket/features/staff/reject_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/remove_support_ticket_subscriber_feat.ts","../src/slices/support_ticket/features/staff/revert_support_ticket_feat.ts","../src/slices/support_ticket/features/staff/validate_staff_business_rules.ts","../src/slices/support_ticket/features/staff/update_support_ticket_admin_feat.ts","../src/slices/support_ticket/support_ticket_registration.ts","../src/slices/team/db/team_repo.ts","../src/slices/team_member/team_member_tokens.ts","../src/slices/team/db/team_mapper.ts","../src/slices/team/features/enrich_team.ts","../src/slices/team/features/create_team_feat.ts","../src/slices/team/features/delete_team_feat.ts","../src/slices/team/features/read_all_teams_feat.ts","../src/slices/team/features/read_team_feat.ts","../src/slices/team/features/update_team_feat.ts","../src/slices/team/team_container.ts","../src/slices/team_member/db/team_member_query_config.ts","../src/slices/team_member/db/team_member_repo.ts","../src/slices/team_member/features/enrich_team_member.ts","../src/slices/team_member/features/create_team_member_feat.ts","../src/slices/team_member/features/delete_team_member_feat.ts","../src/slices/team_member/features/get_team_members_feat.ts","../src/slices/team_member/features/get_user_team_members_feat.ts","../src/slices/team_member/features/get_user_teams_feat.ts","../src/slices/team_member/features/update_team_member_feat.ts","../src/slices/team_member/team_member_container.ts","../src/slices/user/db/user_repo.ts","../src/slices/user/features/create_user.ts","../src/slices/user/features/user_display_lookup_feat.ts","../src/slices/user/features/delete_user.ts","../src/slices/user/features/get_all_users_feat.ts","../src/slices/user/features/get_user_feat.ts","../src/slices/user/features/get_triage_users_feat.ts","../src/slices/user/features/get_users_for_selection_feat.ts","../src/slices/user/features/read_all_users.ts","../src/slices/user/features/read_consumers.ts","../src/slices/user/features/read_user.ts","../src/slices/user/features/sign_up_user.ts","../src/slices/user/features/update_user_feat.ts","../src/slices/user/user_container.ts","../src/slices/user_profile/db/user_profile_repo.ts","../src/slices/user_profile/features/read_user_profile.ts","../src/slices/user_profile/features/update_user_profile.ts","../src/slices/user_profile/user_profile_container.ts","../src/slices/user_session/db/refresh_token_repo.ts","../src/slices/user_session/user_session_interfaces.ts","../src/slices/user_session/features/login_user_session_feat.ts","../src/slices/user_session/features/read_all_user_sessions_feat.ts","../src/slices/user_session/features/refresh_token_feat.ts","../src/slices/user_session/features/revoke_refresh_token_feat.ts","../src/slices/user_session/jwt/access_token_generator.ts","../src/slices/user_session/jwt/refresh_token_generator.ts","../src/slices/user_session/jwt/refresh_token_verifier.ts","../src/slices/user_session/jwt/user_details_token_generator.ts","../src/slices/user_session/user_session_container.ts","../src/container.ts","../src/slices/app_settings/app-settings-api.server.ts","../src/slices/app_settings/app_settings_tokens.ts","../src/slices/attachment/attachment-api.server.ts","../src/slices/customer/customer-api.server.ts","../src/slices/note/note-api.server.ts","../src/slices/password_reset/password-reset-api.server.ts","../src/slices/record_version/record-version-api.server.ts","../src/slices/support_staff/support-staff-api.server.ts","../src/slices/saved_filter/saved-filter-api.server.ts","../src/slices/support_ticket/support-ticket-api.server.ts","../src/slices/team/team-api.server.ts","../src/slices/team_member/team-member-api.server.ts","../src/slices/user/user-api.server.ts","../src/slices/user_profile/user-profile-api.server.ts","../src/slices/user_session/user_session_cookie_utils.ts","../src/slices/user_session/user-session-api.server.ts"],"sourcesContent":["export const TOKENS = {\n SESSION: Symbol('SESSION'),\n ENV: Symbol('env'),\n AUTHENTICATED_SESSION: Symbol('AUTHENTICATED_SESSION'),\n REQUEST: Symbol('request'),\n HONO_CONTEXT: Symbol('HONO_CONTEXT'),\n PASSWORD_SERVICE: Symbol('PASSWORD_SERVICE'),\n COOKIE_SERVICE: Symbol('COOKIE_SERVICE'),\n ID_GENERATOR: Symbol('ID_GENERATOR'),\n IDatabaseRouter: Symbol('IDatabaseRouter'),\n IHistoricalDatabaseRouter: Symbol('IHistoricalDatabaseRouter'),\n IAppSettingsDb: Symbol('IAppSettingsDb'),\n IAppSettingsRepo: Symbol('IAppSettingsRepo'),\n LOGGER: Symbol('LOGGER'),\n IEmailService: Symbol('IEmailService'),\n} as const;\n","import { DrizzleD1Database, drizzle } from 'drizzle-orm/d1';\nimport { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../di_tokens';\nimport { DecodedId, IUniversalIdGenerator } from '../lib/universal_id_generator';\nimport { Logger } from '../utils/logger';\nimport { RecordType } from './dbTypes';\n\n// ShardIds type - maps environment names to binding-to-shard mappings\nexport type ShardIdsConfig = Record<string, Record<string, string>>;\n\n@injectable()\nexport class DatabaseRouter implements IDatabaseRouter {\n private shardConnections: Map<string, ParsedBinding>;\n private latestShard: string | null = null;\n private databaseType: DatabaseType;\n private shardIds: ShardIdsConfig;\n\n constructor(\n @inject(TOKENS.ENV) private env: Env,\n @inject(TOKENS.ID_GENERATOR) private idGenerator: IUniversalIdGenerator,\n @inject(TOKENS.LOGGER) private logger: Logger | null,\n shardIds: ShardIdsConfig,\n // Default to primary database type if not specified\n databaseType: DatabaseType = 'primary',\n ) {\n this.databaseType = databaseType;\n this.shardIds = shardIds;\n // console.log(`[DatabaseRouter] Initializing for ${databaseType} databases...`);\n this.shardConnections = this.getDbBindings();\n }\n\n /**\n * Parse database bindings from environment\n * @returns Array of parsed bindings\n */\n private getDbBindings(): Map<string, ParsedBinding> {\n console.log(`[DatabaseRouter] Parsing ${this.databaseType} database bindings...`);\n // console.log(`[DatabaseRouter] Env check:`, {\n // envType: typeof this.env,\n // envIsNull: this.env === null,\n // envIsUndefined: this.env === undefined,\n // envIsObject: typeof this.env === 'object',\n // envConstructor: this.env?.constructor?.name,\n // });\n\n if (!this.env) {\n console.error(`[DatabaseRouter] ERROR: Environment not set`);\n throw new Error('Environment not set');\n }\n\n if (typeof this.env !== 'object') {\n console.error(`[DatabaseRouter] ERROR: Environment is not an object:`, {\n type: typeof this.env,\n value: this.env,\n });\n throw new Error(`Environment is not an object, got: ${typeof this.env}`);\n }\n\n const resultMap = new Map<string, ParsedBinding>();\n\n // Determine the prefix based on database type\n const bindingPrefix = this.databaseType === 'historical' ? 'DBH_' : 'DB_';\n\n // console.log(`[DatabaseRouter] Getting Object.entries(this.env) for prefix: ${bindingPrefix}`);\n // Get all bindings that start with the correct prefix\n const db_bindings = Object.entries(this.env).filter(([key]) =>\n key.startsWith(bindingPrefix),\n );\n console.log(\n `[DatabaseRouter] Found ${db_bindings.length} bindings with prefix ${bindingPrefix}`,\n );\n\n // For primary database, throw error if no bindings found (app won't work)\n // For historical, just warn and return empty map (app can still work without historical)\n if (db_bindings.length === 0) {\n throw new Error(\n `No ${this.databaseType} database bindings found. Application cannot function.`,\n );\n }\n\n return this.processBindings(db_bindings, resultMap);\n }\n\n /**\n * Process database bindings and extract connection information\n */\n private processBindings(\n bindings: [string, any][],\n resultMap: Map<string, ParsedBinding>,\n ): Map<string, ParsedBinding> {\n let latestDate: Date | null = null;\n let latestShard: string | null = null;\n\n bindings.forEach(([binding, value]) => {\n // Parse the date and tenant from the binding name\n const parts = binding.split('_');\n if (parts.length < 6) return;\n\n let date: Date | undefined;\n let bindingTenantId: string;\n\n // Format for both primary and historical: DB(H)_YYYY_MM_DD_T_tenantId\n const [_, year, month, day, t, id] = parts;\n if (t !== 'T') return;\n bindingTenantId = id;\n date = new Date(Number(year), Number(month) - 1, Number(day));\n\n const parsedBinding: ParsedBinding = {\n binding,\n tenantId: bindingTenantId,\n // @ts-ignore\n drizzleDb: drizzle(value),\n date,\n type: this.databaseType,\n };\n\n // Safely check if environment exists in ShardIds\n const envShards = this.shardIds[this.env.ENVIRONMENT];\n if (!envShards) {\n const errorMessage = `Database binding lookup failed: Environment \"${this.env.ENVIRONMENT}\" not found in ShardIds configuration. Available environments: ${Object.keys(this.shardIds).join(', ')}.`;\n if (this.logger) {\n this.logger.error(errorMessage, {\n binding,\n environment: this.env.ENVIRONMENT,\n databaseType: this.databaseType,\n tenantId: bindingTenantId,\n availableEnvironments: Object.keys(this.shardIds),\n });\n }\n throw new Error(errorMessage);\n }\n\n // Safely check if binding exists for this environment\n // @ts-ignore\n const shardId = envShards[binding] as string | undefined;\n if (!shardId) {\n const errorMessage = `Database binding lookup failed: No shard ID found for binding \"${binding}\" in environment \"${this.env.ENVIRONMENT}\" for ${this.databaseType} database type. The binding exists in the environment but is not configured in ShardIds for this environment. Available bindings for ${this.env.ENVIRONMENT}: ${Object.keys(envShards).join(', ')}.`;\n if (this.logger) {\n this.logger.error(errorMessage, {\n binding,\n environment: this.env.ENVIRONMENT,\n databaseType: this.databaseType,\n tenantId: bindingTenantId,\n availableBindings: Object.keys(envShards),\n });\n }\n throw new Error(errorMessage);\n }\n\n // Add to map with shardId as key\n resultMap.set(shardId, parsedBinding);\n // console.log(\n // `[DatabaseRouter] Added ${this.databaseType} binding ${binding} with tenantId ${bindingTenantId} to map`,\n // );\n\n // Keep track of the latest binding (if it has a date)\n if (date && (!latestDate || date > latestDate)) {\n latestDate = date;\n latestShard = shardId;\n }\n });\n\n // Store the latest binding for quick access\n this.latestShard = latestShard;\n\n return resultMap;\n }\n /**\n * Get a database connection for a specific shard\n * @param shardId The shard ID to get a connection for\n * @returns A DrizzleD1Database instance for the shard\n */\n async getShardConnection(shardId: string): Promise<DrizzleD1Database> {\n if (this.shardConnections.has(shardId)) {\n // console.log(`[DatabaseRouter] Returning connection for shard ${shardId}`);\n return this.shardConnections.get(shardId)!.drizzleDb!;\n }\n\n throw new Error(`No database binding found for shard ${shardId}`);\n }\n\n /**\n * Get the appropriate database connection for a given ID\n * @param id The universal ID to get a connection for\n * @returns A DrizzleD1Database instance for the shard that owns this ID\n */\n async getConnectionForId(id: string): Promise<DrizzleD1Database> {\n const { shardId } = await this.idGenerator.decode(id);\n return this.getShardConnection(shardId);\n }\n\n /**\n * Get the latest shard ID for the given tenant\n * @returns The shard ID to use for new records\n */\n getLatestShard() {\n if (!this.latestShard) {\n throw new Error(`No ${this.databaseType} database shards found`);\n }\n\n return this.shardConnections.get(this.latestShard)!;\n }\n\n /**\n * Get all database connections\n * @returns A map of shard ID to DrizzleD1Database\n */\n getAllConnections() {\n return this.shardConnections\n .values()\n .toArray()\n .map((binding) => binding.drizzleDb);\n }\n\n /**\n * Query all shards in parallel and return the first match\n * @param queryFn Function that performs the query on a database connection\n * @returns The first matching result or undefined if not found\n */\n async queryAll<T>(queryFn: (db: DrizzleD1Database) => Promise<T | T[]>): Promise<T[]> {\n const startTime = performance.now();\n const connections = this.getAllConnections();\n\n // Query each shard in parallel\n const results = await Promise.all(connections.map((db) => queryFn(db)));\n\n // Handle both single items and arrays in one pass\n const finalResults = results.flatMap((result) =>\n Array.isArray(result) ? result : result != null ? [result] : [],\n );\n const queryTime = performance.now() - startTime;\n if (this.logger) {\n this.logger.perf(\n `DatabaseRouter.queryAll: ${queryTime.toFixed(2)}ms across ${connections.length} shards - ${finalResults.length} results`,\n );\n }\n return finalResults;\n }\n\n async queryById<T>(\n id: string,\n queryFn: (db: DrizzleD1Database) => Promise<T[]>,\n ): Promise<T[]> {\n const startTime = performance.now();\n const db = await this.getConnectionForId(id);\n const result = await queryFn(db);\n const queryTime = performance.now() - startTime;\n if (this.logger) {\n this.logger.perf(\n `DatabaseRouter.queryById: ${queryTime.toFixed(2)}ms for id ${id} - ${result.length} results`,\n );\n }\n return result;\n }\n\n async queryLatest<T>(queryFn: (db: DrizzleD1Database) => Promise<T[]>): Promise<T[]> {\n const db = this.getLatestShard().drizzleDb;\n return queryFn(db);\n }\n\n async queryByIds<T>(\n ids: string[],\n queryFn: (db: DrizzleD1Database, shardIds: string[]) => Promise<T[]>,\n ): Promise<T[]> {\n if (!ids.length) {\n return [];\n }\n\n // Group IDs by shard\n const idsByShardMap = new Map<string, string[]>();\n\n // Decode all IDs in parallel\n const decodedIds = await Promise.all(\n ids.map(async (id) => ({\n id,\n decoded: await this.idGenerator.decode(id),\n })),\n );\n\n // Group by shard\n for (const { id, decoded } of decodedIds) {\n const { shardId } = decoded;\n if (!idsByShardMap.has(shardId)) {\n idsByShardMap.set(shardId, []);\n }\n idsByShardMap.get(shardId)!.push(id);\n }\n\n // Query each shard with its respective IDs\n const results = await Promise.all(\n Array.from(idsByShardMap.entries()).map(async ([shardId, shardIds]) => {\n const db = await this.getShardConnection(shardId);\n return queryFn(db, shardIds);\n }),\n );\n\n // Combine all results\n return results.flat();\n }\n\n async generateId(recordType: RecordType) {\n if (!this.latestShard) {\n throw new Error('No latest shard found');\n }\n return await this.idGenerator.generate({\n recordType,\n shardId: this.latestShard,\n timestamp: Date.now(),\n });\n }\n\n async decodeId(id: string) {\n return await this.idGenerator.decode(id);\n }\n}\n\n/** Database type to determine which bindings to use */\nexport type DatabaseType = 'primary' | 'historical';\n\n/** Parsed database binding information */\ninterface ParsedBinding {\n binding: string;\n tenantId: string;\n date?: Date;\n drizzleDb: DrizzleD1Database;\n type: DatabaseType;\n}\n\nexport interface IDatabaseRouter {\n getShardConnection(shardId: string): Promise<DrizzleD1Database>;\n getConnectionForId(id: string): Promise<DrizzleD1Database>;\n getLatestShard(): ParsedBinding;\n getAllConnections(): DrizzleD1Database[];\n queryAll<T>(queryFn: (db: DrizzleD1Database) => Promise<T | T[]>): Promise<T[]>;\n queryById<T>(id: string, queryFn: (db: DrizzleD1Database) => Promise<T[]>): Promise<T[]>;\n queryLatest<T>(queryFn: (db: DrizzleD1Database) => Promise<T[]>): Promise<T[]>;\n queryByIds<T>(\n ids: string[],\n queryFn: (db: DrizzleD1Database, shardIds: string[]) => Promise<T[]>,\n ): Promise<T[]>;\n generateId(recordType: RecordType): Promise<string>;\n decodeId(id: string): Promise<DecodedId>;\n}\n","import { getTableColumns, getTableName, SQL, sql } from 'drizzle-orm';\nimport { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core';\nimport { customAlphabet } from 'nanoid';\n\nconst alphabet = '23456789abcdefghijkmnpqrstuvwxyz';\nexport const customNanoid = (size?: number) => customAlphabet(alphabet, size ?? 24)();\n\nconst long_alphabet = '23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';\nexport const custom_long_nanoid = (size?: number) =>\n customAlphabet(long_alphabet, size ?? 40)();\n\nexport const MAX_SESSION_LIFETIME_MS = 30 * 24 * 60 * 60 * 1000;\nexport const INACTIVITY_TIMEOUT_MS = 3 * 24 * 60 * 60 * 1000;\nexport const EXTENSION_INTERVAL_MS = 12 * 60 * 60 * 1000;\n\nexport function get_aliased_table_columns<T extends SQLiteTableWithColumns<any>>(table: T) {\n const table_name = getTableName(table);\n\n const columns = getTableColumns(table);\n\n type TableType = typeof table.$inferSelect;\n\n // Alias each column\n const table_columns = Object.entries(columns).reduce(\n (acc, [key]) => {\n acc[\n key as keyof {\n [K in keyof TableType as `${string}_${K}`]: SQL.Aliased<unknown>;\n }\n ] = sql`${table[key]}`.as(`${table_name}_${key}`);\n return acc;\n },\n {} as {\n [K in keyof TableType as K]: SQL.Aliased<unknown>;\n },\n );\n\n return table_columns;\n}\n","export enum RequestStatus {\n ERROR = 'ERROR',\n SUCCESS = 'SUCCESS',\n PENDING = 'PENDING',\n}\n\nexport enum KeyStatus {\n EXPIRED = 'expired',\n INVALID = 'invalid',\n VALID = 'valid',\n DEFAULT = 'default',\n}\n\nexport enum KeyType {\n STREAM_XCHACHA20 = 'stream_xchacha20',\n SECRETSTREAM = 'secretstream',\n SECRETBOX = 'secretbox',\n KDF = 'kdf',\n GENERICHASH = 'generichash',\n SHORTHASH = 'shorthash',\n AUTH = 'auth',\n HMACSHA256 = 'hmacsha256',\n HMACSHA512 = 'hmacsha512',\n AEAD_DET = 'aead-det',\n AEAD_IETF = 'aead-ietf',\n}\n\nexport enum FactorType {\n WEBAUTHN = 'webauthn',\n TOTP = 'totp',\n}\n\nexport enum FactorStatus {\n VERIFIED = 'verified',\n UNVERIFIED = 'unverified',\n}\n\nexport enum AalLevel {\n AAL3 = 'aal3',\n AAL2 = 'aal2',\n AAL1 = 'aal1',\n}\n\nexport enum CodeChallengeMethod {\n PLAIN = 'plain',\n S256 = 's256',\n}\n\nexport enum PricingPlanInterval {\n YEAR = 'year',\n MONTH = 'month',\n WEEK = 'week',\n DAY = 'day',\n}\n\nexport enum PricingType {\n RECURRING = 'recurring',\n ONE_TIME = 'one_time',\n}\n\nexport enum SubscriptionStatus {\n UNPAID = 'unpaid',\n PAST_DUE = 'past_due',\n INCOMPLETE_EXPIRED = 'incomplete_expired',\n INCOMPLETE = 'incomplete',\n CANCELED = 'canceled',\n ACTIVE = 'active',\n TRIALING = 'trialing',\n}\n\nexport const OperationConst = {\n TRUNCATE: 'truncate',\n DELETE: 'delete',\n UPDATE: 'update',\n INSERT: 'insert',\n} as const;\n\nexport type OperationType = (typeof OperationConst)[keyof typeof OperationConst];\n\n// Re-export from shared package\nexport { RecordConst } from '@dragonmastery/dragoncore-shared';\nexport type { RecordType } from '@dragonmastery/dragoncore-shared';\n","import { InferSelectModel } from 'drizzle-orm';\nimport { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n/**\n * Application settings table for storing global configuration\n * that doesn't need to be sharded.\n *\n * This includes default rates and other application-wide settings.\n */\nexport const app_settings_table = sqliteTable(\n 'app_settings',\n {\n // Key is the setting name, used as the primary key\n key: text('key').primaryKey(),\n\n // Value is stored as JSON\n value: text('value', { mode: 'json' })\n .$type<AppSettingValue<AppSettingKeyType>>()\n .notNull(),\n\n // Metadata for auditing and tracking\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by'),\n },\n (table) => ({\n // Essential metadata indexes\n created_at_idx: index('app_settings_created_at_idx').on(table.created_at),\n updated_at_idx: index('app_settings_updated_at_idx').on(table.updated_at),\n }),\n);\n\n/**\n * Known setting keys for type safety\n */\nexport const AppSettingKey = {\n GLOBAL_TAX_SETTINGS: 'global_tax_settings',\n DEFAULT_PRICING_LEVEL: 'default_pricing_level',\n TERMS_OF_SERVICE: 'terms_of_service',\n PRIVACY_POLICY: 'privacy_policy',\n MAX_SUPPORT_TICKET_ITEMS: 'max_support_ticket_items',\n CUSTOMER_MONTHLY_BALANCE: 'customer_monthly_balance',\n CUSTOMER_ROLLOVER_BALANCE: 'customer_rollover_balance',\n CUSTOMER_MONTHLY_ALLOCATION: 'customer_monthly_allocation',\n LAST_SUPPORT_TICKET_ASSIGNEE: 'last_support_ticket_assignee',\n} as const;\n\nexport type AppSettingKeyType = (typeof AppSettingKey)[keyof typeof AppSettingKey];\n/**\n */\nexport interface AppSettingValueMap {\n [AppSettingKey.DEFAULT_PRICING_LEVEL]: string;\n [AppSettingKey.TERMS_OF_SERVICE]: string;\n [AppSettingKey.PRIVACY_POLICY]: string;\n [AppSettingKey.MAX_SUPPORT_TICKET_ITEMS]: number;\n [AppSettingKey.CUSTOMER_MONTHLY_BALANCE]: string;\n [AppSettingKey.CUSTOMER_ROLLOVER_BALANCE]: string;\n [AppSettingKey.CUSTOMER_MONTHLY_ALLOCATION]: string;\n [AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE]: string;\n}\n\n/**\n * Type that returns the appropriate value type for a given setting key\n */\nexport type AppSettingValue<K extends AppSettingKeyType> = K extends keyof AppSettingValueMap\n ? AppSettingValueMap[K]\n : unknown;\n\nexport type AppSettingEntity = InferSelectModel<typeof app_settings_table>;\n","import { eq, inArray, sql } from 'drizzle-orm';\nimport { DrizzleD1Database } from 'drizzle-orm/d1';\nimport { inject, injectable } from 'tsyringe';\nimport { RecordType } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { UserSessionDetails } from '../../user_app_session';\nimport {\n AppSettingEntity,\n AppSettingKeyType,\n AppSettingValue,\n app_settings_table,\n} from './app_settings_table';\n\n/**\n * Repository for managing application settings\n * These settings are stored in a non-shardable database\n */\n@injectable()\nexport class AppSettingsRepo implements IAppSettingsRepo {\n constructor(\n @inject(TOKENS.IAppSettingsDb)\n private appSettingsDb: DrizzleD1Database<Record<string, never>>,\n ) {}\n\n /**\n * Get a setting value by key\n * @param key Setting key\n * @returns The setting value or undefined if not found\n */\n async readSetting<K extends AppSettingKeyType>(\n key: K,\n ): Promise<AppSettingEntity | undefined> {\n const [setting] = await this.appSettingsDb\n .select()\n .from(app_settings_table)\n .where(eq(app_settings_table.key, key))\n .limit(1);\n\n return setting;\n }\n\n /**\n * Save a setting value\n * @param key Setting key\n * @param value Setting value\n * @param user User making the change\n */\n async createSetting<K extends AppSettingKeyType>(\n key: K,\n value: AppSettingValue<K>,\n user: UserSessionDetails,\n ): Promise<void> {\n const now = new Date().toISOString();\n\n await this.appSettingsDb.insert(app_settings_table).values({\n key,\n value,\n created_at: now,\n created_by: user.userId,\n updated_at: now,\n updated_by: user.userId,\n });\n }\n\n async updateSetting<K extends AppSettingKeyType>(\n key: K,\n value: AppSettingValue<K>,\n user: UserSessionDetails,\n ): Promise<AppSettingValue<K>> {\n const now = new Date().toISOString();\n\n // Try to update first\n const [updated] = await this.appSettingsDb\n .update(app_settings_table)\n .set({\n value,\n updated_at: now,\n updated_by: user.userId,\n })\n .where(eq(app_settings_table.key, key))\n .returning();\n\n // If no row was updated, insert instead (upsert behavior)\n if (!updated) {\n const [inserted] = await this.appSettingsDb\n .insert(app_settings_table)\n .values({\n key,\n value,\n created_at: now,\n created_by: user.userId,\n updated_at: now,\n updated_by: user.userId,\n })\n .returning();\n\n return inserted?.value as AppSettingValue<K>;\n }\n\n return updated.value as AppSettingValue<K>;\n }\n\n /**\n * Delete a setting\n * @param key Setting key\n * @returns True if the setting was deleted, false if it didn't exist\n */\n async deleteSetting(key: AppSettingKeyType): Promise<boolean> {\n const result = await this.appSettingsDb\n .delete(app_settings_table)\n .where(eq(app_settings_table.key, key))\n .returning();\n\n return result.length > 0;\n }\n\n /**\n * Get all settings\n * @returns All settings as a map of key to value\n */\n async getAllSettings(): Promise<Map<AppSettingKeyType, unknown>> {\n const settings = await this.appSettingsDb.select().from(app_settings_table);\n\n const settingsMap = new Map<AppSettingKeyType, unknown>();\n for (const setting of settings) {\n settingsMap.set(setting.key as AppSettingKeyType, setting.value);\n }\n\n return settingsMap;\n }\n\n async readMultipleSettings<K extends AppSettingKeyType>(\n keys: K[],\n ): Promise<Map<K, AppSettingValue<K>>> {\n const settings = await this.appSettingsDb\n .select()\n .from(app_settings_table)\n .where(inArray(app_settings_table.key, keys));\n\n const settingsMap = new Map<K, AppSettingValue<K>>();\n for (const setting of settings) {\n settingsMap.set(setting.key as K, setting.value as AppSettingValue<K>);\n }\n\n return settingsMap;\n }\n\n async getNextSequentialId(recordType: RecordType, userId: string): Promise<string> {\n const counterKey = `${recordType}:counter`;\n const now = new Date().toISOString();\n\n const [result] = await this.appSettingsDb\n .insert(app_settings_table)\n .values({\n key: counterKey,\n value: 1, // Store as number, Drizzle will JSON-encode it as a number\n created_at: now,\n created_by: userId,\n updated_at: now,\n updated_by: userId,\n })\n .onConflictDoUpdate({\n target: app_settings_table.key,\n set: {\n // Simpler: json_extract already returns a number for JSON numbers, just increment and re-encode\n value: sql`json(json_extract(${app_settings_table.value}, '$') + 1)`,\n updated_at: now,\n updated_by: userId,\n },\n })\n .returning({ value: app_settings_table.value });\n\n // Ensure we always return a string, even if Drizzle's JSON mode parsed it as a number\n return String(result.value);\n }\n}\n\n/**\n * Interface for the AppSettingsRepo\n */\nexport interface IAppSettingsRepo {\n /**\n * Get a setting value by key\n * @param key Setting key\n * @returns The setting value or undefined if not found\n */\n readSetting<K extends AppSettingKeyType>(key: K): Promise<AppSettingEntity | undefined>;\n\n /**\n * Save a setting value\n * @param key Setting key\n * @param value Setting value\n * @param user User making the change\n */\n createSetting<K extends AppSettingKeyType>(\n key: K,\n value: AppSettingValue<K>,\n user: UserSessionDetails,\n ): Promise<void>;\n\n updateSetting<K extends AppSettingKeyType>(\n key: K,\n value: AppSettingValue<K>,\n user: UserSessionDetails,\n ): Promise<AppSettingValue<K>>;\n /**\n * Delete a setting\n * @param key Setting key\n * @returns True if the setting was deleted, false if it didn't exist\n */\n deleteSetting(key: AppSettingKeyType): Promise<boolean>;\n\n /**\n * Get all settings\n * @returns All settings as a map of key to value\n */\n getAllSettings(): Promise<Map<AppSettingKeyType, unknown>>;\n\n /**\n * Get multiple setting values by key\n * @param keys Array of setting keys\n * @returns A map of the requested settings\n */\n readMultipleSettings<K extends AppSettingKeyType>(\n keys: K[],\n ): Promise<Map<K, AppSettingValue<K>>>;\n\n getNextSequentialId(recordType: RecordType, userId: string): Promise<string>;\n}\n","import { sql } from 'drizzle-orm';\nimport { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { OperationType, RecordType } from '../../../db/dbTypes';\n\n// Note: No unique constraints are used due to sharding requirements\nexport const record_version_table = sqliteTable(\n 'record_version',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n record_id: text('record_id').notNull(),\n operation: text('operation').$type<OperationType>().notNull(),\n recorded_at: text('recorded_at')\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull(),\n record_type: text('record_type').$type<RecordType>().notNull(),\n record: text('record', { mode: 'json' }).$type<any>(),\n old_record: text('old_record', { mode: 'json' }).$type<any>(),\n auth_uid: text('auth_uid'),\n auth_role: text('auth_role'),\n auth_username: text('auth_username'),\n },\n (table) => {\n return {\n // Non-unique indexes for common lookups\n record_id_idx: index('record_version_record_id_idx').on(table.record_id),\n record_type_idx: index('record_version_record_type_idx').on(table.record_type),\n recorded_at_idx: index('record_version_recorded_at_idx').on(table.recorded_at),\n auth_uid_idx: index('record_version_auth_uid_idx').on(table.auth_uid),\n operation_idx: index('record_version_operation_idx').on(table.operation),\n };\n },\n);\n","import { blob, index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n// Note: No foreign key constraints are used due to sharding requirements\nexport const mfa_secret_table = sqliteTable(\n 'mfa_secret',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n // Foreign key reference removed due to sharding\n user_id: text('user_id').notNull(),\n secret: text('secret').notNull(),\n recovery_codes: blob('recovery_codes').notNull(),\n enabled: text('enabled', {\n enum: ['true', 'false'],\n })\n .notNull()\n .default('false'),\n mfa_method: text('mfa_method').notNull(),\n },\n (table) => {\n return {\n // Non-unique indexes for common lookups\n user_id_idx: index('mfa_secret_user_id_idx').on(table.user_id),\n };\n },\n);\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n// Note: No unique constraints are used due to sharding requirements\nexport const oauth_provider_table = sqliteTable(\n 'oauth_provider',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n provider_name: text('provider_name').notNull(), // google, facebook, etc\n client_id: text('client_id').notNull(),\n client_secret: text('client_secret').notNull(),\n enabled: text('enabled', {\n enum: ['true', 'false'],\n })\n .notNull()\n .default('false'),\n },\n (table) => ({\n // Non-unique indexes for common lookups\n provider_name_idx: index('oauth_provider_name_idx').on(table.provider_name),\n }),\n);\n","import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n// Note: No unique constraints or foreign keys are used due to sharding requirements\nexport const user_oauth_account_table = sqliteTable(\n 'user_oauth_account',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n // Foreign key references removed due to sharding\n user_id: text('user_id').notNull(),\n provider_id: text('provider_id').notNull(),\n provider_user_id: text('provider_user_id'),\n access_token: text('access_token'),\n refresh_token: text('refresh_token'),\n token_type: text('token_type'),\n scope: text('scope'),\n expires_at: integer('expires_at', {\n mode: 'number',\n }),\n profile_info: text('profile_info'),\n },\n (table) => {\n return {\n // Non-unique indexes for common lookups\n user_id_idx: index('user_oauth_user_id_idx').on(table.user_id),\n provider_id_idx: index('user_oauth_provider_id_idx').on(table.provider_id),\n };\n },\n);\n","import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { RecordType } from '../../../db/dbTypes';\n\n// Note: No unique constraints are used due to sharding requirements\nexport const attachment_folder_table = sqliteTable(\n 'attachment_folder',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n record_id: text('record_id').notNull(),\n record_type: text('record_type').$type<RecordType>().notNull(),\n sanitized_name: text('sanitized_name').notNull(),\n original_name: text('original_name').notNull(),\n description: text('description'),\n metadata: text('metadata'),\n parent_folder_id: text('parent_folder_id'), // For nested folders\n file_count: integer('file_count').notNull().default(0), // Cached count of files in folder\n\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n archived_at: text('archived_at'),\n archived_by: text('archived_by'),\n deleted_at: text('deleted_at'),\n deleted_by: text('deleted_by'),\n },\n (table) => {\n return {\n // Non-unique indexes for common lookups\n record_id_idx: index('attachment_folder_record_id_idx').on(table.record_id),\n record_type_idx: index('attachment_folder_record_type_idx').on(table.record_type),\n sanitized_name_idx: index('attachment_folder_sanitized_name_idx').on(\n table.sanitized_name,\n ),\n parent_folder_id_idx: index('attachment_folder_parent_folder_id_idx').on(\n table.parent_folder_id,\n ),\n\n // Essential metadata indexes\n created_at_idx: index('attachment_folder_created_at_idx').on(table.created_at),\n updated_at_idx: index('attachment_folder_updated_at_idx').on(table.updated_at),\n deleted_at_idx: index('attachment_folder_deleted_at_idx').on(table.deleted_at),\n\n // Optional: Composite index for hottest query path\n deletedUpdatedIdx: index('attachment_folder_deleted_updated_idx').on(\n table.deleted_at,\n table.updated_at,\n ),\n };\n },\n);\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { RecordType } from '../../../db/dbTypes';\n\n// Note: No unique constraints are used due to sharding requirements\nexport const attachment_table = sqliteTable(\n 'attachment',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n record_id: text('record_id').notNull(),\n record_type: text('record_type').$type<RecordType>().notNull(),\n sanitized_name: text('sanitized_name').notNull(),\n original_name: text('original_name').notNull(),\n content_type: text('content_type').notNull(),\n file_size: text('file_size').notNull(),\n description: text('description'),\n metadata: text('metadata'),\n folder_id: text('folder_id'), // References attachment_folder.id\n\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n archived_at: text('archived_at'),\n archived_by: text('archived_by'),\n deleted_at: text('deleted_at'),\n deleted_by: text('deleted_by'),\n },\n (table) => {\n return {\n // Non-unique indexes for common lookups\n record_id_idx: index('attachment_record_id_idx').on(table.record_id),\n record_type_idx: index('attachment_record_type_idx').on(table.record_type),\n sanitized_name_idx: index('attachment_sanitized_name_idx').on(table.sanitized_name),\n folder_id_idx: index('attachment_folder_id_idx').on(table.folder_id),\n\n // Essential metadata indexes\n created_at_idx: index('attachment_created_at_idx').on(table.created_at),\n updated_at_idx: index('attachment_updated_at_idx').on(table.updated_at),\n deleted_at_idx: index('attachment_deleted_at_idx').on(table.deleted_at),\n\n // Optional: Composite index for hottest query path\n deletedUpdatedIdx: index('attachment_deleted_updated_idx').on(\n table.deleted_at,\n table.updated_at,\n ),\n };\n },\n);\n","import { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const note_table = sqliteTable(\n 'note',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n record_id: text('record_id').notNull(),\n record_type: text('record_type').$type<RecordType>().notNull(),\n tag: text('tag'),\n title: text('title'),\n body: text('body'),\n original_id: integer('original_id'),\n is_internal: integer('is_internal', { mode: 'boolean' }).notNull().default(false),\n\n created_by: text('created_by').notNull(),\n created_at: text('created_at').notNull(),\n updated_by: text('updated_by').notNull(),\n updated_at: text('updated_at').notNull(),\n archived_by: text('archived_by'),\n archived_at: text('archived_at'),\n deleted_by: text('deleted_by'),\n deleted_at: text('deleted_at'),\n },\n (table) => [\n // Indexes for efficient queries\n index('note_record_id_idx').on(table.record_id),\n index('note_record_type_idx').on(table.record_type),\n index('note_tag_idx').on(table.tag),\n index('note_title_idx').on(table.title),\n index('note_is_internal_idx').on(table.is_internal),\n index('note_created_at_idx').on(table.created_at),\n index('note_updated_at_idx').on(table.updated_at),\n index('note_deleted_at_idx').on(table.deleted_at),\n\n // Optional: Composite index for hottest query path\n index('note_deleted_updated_idx').on(table.deleted_at, table.updated_at),\n ],\n);\n\nexport type InsertNoteEntity = Omit<typeof note_table.$inferInsert, 'id'>;\nexport interface UpdateNoteEntity extends Omit<\n typeof note_table.$inferInsert,\n 'created_at' | 'created_by'\n> {}\nexport type ReadNoteEntity = typeof note_table.$inferSelect;\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const saved_filter_table = sqliteTable(\n 'saved_filter',\n {\n id: text('id').primaryKey(),\n user_id: text('user_id').notNull(),\n name: text('name').notNull(),\n context: text('context').notNull(),\n route_path: text('route_path').notNull(),\n filters: text('filters').notNull(), // JSON string of Record<string, string | string[]>\n sort_by: text('sort_by'),\n sort_direction: text('sort_direction'),\n created_at: text('created_at').notNull(),\n updated_at: text('updated_at').notNull(),\n },\n (table) => [\n index('saved_filter_user_id_idx').on(table.user_id),\n index('saved_filter_context_idx').on(table.context),\n index('saved_filter_user_context_idx').on(table.user_id, table.context),\n ],\n);\n","import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const MAX_PINNED_PRESETS = 5;\n\nexport const user_pinned_preset_table = sqliteTable(\n 'user_pinned_preset',\n {\n id: text('id').primaryKey(),\n user_id: text('user_id').notNull(),\n preset_id: text('preset_id').notNull(),\n position: integer('position').notNull(),\n },\n (table) => [\n index('user_pinned_preset_user_id_idx').on(table.user_id),\n index('user_pinned_preset_preset_id_idx').on(table.preset_id),\n ],\n);\n","import type { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n/**\n * Generic record subscribers - users who receive notifications on record events.\n * Reusable for support_ticket, tracker, and other record types.\n * Email address comes from user's notification_email (user_profile) or user.email if not set.\n */\nexport const record_subscriber_table = sqliteTable(\n 'record_subscriber',\n {\n id: text('id').primaryKey(),\n record_type: text('record_type').$type<RecordType>().notNull(),\n record_id: text('record_id').notNull(),\n user_id: text('user_id').notNull(),\n // JSON array of event types to subscribe to; null = all events. Event types are entity-specific.\n subscribed_events: text('subscribed_events', { mode: 'json' }).$type<string[]>(),\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n },\n (table) => ({\n recordTypeIdIdx: index('record_subscriber_record_type_id_idx').on(\n table.record_type,\n table.record_id,\n ),\n userIdIdx: index('record_subscriber_user_id_idx').on(table.user_id),\n uniqueSubscriberIdx: index('record_subscriber_unique_idx').on(\n table.record_type,\n table.record_id,\n table.user_id,\n ),\n }),\n);\n\nexport type InsertRecordSubscriberEntity = Omit<\n typeof record_subscriber_table.$inferInsert,\n 'id'\n>;\nexport type ReadRecordSubscriberEntity = typeof record_subscriber_table.$inferSelect;\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n/**\n * Support staff - users who can triage support tickets.\n * Small table instead of flagging every user with can_triage.\n */\nexport const support_staff_table = sqliteTable(\n 'support_staff',\n {\n id: text('id').primaryKey(),\n user_id: text('user_id').notNull().unique(),\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n },\n (table) => ({\n userIdIdx: index('support_staff_user_id_idx').on(table.user_id),\n }),\n);\n\nexport type InsertSupportStaffEntity = Omit<\n typeof support_staff_table.$inferInsert,\n 'id'\n>;\nexport type ReadSupportStaffEntity = typeof support_staff_table.$inferSelect;\n","import {\n SupportTicketApprovalEnum,\n SupportTicketDevLifecycleEnum,\n SupportTicketTypeEnum,\n} from '@dragonmastery/dragoncore-shared';\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n/**\n * Database schema for feature requests\n */\nexport const support_ticket_table = sqliteTable(\n 'support_ticket',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n display_id: text('display_id'),\n title: text('title').notNull(),\n description: text('description').notNull(),\n type: text('type', {\n enum: SupportTicketTypeEnum,\n })\n .notNull()\n .default('FEATURE_REQUEST'),\n priority: integer('priority', { mode: 'number' }).notNull().default(2),\n approval_status: text('approval_status', {\n enum: SupportTicketApprovalEnum,\n })\n .notNull()\n .default('PENDING'),\n // Assigned triage person (round-robin among users with can_triage)\n assigned_to: text('assigned_to'),\n\n // Note: customer_notes and internal_notes are stored in record_versions table\n // with record types SUPPORT_TICKET_CUSTOMER_NOTE and SUPPORT_TICKET_INTERNAL_NOTE\n // This provides complete audit history for chat-like interfaces\n\n // Amount charged to customer (locked at approval)\n credit_value: text('credit_value'),\n // staff only - the value of the work done (for staff tracking purposes only, set at completion)\n delivered_value: text('delivered_value'),\n\n // staff only - the development lifecycle state of the support_ticket\n dev_lifecycle: text('dev_lifecycle', {\n enum: SupportTicketDevLifecycleEnum,\n }),\n\n // Timestamp when approval status changed from PENDING - locks the customer side to read-only\n locked_approval_at: text('locked_approval_at'),\n credits_set_at: text('credits_set_at'), // When credits were set (staff managed)\n\n // Timeline tracking (staff managed)\n start_at: text('start_at'), // When work started\n target_at: text('target_at'), // Target completion date\n completed_at: text('completed_at'), // When work reached terminal state (DEPLOYED or CANCELLED)\n\n // Metadata\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n archived_at: text('archived_at'),\n archived_by: text('archived_by'),\n deleted_at: text('deleted_at'),\n deleted_by: text('deleted_by'),\n },\n (table) => ({\n // Core filtering indexes\n displayIdIdx: index('support_ticket_display_id_idx').on(table.display_id),\n titleIdx: index('support_ticket_title_idx').on(table.title),\n typeIdx: index('support_ticket_type_idx').on(table.type),\n priorityIdx: index('support_ticket_priority_idx').on(table.priority),\n approvalStatusIdx: index('support_ticket_approval_status_idx').on(table.approval_status),\n devLifecycleIdx: index('support_ticket_dev_lifecycle_idx').on(table.dev_lifecycle),\n\n // User-based indexes\n assignedToIdx: index('support_ticket_assigned_to_idx').on(table.assigned_to),\n\n // Value-based indexes\n creditValueIdx: index('support_ticket_credit_value_idx').on(table.credit_value),\n deliveredValueIdx: index('support_ticket_delivered_value_idx').on(table.delivered_value),\n\n // Timestamp indexes\n createdAtIdx: index('support_ticket_created_at_idx').on(table.created_at),\n updatedAtIdx: index('support_ticket_updated_at_idx').on(table.updated_at),\n startAtIdx: index('support_ticket_start_at_idx').on(table.start_at),\n targetAtIdx: index('support_ticket_target_at_idx').on(table.target_at),\n completedAtIdx: index('support_ticket_completed_at_idx').on(table.completed_at),\n\n // Essential metadata indexes\n deletedAtIdx: index('support_ticket_deleted_at_idx').on(table.deleted_at),\n deletedByIdx: index('support_ticket_deleted_by_idx').on(table.deleted_by),\n\n // Optional: Composite index for hottest query path\n deletedUpdatedIdx: index('support_ticket_deleted_updated_idx').on(\n table.deleted_at,\n table.updated_at,\n ),\n }),\n);\n\nexport type InsertSupportTicketEntity = Omit<typeof support_ticket_table.$inferInsert, 'id'>;\nexport interface UpdateSupportTicketEntity extends Omit<\n typeof support_ticket_table.$inferInsert,\n 'created_at' | 'created_by'\n> {}\nexport type ReadSupportTicketEntity = typeof support_ticket_table.$inferSelect;\n","import { sql } from 'drizzle-orm';\nimport { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const team_table = sqliteTable(\n 'team',\n {\n // Primary key\n id: text('id').primaryKey(),\n original_id: text('original_id').unique(), // nullable - for imported/migrated records\n\n // Basic team information\n unique_name: text('unique_name'),\n display_name: text('display_name').notNull(),\n display_name_lower: text('display_name_lower').notNull().unique(),\n legal_name: text('legal_name'),\n legal_name_lower: text('legal_name_lower').unique(),\n description: text('description'),\n\n // Contact information\n contact_name: text('contact_name'),\n contact_email: text('contact_email'),\n contact_business_phone: text('contact_business_phone'),\n contact_mobile_phone: text('contact_mobile_phone'),\n contact_time_zone: text('contact_time_zone'),\n\n // Address information\n address_full: text('address_full'),\n address_city: text('address_city'),\n address_zip: text('address_zip'),\n\n // Web presence\n twitter_username: text('twitter_username'),\n url: text('url'),\n\n // Branding\n logo: text('logo'),\n\n // Email settings\n email_sent_from: text('email_sent_from'),\n email_reply_to: text('email_reply_to'),\n\n // Computed fields (stored for performance)\n path: text('path'),\n\n // Audit fields\n created_at: text('created_at')\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n\n archived_at: text('archived_at'),\n archived_by: text('archived_by'),\n },\n (table) => ({\n // Core filtering indexes\n unique_name_idx: index('team_unique_name_idx').on(table.unique_name),\n display_name_idx: index('team_display_name_idx').on(table.display_name),\n display_name_lower_idx: index('team_display_name_lower_idx').on(table.display_name_lower),\n legal_name_idx: index('team_legal_name_idx').on(table.legal_name),\n contact_email_idx: index('team_contact_email_idx').on(table.contact_email),\n\n // Essential metadata indexes\n created_at_idx: index('team_created_at_idx').on(table.created_at),\n updated_at_idx: index('team_updated_at_idx').on(table.updated_at),\n archived_at_idx: index('team_archived_at_idx').on(table.archived_at),\n }),\n);\n","import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const team_member_table = sqliteTable(\n 'team_member',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n original_id: integer('original_id'),\n team_id: text('team_id').notNull(),\n original_team_id: text('original_team_id'),\n user_id: text('user_id').notNull(),\n original_user_id: text('original_user_id'),\n role: text('role').notNull(),\n display_name: text('display_name').notNull(),\n display_name_lower: text('display_name_lower').notNull(),\n business_phone: text('business_phone'),\n mobile_phone: text('mobile_phone'),\n email_address: text('email_address').notNull(),\n website_address: text('website_address'),\n time_zone: text('time_zone'),\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n deleted_at: text('deleted_at'),\n deleted_by: text('deleted_by'),\n },\n (table) => ({\n // Indexes for efficient queries\n originalIdIdx: index('team_member_original_id_idx').on(table.original_id),\n teamIdIdx: index('team_member_team_id_idx').on(table.team_id),\n originalTeamIdIdx: index('team_member_original_team_id_idx').on(table.original_team_id),\n userIdIdx: index('team_member_user_id_idx').on(table.user_id),\n originalUserIdIdx: index('team_member_original_user_id_idx').on(table.original_user_id),\n roleIdx: index('team_member_role_idx').on(table.role),\n displayNameLowerIdx: index('team_member_display_name_lower_idx').on(\n table.display_name_lower,\n ),\n emailAddressIdx: index('team_member_email_address_idx').on(table.email_address),\n timeZoneIdx: index('team_member_time_zone_idx').on(table.time_zone),\n createdAtIdx: index('team_member_created_at_idx').on(table.created_at),\n updatedAtIdx: index('team_member_updated_at_idx').on(table.updated_at),\n deletedAtIdx: index('team_member_deleted_at_idx').on(table.deleted_at),\n deletedByIdx: index('team_member_deleted_by_idx').on(table.deleted_by),\n\n // Optional: Composite index for hottest query path\n deletedUpdatedIdx: index('team_member_deleted_updated_idx').on(\n table.deleted_at,\n table.updated_at,\n ),\n }),\n);\n\nexport type InsertTeamMemberEntity = Omit<typeof team_member_table.$inferInsert, 'id'>;\nexport type UpdateTeamMemberEntity = Partial<\n Omit<typeof team_member_table.$inferInsert, 'id'>\n> & {\n id: string;\n};\nexport type ReadTeamMemberEntity = typeof team_member_table.$inferSelect;\n","import { USER_TYPES } from '@dragonmastery/dragoncore-shared';\nimport { sql } from 'drizzle-orm';\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport interface UserUniqueConstraints {\n username: string;\n email: string;\n}\n\n// Note: No unique constraints are used due to sharding requirements\nexport const user_table = sqliteTable(\n 'user',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n original_id: text('original_id').unique(), // nullable - for imported/migrated records\n username: text('username').notNull().unique(),\n hashed_password: text('hashed_password').notNull(),\n // ideally unique, but we wont enforce it across shards\n salt: text('salt').notNull().unique(),\n email: text('email').notNull().unique(),\n email_verified: integer('email_verified', {\n mode: 'boolean',\n }).notNull(),\n user_type: text('user_type', {\n enum: USER_TYPES,\n })\n .notNull()\n .default('consumer'),\n failed_login_attempts: integer('failed_login_attempts').notNull().default(0),\n lockout_until: integer('lockout_until', {\n mode: 'number',\n }),\n created_at: text('created_at')\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull(),\n updated_at: text('updated_at'),\n },\n (table) => ({\n // Core filtering indexes\n username_idx: index('user_username_idx').on(table.username),\n email_idx: index('user_email_idx').on(table.email),\n userTypeIdx: index('user_user_type_idx').on(table.user_type),\n\n // Essential metadata indexes\n created_at_idx: index('user_created_at_idx').on(table.created_at),\n updated_at_idx: index('user_updated_at_idx').on(table.updated_at),\n }),\n);\n","import { sql } from 'drizzle-orm';\nimport { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\n// Note: No foreign key constraints are used due to sharding requirements\nexport const user_profile_table = sqliteTable(\n 'user_profile',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n user_id: text('user_id').notNull().unique(),\n first_name: text('first_name', { length: 255 }),\n last_name: text('last_name', { length: 255 }),\n additional_name: text('additional_name', { length: 255 }),\n avatar_url: text('avatar_url'),\n // Notification email for support ticket followers, etc. (GitHub-style). Falls back to user.email if null.\n notification_email: text('notification_email'),\n gender: text('gender', { length: 255 }),\n bio: text('bio'),\n birthday: text('birthday'),\n created_at: text('created_at')\n .default(sql`CURRENT_TIMESTAMP`)\n .notNull(),\n created_by: text('created_by').notNull(),\n updated_at: text('updated_at').notNull(),\n updated_by: text('updated_by').notNull(),\n },\n (table) => ({\n // Essential metadata indexes\n user_id_idx: index('user_profile_user_id_idx').on(table.user_id),\n created_at_idx: index('user_profile_created_at_idx').on(table.created_at),\n updated_at_idx: index('user_profile_updated_at_idx').on(table.updated_at),\n }),\n);\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport type TokenStatus = 'active' | 'revoked' | 'expired';\n\n// Note: No foreign key constraints are used due to sharding requirements\nexport const refresh_token_table = sqliteTable(\n 'refresh_token',\n {\n // ID is generated by universal_id_generator\n id: text('id').primaryKey(),\n // Foreign key reference removed due to sharding\n user_id: text('user_id').notNull(),\n token_family: text('token_family').notNull(), // For token rotation tracking\n previous_token_id: text('previous_token_id'), // Link to previous token in rotation\n created_at: text('created_at').notNull(),\n expires_at: text('expires_at').notNull(),\n status: text('status', { enum: ['active', 'revoked', 'expired'] })\n .notNull()\n .default('active'),\n user_agent: text('user_agent'),\n ip_address: text('ip_address'),\n revoked_at: text('revoked_at'),\n revocation_reason: text('revocation_reason'),\n },\n (table) => ({\n // Non-unique indexes for common lookups\n user_id_idx: index('refresh_token_user_id_idx').on(table.user_id),\n status_idx: index('refresh_token_status_idx').on(table.status),\n family_idx: index('refresh_token_family_idx').on(table.token_family),\n previous_token_idx: index('refresh_token_previous_token_idx').on(table.previous_token_id),\n\n // Essential metadata indexes\n created_at_idx: index('refresh_token_created_at_idx').on(table.created_at),\n expires_at_idx: index('refresh_token_expires_at_idx').on(table.expires_at),\n }),\n);\n","import { UserAppSession } from './user_app_session';\n\nexport type SessionState =\n | AuthenticatedSessionState\n | UnauthenticatedSessionState\n | RevokedSessionState\n | ExpiredSessionState;\n\nexport interface AuthenticatedSessionState {\n type: 'authenticated';\n session: UserAppSession;\n}\n\nexport interface UnauthenticatedSessionState {\n type: 'unauthenticated';\n}\n\nexport interface RevokedSessionState {\n type: 'revoked';\n}\n\nexport interface ExpiredSessionState {\n type: 'expired';\n}\n\nexport const createUnauthenticatedState = (): SessionState => ({\n type: 'unauthenticated',\n});\n\nexport const createRevokedState = (): SessionState => ({\n type: 'revoked',\n});\n\nexport const createExpiredState = (): SessionState => ({\n type: 'expired',\n});\n\nexport const createAuthenticatedState = (session: UserAppSession): SessionState => ({\n type: 'authenticated',\n session,\n});\n\n/**\n * Helper to get session from state when we know it's authenticated\n * Should only be used after validateSessionMiddleware has run\n */\nexport const getAuthenticatedSession = (state: SessionState): UserAppSession => {\n if (state.type !== 'authenticated') {\n console.error(\n 'Session not authenticated - validateSessionMiddleware should have caught this',\n );\n throw new Error('Session not authenticated');\n }\n return state.session;\n};\n","/**\n * Shared utility functions for formatting credit values\n */\n\n/**\n * Helper function to check if credit value is empty\n */\nexport function isCreditValueEmpty(value: string | null | undefined): boolean {\n return value === null || value === undefined || value.trim() === '';\n}\n\n/**\n * Helper function to format credit value to 2 decimal places\n * Returns null for empty values, formatted string for valid numbers\n *\n * @param value - The credit value to format\n * @returns Formatted credit value or null\n *\n * @example\n * formatCreditValue(\"5.50\") // \"5.5\"\n * formatCreditValue(\"0\") // \"0\"\n * formatCreditValue(\"0.00\") // \"0\"\n * formatCreditValue(\"\") // null\n * formatCreditValue(null) // null\n */\nexport function formatCreditValue(value: string | null | undefined): string | null {\n if (isCreditValueEmpty(value)) {\n return null;\n }\n\n const trimmed = value!.trim();\n const num = parseFloat(trimmed);\n\n // Explicit zero is valid - preserve it\n if (num === 0) {\n return '0';\n }\n\n // Remove trailing zeros for non-zero values\n // Examples: \"100.00\" -> \"100\", \"9.50\" -> \"9.5\", \"123.45\" -> \"123.45\"\n return trimmed.replace(/\\.?0+$/, '');\n}\n","/**\n * Log levels as string literals\n */\nexport const LOG_LEVEL = {\n NONE: 'NONE',\n ERROR: 'ERROR',\n WARN: 'WARN',\n INFO: 'INFO',\n DEBUG: 'DEBUG',\n TRACE: 'TRACE',\n} as const;\n\nexport type LogLevel = (typeof LOG_LEVEL)[keyof typeof LOG_LEVEL];\n\n/**\n * Interface for logger context that can be passed to each log method\n */\nexport interface LoggerContext {\n requestId?: string;\n [key: string]: any;\n}\n\n/**\n * Logger utility that conditionally logs based on configured log level\n */\nexport class Logger {\n private logLevel: LogLevel = LOG_LEVEL.INFO; // Default log level\n private requestId?: string;\n private performanceLogsEnabled: boolean = false; // Default performance logs off\n\n constructor(env?: Env, requestId?: string) {\n if (env) {\n this.setLogLevelFromWorkerEnv(env, requestId);\n } else {\n this.setLogLevelFromEnv();\n }\n }\n\n /**\n * Set the log level from environment variables\n * This is a no-op initially since we can't access env in the constructor\n * The actual log level will be set when setLogLevelFromEnv is called with the env object\n */\n private setLogLevelFromEnv(): void {\n // Default to INFO until explicitly set\n this.logLevel = LOG_LEVEL.INFO;\n }\n\n /**\n * Set the log level from the Cloudflare Worker environment\n * Optimized: Cache environment checks to avoid repeated string operations\n */\n public setLogLevelFromWorkerEnv(env: Env, requestId?: string): void {\n // Set request ID if provided\n if (requestId) {\n this.requestId = requestId;\n }\n\n // Set performance logging based on environment\n this.setPerformanceLogsFromEnv(env);\n\n // For server-side, we use env.LOG_LEVEL\n const logLevel = env.LOG_LEVEL;\n if (logLevel && typeof logLevel === 'string') {\n const upperCaseLevel = logLevel.toUpperCase() as LogLevel;\n if (upperCaseLevel in LOG_LEVEL) {\n this.logLevel = upperCaseLevel;\n return; // Early return to skip environment check\n }\n }\n\n // Default log level based on environment (cached check)\n const environment = env.ENVIRONMENT;\n if (environment === 'local' || environment === 'dev') {\n this.logLevel = LOG_LEVEL.DEBUG;\n } else if (environment === 'staging' || environment === 'prod') {\n this.logLevel = LOG_LEVEL.INFO;\n }\n }\n\n /**\n * Set performance logging based on environment variables\n * Optimized: Cache environment check\n */\n private setPerformanceLogsFromEnv(env: Env): void {\n // Check if performance logs are explicitly enabled/disabled\n // ENABLE_PERFORMANCE_LOGS is an optional env var not in the generated types\n const envWithOptional = env as Env & { ENABLE_PERFORMANCE_LOGS?: string };\n const perfLogsEnv = envWithOptional.ENABLE_PERFORMANCE_LOGS;\n if (perfLogsEnv !== undefined) {\n this.performanceLogsEnabled = perfLogsEnv === 'true';\n } else {\n // Default behavior: only enable in local/dev environments\n const environment = env.ENVIRONMENT;\n this.performanceLogsEnabled = environment === 'local' || environment === 'dev';\n }\n }\n\n /**\n * Set the log level manually\n */\n public setLogLevel(level: LogLevel): void {\n this.logLevel = level;\n }\n\n /**\n * Get the current log level\n */\n public getLogLevel(): LogLevel {\n return this.logLevel;\n }\n\n /**\n * Set performance logging enabled/disabled\n */\n public setPerformanceLogsEnabled(enabled: boolean): void {\n this.performanceLogsEnabled = enabled;\n }\n\n /**\n * Get the current performance logging setting\n */\n public getPerformanceLogsEnabled(): boolean {\n return this.performanceLogsEnabled;\n }\n\n /**\n * Static helper to check if performance logs would be enabled for an environment\n * Useful for checking before creating a Logger instance\n */\n public static wouldEnablePerfLogs(env: Env): boolean {\n const envWithOptional = env as Env & { ENABLE_PERFORMANCE_LOGS?: string };\n const perfLogsEnv = envWithOptional.ENABLE_PERFORMANCE_LOGS;\n if (perfLogsEnv !== undefined) {\n return perfLogsEnv === 'true';\n }\n // Default behavior: only enable in local/dev environments\n const environment = env.ENVIRONMENT;\n return environment === 'local' || environment === 'dev';\n }\n\n /**\n * Determine if a message at the given level should be logged\n * based on the current log level setting\n * Optimized: Use static map to avoid object creation on every call\n */\n private static readonly LEVEL_PRIORITY: Record<LogLevel, number> = {\n [LOG_LEVEL.NONE]: 0,\n [LOG_LEVEL.ERROR]: 1,\n [LOG_LEVEL.WARN]: 2,\n [LOG_LEVEL.INFO]: 3,\n [LOG_LEVEL.DEBUG]: 4,\n [LOG_LEVEL.TRACE]: 5,\n };\n\n private shouldLog(level: LogLevel): boolean {\n // Compare current log level with requested level\n // Only log if the current level is equal to or higher than the requested level\n return Logger.LEVEL_PRIORITY[this.logLevel] >= Logger.LEVEL_PRIORITY[level];\n }\n\n /**\n * Set the request ID for the current logger instance\n */\n public setRequestId(requestId: string): void {\n this.requestId = requestId;\n }\n\n /**\n * Get the request ID for the current logger instance\n */\n public getRequestId(): string | undefined {\n return this.requestId;\n }\n\n /**\n * Log structured data as JSON for better indexing and querying\n * Optimized: No expensive sanitization - Cloudflare Workers handles large objects gracefully\n */\n private logStructured(level: LogLevel, message: string, data?: Record<string, any>): void {\n const structuredLog = {\n level,\n message,\n request_id: this.requestId,\n timestamp: new Date().toISOString(),\n ...(data || {}),\n };\n console.log(structuredLog);\n }\n\n /**\n * Log a trace message (most verbose)\n */\n public trace(message: string, data?: Record<string, any>): void {\n if (this.shouldLog(LOG_LEVEL.TRACE)) {\n this.logStructured(LOG_LEVEL.TRACE, message, data);\n }\n }\n\n /**\n * Log a debug message\n */\n public debug(message: string, data?: Record<string, any>): void {\n if (this.shouldLog(LOG_LEVEL.DEBUG)) {\n this.logStructured(LOG_LEVEL.DEBUG, message, data);\n }\n }\n\n /**\n * Log an info message\n */\n public info(message: string, data?: Record<string, any>): void {\n if (this.shouldLog(LOG_LEVEL.INFO)) {\n this.logStructured(LOG_LEVEL.INFO, message, data);\n }\n }\n\n /**\n * Log a warning message\n */\n public warn(message: string, data?: Record<string, any>): void {\n if (this.shouldLog(LOG_LEVEL.WARN)) {\n this.logStructured(LOG_LEVEL.WARN, message, data);\n }\n }\n\n /**\n * Log an error message\n */\n public error(message: string, data?: Record<string, any>): void {\n if (this.shouldLog(LOG_LEVEL.ERROR)) {\n this.logStructured(LOG_LEVEL.ERROR, message, data);\n }\n }\n\n /**\n * Log a performance message (controlled by performance logging setting)\n */\n public perf(message: string, data?: Record<string, any>): void {\n if (this.performanceLogsEnabled) {\n this.logStructured('PERF' as LogLevel, message, data);\n }\n }\n}\n\n// Export the Logger class and a default instance for simple use cases\nexport const logger = new Logger();\n","import { CookieOptions, CookiePrefixOptions, parseSigned } from 'hono/utils/cookie';\nimport { injectable } from 'tsyringe';\nimport type { HonoContext } from '../types/hono_context';\n\nexport interface CookieContext extends HonoContext {\n env: Env;\n cookies?: Array<{\n name: string;\n value: string;\n secret: string | BufferSource;\n options?: CookieOptions & {\n prefix?: CookiePrefixOptions;\n };\n }>;\n}\n\nexport interface ICookieService {\n getSignedCookie(\n context: CookieContext,\n secret: string | BufferSource,\n key: string,\n prefix?: CookiePrefixOptions,\n ): Promise<string | undefined | false>;\n setSignedCookie(\n context: CookieContext,\n name: string,\n value: string,\n secret: string | BufferSource,\n options?: CookieOptions & {\n prefix?: CookiePrefixOptions;\n },\n ): Promise<void>;\n}\n\n@injectable()\nexport class CookieService implements ICookieService {\n async getSignedCookie(\n context: CookieContext,\n secret: string | BufferSource,\n key: string,\n prefix?: CookiePrefixOptions,\n ): Promise<string | undefined | false> {\n const cookie = context.req.header('Cookie');\n if (!cookie) {\n return undefined;\n }\n let finalKey = key;\n if (prefix === 'secure') {\n finalKey = '__Secure-' + key;\n } else if (prefix === 'host') {\n finalKey = '__Host-' + key;\n }\n const obj = await parseSigned(cookie, secret, finalKey);\n const result = obj[finalKey];\n return result;\n }\n\n async setSignedCookie(\n context: CookieContext,\n name: string,\n value: string,\n secret: string | BufferSource,\n options?: CookieOptions & {\n prefix?: CookiePrefixOptions;\n },\n ): Promise<void> {\n if (!context.cookies) {\n context.cookies = [];\n }\n\n context.cookies.push({\n name,\n value,\n secret,\n options,\n });\n }\n}\n","export const DISPLAY_ID_PREFIX_TOKENS = {\n IDisplayIdPrefixService: Symbol('IDisplayIdPrefixService'),\n PrefixRegistry: Symbol('PrefixRegistry'),\n};\n","import { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { DISPLAY_ID_PREFIX_TOKENS } from './display_id_prefix_tokens';\nexport { DISPLAY_ID_PREFIX_TOKENS } from './display_id_prefix_tokens';\n\n/**\n * Interface for display ID prefix service\n * Consuming projects can implement this to provide custom prefixes\n */\nexport interface IDisplayIdPrefixService {\n /**\n * Gets the display ID prefix for a record type\n * @param recordType - The record type to get the prefix for\n * @returns The prefix string or null if no prefix is configured\n */\n getPrefix(recordType: RecordType): string | null;\n}\n\n/**\n * Registry for custom display ID prefixes\n * Consuming projects can register prefixes for specific record types\n */\nexport class DisplayIdPrefixRegistry {\n private prefixes = new Map<RecordType, string>();\n\n /**\n * Register a prefix for a specific record type\n */\n register(recordType: RecordType, prefix: string): void {\n this.prefixes.set(recordType, prefix);\n }\n\n /**\n * Get a prefix for a specific record type\n */\n get(recordType: RecordType): string | undefined {\n return this.prefixes.get(recordType);\n }\n\n /**\n * Check if a prefix exists for a record type\n */\n has(recordType: RecordType): boolean {\n return this.prefixes.has(recordType);\n }\n}\n\n/**\n * Registers display ID prefixes from a map into the registry\n * Generic utility function that can be used by any consumer\n *\n * @param registry - The DisplayIdPrefixRegistry instance\n * @param prefixMap - Map of record types to their display ID prefixes\n */\nexport function registerDisplayIdPrefixesFromMap(\n registry: DisplayIdPrefixRegistry,\n prefixMap: Partial<Record<string, string>>,\n): void {\n for (const [recordType, prefix] of Object.entries(prefixMap)) {\n if (prefix) {\n // Cast to RecordType - consumers are responsible for ensuring valid types\n registry.register(recordType as RecordType, prefix);\n }\n }\n}\n\n/**\n * Default implementation of display ID prefix service\n * Uses a registry that can be extended by consuming projects\n */\n@injectable()\nexport class DisplayIdPrefixService implements IDisplayIdPrefixService {\n constructor(\n @inject(DISPLAY_ID_PREFIX_TOKENS.PrefixRegistry)\n private prefixRegistry: DisplayIdPrefixRegistry,\n ) {}\n\n getPrefix(recordType: RecordType): string | null {\n // Check if there's a custom prefix registered\n const customPrefix = this.prefixRegistry.get(recordType);\n if (customPrefix) {\n return customPrefix;\n }\n\n // Default prefixes for record types in dragoncore-api\n const defaultPrefixes: Partial<Record<RecordType, string>> = {\n support_ticket: 'SP',\n // Add other default prefixes as needed\n };\n\n return defaultPrefixes[recordType] || null;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../di_tokens';\nimport { Logger } from '../utils/logger';\n\n/**\n * Interface for email message\n */\nexport interface EmailMessage {\n to: string | string[];\n subject: string;\n bodyHtml?: string;\n bodyText: string;\n from?: {\n email: string;\n name: string;\n };\n}\n\n/**\n * Interface for the email service\n */\nexport interface IEmailService {\n sendEmail(message: EmailMessage): Promise<void>;\n sendBulkEmail(\n emails: string[],\n subject: string,\n bodyText: string,\n bodyHtml?: string,\n ): Promise<void>;\n}\n\n/**\n * Email service for sending emails via Mailgun or Mailinator (for local dev)\n * Extracted pattern from ForgotPassword to be reusable across the application\n */\n@injectable()\nexport class EmailService implements IEmailService {\n constructor(\n @inject(TOKENS.ENV) private env: Env,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n /**\n * Send an email to one or more recipients\n */\n async sendEmail(message: EmailMessage): Promise<void> {\n const fromEmail = message.from?.email || this.env.NO_REPLY_EMAIL;\n\n const recipients = Array.isArray(message.to) ? message.to : [message.to];\n\n if (this.env.ENVIRONMENT === 'local') {\n // For local development, use Mailinator\n await this.sendViaMailinator(recipients, message.subject, message.bodyText, fromEmail);\n } else {\n // For production/staging, use Mailgun\n await this.sendViaMailgun(\n recipients,\n message.subject,\n message.bodyText,\n message.bodyHtml,\n fromEmail,\n );\n }\n }\n\n /**\n * Send the same email to multiple recipients\n */\n async sendBulkEmail(\n emails: string[],\n subject: string,\n bodyText: string,\n bodyHtml?: string,\n ): Promise<void> {\n await this.sendEmail({\n to: emails,\n subject,\n bodyText,\n bodyHtml,\n });\n }\n\n /**\n * Send email via Mailinator (local development)\n * Pattern extracted from ForgotPassword\n */\n private async sendViaMailinator(\n recipients: string[],\n subject: string,\n bodyText: string,\n fromEmail: string,\n ): Promise<void> {\n const webhook = `https://www.mailinator.com/api/v2/domains/${this.env.MAILINATOR_API_KEY}/webhook/`;\n\n for (const recipient of recipients) {\n const response = await fetch(webhook, {\n method: 'POST',\n body: JSON.stringify({\n from: fromEmail,\n to: recipient.split('@')[0] + this.env.MAILINATOR_DOMAIN,\n subject,\n text: bodyText,\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n this.logger.debug(`Mailinator response for ${recipient}: ${response.statusText}`);\n\n if (!response.ok) {\n this.logger.error(`Failed to send email to ${recipient} via Mailinator`);\n }\n }\n }\n\n /**\n * Send email via Mailgun (production/staging)\n * Pattern extracted from ForgotPassword\n */\n private async sendViaMailgun(\n recipients: string[],\n subject: string,\n bodyText: string,\n bodyHtml: string | undefined,\n fromEmail: string,\n ): Promise<void> {\n // Validate required configuration\n if (!this.env.MAILGUN_PRIVATE_KEY) {\n this.logger.error('MAILGUN_PRIVATE_KEY is not set in environment variables');\n throw new Error('Mailgun API key is not configured');\n }\n\n if (!this.env.MAILGUN_DOMAIN) {\n this.logger.error('MAILGUN_DOMAIN is not set in environment variables');\n throw new Error('Mailgun domain is not configured');\n }\n\n const form = new FormData();\n form.append('text', bodyText);\n\n if (bodyHtml) {\n form.append('html', bodyHtml);\n }\n\n // Join recipients with comma for bulk sending\n form.append('to', recipients.join(','));\n form.append('from', fromEmail);\n form.append('subject', subject);\n\n const auth_base64 = btoa(`api:${this.env.MAILGUN_PRIVATE_KEY}`);\n const apiUrl = `https://api.mailgun.net/v3/${this.env.MAILGUN_DOMAIN}/messages`;\n\n this.logger.debug(`Sending email via Mailgun to domain: ${this.env.MAILGUN_DOMAIN}`);\n\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n Authorization: 'Basic ' + auth_base64,\n },\n body: form,\n });\n\n const body_response = await response?.text();\n this.logger.debug(`Mailgun response status: ${response?.status} ${response?.statusText}`);\n this.logger.debug(`Mailgun response body: ${body_response}`);\n\n if (!response || response.status >= 400) {\n const errorMessage = body_response || 'Unknown error';\n this.logger.error(\n `Failed to send email via Mailgun: ${response?.status} ${response?.statusText}`,\n {\n errorMessage,\n domain: this.env.MAILGUN_DOMAIN,\n apiKeyPresent: !!this.env.MAILGUN_PRIVATE_KEY,\n apiKeyLength: this.env.MAILGUN_PRIVATE_KEY?.length || 0,\n },\n );\n throw new Error(\n `Failed to send email notification: ${response?.status} ${response?.statusText} - ${errorMessage}`,\n );\n }\n }\n}\n","import type {\n AnyFilterOperator,\n DateOperator,\n EqualityOperator,\n FieldRegistryMetadata,\n NumberOperator,\n StringOperator,\n} from '@dragonmastery/dragoncore-shared';\nimport { OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport {\n and,\n eq,\n gt,\n gte,\n inArray,\n isNull,\n lt,\n lte,\n ne,\n notInArray,\n or,\n sql,\n type SQL,\n} from 'drizzle-orm';\nimport type { SortColumn } from '../pagination/pagination-types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type BackendFieldType =\n | 'string'\n | 'number'\n | 'date'\n | 'boolean'\n | 'money'\n | 'percentage';\n\nexport interface FieldDefinition {\n column: any;\n type: BackendFieldType;\n filterable?: boolean;\n searchable?: boolean;\n sortable?: boolean;\n}\n\nexport type FieldRegistry = Record<string, FieldDefinition>;\n\nexport interface FilterConfig {\n fieldRegistry: FieldRegistry;\n processedSeparately?: string[];\n}\n\nexport interface FilterResult {\n conditions: SQL[];\n skipQuery?: boolean;\n}\n\n/**\n * Create backend registry from shared metadata + column map\n */\nexport function createBackendRegistry(\n metadata: FieldRegistryMetadata,\n columnMap: Record<string, any>,\n): FieldRegistry {\n const registry: FieldRegistry = {};\n for (const [fieldName, meta] of Object.entries(metadata)) {\n const column = columnMap[fieldName];\n if (!column) continue;\n registry[fieldName] = {\n column,\n type: meta.type === 'enum' ? 'string' : (meta.type as BackendFieldType),\n filterable: meta.filterable,\n searchable: meta.searchable,\n sortable: meta.sortable,\n };\n }\n return registry;\n}\n\n/**\n * Derive pagination columnMap from field registry\n */\nexport function deriveColumnMap(registry: FieldRegistry): Record<string, SortColumn> {\n const columnMap: Record<string, SortColumn> = {};\n for (const [fieldName, def] of Object.entries(registry)) {\n if (def.sortable !== false) {\n columnMap[fieldName] = { column: def.column, type: def.type };\n }\n }\n return columnMap;\n}\n\n// ============================================================================\n// Internal: SQL Operators\n// ============================================================================\n\nconst escapeLike = (v: string) => v.replace(/[%_]/g, '\\\\$&');\n\nfunction execString(\n col: any,\n op: StringOperator,\n val?: string,\n vals?: string[],\n cs = false,\n): SQL | undefined {\n switch (op) {\n case OPERATORS.EQUALS:\n return val !== undefined\n ? cs\n ? eq(col, val)\n : sql`lower(${col}) = lower(${val})`\n : undefined;\n case OPERATORS.NOT_EQUALS:\n return val !== undefined\n ? cs\n ? ne(col, val)\n : sql`lower(${col}) != lower(${val})`\n : undefined;\n case OPERATORS.CONTAINS: {\n if (val === undefined) return undefined;\n const e = escapeLike(cs ? val : val.toLowerCase());\n return cs ? sql`${col} like ${`%${e}%`}` : sql`lower(${col}) like ${`%${e}%`}`;\n }\n case OPERATORS.STARTS_WITH: {\n if (val === undefined) return undefined;\n const e = escapeLike(cs ? val : val.toLowerCase());\n return cs ? sql`${col} like ${`${e}%`}` : sql`lower(${col}) like ${`${e}%`}`;\n }\n case OPERATORS.ENDS_WITH: {\n if (val === undefined) return undefined;\n const e = escapeLike(cs ? val : val.toLowerCase());\n return cs ? sql`${col} like ${`%${e}`}` : sql`lower(${col}) like ${`%${e}`}`;\n }\n case OPERATORS.IS_ONE_OF:\n return vals?.length ? inArray(col, vals) : undefined;\n case OPERATORS.IS_NOT_ONE_OF:\n return vals?.length ? or(notInArray(col, vals), sql`${col} IS NULL`) : undefined;\n }\n}\n\nfunction execDate(col: any, op: DateOperator, val?: string, vals?: string[]): SQL | undefined {\n switch (op) {\n case OPERATORS.EQUALS:\n return val !== undefined ? eq(col, val) : undefined;\n case OPERATORS.NOT_EQUALS:\n return val !== undefined ? ne(col, val) : undefined;\n case OPERATORS.GREATER_THAN:\n return val !== undefined ? gt(col, val) : undefined;\n case OPERATORS.GREATER_THAN_OR_EQUAL:\n return val !== undefined ? gte(col, val) : undefined;\n case OPERATORS.LESS_THAN:\n return val !== undefined ? lt(col, val) : undefined;\n case OPERATORS.LESS_THAN_OR_EQUAL:\n return val !== undefined ? lte(col, val) : undefined;\n case OPERATORS.BETWEEN:\n return vals?.length === 2 ? and(gte(col, vals[0]), lte(col, vals[1])) : undefined;\n case OPERATORS.IS_EMPTY:\n return isNull(col);\n case OPERATORS.IS_NOT_EMPTY:\n return sql`${col} IS NOT NULL`;\n }\n}\n\nfunction execNumber(\n col: any,\n op: NumberOperator,\n val?: number,\n vals?: number[],\n): SQL | undefined {\n switch (op) {\n case OPERATORS.EQUALS:\n return val !== undefined ? eq(col, val) : undefined;\n case OPERATORS.NOT_EQUALS:\n return val !== undefined ? ne(col, val) : undefined;\n case OPERATORS.GREATER_THAN:\n return val !== undefined ? gt(col, val) : undefined;\n case OPERATORS.GREATER_THAN_OR_EQUAL:\n return val !== undefined ? gte(col, val) : undefined;\n case OPERATORS.LESS_THAN:\n return val !== undefined ? lt(col, val) : undefined;\n case OPERATORS.LESS_THAN_OR_EQUAL:\n return val !== undefined ? lte(col, val) : undefined;\n case OPERATORS.BETWEEN:\n return vals?.length === 2 ? and(gte(col, vals[0]), lte(col, vals[1])) : undefined;\n case OPERATORS.IS_ONE_OF:\n return vals?.length ? inArray(col, vals) : undefined;\n case OPERATORS.IS_NOT_ONE_OF:\n return vals?.length ? or(notInArray(col, vals), sql`${col} IS NULL`) : undefined;\n }\n}\n\nfunction execBool(col: any, op: EqualityOperator, val: any): SQL {\n if (op === OPERATORS.EQUALS) return val === null ? isNull(col) : eq(col, val);\n return ne(col, val);\n}\n\nexport function applyFilter(col: any, filter: any, type: BackendFieldType): SQL | undefined {\n if (!filter || typeof filter !== 'object' || !filter.operator) return undefined;\n\n const { operator, value, values, caseSensitive } = filter as {\n operator: AnyFilterOperator;\n value?: any;\n values?: any[];\n caseSensitive?: boolean;\n };\n const cs = caseSensitive ?? false;\n\n // String-only operators\n if (\n operator === OPERATORS.CONTAINS ||\n operator === OPERATORS.STARTS_WITH ||\n operator === OPERATORS.ENDS_WITH\n ) {\n return execString(col, operator, value, values, cs);\n }\n // Date-only operators\n if (operator === OPERATORS.IS_EMPTY || operator === OPERATORS.IS_NOT_EMPTY) {\n return execDate(col, operator, value, values);\n }\n // Between - route by type\n if (operator === OPERATORS.BETWEEN) {\n if (type === 'date') return execDate(col, operator, value, values);\n if (type === 'number' || type === 'money' || type === 'percentage')\n return execNumber(col, operator, value, values);\n }\n // Route by type\n if (type === 'string') return execString(col, operator as StringOperator, value, values, cs);\n if (type === 'number' || type === 'money' || type === 'percentage')\n return execNumber(col, operator as NumberOperator, value, values);\n if (type === 'date') return execDate(col, operator as DateOperator, value, values);\n if (type === 'boolean') return execBool(col, operator as EqualityOperator, value);\n\n return eq(col, value);\n}\n\nfunction combine(andConds: SQL[], orConds: SQL[]): SQL[] {\n if (andConds.length > 0 && orConds.length > 0) {\n const c = and(and(...andConds), or(...orConds));\n return c ? [c] : [];\n }\n if (andConds.length > 0) {\n const c = and(...andConds);\n return c ? [c] : [];\n }\n if (orConds.length > 0) {\n const c = or(...orConds);\n return c ? [c] : [];\n }\n return [];\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Create a filter builder function bound to config\n *\n * @example\n * const buildFilters = createFilterBuilder({\n * fieldRegistry,\n * processedSeparately: ['team_id', 'archived_at'],\n * });\n *\n * const { conditions } = buildFilters(filters);\n */\nexport function createFilterBuilder(config: FilterConfig): (filters?: any) => FilterResult {\n const { fieldRegistry, processedSeparately = [] } = config;\n\n // Pre-compute fields to skip\n const skipFields = new Set<string>(processedSeparately);\n skipFields.add('_userTeamIds'); // Always skip internal field\n skipFields.add('includeArchived'); // Always skip internal field\n skipFields.add('search'); // Always skip search (handled separately)\n\n return (filters?: any): FilterResult => {\n const andConds: SQL[] = [];\n const orConds: SQL[] = [];\n\n // Standard filters\n if (filters) {\n for (const [key, val] of Object.entries(filters)) {\n // Skip fields processed separately or internal fields\n if (skipFields.has(key)) continue;\n const def = fieldRegistry[key];\n if (!def || def.filterable === false) continue;\n const cond = applyFilter(def.column, val, def.type);\n if (cond) andConds.push(cond);\n }\n }\n\n // Search\n if (filters?.search?.query) {\n const { query, searchableFields } = filters.search;\n const fields = searchableFields?.length\n ? searchableFields\n : Object.keys(fieldRegistry).filter((k) => fieldRegistry[k].searchable);\n for (const key of fields) {\n const def = fieldRegistry[key];\n if (!def) continue;\n const cond = applyFilter(\n def.column,\n { operator: OPERATORS.CONTAINS, value: query },\n def.type,\n );\n if (cond) orConds.push(cond);\n }\n }\n\n return { conditions: combine(andConds, orConds), skipQuery: false };\n };\n}\n","import { OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport { eq, gt, gte, isNull, lt, lte, ne, sql, type SQL } from 'drizzle-orm';\n\n/**\n * Create archive conditions based on filters\n * @param filters - Filter object that may contain includeArchived and archived_at\n * @param column - The archived_at column to check\n * @returns Array of SQL conditions for archive filtering\n */\nexport function archiveConditions(filters: any, column: any): SQL[] {\n const conditions: SQL[] = [];\n\n // If includeArchived is true, don't filter by archive status\n if (filters?.includeArchived === true) {\n return conditions;\n }\n\n // If archived_at filter is provided, use it\n if (filters?.archived_at) {\n const filter = filters.archived_at;\n if (!filter || typeof filter !== 'object' || !filter.operator) {\n // Invalid filter, default to active\n conditions.push(isNull(column));\n return conditions;\n }\n\n const { operator, value } = filter;\n let cond: SQL | undefined;\n\n switch (operator) {\n case OPERATORS.EQUALS:\n cond = value !== undefined ? eq(column, value) : undefined;\n break;\n case OPERATORS.NOT_EQUALS:\n cond = value !== undefined ? ne(column, value) : undefined;\n break;\n case OPERATORS.GREATER_THAN:\n cond = value !== undefined ? gt(column, value) : undefined;\n break;\n case OPERATORS.GREATER_THAN_OR_EQUAL:\n cond = value !== undefined ? gte(column, value) : undefined;\n break;\n case OPERATORS.LESS_THAN:\n cond = value !== undefined ? lt(column, value) : undefined;\n break;\n case OPERATORS.LESS_THAN_OR_EQUAL:\n cond = value !== undefined ? lte(column, value) : undefined;\n break;\n case OPERATORS.IS_EMPTY:\n cond = isNull(column);\n break;\n case OPERATORS.IS_NOT_EMPTY:\n cond = sql`${column} IS NOT NULL`;\n break;\n default:\n cond = undefined;\n }\n\n if (cond) {\n conditions.push(cond);\n } else {\n // Fallback to active (non-archived) if filter is invalid\n conditions.push(isNull(column));\n }\n } else {\n // Default: only show active (non-archived) records\n conditions.push(isNull(column));\n }\n\n return conditions;\n}\n","import { eq, inArray, type SQL } from 'drizzle-orm';\n\ntype AccessResult =\n | { denied: true }\n | { denied: false; effectiveTeamIds: string[] | null };\n\n/**\n * Resolve team access logic (pure function, no SQL)\n * @param userTeamIds - Array of team IDs the user belongs to, or undefined for staff/admin\n * @param teamFilter - Optional team_id filter from the filters object\n * @returns Access result with denied flag and effective team IDs\n */\nexport function resolveTeamAccess(\n userTeamIds: string[] | undefined,\n teamFilter?: { operator: string; value?: string; values?: string[] },\n): AccessResult {\n // Admin/staff — no restriction, or use their explicit filter\n if (userTeamIds === undefined) {\n if (teamFilter?.operator === 'eq' && teamFilter.value) {\n return { denied: false, effectiveTeamIds: [teamFilter.value] };\n }\n if (teamFilter?.operator === 'in' && teamFilter.values?.length) {\n return { denied: false, effectiveTeamIds: teamFilter.values };\n }\n return { denied: false, effectiveTeamIds: null };\n }\n\n // User has no teams\n if (userTeamIds.length === 0) {\n return { denied: true };\n }\n\n // User filtering to specific team(s) — check access\n if (teamFilter?.operator === 'eq' && teamFilter.value) {\n if (userTeamIds.includes(teamFilter.value)) {\n return { denied: false, effectiveTeamIds: [teamFilter.value] };\n }\n return { denied: true };\n }\n if (teamFilter?.operator === 'in' && teamFilter.values?.length) {\n const allowed = teamFilter.values.filter((id) => userTeamIds.includes(id));\n if (allowed.length === 0) return { denied: true };\n return { denied: false, effectiveTeamIds: allowed };\n }\n\n // Default: user's teams\n return { denied: false, effectiveTeamIds: userTeamIds };\n}\n\n/**\n * Create team condition SQL from effective team IDs\n * @param column - The team_id column to check\n * @param teamIds - Array of team IDs, or null for no restriction\n * @returns SQL condition or null if no restriction needed\n */\nexport function teamCondition(column: any, teamIds: string[] | null): SQL | null {\n if (teamIds === null) return null;\n if (teamIds.length === 1) return eq(column, teamIds[0]);\n return inArray(column, teamIds);\n}\n","import { OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport { or, type SQL } from 'drizzle-orm';\nimport { applyFilter, type FieldRegistry } from './filter-builder';\n\n/**\n * Create search OR condition for text search across searchable fields\n * @param query - Search query string\n * @param fieldRegistry - Field registry to look up columns\n * @param searchableFields - Optional array of field names to search (defaults to all searchable fields in registry)\n * @returns Single OR condition or null if no query\n */\nexport function searchOrCondition(\n query: string | undefined,\n fieldRegistry: FieldRegistry,\n searchableFields?: string[],\n): SQL | null {\n if (!query || query.trim() === '') return null;\n\n const fields = searchableFields?.length\n ? searchableFields\n : Object.keys(fieldRegistry).filter((k) => fieldRegistry[k].searchable);\n\n const conditions: SQL[] = [];\n for (const key of fields) {\n const def = fieldRegistry[key];\n if (!def) continue;\n const cond = applyFilter(\n def.column,\n { operator: OPERATORS.CONTAINS, value: query },\n def.type,\n );\n if (cond) conditions.push(cond);\n }\n\n if (conditions.length === 0) return null;\n if (conditions.length === 1) return conditions[0];\n return or(...conditions) ?? null;\n}\n","import { and, or, type SQL } from 'drizzle-orm';\n\n/**\n * Combine AND and OR conditions into a single SQL array\n * @param andConditions - Array of AND conditions\n * @param orConditions - Array of OR conditions\n * @returns Combined array of SQL conditions\n */\nexport function combineConditions(andConditions: SQL[], orConditions: SQL[]): SQL[] {\n if (andConditions.length > 0 && orConditions.length > 0) {\n const combined = and(and(...andConditions), or(...orConditions));\n return combined ? [combined] : [];\n }\n if (andConditions.length > 0) {\n const combined = and(...andConditions);\n return combined ? [combined] : [];\n }\n if (orConditions.length > 0) {\n const combined = or(...orConditions);\n return combined ? [combined] : [];\n }\n return [];\n}\n","type AnyObject = { [key: string]: any };\n\nfunction isObject(item: any): item is AnyObject {\n return item && typeof item === 'object' && !Array.isArray(item);\n}\n\nfunction arraysAreEqual(arr1: any[], arr2: any[]): boolean {\n if (arr1.length !== arr2.length) return false;\n for (let i = 0; i < arr1.length; i++) {\n if (!areEqual(arr1[i], arr2[i])) return false;\n }\n return true;\n}\n\nfunction areEqual(value1: any, value2: any): boolean {\n if (value1 === undefined) value1 = null;\n if (value2 === undefined) value2 = null;\n\n if (isObject(value1) && isObject(value2)) {\n return JSON.stringify(value1) === JSON.stringify(value2);\n } else if (Array.isArray(value1) && Array.isArray(value2)) {\n return arraysAreEqual(value1, value2);\n } else {\n return value1 === value2;\n }\n}\n\nexport function getChangedProperties(\n obj1: AnyObject | null | undefined,\n obj2: AnyObject | null | undefined,\n ignoreKeys: string[] = ['id', 'created_at', 'updated_at', 'updated_by', 'created_by'],\n): AnyObject {\n const result: AnyObject = {};\n\n if (!obj2) {\n throw new Error('obj2 cannot be null or undefined');\n }\n\n const keysObj1 = obj1 ? Object.keys(obj1) : [];\n const allKeys = new Set([...keysObj1, ...Object.keys(obj2)]);\n\n for (const key of allKeys) {\n if (ignoreKeys.includes(key)) continue; // Skip the ignored keys\n\n const valueObj1 = obj1 ? obj1[key] : undefined;\n if (!areEqual(valueObj1, obj2[key])) {\n result[key] = obj2[key] !== undefined ? obj2[key] : null;\n }\n }\n\n return result;\n}\n","import type { PaginationState } from './pagination-types';\n\nexport class BreadcrumbUtils {\n private static readonly MAX_BREADCRUMBS = 100;\n\n /**\n * Encode pagination state to a base64 token\n */\n static encodeToken(state: PaginationState): string {\n try {\n const json = JSON.stringify(state);\n return btoa(json);\n } catch (error) {\n console.warn('Failed to encode pagination token:', error);\n return '';\n }\n }\n\n /**\n * Decode pagination token to state, returns null if invalid\n */\n static decodeToken(token?: string): PaginationState | null {\n if (!token) return null;\n\n try {\n const json = atob(token);\n const state = JSON.parse(json) as PaginationState;\n\n // Validate basic structure\n if (!Array.isArray(state.cursors) || typeof state.currentPageIndex !== 'number') {\n return null;\n }\n\n // Validate bounds\n if (state.currentPageIndex < 0 || state.currentPageIndex >= state.cursors.length) {\n return null;\n }\n\n return state;\n } catch (error) {\n console.warn('Failed to decode pagination token:', error);\n return null;\n }\n }\n\n /**\n * Create initial pagination state for first page\n */\n static createInitialState(sortBy?: string, sortDirection?: 'asc' | 'desc'): PaginationState {\n return {\n cursors: [null as any], // First page has null cursor\n currentPageIndex: 0,\n sortBy,\n sortDirection,\n };\n }\n\n /**\n * Update state for next page navigation\n */\n static navigateNext(state: PaginationState, newCursor: string): PaginationState {\n const cursors = [...state.cursors];\n\n // Add new cursor for next page if we don't already have it\n if (cursors.length <= state.currentPageIndex + 1) {\n cursors.push(newCursor);\n }\n\n // Limit breadcrumb size\n if (cursors.length > this.MAX_BREADCRUMBS) {\n cursors.shift(); // Remove oldest cursor\n return {\n ...state,\n cursors,\n currentPageIndex: this.MAX_BREADCRUMBS - 1,\n };\n }\n\n return {\n ...state,\n cursors,\n currentPageIndex: state.currentPageIndex + 1,\n };\n }\n\n /**\n * Update state for previous page navigation\n */\n static navigatePrevious(state: PaginationState): PaginationState {\n if (state.currentPageIndex <= 0) {\n return state; // Already on first page\n }\n\n return {\n ...state,\n currentPageIndex: state.currentPageIndex - 1,\n };\n }\n\n /**\n * Get cursor for current page\n */\n static getCurrentCursor(state: PaginationState): string | null {\n return state.cursors[state.currentPageIndex] || null;\n }\n\n /**\n * Check if we can navigate to previous page\n */\n static canNavigatePrevious(state: PaginationState): boolean {\n return state.currentPageIndex > 0;\n }\n\n /**\n * Check if we have a known next page cursor\n */\n static hasKnownNext(state: PaginationState): boolean {\n return state.cursors.length > state.currentPageIndex + 1;\n }\n\n /**\n * Get the cursor for the next page if known\n */\n static getNextCursor(state: PaginationState): string | null {\n if (!this.hasKnownNext(state)) return null;\n return state.cursors[state.currentPageIndex + 1] || null;\n }\n\n /**\n * Check if sort criteria matches current state\n */\n static sortMatches(\n state: PaginationState,\n sortBy?: string,\n sortDirection?: 'asc' | 'desc',\n ): boolean {\n return state.sortBy === sortBy && state.sortDirection === sortDirection;\n }\n}\n","import { and, asc, desc, eq, gt, isNull, lt, or, sql, type SQL } from 'drizzle-orm';\nimport type { PaginationFilters, SortColumn } from './pagination-types';\n\nexport class CursorUtils {\n static encodeCursor<T extends { id: string }>(\n record: T,\n filters?: PaginationFilters,\n columnMap?: Record<string, SortColumn>,\n offset?: number,\n ): string {\n // Simple approach: encode the offset in the cursor\n if (offset !== undefined) {\n return btoa(JSON.stringify({ offset }));\n }\n\n if (!filters?.sortBy || !columnMap?.[filters.sortBy]) {\n return record.id;\n }\n\n const sortField = filters.sortBy as keyof T;\n const sortValue = record[sortField];\n\n const cursorData = { sortValue, id: record.id };\n return btoa(JSON.stringify(cursorData));\n }\n\n static decodeCursor(cursor: string): { sortValue: any; id: string } {\n try {\n return JSON.parse(atob(cursor));\n } catch {\n throw new Error('Invalid cursor format');\n }\n }\n\n /**\n * Build cursor conditions - ONLY FORWARD PAGINATION\n * Always uses 'after' logic regardless of the cursor parameter name\n */\n static buildCursorConditions(\n filters: PaginationFilters,\n columnMap: Record<string, SortColumn>,\n table: any,\n ): SQL[] {\n // Only handle 'after' cursor - this simplifies everything!\n const cursor = filters.after;\n if (!cursor) return [];\n\n try {\n if (!filters.sortBy || !columnMap[filters.sortBy]) {\n // Universal IDs are lexicographically sortable by timestamp (ASC default)\n return [gt(table.id, cursor)];\n }\n\n // Complex cursor with sort column\n const { sortValue, id } = this.decodeCursor(cursor);\n const column = columnMap[filters.sortBy];\n const isDesc = filters.sortDirection === 'desc';\n\n return this.buildAfterCondition(table, column, sortValue, id, isDesc);\n } catch (error) {\n console.error('Invalid cursor format:', error);\n return [];\n }\n }\n\n /**\n * Build ORDER BY - ALWAYS RESPECT USER'S SORT PREFERENCE\n */\n static buildOrderBy(\n filters: PaginationFilters,\n columnMap: Record<string, SortColumn>,\n table: any,\n ): SQL {\n if (filters.sortBy && columnMap[filters.sortBy]) {\n const direction = filters.sortDirection === 'asc' ? asc : desc;\n const column = columnMap[filters.sortBy];\n\n let orderBy: SQL;\n if (column.type === 'string') {\n orderBy = direction(sql`LOWER(${column.column})`);\n } else if (\n column.type === 'number' ||\n column.type === 'money' ||\n column.type === 'percentage'\n ) {\n orderBy = direction(sql`CAST(${column.column} AS REAL)`);\n } else {\n orderBy = direction(column.column);\n }\n\n const secondaryOrderBy = direction(table.id);\n return sql`${orderBy}, ${secondaryOrderBy}`;\n } else {\n // Default sort by Universal ID ASC (oldest to newest)\n return asc(table.id);\n }\n }\n\n /**\n * Get items AFTER this cursor position in the sorted order\n */\n private static buildAfterCondition(\n table: any,\n column: SortColumn,\n sortValue: any,\n id: string,\n isDesc: boolean,\n ): SQL[] {\n // SQL: column < NULL and column = NULL both return NULL, so no rows match.\n // When cursor has null sortValue, use IS NULL + id comparison.\n if (sortValue === null || sortValue === undefined) {\n const sortCol = column.column;\n if (isDesc) {\n // DESC: nulls sort first (SQLite). After (null, id) = (null, id < cursorId)\n return [and(isNull(sortCol), lt(table.id, id))];\n } else {\n // ASC: nulls sort last. After (null, id) = (null, id > cursorId)\n return [and(isNull(sortCol), gt(table.id, id))];\n }\n }\n\n const { sortColumn, processedValue } = this.processSortColumn(column, sortValue);\n\n if (isDesc) {\n // DESC: items after cursor have smaller values\n return [\n or(\n lt(sortColumn, processedValue),\n and(eq(sortColumn, processedValue), lt(table.id, id)),\n )!,\n ];\n } else {\n // ASC: items after cursor have larger values\n return [\n or(\n gt(sortColumn, processedValue),\n and(eq(sortColumn, processedValue), gt(table.id, id)),\n )!,\n ];\n }\n }\n\n private static processSortColumn(column: SortColumn, sortValue: any) {\n if (column.type === 'string') {\n return {\n sortColumn: sql`LOWER(${column.column})`,\n processedValue: sortValue.toLowerCase(),\n };\n } else if (\n column.type === 'number' ||\n column.type === 'money' ||\n column.type === 'percentage'\n ) {\n return {\n sortColumn: sql`CAST(${column.column} AS REAL)`,\n processedValue: parseFloat(sortValue),\n };\n } else {\n return {\n sortColumn: column.column,\n processedValue: sortValue,\n };\n }\n }\n}\n","import { DrizzleD1Database } from 'drizzle-orm/d1';\n\n/**\n * Adapter that wraps a single DrizzleD1Database to provide\n * the same interface as DatabaseRouter for pagination utilities.\n *\n * This allows non-sharded tables to use the same pagination\n * utilities that sharded tables use.\n */\nexport class DirectDatabaseAdapter {\n constructor(private db: DrizzleD1Database<Record<string, never>>) {}\n\n /**\n * Query the single database (simulating router.queryAll behavior)\n * @param queryFn Function that performs the query on a database connection\n * @returns Array of results\n */\n async queryAll<T>(queryFn: (db: DrizzleD1Database) => Promise<T | T[]>): Promise<T[]> {\n const result = await queryFn(this.db);\n\n // Handle both single items and arrays\n if (Array.isArray(result)) {\n return result;\n }\n return result != null ? [result] : [];\n }\n}\n","import { and, type SQL } from 'drizzle-orm';\nimport { BreadcrumbUtils } from './breadcrumb-utils';\nimport { CursorUtils } from './cursor-utils';\nimport type {\n PaginatedResult,\n PaginationConfig,\n PaginationFilters,\n PaginationState,\n} from './pagination-types';\n\nexport class PaginationUtils {\n /**\n * Execute a paginated query with cursor breadcrumb strategy\n */\n static async findAllPaginated<\n Entity extends { id: string },\n Filters extends PaginationFilters,\n >(\n config: PaginationConfig<Entity>,\n filters: Filters,\n baseConditions: SQL[] = [],\n ): Promise<PaginatedResult<Entity>> {\n const { columnMap } = config;\n const limit = filters.first || 50;\n\n // Decode pagination state from token or start fresh\n let paginationState = BreadcrumbUtils.decodeToken(filters.paginationToken);\n let effectiveAfter = filters.after;\n\n if (\n !paginationState ||\n !BreadcrumbUtils.sortMatches(paginationState, filters.sortBy, filters.sortDirection)\n ) {\n paginationState = BreadcrumbUtils.createInitialState(\n filters.sortBy,\n filters.sortDirection,\n );\n // Clear the after cursor when sort criteria changes to prevent using stale cursor\n effectiveAfter = undefined;\n }\n\n // Determine navigation intent and target cursor\n const { targetCursor, updatedState, navigationDirection } = this.determineNavigation(\n paginationState,\n effectiveAfter,\n );\n\n // Execute query with graceful degradation\n let queryResult;\n try {\n queryResult = await this.executeQuery(\n config,\n filters,\n baseConditions,\n targetCursor,\n limit,\n );\n } catch (error) {\n // Cursor is stale/invalid - start fresh\n paginationState = BreadcrumbUtils.createInitialState(\n filters.sortBy,\n filters.sortDirection,\n );\n queryResult = await this.executeQuery(config, filters, baseConditions, null, limit);\n updatedState.currentPageIndex = 0;\n }\n\n const { items, hasMore } = queryResult;\n\n // Update breadcrumb state based on results\n const finalState = this.updateBreadcrumbState(\n updatedState,\n items,\n targetCursor,\n navigationDirection,\n );\n\n // Generate navigation cursors and response\n return this.buildResponse(finalState, items, hasMore, columnMap, filters);\n }\n\n /**\n * Determine navigation intent from cursor and return target state\n */\n private static determineNavigation(\n state: PaginationState,\n requestedCursor: string | null | undefined,\n ) {\n // Navigate to first page\n if (requestedCursor === null || requestedCursor === undefined) {\n return {\n targetCursor: null,\n updatedState: { ...state, currentPageIndex: 0 },\n navigationDirection: 'previous' as const,\n };\n }\n\n const nextCursor = BreadcrumbUtils.getNextCursor(state);\n\n // Navigate to next page (known cursor)\n if (requestedCursor === nextCursor) {\n return {\n targetCursor: requestedCursor,\n updatedState: BreadcrumbUtils.navigateNext(state, requestedCursor),\n navigationDirection: 'next' as const,\n };\n }\n\n // Navigate using breadcrumb (previous or jump to specific page)\n const cursorIndex = state.cursors.indexOf(requestedCursor);\n if (cursorIndex >= 0) {\n return {\n targetCursor: requestedCursor,\n updatedState: { ...state, currentPageIndex: cursorIndex },\n navigationDirection:\n cursorIndex < state.currentPageIndex ? ('previous' as const) : ('next' as const),\n };\n }\n\n // Stay on current page or unknown cursor (fallback)\n return {\n targetCursor: requestedCursor,\n updatedState: state,\n navigationDirection: 'none' as const,\n };\n }\n\n /**\n * Update breadcrumb state based on query results\n */\n private static updateBreadcrumbState(\n state: PaginationState,\n items: any[],\n targetCursor: string | null,\n navigationDirection: 'next' | 'previous' | 'none',\n ) {\n // For fresh queries or staying on same page, update the current cursor\n if (items.length > 0 && navigationDirection === 'none') {\n state.cursors[state.currentPageIndex] = targetCursor;\n }\n return state;\n }\n\n /**\n * Build the final response with navigation cursors\n */\n private static buildResponse(\n state: PaginationState,\n items: any[],\n hasMore: boolean,\n columnMap: Record<string, any>,\n filters: any,\n ) {\n // Calculate end cursor for next page navigation\n const endCursor =\n items.length > 0\n ? CursorUtils.encodeCursor(items[items.length - 1], filters, columnMap)\n : undefined;\n\n // Prepare state for next navigation if we have more results\n let responseState = state;\n const nextPageCursor = hasMore && endCursor ? endCursor : undefined;\n if (nextPageCursor && !BreadcrumbUtils.hasKnownNext(state)) {\n responseState = BreadcrumbUtils.navigateNext(state, nextPageCursor);\n responseState.currentPageIndex = state.currentPageIndex; // Don't actually navigate yet\n }\n\n // Generate navigation cursors - preserve null for \"first page\" so frontend knows to omit after\n const prevPageCursor = BreadcrumbUtils.canNavigatePrevious(state)\n ? state.cursors[state.currentPageIndex - 1]\n : undefined;\n\n return {\n items,\n pageInfo: {\n hasNextPage: hasMore,\n hasPreviousPage: BreadcrumbUtils.canNavigatePrevious(state),\n prevPageCursor,\n nextPageCursor,\n currentPageIndex: state.currentPageIndex,\n paginationToken: BreadcrumbUtils.encodeToken(responseState),\n },\n };\n }\n\n /**\n * Execute the actual database query\n */\n private static async executeQuery<\n Entity extends { id: string },\n Filters extends PaginationFilters,\n >(\n config: PaginationConfig<Entity>,\n filters: Filters,\n baseConditions: SQL[],\n cursor: string | null,\n limit: number,\n ) {\n const { table, columnMap, router } = config;\n\n // Build query with cursor\n const filtersWithCursor = { ...filters, after: cursor };\n const cursorConditions = CursorUtils.buildCursorConditions(\n filtersWithCursor,\n columnMap,\n table,\n );\n const orderBy = CursorUtils.buildOrderBy(filtersWithCursor, columnMap, table);\n const allConditions = [...baseConditions, ...cursorConditions];\n\n // Get current page items + peek ahead\n const pageItems: Entity[] = await router.queryAll((db: any) => {\n const query = db\n .select()\n .from(table)\n .orderBy(orderBy)\n .limit(limit + 1);\n\n if (allConditions.length > 0) {\n query.where(and(...allConditions));\n }\n\n return query;\n });\n\n const hasMore = pageItems.length > limit;\n const items = hasMore ? pageItems.slice(0, limit) : pageItems;\n\n return {\n items,\n hasMore,\n };\n }\n}\n","import { injectable } from 'tsyringe';\nimport {\n hashPassword as workerHashPassword,\n verifyPassword as workerVerifyPassword,\n} from 'worker-password-auth';\n\nexport interface IPasswordService {\n hashPassword(password: string): Promise<string>;\n verifyPassword(password: string, hash: string): Promise<boolean>;\n}\n\n@injectable()\nexport class PasswordService implements IPasswordService {\n async hashPassword(password: string): Promise<string> {\n return workerHashPassword(password);\n }\n\n async verifyPassword(password: string, hash: string): Promise<boolean> {\n return workerVerifyPassword(password, hash);\n }\n}\n","/**\n * Finds the R2 bucket binding for a given tenant ID\n * Looks for bindings matching the pattern: R2_PRIVATE_YYYY_MM_DD_T_tenantId\n */\nexport function findR2Bucket(env: Env, tenantId: string): R2Bucket | null {\n // Find all R2 bucket bindings in the environment\n const r2Bindings = Object.keys(env).filter(\n (key) => key.startsWith('R2_PRIVATE_') || key.startsWith('R2_PUBLIC_'),\n );\n\n // Find the binding that matches the tenant ID\n for (const binding of r2Bindings) {\n // Pattern: R2_PRIVATE_YYYY_MM_DD_T_tenantId or R2_PUBLIC_YYYY_MM_DD_T_tenantId\n if (binding.endsWith(`_T_${tenantId}`)) {\n // @ts-ignore - R2 buckets are dynamically added to env\n return env[binding] as R2Bucket;\n }\n }\n\n return null;\n}\n\n/**\n * Gets the R2 bucket binding name for a given tenant ID\n */\nexport function getR2BucketBindingName(env: Env, tenantId: string): string | null {\n const r2Bindings = Object.keys(env).filter(\n (key) => key.startsWith('R2_PRIVATE_') || key.startsWith('R2_PUBLIC_'),\n );\n\n for (const binding of r2Bindings) {\n if (binding.endsWith(`_T_${tenantId}`)) {\n return binding;\n }\n }\n\n return null;\n}\n","import { injectable } from 'tsyringe';\n\n/**\n * Interface for accessing tenant information in the current context\n */\nexport interface ITenantContext {\n /** Get the current tenant ID */\n getCurrentTenantId(): string;\n\n /** Set the current tenant ID */\n setCurrentTenantId(tenantId: string): void;\n}\n\n/**\n * Async local storage based tenant context implementation\n */\n@injectable()\nexport class TenantContext implements ITenantContext {\n private tenantId: string | undefined;\n\n getCurrentTenantId(): string {\n if (!this.tenantId) {\n throw new Error('No tenant ID set in current context');\n }\n return this.tenantId;\n }\n\n setCurrentTenantId(tenantId: string): void {\n if (!tenantId?.trim()) {\n throw new Error('Tenant ID is required');\n }\n this.tenantId = tenantId;\n }\n}\n","import { customAlphabet } from 'nanoid';\n\n/** Allowed characters for ID generation - excludes easily confused characters */\nconst ALPHABET = '23456789abcdefghjkmnpqrstvwxyz';\n\n/** Constants for ID structure */\nconst ID_CONSTANTS = {\n TIMESTAMP_LENGTH: 10,\n SHARD_LENGTH: 10,\n TYPE_LENGTH: 4,\n RANDOM_LENGTH: 8,\n BASE: 28,\n} as const;\n\n/** Maximum size for the hash cache to prevent memory issues */\nconst MAX_CACHE_SIZE = 10000;\n\n/** Supported component types in the ID */\nexport enum IdComponentType {\n Shard = 'shard',\n RecordType = 'recordType',\n}\n\n/** Error messages */\nexport const ID_ERRORS = {\n INVALID_LENGTH: 'Invalid ID length',\n INVALID_TIMESTAMP: 'Invalid timestamp',\n INVALID_COMPONENT: 'Invalid component value',\n DECODE_FAILED: 'Unable to decode ID - original values not in cache',\n CACHE_FULL: 'Cache capacity exceeded',\n} as const;\n\n/** Metadata required for ID generation */\nexport interface IdMetadata {\n timestamp?: number;\n recordType: string;\n shardId: string;\n}\n\n/** Structure of a decoded ID */\nexport interface DecodedId {\n timestamp: number;\n shardId: string;\n recordType: string;\n random: string;\n}\n\n/** Configuration for the ID generator */\nexport interface UniversalIdConfig {\n preloadValues?: {\n shards?: string[];\n recordTypes?: string[];\n };\n}\n\n/** Interface for the universal ID generator */\nexport interface IUniversalIdGenerator {\n /** Generate a new universal ID */\n generate(metadata: IdMetadata): Promise<string>;\n\n /** Decode a universal ID back to its components */\n decode(id: string): Promise<DecodedId>;\n\n /** Preload known values into the cache */\n preloadMapping(type: IdComponentType, values: string[]): Promise<void>;\n\n /** Clear all cached mappings */\n clearCache(): void;\n\n /** Get cache statistics */\n getCacheStats(): { size: number; capacity: number };\n\n /** Get all known mappings (for debugging) */\n getMappings(): {\n forward: Record<string, string>;\n reverse: Record<string, string>;\n };\n}\n\n/**\n * Generates and manages universal IDs with embedded metadata\n * Format: <timestamp(10)><shardHash(10)><typeHash(4)><random(8)>\n */\nexport class UniversalIdGenerator implements IUniversalIdGenerator {\n private readonly hashCache = new Map<string, string>();\n private readonly reverseCache = new Map<string, string>();\n private readonly randomGenerator = customAlphabet(ALPHABET, ID_CONSTANTS.RANDOM_LENGTH);\n private readonly encoder = new TextEncoder();\n private preloadPromise: Promise<void> | null = null;\n\n constructor(config?: UniversalIdConfig) {\n if (config?.preloadValues) {\n // Store the preload promise so decode() can await it if needed\n const preloadPromises: Promise<void>[] = [];\n\n if (config.preloadValues.shards?.length) {\n preloadPromises.push(\n this.preloadMapping(IdComponentType.Shard, config.preloadValues.shards),\n );\n }\n\n if (config.preloadValues.recordTypes?.length) {\n preloadPromises.push(\n this.preloadMapping(IdComponentType.RecordType, config.preloadValues.recordTypes),\n );\n }\n\n if (preloadPromises.length > 0) {\n this.preloadPromise = Promise.all(preloadPromises).then(() => {\n // Mark preload as complete\n this.preloadPromise = null;\n });\n }\n }\n }\n\n /**\n * Validates the input metadata before ID generation\n * @throws Error if validation fails\n */\n private validateMetadata(metadata: IdMetadata): void {\n if (!metadata.shardId?.trim()) {\n throw new Error(`${ID_ERRORS.INVALID_COMPONENT}: shardId is required`);\n }\n if (!metadata.recordType?.trim()) {\n throw new Error(`${ID_ERRORS.INVALID_COMPONENT}: recordType is required`);\n }\n if (metadata.timestamp && (isNaN(metadata.timestamp) || metadata.timestamp < 0)) {\n throw new Error(ID_ERRORS.INVALID_TIMESTAMP);\n }\n }\n\n /**\n * Manages cache size by removing oldest entries when limit is reached\n */\n private manageCache(): void {\n if (this.hashCache.size >= MAX_CACHE_SIZE) {\n const oldestKey = this.hashCache.keys().next().value;\n if (oldestKey === undefined) {\n return; // Cache is empty, nothing to manage\n }\n const oldestValue = this.hashCache.get(oldestKey);\n this.hashCache.delete(oldestKey);\n if (oldestValue) {\n this.reverseCache.delete(oldestValue);\n }\n }\n }\n\n /**\n * Generates a hash for a component of the ID\n */\n private async getHash(str: string, length: number): Promise<string> {\n const cacheKey = `${str}:${length}`;\n let cached = this.hashCache.get(cacheKey);\n\n if (!cached) {\n this.manageCache();\n const data = this.encoder.encode(str);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const firstFourBytes = new Uint8Array(hashBuffer.slice(0, 4));\n const value =\n (firstFourBytes[0] << 24) +\n (firstFourBytes[1] << 16) +\n (firstFourBytes[2] << 8) +\n firstFourBytes[3];\n\n // Convert to a positive number to ensure consistent results\n const positiveValue = value >>> 0;\n\n // Generate a string using only characters from our alphabet\n let result = '';\n let remainingValue = positiveValue;\n\n for (let i = 0; i < length; i++) {\n const index = remainingValue % ALPHABET.length;\n result = ALPHABET[index] + result;\n remainingValue = Math.floor(remainingValue / ALPHABET.length);\n\n // If we've used up all the entropy, but still need more characters\n if (remainingValue === 0 && i < length - 1) {\n // Use a deterministic approach to fill the remaining characters\n result = ALPHABET[i % ALPHABET.length] + result;\n }\n }\n\n // Ensure the result is exactly the requested length\n cached = result.slice(-length).padStart(length, ALPHABET[0]);\n\n this.hashCache.set(cacheKey, cached);\n this.reverseCache.set(cached, str);\n }\n\n return cached;\n }\n\n /**\n * Generates a new universal ID\n * @throws Error if metadata validation fails\n */\n async generate(metadata: IdMetadata): Promise<string> {\n this.validateMetadata(metadata);\n\n const timestamp = (metadata.timestamp || Date.now())\n .toString(ID_CONSTANTS.BASE)\n .padStart(ID_CONSTANTS.TIMESTAMP_LENGTH, '0');\n\n const [shardHash, typeHash] = await Promise.all([\n this.getHash(metadata.shardId, ID_CONSTANTS.SHARD_LENGTH),\n this.getHash(metadata.recordType, ID_CONSTANTS.TYPE_LENGTH),\n ]);\n\n const randomPart = this.randomGenerator();\n\n // console.log(\n // `timestamp: ${timestamp}| ${metadata.timestamp}, shardHash: ${shardHash} | ${metadata.shardId}, typeHash: ${typeHash} | ${metadata.recordType}, randomPart: ${randomPart}`,\n // );\n\n return `${timestamp}${shardHash}${typeHash}${randomPart}`;\n }\n\n /**\n * Decodes a universal ID back to its components\n * @throws Error if ID format is invalid or components cannot be decoded\n */\n async decode(id: string): Promise<DecodedId> {\n // Await preload if it's still in progress\n if (this.preloadPromise) {\n await this.preloadPromise;\n }\n\n const expectedLength =\n ID_CONSTANTS.TIMESTAMP_LENGTH +\n ID_CONSTANTS.SHARD_LENGTH +\n ID_CONSTANTS.TYPE_LENGTH +\n ID_CONSTANTS.RANDOM_LENGTH;\n\n if (id.length !== expectedLength) {\n throw new Error(ID_ERRORS.INVALID_LENGTH);\n }\n\n const timestampValue = parseInt(\n id.slice(0, ID_CONSTANTS.TIMESTAMP_LENGTH),\n ID_CONSTANTS.BASE,\n );\n if (isNaN(timestampValue)) {\n throw new Error(ID_ERRORS.INVALID_TIMESTAMP);\n }\n\n const shardHash = id.slice(\n ID_CONSTANTS.TIMESTAMP_LENGTH,\n ID_CONSTANTS.TIMESTAMP_LENGTH + ID_CONSTANTS.SHARD_LENGTH,\n );\n const typeHash = id.slice(\n ID_CONSTANTS.TIMESTAMP_LENGTH + ID_CONSTANTS.SHARD_LENGTH,\n ID_CONSTANTS.TIMESTAMP_LENGTH + ID_CONSTANTS.SHARD_LENGTH + ID_CONSTANTS.TYPE_LENGTH,\n );\n const random = id.slice(\n ID_CONSTANTS.TIMESTAMP_LENGTH + ID_CONSTANTS.SHARD_LENGTH + ID_CONSTANTS.TYPE_LENGTH,\n );\n\n const shardId = this.reverseCache.get(shardHash);\n const recordType = this.reverseCache.get(typeHash);\n\n if (!shardId || !recordType) {\n throw new Error(ID_ERRORS.DECODE_FAILED);\n }\n\n return {\n timestamp: timestampValue,\n shardId,\n recordType,\n random,\n };\n }\n\n /**\n * Preloads known values into the cache for faster lookups\n */\n async preloadMapping(type: IdComponentType, values: string[]): Promise<void> {\n const length =\n type === IdComponentType.Shard ? ID_CONSTANTS.SHARD_LENGTH : ID_CONSTANTS.TYPE_LENGTH;\n\n await Promise.all(values.map((value) => this.getHash(value, length)));\n }\n\n /**\n * Clears all cached mappings\n */\n clearCache(): void {\n this.hashCache.clear();\n this.reverseCache.clear();\n }\n\n /**\n * Returns current cache statistics\n */\n getCacheStats() {\n return {\n size: this.hashCache.size,\n capacity: MAX_CACHE_SIZE,\n };\n }\n\n /**\n * Gets all known mappings (useful for debugging)\n */\n getMappings() {\n return {\n forward: Object.fromEntries(this.hashCache),\n reverse: Object.fromEntries(this.reverseCache),\n };\n }\n}\n","import { container, inject } from 'tsyringe';\nimport { SessionState } from '../db/session_state';\nimport { TOKENS } from '../di_tokens';\nimport { Logger } from '../utils/logger';\n\n// Register a factory for the authenticated session token\nexport function registerAuthenticatedSession() {\n container.register(TOKENS.AUTHENTICATED_SESSION, {\n useFactory: (container) => {\n const sessionState = container.resolve<SessionState>(TOKENS.SESSION);\n if (sessionState.type !== 'authenticated') {\n try {\n const logger = container.resolve<Logger>(TOKENS.LOGGER);\n logger.error(\n 'Session is not authenticated - validateSessionMiddleware should have caught this',\n );\n } catch {\n // Fallback to console if logger resolution fails\n console.error(\n 'Session is not authenticated - validateSessionMiddleware should have caught this',\n );\n }\n throw new Error('Session is not authenticated');\n }\n return sessionState.session;\n },\n });\n}\n\n/**\n * A parameter decorator that injects an authenticated UserAppSession.\n * This will throw an error if the session is not authenticated.\n *\n * Example usage:\n * ```typescript\n * class MyFeature {\n * constructor(@injectSession() private session: UserAppSession) {}\n * }\n * ```\n */\nexport const injectSession = () => {\n return inject(TOKENS.AUTHENTICATED_SESSION);\n};\n","import { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { TOKENS } from '../di_tokens';\nimport { Logger } from '../utils/logger';\nimport {\n requireAdmin,\n requireAuthenticated,\n requireLeadOrStaff,\n requireStaff,\n} from './auth-checks';\n\nexport type AuthLevel = 'public' | 'protected' | 'staff' | 'admin' | 'lead_or_staff';\n\n// ============================================================================\n// HELPER FUNCTIONS (Use these INSIDE methods instead of decorators)\n// ============================================================================\n\n/**\n * Serializes an error object into a plain object with all relevant properties\n * This ensures errors are properly logged with all their details\n */\nfunction serializeError(error: unknown): Record<string, any> {\n // Handle null/undefined\n if (error === null || error === undefined) {\n return { error_type: 'null_or_undefined', error_value: String(error) };\n }\n\n // Handle Error instances\n if (error instanceof Error) {\n const serialized: Record<string, any> = {\n error_type: error.constructor.name || 'Error',\n error_name: error.name,\n error_message: error.message,\n };\n\n // Add stack trace if available\n if (error.stack) {\n serialized.error_stack = error.stack;\n }\n\n // Handle ZodError specifically - extract validation details\n if (error instanceof z.ZodError) {\n serialized.zod_errors = error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n code: e.code,\n }));\n }\n\n // Handle custom error classes with additional properties\n if (error instanceof ValidationError) {\n serialized.error_type = 'ValidationError';\n } else if (error instanceof AuthenticationError) {\n serialized.error_type = 'AuthenticationError';\n if (error.code) {\n serialized.error_code = error.code;\n }\n } else if (error instanceof BusinessError) {\n serialized.error_type = 'BusinessError';\n } else if (error instanceof InternalServerError) {\n serialized.error_type = 'InternalServerError';\n if (error.originalError) {\n serialized.original_error = serializeError(error.originalError);\n }\n }\n\n // Add any enumerable properties from the error object\n const errorObj = error as Record<string, any>;\n for (const key in errorObj) {\n if (key !== 'name' && key !== 'message' && key !== 'stack' && !serialized[key]) {\n try {\n // Only include serializable values\n const value = errorObj[key];\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n serialized[key] = value;\n } else if (value === null || value === undefined) {\n serialized[key] = value;\n } else if (typeof value === 'object') {\n // Try to serialize objects, but limit depth to avoid circular references\n try {\n JSON.stringify(value);\n serialized[key] = value;\n } catch {\n serialized[key] = '[Non-serializable object]';\n }\n }\n } catch {\n // Skip properties that can't be accessed\n }\n }\n }\n\n // Handle cause property (standard Error.cause)\n if ('cause' in error && error.cause) {\n serialized.error_cause = serializeError(error.cause);\n }\n\n return serialized;\n }\n\n // Handle plain objects\n if (typeof error === 'object') {\n try {\n // Try to serialize as-is\n JSON.stringify(error);\n return {\n error_type: 'object',\n error_value: error as Record<string, any>,\n };\n } catch {\n return {\n error_type: 'object',\n error_value: '[Non-serializable object]',\n error_string: String(error),\n };\n }\n }\n\n // Handle primitives\n return {\n error_type: typeof error,\n error_value: error,\n };\n}\n\n/**\n * Validates input data against a Zod schema\n * @throws ValidationError if validation fails\n */\nexport function validateInput<T extends z.ZodSchema>(schema: T, data: unknown): z.infer<T> {\n try {\n return schema.parse(data);\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new ValidationError(\n `Validation failed: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,\n );\n }\n throw error;\n }\n}\n\n/**\n * Validates output data against a Zod schema\n * @throws ValidationError if validation fails\n */\nexport function validateOutput<T extends z.ZodSchema>(schema: T, data: unknown): z.infer<T> {\n try {\n return schema.parse(data);\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw new ValidationError(\n `Output validation failed: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,\n );\n }\n throw error;\n }\n}\n\n/**\n * Checks authentication level based on the specified level\n * @throws AuthenticationError if auth check fails\n */\nexport async function checkAuth(\n level: AuthLevel,\n container: DependencyContainer,\n): Promise<void> {\n if (!container) {\n throw new AuthenticationError('Container not available');\n }\n switch (level) {\n case 'public':\n // No auth required\n break;\n case 'protected':\n await requireAuthenticated(container);\n break;\n case 'staff':\n await requireStaff(container);\n break;\n case 'admin':\n await requireAdmin(container);\n break;\n case 'lead_or_staff':\n await requireLeadOrStaff(container);\n break;\n }\n}\n\n/**\n * Wraps an async function with error handling\n * Use this to wrap your entire method logic\n */\nexport async function withErrorHandling<T>(\n fn: () => Promise<T>,\n context: string,\n container?: DependencyContainer,\n): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n // Serialize error to ensure all details are captured\n const serializedError = serializeError(error);\n\n // Resolve logger from container if available, otherwise use console.error as fallback\n if (container) {\n try {\n const logger = container.resolve<Logger>(TOKENS.LOGGER);\n logger.error(`[RPC Error] ${context}`, { error: serializedError });\n } catch (loggerError) {\n // Fallback to console if logger resolution fails\n console.error(`[RPC Error] ${context}:`, error);\n console.error('Logger resolution failed:', loggerError);\n }\n } else {\n console.error(`[RPC Error] ${context}:`, error);\n console.error('Serialized error:', serializedError);\n }\n\n if (\n error instanceof ValidationError ||\n error instanceof AuthenticationError ||\n error instanceof BusinessError\n ) {\n throw error;\n }\n\n throw new InternalServerError(\n 'An unexpected error occurred',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Streamlined RPC method handler - combines auth, validation, and error handling\n * REQUIRES both input and output schemas for validation\n *\n * @example\n * ```typescript\n * async read(input: { id: string }): Promise<MyDto | null> {\n * return rpcMethod({\n * auth: 'protected',\n * container: this.container,\n * context: 'MyApiServer.read',\n * input,\n * inputSchema: z.object({ id: z.string() }),\n * outputSchema: MySchema.nullable(),\n * execute: async (validated, container) => {\n * const feature = container.resolve<IReadFeature>(TOKENS.IReadFeature);\n * return await feature.execute(validated.id);\n * }\n * });\n * }\n * ```\n */\nexport async function rpcMethod<TInput, TOutput>(config: {\n auth: AuthLevel;\n container: DependencyContainer;\n context: string;\n input: TInput;\n inputSchema: z.ZodSchema<TInput>;\n outputSchema: z.ZodSchema<TOutput>;\n execute: (validated: TInput, container: DependencyContainer) => Promise<TOutput>;\n}): Promise<TOutput> {\n return withErrorHandling(\n async () => {\n // 1. Check authentication\n await checkAuth(config.auth, config.container);\n\n // 2. Validate input\n const validated = validateInput(config.inputSchema, config.input);\n\n // 3. Execute business logic\n const result = await config.execute(validated, config.container);\n\n // 4. Validate output\n return validateOutput(config.outputSchema, result);\n },\n config.context,\n config.container,\n );\n}\n\n/**\n * RPC method handler with OPTIONAL validation schemas\n * Use this only when you have a specific reason to skip validation\n *\n * @example\n * ```typescript\n * async someMethod(input: MyDto): Promise<MyDto> {\n * return rpcMethodPartial({\n * auth: 'protected',\n * container: this.container,\n * context: 'MyApiServer.someMethod',\n * input,\n * execute: async (input, container) => {\n * // No validation needed - input is already validated elsewhere\n * return await processInput(input);\n * }\n * });\n * }\n * ```\n */\nexport async function rpcMethodPartial<TInput, TOutput>(config: {\n auth: AuthLevel;\n container: DependencyContainer;\n context: string;\n input: TInput;\n inputSchema?: z.ZodSchema<TInput>;\n outputSchema?: z.ZodSchema<TOutput>;\n execute: (validated: TInput, container: DependencyContainer) => Promise<TOutput>;\n}): Promise<TOutput> {\n return withErrorHandling(\n async () => {\n // 1. Check authentication\n await checkAuth(config.auth, config.container);\n\n // 2. Validate input (if schema provided)\n const validated = config.inputSchema\n ? validateInput(config.inputSchema, config.input)\n : config.input;\n\n // 3. Execute business logic\n const result = await config.execute(validated, config.container);\n\n // 4. Validate output (if schema provided)\n return config.outputSchema ? validateOutput(config.outputSchema, result) : result;\n },\n config.context,\n config.container,\n );\n}\n\n/**\n * Simplified RPC method handler for cases without input/output validation\n * Use this when you don't need to validate input or output schemas\n *\n * @example\n * ```typescript\n * async getCurrentUser(): Promise<UserDto | null> {\n * return rpcMethodSimple({\n * auth: 'protected',\n * container: this.container,\n * context: 'UserApiServer.getCurrentUser',\n * execute: async (container) => {\n * const feature = container.resolve<IGetCurrentUserFeature>(TOKENS.IGetCurrentUserFeature);\n * return await feature.execute();\n * }\n * });\n * }\n * ```\n */\nexport async function rpcMethodSimple<TOutput>(config: {\n auth: AuthLevel;\n container: DependencyContainer;\n context: string;\n execute: (container: DependencyContainer) => Promise<TOutput>;\n}): Promise<TOutput> {\n return withErrorHandling(\n async () => {\n await checkAuth(config.auth, config.container);\n return await config.execute(config.container);\n },\n config.context,\n config.container,\n );\n}\n\n// Custom error classes\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\nexport class AuthenticationError extends Error {\n public code?: string;\n\n constructor(message: string, code?: string) {\n super(message);\n this.name = 'AuthenticationError';\n this.code = code;\n }\n}\n\nexport class BusinessError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BusinessError';\n }\n}\n\nexport class InternalServerError extends Error {\n public originalError?: Error;\n\n constructor(message: string, originalError?: Error) {\n super(message);\n this.name = 'InternalServerError';\n this.originalError = originalError;\n }\n}\n","import type { DependencyContainer } from 'tsyringe';\nimport { SessionState } from '../db/session_state';\nimport { TOKENS } from '../di_tokens';\nimport { AuthenticationError } from './rpc_mid';\n\nexport enum AuthErrorCode {\n NO_ACCESS_TOKEN = 'NO_ACCESS_TOKEN',\n ACCESS_TOKEN_EXPIRED = 'ACCESS_TOKEN_EXPIRED',\n ACCESS_TOKEN_REVOKED = 'ACCESS_TOKEN_REVOKED',\n FORBIDDEN = 'FORBIDDEN',\n}\n\nexport async function requireAuthenticated(container: DependencyContainer) {\n const sessionState = container.resolve<SessionState>(TOKENS.SESSION);\n\n switch (sessionState.type) {\n case 'unauthenticated':\n throw new AuthenticationError('Unauthorized', AuthErrorCode.NO_ACCESS_TOKEN);\n case 'revoked':\n throw new AuthenticationError('Unauthorized', AuthErrorCode.ACCESS_TOKEN_REVOKED);\n case 'expired':\n throw new AuthenticationError('Unauthorized', AuthErrorCode.ACCESS_TOKEN_EXPIRED);\n }\n\n // Return the authenticated session for further checks\n return sessionState;\n}\n\nexport async function requireAdmin(container: DependencyContainer) {\n const sessionState = await requireAuthenticated(container);\n\n // Check if user is admin\n if (sessionState.session.user.user_type !== 'super_admin') {\n throw new AuthenticationError('Unauthorized', AuthErrorCode.FORBIDDEN);\n }\n\n return sessionState;\n}\n\nexport async function requireStaff(container: DependencyContainer) {\n const sessionState = await requireAuthenticated(container);\n\n // Add your staff check logic here\n // Example (adjust based on your actual SessionState structure):\n if (\n sessionState.session.user.user_type !== 'staff' &&\n sessionState.session.user.user_type !== 'super_admin'\n ) {\n throw new AuthenticationError('Unauthorized', AuthErrorCode.FORBIDDEN);\n }\n\n return sessionState;\n}\n\nexport async function requireLeadOrStaff(container: DependencyContainer) {\n const sessionState = await requireAuthenticated(container);\n\n // Add your lead or staff check logic here\n // Example (adjust based on your actual SessionState structure):\n const userType = sessionState.session.user.user_type;\n if (userType !== 'lead' && userType !== 'staff' && userType !== 'super_admin') {\n throw new AuthenticationError('Unauthorized', AuthErrorCode.FORBIDDEN);\n }\n\n return sessionState;\n}\n","import type { DrizzleD1Database } from 'drizzle-orm/d1';\nimport type { DependencyContainer } from 'tsyringe';\nimport type { DatabaseRouter } from '../db/database_router';\nimport { TOKENS } from '../di_tokens';\nimport type { Env } from '../env.d';\nimport type { IUniversalIdGenerator } from '../lib/universal_id_generator';\nimport type { Logger } from '../utils/logger';\n\n/**\n * Factory functions for creating container dependencies\n */\nexport interface ContainerFactories<TEnv = Env> {\n /** Factory for creating logger */\n createLogger: (env: TEnv, requestId: string) => Logger;\n /** Factory for creating ID generator */\n createIdGenerator: (\n env: TEnv,\n config: {\n recordTypes: string[];\n shards: string[];\n },\n ) => IUniversalIdGenerator;\n /** Factory for creating primary database router */\n createPrimaryDatabaseRouter: (\n env: TEnv,\n idGenerator: IUniversalIdGenerator,\n logger: Logger,\n shardIds: Record<string, Record<string, string>>,\n ) => DatabaseRouter;\n /** Factory for creating historical database router */\n createHistoricalDatabaseRouter?: (\n env: TEnv,\n idGenerator: IUniversalIdGenerator,\n logger: Logger,\n shardIds: Record<string, Record<string, string>>,\n ) => DatabaseRouter;\n /** Factory for creating app settings database */\n createAppSettingsDb?: (env: TEnv) => DrizzleD1Database;\n}\n\n/**\n * Configuration for building container factories\n */\nexport interface ContainerFactoryConfig<TEnv = Env> {\n env: TEnv;\n requestId: string;\n factories: ContainerFactories<TEnv>;\n /** Record types for ID generator */\n recordTypes: string[];\n /** Shards for ID generator */\n shards: string[];\n /** ShardIds configuration - maps environment names to binding-to-shard mappings */\n shardIds: Record<string, Record<string, string>>;\n /** Optional performance logger */\n perfLog?: (message: string) => void;\n /** Optional getter for logger instance */\n getLogger?: () => Logger;\n}\n\n/**\n * Builds container instances and factories for dependency injection\n * Separates eager instances from lazy factories for performance\n *\n * @param config - Configuration for building factories\n * @returns Object with instances and factories\n */\nexport function buildContainerFactories<TEnv extends Env = Env>(\n config: ContainerFactoryConfig<TEnv>,\n): {\n instances: Record<symbol, any>;\n factories: Record<symbol, (container: DependencyContainer) => any>;\n} {\n // console.log('[buildContainerFactories] Function called');\n const {\n env,\n requestId,\n factories,\n recordTypes: _recordTypes,\n shards: _shards,\n shardIds,\n perfLog,\n getLogger,\n } = config;\n\n // console.log('[buildContainerFactories] Config env check:', {\n // envType: typeof env,\n // envIsNull: env === null,\n // envIsUndefined: env === undefined,\n // envIsObject: typeof env === 'object',\n // envConstructor: env?.constructor?.name,\n // });\n\n // Eager instances - needed immediately or very lightweight\n const instances: Record<symbol, any> = {\n [TOKENS.ENV]: env,\n // Note: SESSION, REQUEST, HONO_CONTEXT should be set by the caller\n // as they depend on the request context\n };\n // console.log('[buildContainerFactories] Created instances with env');\n\n // Lazy factories - expensive dependencies initialized only when first accessed\n const factoriesMap: Record<symbol, (container: DependencyContainer) => any> = {\n // Logger - create lazily only when actually needed\n [TOKENS.LOGGER]: () => {\n return factories.createLogger(env, requestId);\n },\n // ID generator - preloads values asynchronously but doesn't block\n [TOKENS.ID_GENERATOR]: () => {\n const initStart = perfLog ? performance.now() : 0;\n const generator = factories.createIdGenerator(env, {\n recordTypes: config.recordTypes,\n shards: config.shards,\n });\n if (perfLog && initStart > 0 && getLogger) {\n const initTime = performance.now() - initStart;\n getLogger().perf(`ID generator initialization: ${initTime.toFixed(2)}ms`);\n }\n return generator;\n },\n // Primary database router - expensive initialization (parses all DB bindings)\n [TOKENS.IDatabaseRouter]: (container) => {\n console.log('[IDatabaseRouter factory] Factory called');\n const initStart = perfLog ? performance.now() : 0;\n const idGenerator = container.resolve<IUniversalIdGenerator>(TOKENS.ID_GENERATOR);\n const logger = getLogger ? getLogger() : container.resolve<Logger>(TOKENS.LOGGER);\n // Get env from container instead of closure to avoid issues with Proxy objects\n const envFromContainer = container.resolve<Env>(TOKENS.ENV) as TEnv;\n // console.log('[IDatabaseRouter factory] Resolved env from container:', {\n // envType: typeof envFromContainer,\n // envIsNull: envFromContainer === null,\n // envIsUndefined: envFromContainer === undefined,\n // envIsObject: typeof envFromContainer === 'object',\n // envConstructor: envFromContainer?.constructor?.name,\n // });\n const router = factories.createPrimaryDatabaseRouter(\n envFromContainer,\n idGenerator,\n logger,\n shardIds,\n );\n if (perfLog && initStart > 0 && getLogger) {\n const initTime = performance.now() - initStart;\n getLogger().perf(`Primary router initialization: ${initTime.toFixed(2)}ms`);\n }\n\n return router;\n },\n };\n\n // Historical database router - only if factory is provided\n if (factories.createHistoricalDatabaseRouter) {\n factoriesMap[TOKENS.IHistoricalDatabaseRouter] = (container) => {\n // console.log('[IHistoricalDatabaseRouter factory] Factory called');\n const initStart = perfLog ? performance.now() : 0;\n const idGenerator = container.resolve<IUniversalIdGenerator>(TOKENS.ID_GENERATOR);\n const logger = getLogger ? getLogger() : container.resolve<Logger>(TOKENS.LOGGER);\n // Get env from container instead of closure to avoid issues with Proxy objects\n const envFromContainer = container.resolve<Env>(TOKENS.ENV) as TEnv;\n // console.log('[IHistoricalDatabaseRouter factory] Resolved env from container:', {\n // envType: typeof envFromContainer,\n // envIsNull: envFromContainer === null,\n // envIsUndefined: envFromContainer === undefined,\n // envIsObject: typeof envFromContainer === 'object',\n // envConstructor: envFromContainer?.constructor?.name,\n // });\n const router = factories.createHistoricalDatabaseRouter!(\n envFromContainer,\n idGenerator,\n logger,\n shardIds,\n );\n if (perfLog && initStart > 0 && getLogger) {\n const initTime = performance.now() - initStart;\n getLogger().perf(`Historical router initialization: ${initTime.toFixed(2)}ms`);\n }\n // console.log('[IHistoricalDatabaseRouter factory] Router created successfully');\n return router;\n };\n }\n\n // App settings database - only if factory is provided\n if (factories.createAppSettingsDb) {\n factoriesMap[TOKENS.IAppSettingsDb] = (container) => {\n // console.log('[createAppSettingsDb factory] Factory called');\n const initStart = perfLog ? performance.now() : 0;\n // Get env from container instead of closure to avoid issues with Proxy objects\n // in production Cloudflare Workers environment\n const envFromContainer = container.resolve<Env>(TOKENS.ENV) as TEnv;\n // console.log('[createAppSettingsDb factory] Resolved env from container:', {\n // envType: typeof envFromContainer,\n // envIsNull: envFromContainer === null,\n // envIsUndefined: envFromContainer === undefined,\n // envIsObject: typeof envFromContainer === 'object',\n // envKeys: envFromContainer ? Object.keys(envFromContainer).slice(0, 10) : 'N/A',\n // envConstructor: envFromContainer?.constructor?.name,\n // });\n const db = factories.createAppSettingsDb!(envFromContainer);\n if (perfLog && initStart > 0 && getLogger) {\n const initTime = performance.now() - initStart;\n getLogger().perf(`App settings DB initialization: ${initTime.toFixed(2)}ms`);\n }\n // console.log('[createAppSettingsDb factory] Database created successfully');\n return db;\n };\n }\n\n return {\n instances,\n factories: factoriesMap,\n };\n}\n","import { UserTypeValues } from '@dragonmastery/dragoncore-shared';\nimport * as jose from 'jose';\nimport { custom_long_nanoid } from '../../../db/db_utils';\nimport { FrontendSessionDto } from '../user_session_interfaces';\nimport {\n AccessTokenPayload,\n PasswordResetTokenPayload,\n RefreshTokenPayload,\n UserDetailsTokenPayload,\n} from './jwt_types';\n\nexport async function generateAccessToken(\n userId: string,\n userType: UserTypeValues,\n emailVerified: boolean,\n username: string,\n email: string,\n secret: string,\n expiresIn: number,\n family: string,\n): Promise<string> {\n const payload: AccessTokenPayload = {\n sub: userId,\n type: 'access',\n user_type: userType,\n email_verified: emailVerified,\n username: username,\n email: email,\n exp: Math.floor(Date.now() / 1000) + expiresIn,\n iat: Math.floor(Date.now() / 1000),\n jti: custom_long_nanoid(),\n family: family,\n };\n\n return await new jose.SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(new TextEncoder().encode(secret));\n}\n\nexport async function generateRefreshToken(\n userId: string,\n tokenFamily: string,\n secret: string,\n expiresIn: number,\n token_id: string,\n): Promise<string> {\n const payload: RefreshTokenPayload = {\n sub: userId,\n type: 'refresh',\n family: tokenFamily,\n exp: Math.floor(Date.now() / 1000) + expiresIn,\n iat: Math.floor(Date.now() / 1000),\n jti: token_id,\n };\n\n return await new jose.SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(new TextEncoder().encode(secret));\n}\n\nexport async function generateUserDetailsToken(\n userId: string,\n userDetails: FrontendSessionDto,\n secret: string,\n expiresIn: number,\n): Promise<string> {\n const payload: UserDetailsTokenPayload = {\n sub: userId,\n type: 'details',\n details: userDetails,\n exp: Math.floor(Date.now() / 1000) + expiresIn,\n iat: Math.floor(Date.now() / 1000),\n jti: custom_long_nanoid(),\n };\n\n return await new jose.SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(new TextEncoder().encode(secret));\n}\n\nexport async function generatePasswordResetToken(\n userId: string,\n secret: string,\n expiresIn: number,\n): Promise<string> {\n const payload: PasswordResetTokenPayload = {\n sub: userId,\n type: 'password_reset',\n exp: Math.floor(Date.now() / 1000) + expiresIn,\n iat: Math.floor(Date.now() / 1000),\n jti: custom_long_nanoid(),\n };\n\n return await new jose.SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(new TextEncoder().encode(secret));\n}\n\nexport async function verifyToken<\n T extends\n | AccessTokenPayload\n | RefreshTokenPayload\n | UserDetailsTokenPayload\n | PasswordResetTokenPayload,\n>(token: string, secret: string): Promise<T> {\n try {\n const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret));\n return payload as T;\n } catch (error) {\n throw new Error('Invalid token');\n }\n}\n","import type { SessionState } from '../db/session_state';\nimport type { UserAppSession } from '../db/user_app_session';\nimport type { Env } from '../env.d';\nimport type { AccessTokenPayload } from '../slices/user_session/jwt/jwt_types';\n\n/**\n * Configuration for session validation from JWT\n */\nexport interface SessionValidationConfig {\n /** Environment object with JWT secret */\n env: Env;\n /** Function to verify JWT token */\n verifyToken: (token: string, secret: string) => Promise<AccessTokenPayload>;\n /** Function to create authenticated session state */\n createAuthenticatedState: (session: UserAppSession) => SessionState;\n /** Function to create expired session state */\n createExpiredState: () => SessionState;\n /** Function to create revoked session state */\n createRevokedState: () => SessionState;\n /** Function to create unauthenticated session state */\n createUnauthenticatedState: () => SessionState;\n /** Optional function to get global revoke timestamp from env */\n getGlobalRevokeTimestamp?: (env: Env) => string | undefined;\n /** Optional function to check if tokens should be revoked */\n shouldRevokeTokens?: (env: Env) => boolean;\n /** Optional function to get request headers (for user agent, IP, etc.) */\n getRequestHeaders?: () => {\n userAgent?: string | null;\n ipAddress?: string | null;\n };\n /** Optional logger for error logging */\n logger?: {\n warn: (message: string, data?: Record<string, any>) => void;\n error: (message: string, data?: Record<string, any>) => void;\n };\n /** Optional performance logger */\n perfLog?: (message: string) => void;\n}\n\n/**\n * Validates session from JWT token in Authorization header\n * Returns the appropriate SessionState based on token validation\n *\n * @param authHeader - Authorization header value (e.g., \"Bearer <token>\")\n * @param config - Configuration for session validation\n * @returns Promise resolving to SessionState\n */\nexport async function validateSessionFromJWT(\n authHeader: string | undefined,\n config: SessionValidationConfig,\n): Promise<SessionState> {\n // Default to unauthenticated if no auth header\n if (!authHeader?.startsWith('Bearer ')) {\n return config.createUnauthenticatedState();\n }\n\n try {\n const accessToken = authHeader.replace('Bearer ', '');\n const payload = await config.verifyToken(accessToken, config.env.ACCESS_JWT_SECRET);\n\n // Check if token is expired\n if (payload.exp < Math.floor(Date.now() / 1000)) {\n return config.createExpiredState();\n }\n\n // Check if token is valid access token\n if (payload.type === 'access' && payload.exp > Math.floor(Date.now() / 1000)) {\n // Global token revocation check\n const globalRevokeTimestamp = config.getGlobalRevokeTimestamp?.(config.env);\n const shouldRevoke = config.shouldRevokeTokens?.(config.env);\n\n if (\n shouldRevoke &&\n globalRevokeTimestamp &&\n payload.iat < parseInt(globalRevokeTimestamp)\n ) {\n config.logger?.warn('Token revoked by global timestamp', {\n issued_at: payload.iat,\n revoke_threshold: globalRevokeTimestamp,\n });\n return config.createRevokedState();\n }\n\n // Build minimal session directly from JWT payload (no DB lookup)\n const headers = config.getRequestHeaders?.() || {};\n const minimalSession: UserAppSession = {\n id: payload.jti,\n created_at: new Date(payload.iat * 1000).toISOString(),\n expires_at: new Date(payload.exp * 1000).toISOString(),\n status: 'active' as const,\n user_agent: headers.userAgent || null,\n ip_address: headers.ipAddress || null,\n user: {\n userId: payload.sub,\n username: payload.username,\n email: payload.email,\n email_verified: payload.email_verified,\n user_type: payload.user_type,\n first_name: null,\n last_name: null,\n avatar_url: null,\n subscriptions: [],\n },\n };\n\n return config.createAuthenticatedState(minimalSession);\n }\n\n return config.createUnauthenticatedState();\n } catch (error) {\n config.logger?.error('Error validating session', {\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n return config.createUnauthenticatedState();\n }\n}\n","import { drizzle } from 'drizzle-orm/d1';\nimport { DatabaseRouter as DatabaseRouterClass } from '../db/database_router';\nimport { customNanoid } from '../db/db_utils';\nimport { RecordConst } from '../db/dbTypes';\nimport {\n createAuthenticatedState,\n createExpiredState,\n createRevokedState,\n createUnauthenticatedState,\n} from '../db/session_state';\nimport type { Env } from '../env.d';\nimport { UniversalIdGenerator } from '../lib/universal_id_generator';\nimport type { AccessTokenPayload } from '../slices/user_session/jwt/jwt_types';\nimport { verifyToken } from '../slices/user_session/jwt/jwt_utils';\nimport type { HonoContext } from '../types/hono_context';\nimport { Logger } from '../utils/logger';\nimport type { ContainerFactories } from './container_factory_builder';\nimport { buildContainerFactories } from './container_factory_builder';\nimport type { ContainerSetupConfig } from './container_setup_mid';\nimport { validateSessionFromJWT, type SessionValidationConfig } from './session_validation';\n\n/**\n * Helper to create logger and performance logging utilities\n */\nexport function createLoggerHelpers(env: Env, requestId: string) {\n const perfLogsEnabled = Logger.wouldEnablePerfLogs(env);\n let requestLogger: Logger | null = null;\n\n const getLogger = () => {\n if (!requestLogger) {\n requestLogger = new Logger(env, requestId);\n }\n return requestLogger;\n };\n\n const perfLog = (message: string) => {\n if (perfLogsEnabled) {\n getLogger().perf(message);\n }\n };\n\n return { getLogger, perfLog };\n}\n\n/**\n * Creates default factory implementations for container dependencies\n * These are generic implementations that work for most use cases\n */\nexport function createDefaultContainerFactories<TEnv extends Env = Env>(\n _env: TEnv,\n shardIds: Record<string, Record<string, string>>,\n): ContainerFactories<TEnv> {\n return {\n createLogger: (env: TEnv, requestId: string) => new Logger(env, requestId),\n createIdGenerator: (_env: TEnv, config) =>\n new UniversalIdGenerator({\n preloadValues: {\n recordTypes: config.recordTypes,\n shards: config.shards,\n },\n }),\n // Capture shardIds from closure - factory functions receive it as parameter for consistency\n createPrimaryDatabaseRouter: (env: TEnv, idGenerator, logger, _shardIds) =>\n new DatabaseRouterClass(env, idGenerator, logger, shardIds, 'primary'),\n createHistoricalDatabaseRouter: (env: TEnv, idGenerator, logger, _shardIds) =>\n new DatabaseRouterClass(env, idGenerator, logger, shardIds, 'historical'),\n createAppSettingsDb: (env: TEnv) => {\n // console.log('[createAppSettingsDb] Function called with env:', {\n // envType: typeof env,\n // envIsNull: env === null,\n // envIsUndefined: env === undefined,\n // envIsObject: typeof env === 'object',\n // envConstructor: env?.constructor?.name,\n // });\n\n // Guard against null/undefined env to prevent \"Cannot convert undefined or null to object\" error\n if (!env || typeof env !== 'object') {\n console.error('[createAppSettingsDb] ERROR: env is null or undefined:', {\n env,\n type: typeof env,\n });\n throw new Error(\n 'Environment object is null or undefined. Cannot create app settings database.',\n );\n }\n\n // console.log('[createAppSettingsDb] Getting Object.keys(env)...');\n const envKeys = Object.keys(env);\n // console.log('[createAppSettingsDb] Env keys found:', {\n // totalKeys: envKeys.length,\n // sampleKeys: envKeys.slice(0, 20),\n // dbAppSettingKeys: envKeys.filter((key) => key.startsWith('DB_APP_SETTING_')),\n // });\n\n const appSettingsDbKey = envKeys.find((key) => key.startsWith('DB_APP_SETTING_'));\n // console.log('[createAppSettingsDb] App settings DB key:', appSettingsDbKey);\n\n if (!appSettingsDbKey) {\n console.error('[createAppSettingsDb] ERROR: No app settings database binding found');\n throw new Error('No app settings database binding found');\n }\n\n // console.log('[createAppSettingsDb] Creating drizzle instance for:', appSettingsDbKey);\n // @ts-ignore - env keys are dynamic\n const db = drizzle(env[appSettingsDbKey]);\n // console.log('[createAppSettingsDb] Database instance created successfully');\n return db;\n },\n };\n}\n\n/**\n * Creates default session validation configuration\n * Uses standard JWT verification and session state creation\n */\nexport function createDefaultSessionValidationConfig<TEnv extends Env = Env>(\n env: TEnv,\n ctx: HonoContext,\n getLogger: () => Logger,\n perfLog: (message: string) => void,\n options?: {\n getGlobalRevokeTimestamp?: (env: TEnv) => string | undefined;\n shouldRevokeTokens?: (env: TEnv) => boolean;\n },\n): SessionValidationConfig {\n return {\n env,\n verifyToken: async (token: string, secret: string) => {\n return verifyToken<AccessTokenPayload>(token, secret);\n },\n createAuthenticatedState,\n createExpiredState,\n createRevokedState,\n createUnauthenticatedState,\n getGlobalRevokeTimestamp: options?.getGlobalRevokeTimestamp\n ? (env: Env) => options!.getGlobalRevokeTimestamp!(env as TEnv)\n : undefined,\n shouldRevokeTokens: options?.shouldRevokeTokens\n ? (env: Env) => options!.shouldRevokeTokens!(env as TEnv)\n : undefined,\n getRequestHeaders: () => ({\n userAgent: ctx.req.header('User-Agent') || null,\n ipAddress:\n ctx.req.header('CF-Connecting-IP') || ctx.req.header('X-Forwarded-For') || null,\n }),\n logger: getLogger(),\n perfLog,\n };\n}\n\n/**\n * Creates a default container setup configuration with sensible defaults\n * Workers can override specific parts as needed\n */\nexport function createDefaultContainerSetupConfig<TEnv extends Env = Env>(config: {\n createRequestContainer: ContainerSetupConfig<TEnv>['createRequestContainer'];\n env: TEnv;\n ctx: HonoContext;\n shards: string[];\n shardIds: Record<string, Record<string, string>>;\n recordTypes?: string[];\n factories?: Partial<ContainerFactories<TEnv>>;\n sessionValidationOptions?: {\n getGlobalRevokeTimestamp?: (env: TEnv) => string | undefined;\n shouldRevokeTokens?: (env: TEnv) => boolean;\n };\n}): ContainerSetupConfig<TEnv> {\n // console.log('[createDefaultContainerSetupConfig] Function called');\n // console.log('[createDefaultContainerSetupConfig] config.env check:', {\n // envType: typeof config.env,\n // envIsNull: config.env === null,\n // envIsUndefined: config.env === undefined,\n // envIsObject: typeof config.env === 'object',\n // envConstructor: config.env?.constructor?.name,\n // });\n\n const requestId = config.ctx.req.header('x-request-id') || customNanoid();\n // console.log('[createDefaultContainerSetupConfig] Calling createLoggerHelpers...');\n const { getLogger, perfLog } = createLoggerHelpers(config.env, requestId);\n // console.log('[createDefaultContainerSetupConfig] createLoggerHelpers completed');\n\n // console.log(\n // '[createDefaultContainerSetupConfig] Calling createDefaultContainerFactories...',\n // );\n const defaultFactories = createDefaultContainerFactories(config.env, config.shardIds);\n // console.log('[createDefaultContainerSetupConfig] createDefaultContainerFactories completed');\n\n const factories: ContainerFactories<TEnv> = {\n ...defaultFactories,\n ...config.factories,\n };\n // console.log('[createDefaultContainerSetupConfig] Factories merged');\n\n // console.log('[createDefaultContainerSetupConfig] Creating return object...');\n // console.log('[createDefaultContainerSetupConfig] RecordConst check:', {\n // RecordConstType: typeof RecordConst,\n // RecordConstIsNull: RecordConst === null,\n // RecordConstIsUndefined: RecordConst === undefined,\n // RecordConstIsObject: typeof RecordConst === 'object',\n // });\n\n let recordTypesValue: string[];\n try {\n // console.log('[createDefaultContainerSetupConfig] Getting Object.values(RecordConst)...');\n recordTypesValue = config.recordTypes || Object.values(RecordConst);\n // console.log('[createDefaultContainerSetupConfig] Object.values(RecordConst) succeeded:', {\n // recordTypesLength: recordTypesValue.length,\n // sampleRecordTypes: recordTypesValue.slice(0, 5),\n // });\n } catch (error) {\n console.error(\n '[createDefaultContainerSetupConfig] ERROR calling Object.values(RecordConst):',\n error,\n );\n throw error;\n }\n\n return {\n createRequestContainer: config.createRequestContainer,\n validateSession: async (authHeader: string | undefined) => {\n const sessionConfig = createDefaultSessionValidationConfig(\n config.env,\n config.ctx,\n getLogger,\n perfLog,\n config.sessionValidationOptions,\n );\n return validateSessionFromJWT(authHeader, sessionConfig);\n },\n buildFactories: () => {\n return buildContainerFactories({\n env: config.env,\n requestId,\n recordTypes: recordTypesValue,\n shards: config.shards,\n shardIds: config.shardIds,\n factories,\n perfLog,\n getLogger,\n });\n },\n getRequestId: (ctx: HonoContext) => ctx.req.header('x-request-id') || customNanoid(),\n perfLog,\n };\n}\n","import type { DependencyContainer } from 'tsyringe';\nimport type { SessionState } from '../db/session_state';\nimport { TOKENS } from '../di_tokens';\nimport type { Env } from '../env.d';\nimport type { HonoContext } from '../types/hono_context';\n\n/**\n * Configuration for container setup middleware\n */\nexport interface ContainerSetupConfig<_TEnv extends Env = Env> {\n /** Function to create the request container */\n createRequestContainer: (config: {\n instances: Record<symbol, any>;\n factories: Record<symbol, (container: DependencyContainer) => any>;\n }) => DependencyContainer;\n /** Function to validate session from JWT */\n validateSession: (authHeader: string | undefined) => Promise<SessionState>;\n /** Function to build container factories */\n buildFactories: () => {\n instances: Record<symbol, any>;\n factories: Record<symbol, (container: DependencyContainer) => any>;\n };\n /** Function to get request ID */\n getRequestId: (ctx: HonoContext) => string;\n /** Optional performance logger */\n perfLog?: (message: string) => void;\n}\n\n/**\n * Creates a container setup middleware function\n * Sets up dependency injection container for each request\n *\n * @param config - Configuration for container setup\n * @returns Hono middleware function\n */\nexport function createContainerSetupMiddleware<TEnv extends Env = Env>(\n config: ContainerSetupConfig<TEnv>,\n) {\n return async (ctx: HonoContext): Promise<void> => {\n console.log('[container_setup_mid] Container setup middleware called');\n // console.log('[container_setup_mid] ctx.env check:', {\n // envType: typeof ctx.env,\n // envIsNull: ctx.env === null,\n // envIsUndefined: ctx.env === undefined,\n // envIsObject: typeof ctx.env === 'object',\n // envConstructor: ctx.env?.constructor?.name,\n // hasENVIRONMENT: 'ENVIRONMENT' in (ctx.env || {}),\n // });\n \n const setupStart = config.perfLog ? performance.now() : 0;\n\n // Validate session from JWT\n const authHeader = ctx.req.header('Authorization');\n // console.log('[container_setup_mid] Validating session...');\n const sessionState = await config.validateSession(authHeader);\n // console.log('[container_setup_mid] Session validated');\n\n // Build container instances and factories\n console.log('[container_setup_mid] Building factories...');\n const { instances, factories } = config.buildFactories();\n // console.log('[container_setup_mid] Factories built');\n\n // Add request-specific instances\n // console.log('[container_setup_mid] Adding request-specific instances...');\n instances[TOKENS.SESSION] = sessionState;\n instances[TOKENS.ENV] = ctx.env;\n // console.log('[container_setup_mid] Stored ctx.env in instances:', {\n // storedEnvType: typeof instances[TOKENS.ENV],\n // storedEnvIsNull: instances[TOKENS.ENV] === null,\n // storedEnvIsUndefined: instances[TOKENS.ENV] === undefined,\n // });\n instances[TOKENS.REQUEST] = ctx.req.raw;\n instances[TOKENS.HONO_CONTEXT] = ctx;\n\n // Create the request container\n // console.log('[container_setup_mid] Creating request container...');\n const containerCreateStart = config.perfLog ? performance.now() : 0;\n ctx.set('container', config.createRequestContainer({ instances, factories }));\n // console.log('[container_setup_mid] Container created and set');\n if (config.perfLog && containerCreateStart > 0) {\n const containerCreateTime = performance.now() - containerCreateStart;\n config.perfLog(`Container creation: ${containerCreateTime.toFixed(2)}ms`);\n }\n\n if (config.perfLog && setupStart > 0) {\n const setupTime = performance.now() - setupStart;\n config.perfLog(`Container setup: ${setupTime.toFixed(2)}ms`);\n }\n };\n}\n","import { serializeSigned } from 'hono/utils/cookie';\nimport type { HonoContext } from '../types/hono_context';\n\n/**\n * Apply cookies from Hono context to HTTP response\n * Serializes signed cookies and adds them to response headers\n */\nexport async function applyCookiesToResponse(\n ctx: HonoContext,\n response: Response,\n): Promise<Response> {\n const contextWithCookies = ctx as typeof ctx & {\n cookies?: Array<{\n name: string;\n value: string;\n secret: string | BufferSource;\n options?: any;\n }>;\n };\n\n if (!contextWithCookies.cookies || contextWithCookies.cookies.length === 0) {\n return response;\n }\n\n const cookieHeaders: string[] = [];\n for (const cookie of contextWithCookies.cookies) {\n // serializeSigned expects string secret, convert if needed\n const secretString =\n typeof cookie.secret === 'string'\n ? cookie.secret\n : new TextDecoder().decode(cookie.secret);\n const options = cookie.options || {};\n let serializedCookie: string;\n\n if (options.prefix === 'secure') {\n // For secure prefix, use __Secure- prefix\n serializedCookie = await serializeSigned(\n `__Secure-${cookie.name}`,\n cookie.value,\n secretString,\n {\n path: '/',\n ...options,\n secure: true,\n },\n );\n } else if (options.prefix === 'host') {\n // For host prefix, use __Host- prefix\n serializedCookie = await serializeSigned(\n `__Host-${cookie.name}`,\n cookie.value,\n secretString,\n {\n ...options,\n path: '/',\n secure: true,\n domain: undefined,\n },\n );\n } else {\n // No prefix\n serializedCookie = await serializeSigned(cookie.name, cookie.value, secretString, {\n path: '/',\n ...options,\n });\n }\n\n cookieHeaders.push(serializedCookie);\n }\n\n // Clone response and add cookie headers\n // Preserve all existing headers including CORS headers from the middleware\n const newHeaders = new Headers(response.headers);\n for (const cookieHeader of cookieHeaders) {\n newHeaders.append('Set-Cookie', cookieHeader);\n }\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n}\n","import { HTTPException } from 'hono/http-exception';\nimport type { AuthErrorFactory } from './is_authenticated_mid';\n\n/**\n * Creates a Hono-specific error factory for authentication middleware\n * This is a convenience helper for Hono-based applications\n */\nexport function createHonoErrorFactory(): AuthErrorFactory {\n return {\n createUnauthorizedError: (code: string, message = 'Unauthorized') => {\n return new HTTPException(401, {\n message,\n cause: { code },\n });\n },\n };\n}\n","import type { DependencyContainer } from 'tsyringe';\nimport type { SessionState } from '../db/session_state';\nimport { Logger, TOKENS } from '../index';\nimport type { HonoContext } from '../types/hono_context';\n\n/**\n * Factory interface for creating authentication errors\n * Allows different frameworks to use their own error types\n */\nexport interface AuthErrorFactory {\n createUnauthorizedError(code: string, message?: string): Error;\n}\n\n/**\n * Options for creating the authenticated middleware\n */\nexport interface IsAuthenticatedMiddlewareOptions {\n errorFactory: AuthErrorFactory;\n enablePerformanceLogging?: boolean;\n}\n\n/**\n * Creates a middleware function that validates authentication\n * Throws an error if the session is not authenticated\n *\n * @param options - Configuration options including error factory\n * @returns Hono middleware function\n */\nexport function createIsAuthenticatedMiddleware(options: IsAuthenticatedMiddlewareOptions) {\n return async (ctx: HonoContext): Promise<boolean> => {\n const sessionStart = options.enablePerformanceLogging ? performance.now() : 0;\n const container = ctx.get('container') as DependencyContainer;\n const logger = container.resolve<Logger>(TOKENS.LOGGER);\n\n if (options.enablePerformanceLogging) {\n logger.perf('Session validation started');\n }\n\n const sessionResolveStart = options.enablePerformanceLogging ? performance.now() : 0;\n const sessionState = container.resolve<SessionState>(TOKENS.SESSION) as SessionState;\n const sessionResolveTime = options.enablePerformanceLogging\n ? performance.now() - sessionResolveStart\n : 0;\n\n if (options.enablePerformanceLogging && sessionResolveTime > 0) {\n logger.perf(`Session resolve: ${sessionResolveTime.toFixed(2)}ms`);\n }\n\n switch (sessionState.type) {\n case 'unauthenticated':\n logger.error('unauthenticated');\n throw options.errorFactory.createUnauthorizedError('NO_ACCESS_TOKEN', 'Unauthorized');\n case 'revoked':\n logger.error('revoked');\n throw options.errorFactory.createUnauthorizedError(\n 'ACCESS_TOKEN_REVOKED',\n 'Unauthorized',\n );\n case 'expired':\n logger.error('expired');\n throw options.errorFactory.createUnauthorizedError(\n 'ACCESS_TOKEN_EXPIRED',\n 'Unauthorized',\n );\n }\n\n if (options.enablePerformanceLogging) {\n const sessionTime = performance.now() - sessionStart;\n logger.perf(`Session validation: ${sessionTime.toFixed(2)}ms`);\n }\n\n return true;\n };\n}\n","export function registerAppSettingsContainer() {\n // Notification emails removed - use assignee-based triage instead\n}\n","import {\n AttachmentFilters,\n AttachmentPageDto,\n ReadAttachmentDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { CreateAttachmentEntity, ReadAttachmentEntity } from './db/attachment_entity';\n\nexport const ATTACHMENT_TOKENS = {\n IAttachmentRepo: Symbol('IAttachmentRepo'),\n\n IReadAllAttachmentsByRecord: Symbol('IReadAllAttachmentsByRecord'),\n IReadAttachment: Symbol('IReadAttachment'),\n ICreateAttachment: Symbol('ICreateAttachment'),\n IUpdateAttachment: Symbol('IUpdateAttachment'),\n IDeleteAttachment: Symbol('IDeleteAttachment'),\n};\n\nexport interface CreateAttachmentDto {\n file: File | Blob;\n record_id: string;\n record_type: string;\n file_name: string;\n folder_id?: string | null;\n}\n\nexport interface CreateAttachmentFolderDto {\n record_id: string;\n record_type: string;\n folder_name: string;\n parent_folder_id?: string | null;\n}\n\nexport interface IAttachmentRepo {\n create_attachment(attachment: CreateAttachmentEntity): Promise<ReadAttachmentEntity>;\n read_attachment(id: string): Promise<ReadAttachmentEntity | null>;\n read_all_attachments(args: AttachmentFilters): Promise<AttachmentPageDto>;\n delete_attachment(id: string): Promise<boolean>;\n}\n\nexport interface IReadAttachment {\n execute(id: string): Promise<ReadAttachmentDto>;\n}\n\nexport interface IReadAllAttachmentsByRecord {\n execute(filters: AttachmentFilters): Promise<AttachmentPageDto>;\n}\n\nexport interface ICreateAttachment {\n execute(attachment: CreateAttachmentDto): Promise<ReadAttachmentDto>;\n}\n\nexport interface IDeleteAttachment {\n execute(id: string): Promise<boolean>;\n}\n\nexport const ATTACHMENT_FOLDER_TOKENS = {\n IAttachmentFolderRepo: Symbol('IAttachmentFolderRepo'),\n ICreateAttachmentFolder: Symbol('ICreateAttachmentFolder'),\n};\n\nexport interface ICreateAttachmentFolder {\n execute(\n input: CreateAttachmentFolderDto,\n ): Promise<import('@dragonmastery/dragoncore-shared').AttachmentFolderReadDto>;\n}\n","import { and, eq, getTableColumns, isNull, sql } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport {\n attachment_folder_table,\n attachment_table,\n} from '../../../db/schemas/primary/primary_schema';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n CreateAttachmentFolderEntity,\n ReadAttachmentFolderEntity,\n} from './attachment_folder_entity';\n\nexport interface IAttachmentFolderRepo {\n create_folder(folder: CreateAttachmentFolderEntity): Promise<ReadAttachmentFolderEntity>;\n read_folder(id: string): Promise<ReadAttachmentFolderEntity | null>;\n delete_folder(id: string): Promise<boolean>;\n recompute_file_count(folder_id: string): Promise<void>;\n}\n\n@injectable()\nexport class AttachmentFolderRepo implements IAttachmentFolderRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create_folder(\n folder: CreateAttachmentFolderEntity,\n ): Promise<ReadAttachmentFolderEntity> {\n const id = await this.router.generateId(RecordConst.ATTACHMENT_FOLDER);\n const result = await this.router.queryLatest((db) =>\n db\n .insert(attachment_folder_table)\n .values({\n id,\n ...folder,\n })\n .returning(),\n );\n return result[0];\n }\n\n async read_folder(id: string): Promise<ReadAttachmentFolderEntity | null> {\n const { ...rest } = getTableColumns(attachment_folder_table);\n\n const result = await this.router.queryById(id, (db) =>\n db\n .select({\n ...rest,\n })\n .from(attachment_folder_table)\n .where(\n and(eq(attachment_folder_table.id, id), isNull(attachment_folder_table.deleted_at)),\n )\n .limit(1),\n );\n return result.length > 0 ? result[0] : null;\n }\n\n async delete_folder(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .update(attachment_folder_table)\n .set({ deleted_at: new Date().toISOString() })\n .where(eq(attachment_folder_table.id, id))\n .returning(),\n );\n return result.length > 0;\n }\n\n async recompute_file_count(folder_id: string): Promise<void> {\n // Atomically compute and set the file count using a SQL subquery\n await this.router.queryById(folder_id, (db) =>\n db\n .update(attachment_folder_table)\n .set({\n file_count: sql`(\n SELECT COUNT(*)\n FROM ${attachment_table}\n WHERE ${attachment_table.folder_id} = ${folder_id}\n AND ${attachment_table.deleted_at} IS NULL\n )`,\n updated_at: new Date().toISOString(),\n })\n .where(eq(attachment_folder_table.id, folder_id))\n .returning(),\n );\n }\n}\n","import { AttachmentFilters, AttachmentPageDto } from '@dragonmastery/dragoncore-shared';\nimport { and, desc, eq, getTableColumns, gte, isNull, lte } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst, RecordType } from '../../../db/dbTypes';\nimport {\n attachment_folder_table,\n attachment_table,\n} from '../../../db/schemas/primary/primary_schema';\nimport { TOKENS } from '../../../di_tokens';\nimport { IAttachmentRepo } from '../attachment_interfaces';\nimport { CreateAttachmentEntity, ReadAttachmentEntity } from './attachment_entity';\n\n@injectable()\nexport class AttachmentRepo implements IAttachmentRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create_attachment(\n attachmentRecord: CreateAttachmentEntity,\n ): Promise<ReadAttachmentEntity> {\n const id = await this.router.generateId(RecordConst.ATTACHMENT);\n const result = await this.router.queryLatest((db) =>\n db\n .insert(attachment_table)\n .values({\n id,\n ...attachmentRecord,\n })\n .returning(),\n );\n return result[0];\n }\n\n async read_attachment(id: string): Promise<ReadAttachmentEntity | null> {\n const { ...rest } = getTableColumns(attachment_table);\n\n const result = await this.router.queryById(id, (db) =>\n db\n .select({\n ...rest,\n // with sharded databases we cannot join with user_table\n // username: user_table.username,\n })\n .from(attachment_table)\n // we will have to find a different way to get the username\n // .leftJoin(user_table, eq(record_version_table.auth_uid, user_table.id))\n .where(and(eq(attachment_table.id, id), isNull(attachment_table.deleted_at)))\n .limit(1),\n );\n return result.length > 0 ? result[0] : null;\n }\n\n async read_all_attachments(args: AttachmentFilters): Promise<AttachmentPageDto> {\n const limit = args.filters?.limit || 10;\n const includeFolders = args.filters?.include_folders !== false; // Default to true\n const folderId = args.filters?.folder_id;\n\n // Build conditions for files\n const fileConditions = [\n eq(attachment_table.record_type, args.record_type as RecordType),\n eq(attachment_table.record_id, args.record_id),\n isNull(attachment_table.deleted_at),\n ];\n\n // Build conditions for folders\n const folderConditions = [\n eq(attachment_folder_table.record_type, args.record_type as RecordType),\n eq(attachment_folder_table.record_id, args.record_id),\n isNull(attachment_folder_table.deleted_at),\n ];\n\n // Handle folder_id filter\n if (folderId !== undefined) {\n if (folderId === null) {\n // Root level items (no folder)\n fileConditions.push(isNull(attachment_table.folder_id));\n folderConditions.push(isNull(attachment_folder_table.parent_folder_id));\n } else {\n // Items in a specific folder\n fileConditions.push(eq(attachment_table.folder_id, folderId));\n folderConditions.push(eq(attachment_folder_table.parent_folder_id, folderId));\n }\n }\n\n // Date filters\n if (args.filters?.start_date) {\n fileConditions.push(gte(attachment_table.created_at, args.filters.start_date));\n folderConditions.push(gte(attachment_folder_table.created_at, args.filters.start_date));\n }\n\n if (args.filters?.end_date) {\n fileConditions.push(lte(attachment_table.created_at, args.filters.end_date));\n folderConditions.push(lte(attachment_folder_table.created_at, args.filters.end_date));\n }\n\n // Cursor filter (applied after fetching, since we're combining two tables)\n const cursor = args.filters?.cursor;\n\n // Query files\n const { ...fileColumns } = getTableColumns(attachment_table);\n const files = await this.router.queryAll(\n (db) =>\n db\n .select({\n ...fileColumns,\n })\n .from(attachment_table)\n .where(and(...fileConditions))\n .orderBy(desc(attachment_table.id))\n .limit(includeFolders ? limit + 1 : limit + 1), // Fetch extra to account for folders\n );\n\n // Query folders (if included)\n let folders: any[] = [];\n if (includeFolders) {\n const { ...folderColumns } = getTableColumns(attachment_folder_table);\n folders = await this.router.queryAll(\n (db) =>\n db\n .select({\n ...folderColumns,\n })\n .from(attachment_folder_table)\n .where(and(...folderConditions))\n .orderBy(desc(attachment_folder_table.id))\n .limit(limit + 1), // Fetch extra to account for files\n );\n }\n\n // Sort files and folders separately by ID (descending)\n const sortedFiles = files.sort((a, b) => (b.id < a.id ? -1 : b.id > a.id ? 1 : 0));\n const sortedFolders = folders.sort((a, b) => (b.id < a.id ? -1 : b.id > a.id ? 1 : 0));\n\n // Apply cursor filter if provided\n let filteredFiles = sortedFiles;\n let filteredFolders = sortedFolders;\n if (cursor) {\n filteredFiles = sortedFiles.filter((item) => item.id < cursor);\n filteredFolders = sortedFolders.filter((item) => item.id < cursor);\n }\n\n // Combine for pagination calculation\n const allItems = [...filteredFiles, ...filteredFolders].sort((a, b) =>\n b.id < a.id ? -1 : b.id > a.id ? 1 : 0,\n );\n\n // Apply limit and pagination\n const hasNextPage = allItems.length > limit;\n const pageItems = hasNextPage ? allItems.slice(0, limit) : allItems;\n const endCursor = pageItems.length > 0 ? pageItems[pageItems.length - 1].id : undefined;\n\n // Separate back into files and folders based on what's in the page\n const pageFileIds = new Set(\n pageItems.filter((item) => 'content_type' in item).map((item) => item.id),\n );\n const pageFolderIds = new Set(\n pageItems\n .filter((item) => 'parent_folder_id' in item && !('content_type' in item))\n .map((item) => item.id),\n );\n\n const pageFiles = filteredFiles.filter((f) => pageFileIds.has(f.id));\n const pageFolders = filteredFolders.filter((f) => pageFolderIds.has(f.id));\n\n return {\n files: pageFiles,\n folders: pageFolders,\n pageInfo: {\n hasNextPage,\n endCursor,\n },\n };\n }\n\n async delete_attachment(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .update(attachment_table)\n .set({ deleted_at: new Date().toISOString() })\n .where(eq(attachment_table.id, id))\n .returning(),\n );\n return result.length > 0;\n }\n}\n","import {\n RecordType,\n RecordVersionFiltersBreadcrumbDto,\n RecordVersionFiltersDto,\n RecordVersionPageInfoDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { InferInsertModel, InferSelectModel } from 'drizzle-orm';\nimport { record_version_table } from '../../db/schemas/historical/historical_schema';\nimport { PaginatedResult } from '../../lib/pagination';\nimport {\n CreateRecordVersionEntity,\n ReadRecordVersionEntity,\n} from './db/record_version_entity';\n\nexport const RECORD_VERSION_TOKENS = {\n IRecordVersionsRepo: Symbol('IRecordVersionsRepo'),\n\n IReadAllRecordVersionsByRecord: Symbol('IReadAllRecordVersionsByRecord'),\n IReadAllRecordVersionsByRecordPaginated: Symbol('IReadAllRecordVersionsByRecordPaginated'),\n IReadAllRecordVersionsByRecordPaginatedCustomer: Symbol(\n 'IReadAllRecordVersionsByRecordPaginatedCustomer',\n ),\n IReadTrackerActivityVersions: Symbol('IReadTrackerActivityVersions'),\n IReadRecordVersion: Symbol('IReadRecordVersion'),\n ICreateRecordVersion: Symbol('ICreateRecordVersion'),\n ICreateRecordVersionsBatch: Symbol('ICreateRecordVersionsBatch'),\n IUpdateRecordVersion: Symbol('IUpdateRecordVersion'),\n IDeleteRecordVersion: Symbol('IDeleteRecordVersion'),\n};\n\nexport interface ReadRecordVersionDto extends InferSelectModel<typeof record_version_table> {}\n\nexport interface CreateRecordVersionDto extends Omit<\n InferInsertModel<typeof record_version_table>,\n 'id'\n> {}\nexport interface UpdateRecordVersionDto extends InferInsertModel<\n typeof record_version_table\n> {}\n\n// ===== LEGACY TYPES (Keep for backward compatibility) =====\n// Imported from shared library (inferred from Zod schemas)\n\n/**\n * Legacy filters - inferred from Zod schema\n * Note: record_type is added locally as it's not in the schema filters\n */\nexport interface RecordVersionFilters extends Omit<RecordVersionFiltersDto, 'record_type'> {\n record_type: string;\n}\n\n/**\n * Legacy page response - uses shared PageInfo but with local entity type\n */\nexport interface RecordVersionsPage {\n items: ReadRecordVersionEntity[];\n pageInfo: PageInfo;\n}\n\n/**\n * Legacy page info - inferred from Zod schema\n */\nexport type PageInfo = RecordVersionPageInfoDto;\n\n// ===== NEW BREADCRUMB PAGINATION TYPES =====\n\n/**\n * NEW: Breadcrumb pagination filters for record versions\n * Imported from shared library (inferred from Zod schema)\n */\nexport type RecordVersionFiltersBreadcrumb = RecordVersionFiltersBreadcrumbDto;\n\n/**\n * NEW: Breadcrumb-paginated record version response\n * user_display_map: optional map of user_id -> display_name for timeline (assigned_to, created_by, etc.)\n */\nexport type RecordVersionsBreadcrumbPage = PaginatedResult<ReadRecordVersionEntity> & {\n user_display_map?: Record<string, string>;\n};\n\nexport interface IRecordVersionRepo {\n // Legacy method (keep for backward compatibility)\n create_record_version(\n record_version: CreateRecordVersionEntity,\n ): Promise<ReadRecordVersionDto>;\n create_record_versions_batch(\n record_versions: CreateRecordVersionDto[],\n ): Promise<ReadRecordVersionDto[]>;\n read_record_version(id: string): Promise<ReadRecordVersionEntity>;\n read_all_record_versions(\n record_id: string,\n record_type: RecordType,\n filters?: Omit<RecordVersionFilters, 'record_type'>,\n ): Promise<RecordVersionsPage>;\n delete_record_version(id: string): Promise<boolean>;\n\n // NEW: Breadcrumb pagination method (supports single or multiple record types)\n read_all_record_versions_paginated(\n record_id: string,\n record_type_or_types: RecordType | RecordType[],\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\nexport interface IReadRecordVersion {\n execute(id: string): Promise<ReadRecordVersionDto>;\n}\n\nexport interface IReadAllRecordVersionsByRecord {\n execute(\n record_id: string,\n record_type: RecordType,\n filters?: Omit<RecordVersionFilters, 'record_type'>,\n ): Promise<RecordVersionsPage>;\n}\n\nexport interface IReadAllRecordVersionsByRecordPaginated {\n execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\nexport interface IReadAllRecordVersionsByRecordPaginatedCustomer {\n execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\nexport interface IReadTrackerActivityVersions {\n execute(\n tracker_id: string,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\nexport interface ICreateRecordVersion {\n execute(record_version: CreateRecordVersionDto): Promise<ReadRecordVersionDto>;\n}\n\nexport interface ICreateRecordVersionsBatch {\n execute(record_versions: CreateRecordVersionDto[]): Promise<ReadRecordVersionDto[]>;\n}\n\nexport interface IUpdateRecordVersion {\n execute(record_version: UpdateRecordVersionDto): Promise<UpdateRecordVersionDto>;\n}\n\nexport interface IDeleteRecordVersion {\n execute(id: string): Promise<boolean>;\n}\n","/**\n * Tokens for dependency injection\n * Organized by customer vs staff features\n */\nexport const SUPPORT_TICKET_TOKENS = {\n // Repository\n ISupportTicketRepo: 'ISupportTicketRepo',\n ISupportTicketNotificationService: 'ISupportTicketNotificationService',\n ISupportTicketTriageService: 'ISupportTicketTriageService',\n\n // Customer features\n ICreateSupportTicketFeature: 'ICreateSupportTicketFeature',\n IUpdateSupportTicketCustomerFeature: 'IUpdateSupportTicketCustomerFeature',\n IGetSupportTicketCustomerFeature: 'IGetSupportTicketCustomerFeature',\n IGetSupportTicketsCustomerFeature: 'IGetSupportTicketsCustomerFeature',\n ICustomerToggleSubscriptionFeature: 'ICustomerToggleSubscriptionFeature',\n\n // Staff workflow features\n IApproveSupportTicketFeature: 'IApproveSupportTicketFeature',\n IRejectSupportTicketFeature: 'IRejectSupportTicketFeature',\n IRevertSupportTicketFeature: 'IRevertSupportTicketFeature',\n ICompleteSupportTicketFeature: 'ICompleteSupportTicketFeature',\n IConvertToInternalFeature: 'IConvertToInternalFeature',\n IConvertToCustomerFeature: 'IConvertToCustomerFeature',\n IDeleteSupportTicketFeature: 'IDeleteSupportTicketFeature',\n IArchiveSupportTicketFeature: 'IArchiveSupportTicketFeature',\n ICancelInternalTaskFeature: 'ICancelInternalTaskFeature',\n IReactivateInternalTaskFeature: 'IReactivateInternalTaskFeature',\n\n // Staff create/update features\n ICreateSupportTicketAdminFeature: 'ICreateSupportTicketAdminFeature',\n IUpdateSupportTicketAdminFeature: 'IUpdateSupportTicketAdminFeature',\n\n // Staff query features\n IGetSupportTicketAdminFeature: 'IGetSupportTicketAdminFeature',\n IGetSupportTicketsAdminFeature: 'IGetSupportTicketsAdminFeature',\n IGetRequestorsForActiveSupportTicketsFeature: 'IGetRequestorsForActiveSupportTicketsFeature',\n IGetSupportTicketNotesFeature: 'IGetSupportTicketNotesFeature',\n IAddSupportTicketSubscriberFeature: 'IAddSupportTicketSubscriberFeature',\n IListSupportTicketSubscribersFeature: 'IListSupportTicketSubscribersFeature',\n IRemoveSupportTicketSubscriberFeature: 'IRemoveSupportTicketSubscriberFeature',\n IFixSupportTicketUserIdsFeature: 'IFixSupportTicketUserIdsFeature',\n} as const;\n","import { RecordConst, sanitizeFileName } from '@dragonmastery/dragoncore-shared';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordType } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { TOKENS } from '../../../di_tokens';\nimport { findR2Bucket } from '../../../lib/r2_bucket_finder';\nimport { Logger } from '../../../utils/logger';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../record_version/record_version_interfaces';\nimport { ISupportTicketRepo } from '../../support_ticket/db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket/support_ticket_tokens';\nimport {\n ATTACHMENT_FOLDER_TOKENS,\n ATTACHMENT_TOKENS,\n CreateAttachmentDto,\n IAttachmentRepo,\n ICreateAttachment,\n} from '../attachment_interfaces';\nimport { CreateAttachmentEntity } from '../db/attachment_entity';\nimport { IAttachmentFolderRepo } from '../db/attachment_folder_repo';\n\n@injectable()\nexport class CreateAttachment implements ICreateAttachment {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(ATTACHMENT_TOKENS.IAttachmentRepo)\n private attachment_repo: IAttachmentRepo,\n @inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)\n private attachment_folder_repo: IAttachmentFolderRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticket_repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.ENV)\n private env: Env,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(attachment: CreateAttachmentDto) {\n // Block adding attachments to archived support tickets\n if (attachment.record_type === RecordConst.SUPPORT_TICKET) {\n const ticket = await this.support_ticket_repo.read(attachment.record_id);\n if (ticket?.archived_at) {\n throw new BusinessError('Cannot add attachments to an archived support ticket.');\n }\n }\n this.logger.debug('[CreateAttachment] Starting file upload');\n if (!attachment.file) throw new Error('File is required');\n\n // Get max file size from tenant config (in MB), default to 50MB\n const envWithOptional = this.env as Env & {\n MAX_ATTACHMENT_FILE_SIZE_MB?: string;\n TENANT_ID?: string;\n };\n const maxFileSizeMB = envWithOptional.MAX_ATTACHMENT_FILE_SIZE_MB\n ? parseInt(envWithOptional.MAX_ATTACHMENT_FILE_SIZE_MB, 10)\n : 50;\n const maxFileSize = maxFileSizeMB * 1024 * 1024; // Convert MB to bytes\n\n // Validate file size\n const fileSize = attachment.file?.size ?? 0;\n if (fileSize > maxFileSize) {\n throw new Error(\n `File size (${(fileSize / 1024 / 1024).toFixed(2)}MB) exceeds maximum allowed size (${(maxFileSize / 1024 / 1024).toFixed(2)}MB)`,\n );\n }\n\n // Sanitize file name using shared utility\n const sanitized_name = sanitizeFileName(attachment.file_name);\n this.logger.debug('[CreateAttachment] Sanitized file name:', {\n sanitized_name,\n });\n\n const now = new Date().toISOString();\n const attachment_entity: CreateAttachmentEntity = {\n record_id: attachment.record_id,\n record_type: attachment.record_type as RecordType,\n sanitized_name,\n original_name: attachment.file_name,\n content_type: attachment.file?.type || 'application/octet-stream',\n file_size: attachment.file?.size.toString() || '0',\n folder_id: attachment.folder_id || null,\n created_at: now,\n created_by: this.session.user.userId,\n updated_at: now, // Same as created_at initially\n updated_by: this.session.user.userId, // Same as created_by initially\n };\n\n this.logger.debug('[CreateAttachment] Creating attachment entity');\n const new_attachment = await this.attachment_repo.create_attachment(attachment_entity);\n const r2_key = `${attachment.record_type}/${attachment.record_id}/${new_attachment.id}/${sanitized_name}`;\n\n // Recompute file_count on the folder if file is in a folder\n if (attachment_entity.folder_id) {\n await this.attachment_folder_repo.recompute_file_count(attachment_entity.folder_id);\n }\n\n // Find the R2 bucket dynamically based on tenant ID\n const tenantId = envWithOptional.TENANT_ID;\n const r2Bucket = tenantId ? findR2Bucket(this.env, tenantId) : null;\n if (!r2Bucket) {\n this.logger.error(\n `[CreateAttachment] R2 bucket not found for tenant: ${tenantId || 'unknown'}`,\n );\n attachment_entity.deleted_at = new Date().toISOString();\n attachment_entity.deleted_by = this.session.user.userId;\n await this.attachment_repo.delete_attachment(new_attachment.id);\n throw new Error(`R2 bucket not found for tenant: ${tenantId || 'unknown'}`);\n }\n\n // Upload to R2\n this.logger.debug('[CreateAttachment] Uploading file to R2');\n const r2_res = await r2Bucket.put(r2_key, attachment.file.stream());\n if (!r2_res) {\n await this.attachment_repo.delete_attachment(new_attachment.id);\n this.logger.debug('[CreateAttachment] Failed to upload file to R2');\n throw new Error('Failed to upload file to storage');\n } else {\n this.logger.debug(`[CreateAttachment] File uploaded to R2 with etag: ${r2_res.etag}`);\n }\n\n // Create record version for timeline when attachment is added to a support ticket\n // Use support_ticket_activity (not support_ticket) - these are activity events, not revertible record state\n if (attachment.record_type === RecordConst.SUPPORT_TICKET) {\n await this.create_record_version.execute({\n record_id: attachment.record_id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET_ACTIVITY,\n record: { attachment_added: attachment.file_name },\n old_record: {},\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type ?? undefined,\n auth_username: this.session.user.username ?? undefined,\n });\n }\n\n this.logger.debug('[CreateAttachment] Attachment created successfully');\n return new_attachment;\n }\n}\n","import {\n AttachmentFolderReadDto,\n RecordConst,\n sanitizeFileName,\n} from '@dragonmastery/dragoncore-shared';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { inject, injectable } from 'tsyringe';\nimport { RecordType } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { TOKENS } from '../../../di_tokens';\nimport { Logger } from '../../../utils/logger';\nimport { ISupportTicketRepo } from '../../support_ticket/db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket/support_ticket_tokens';\nimport {\n ATTACHMENT_FOLDER_TOKENS,\n CreateAttachmentFolderDto,\n ICreateAttachmentFolder,\n} from '../attachment_interfaces';\nimport { CreateAttachmentFolderEntity } from '../db/attachment_folder_entity';\nimport { IAttachmentFolderRepo } from '../db/attachment_folder_repo';\n\n@injectable()\nexport class CreateAttachmentFolder implements ICreateAttachmentFolder {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)\n private attachment_folder_repo: IAttachmentFolderRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticket_repo: ISupportTicketRepo,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(input: CreateAttachmentFolderDto): Promise<AttachmentFolderReadDto> {\n // Block creating folders for archived support tickets\n if (input.record_type === RecordConst.SUPPORT_TICKET) {\n const ticket = await this.support_ticket_repo.read(input.record_id);\n if (ticket?.archived_at) {\n throw new BusinessError('Cannot add folders to an archived support ticket.');\n }\n }\n this.logger.debug('[CreateAttachmentFolder] Starting folder creation');\n if (!input.folder_name) throw new Error('Folder name is required');\n\n // Sanitize folder name using shared utility\n const sanitized_name = sanitizeFileName(input.folder_name);\n this.logger.debug('[CreateAttachmentFolder] Sanitized folder name:', { sanitized_name });\n\n const now = new Date().toISOString();\n const folder_entity: CreateAttachmentFolderEntity = {\n record_id: input.record_id,\n record_type: input.record_type as RecordType,\n sanitized_name,\n original_name: input.folder_name,\n description: null,\n metadata: null,\n parent_folder_id: input.parent_folder_id || null,\n created_at: now,\n created_by: this.session.user.userId,\n updated_at: now,\n updated_by: this.session.user.userId,\n };\n\n this.logger.debug('[CreateAttachmentFolder] Creating folder entity');\n const new_folder = await this.attachment_folder_repo.create_folder(folder_entity);\n\n // Map entity to DTO\n const folder_dto: AttachmentFolderReadDto = {\n id: new_folder.id,\n record_id: new_folder.record_id,\n record_type: new_folder.record_type,\n sanitized_name: new_folder.sanitized_name,\n original_name: new_folder.original_name,\n description: new_folder.description || null,\n metadata: new_folder.metadata || null,\n parent_folder_id: new_folder.parent_folder_id || null,\n file_count: new_folder.file_count || 0,\n created_at: new_folder.created_at,\n created_by: new_folder.created_by,\n updated_at: new_folder.updated_at || null,\n updated_by: new_folder.updated_by || null,\n archived_at: new_folder.archived_at || null,\n archived_by: new_folder.archived_by || null,\n deleted_at: new_folder.deleted_at || null,\n deleted_by: new_folder.deleted_by || null,\n };\n\n this.logger.debug('[CreateAttachmentFolder] Folder created successfully');\n return folder_dto;\n }\n}\n","import { RecordConst } from '@dragonmastery/dragoncore-shared';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../record_version/record_version_interfaces';\nimport { ISupportTicketRepo } from '../../support_ticket/db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket/support_ticket_tokens';\nimport {\n ATTACHMENT_FOLDER_TOKENS,\n ATTACHMENT_TOKENS,\n IAttachmentRepo,\n IDeleteAttachment,\n} from '../attachment_interfaces';\nimport { IAttachmentFolderRepo } from '../db/attachment_folder_repo';\n\n@injectable()\nexport class DeleteAttachment implements IDeleteAttachment {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(ATTACHMENT_TOKENS.IAttachmentRepo)\n private attachment_repo: IAttachmentRepo,\n @inject(ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo)\n private attachment_folder_repo: IAttachmentFolderRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticket_repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(id: string) {\n // Get the attachment to find its folder_id before deleting\n const attachment = await this.attachment_repo.read_attachment(id);\n if (!attachment) {\n return false;\n }\n\n // Block deleting attachments from archived support tickets\n if (attachment.record_type === RecordConst.SUPPORT_TICKET) {\n const ticket = await this.support_ticket_repo.read(attachment.record_id);\n if (ticket?.archived_at) {\n throw new BusinessError('Cannot delete attachments from an archived support ticket.');\n }\n }\n\n const deleted = await this.attachment_repo.delete_attachment(id);\n\n // Create record version for timeline when attachment is removed from a support ticket\n // Use support_ticket_activity (not support_ticket) - these are activity events, not revertible record state\n if (deleted && attachment.record_type === RecordConst.SUPPORT_TICKET) {\n const now = new Date().toISOString();\n await this.create_record_version.execute({\n record_id: attachment.record_id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET_ACTIVITY,\n record: { attachment_removed: attachment.original_name },\n old_record: {},\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type ?? undefined,\n auth_username: this.session.user.username ?? undefined,\n });\n }\n\n // Recompute file_count on the folder if file was in a folder\n if (deleted && attachment.folder_id) {\n await this.attachment_folder_repo.recompute_file_count(attachment.folder_id);\n }\n\n return deleted;\n }\n}\n","import { AttachmentFilters } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n ATTACHMENT_TOKENS,\n IAttachmentRepo,\n IReadAllAttachmentsByRecord,\n} from '../attachment_interfaces';\n\n@injectable()\nexport class ReadAllAttachmentsByRecord implements IReadAllAttachmentsByRecord {\n constructor(\n @inject(ATTACHMENT_TOKENS.IAttachmentRepo)\n private attachment_repo: IAttachmentRepo,\n ) {}\n\n async execute(filters: AttachmentFilters) {\n return await this.attachment_repo.read_all_attachments(filters);\n }\n}\n","import { ReadAttachmentDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { ATTACHMENT_TOKENS, IAttachmentRepo, IReadAttachment } from '../attachment_interfaces';\n\n@injectable()\nexport class ReadAttachment implements IReadAttachment {\n constructor(\n @inject(ATTACHMENT_TOKENS.IAttachmentRepo)\n private attachment_repo: IAttachmentRepo,\n ) {}\n\n async execute(record_version_id: string): Promise<ReadAttachmentDto> {\n const record = await this.attachment_repo.read_attachment(record_version_id);\n\n if (!record) {\n throw new Error('Attachment not found');\n }\n\n const record_dto: ReadAttachmentDto = {\n ...record,\n };\n\n return record_dto;\n }\n}\n","import { container } from 'tsyringe';\nimport {\n ATTACHMENT_FOLDER_TOKENS,\n ATTACHMENT_TOKENS,\n IAttachmentRepo,\n ICreateAttachment,\n IDeleteAttachment,\n IReadAllAttachmentsByRecord,\n IReadAttachment,\n} from './attachment_interfaces';\nimport { AttachmentFolderRepo } from './db/attachment_folder_repo';\nimport { AttachmentRepo } from './db/attachment_repo';\nimport { CreateAttachment } from './features/create_attachment';\nimport { CreateAttachmentFolder } from './features/create_attachment_folder';\nimport { DeleteAttachment } from './features/delete_attachment';\nimport { ReadAllAttachmentsByRecord } from './features/read_all_attachments_by_record_feat';\nimport { ReadAttachment } from './features/read_attachment';\n\nexport function registerAttachmentContainer() {\n container.registerSingleton<IAttachmentRepo>(\n ATTACHMENT_TOKENS.IAttachmentRepo,\n AttachmentRepo,\n );\n container.registerSingleton<IReadAllAttachmentsByRecord>(\n ATTACHMENT_TOKENS.IReadAllAttachmentsByRecord,\n ReadAllAttachmentsByRecord,\n );\n container.registerSingleton<IReadAttachment>(\n ATTACHMENT_TOKENS.IReadAttachment,\n ReadAttachment,\n );\n container.registerSingleton<ICreateAttachment>(\n ATTACHMENT_TOKENS.ICreateAttachment,\n CreateAttachment,\n );\n container.registerSingleton<IDeleteAttachment>(\n ATTACHMENT_TOKENS.IDeleteAttachment,\n DeleteAttachment,\n );\n // Folder registrations - singletons\n container.registerSingleton(\n ATTACHMENT_FOLDER_TOKENS.IAttachmentFolderRepo,\n AttachmentFolderRepo,\n );\n container.registerSingleton(\n ATTACHMENT_FOLDER_TOKENS.ICreateAttachmentFolder,\n CreateAttachmentFolder,\n );\n}\n","/**\n * Tokens for customer slice dependency injection\n */\nexport const CUSTOMER_TOKENS = {\n ICreditTransactionRepo: 'ICreditTransactionRepo',\n IGetCreditTransactionsFeature: 'IGetCreditTransactionsFeature',\n IAddCreditsFeature: 'IAddCreditsFeature',\n ISetMonthlyAllocationFeature: 'ISetMonthlyAllocationFeature',\n IResetMonthlyBalanceFeature: 'IResetMonthlyBalanceFeature',\n} as const;\n","import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';\n\nexport const TransactionTypeEnum = [\n 'DEDUCTION',\n 'REFUND',\n 'PURCHASE_ONETIME',\n 'PURCHASE_RECURRING',\n 'ADJUSTMENT',\n] as const;\n\n/**\n * Database schema for credit transactions\n */\nexport const credit_transaction_table = sqliteTable(\n 'credit_transaction',\n {\n id: text('id').primaryKey(),\n support_ticket_id: text('support_ticket_id'), // Optional, for linking to a specific support_ticket item\n amount: text('amount').notNull(),\n type: text('type', {\n enum: TransactionTypeEnum,\n }).notNull(),\n description: text('description'),\n balance_after: text('balance_after').notNull(),\n\n // Metadata\n created_at: text('created_at').notNull(),\n created_by: text('created_by').notNull(),\n },\n (table) => ({\n supportTicketIdIdx: index('credit_transaction_support_ticket_id_idx').on(\n table.support_ticket_id,\n ),\n amountIdx: index('credit_transaction_amount_idx').on(table.amount),\n typeIdx: index('credit_transaction_type_idx').on(table.type),\n balanceAfterIdx: index('credit_transaction_balance_after_idx').on(table.balance_after),\n createdAtIdx: index('credit_transaction_created_at_idx').on(table.created_at),\n createdByIdx: index('credit_transaction_created_by_idx').on(table.created_by),\n }),\n);\n\nexport type InsertCreditTransactionEntity = typeof credit_transaction_table.$inferInsert;\nexport type ReadCreditTransactionEntity = typeof credit_transaction_table.$inferSelect;\n","import {\n createBackendRegistry,\n createFilterBuilder,\n deriveColumnMap,\n searchOrCondition,\n type FieldRegistry,\n} from '../../../lib';\nimport { CreditTransactionFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { type SQL } from 'drizzle-orm';\nimport { credit_transaction_table } from './credit_transaction_table';\n\n// Field registry for FilterBuilder\nexport const creditTransactionFields: FieldRegistry = createBackendRegistry(\n {\n type: {\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n support_ticket_id: {\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n amount: {\n type: 'money',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n balance_after: {\n type: 'money',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n created_at: {\n type: 'date',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n created_by: {\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n },\n {\n type: credit_transaction_table.type,\n support_ticket_id: credit_transaction_table.support_ticket_id,\n amount: credit_transaction_table.amount,\n balance_after: credit_transaction_table.balance_after,\n created_at: credit_transaction_table.created_at,\n created_by: credit_transaction_table.created_by,\n },\n);\n\n// Derive columnMap for pagination from registry\nexport const creditTransactionColumnMap = deriveColumnMap(creditTransactionFields);\n\n// Create field filter builder\n// Note: credit_transaction doesn't have deleted_at or archived_at, so no special handling needed\nconst buildFieldFilters = createFilterBuilder({\n fieldRegistry: creditTransactionFields,\n processedSeparately: [],\n});\n\n/**\n * Build credit transaction query conditions from filters\n */\nexport function buildCreditTransactionQuery(\n filters?: CreditTransactionFiltersDto,\n): { conditions: SQL[]; skipQuery: boolean } {\n // Build each condition piece\n // Note: credit_transaction doesn't have deleted_at or archived_at\n const fields = buildFieldFilters(filters).conditions;\n const search = searchOrCondition(\n filters?.search?.query,\n creditTransactionFields,\n filters?.search?.searchableFields,\n );\n\n const conditions: SQL[] = [...fields, ...(search ? [search] : [])];\n\n return { conditions, skipQuery: false };\n}\n","import { CreditTransactionFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { type SQL } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { type DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n PaginationUtils,\n type PaginatedResult,\n type PaginationConfig,\n} from '../../../lib/pagination';\nimport {\n buildCreditTransactionQuery,\n creditTransactionColumnMap,\n} from './credit_transaction_query_config';\nimport {\n credit_transaction_table,\n InsertCreditTransactionEntity,\n ReadCreditTransactionEntity,\n} from './credit_transaction_table';\n\nexport const CREDIT_TRANSACTION_REPO_TOKEN = 'ICreditTransactionRepo';\n\nexport interface ICreditTransactionRepo {\n createCreditTransaction(\n transaction: Omit<InsertCreditTransactionEntity, 'id'>,\n ): Promise<ReadCreditTransactionEntity>;\n getAll(\n filters?: CreditTransactionFiltersDto,\n ): Promise<PaginatedResult<ReadCreditTransactionEntity>>;\n}\n\n@injectable()\nexport class CreditTransactionRepo implements ICreditTransactionRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n private get paginationConfig(): PaginationConfig<ReadCreditTransactionEntity> {\n return {\n table: credit_transaction_table,\n columnMap: creditTransactionColumnMap,\n router: this.router,\n };\n }\n\n async createCreditTransaction(\n transaction: Omit<InsertCreditTransactionEntity, 'id'>,\n ): Promise<ReadCreditTransactionEntity> {\n const id = await this.router.generateId(RecordConst.CREDIT_TRANSACTION);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(credit_transaction_table)\n .values({\n id,\n ...transaction,\n })\n .returning(),\n );\n return result;\n }\n\n private buildFilters(filters?: CreditTransactionFiltersDto): { conditions: SQL[] } {\n return buildCreditTransactionQuery(filters);\n }\n\n /**\n * Get all credit transactions with breadcrumb pagination\n */\n async getAll(\n filters?: CreditTransactionFiltersDto,\n ): Promise<PaginatedResult<ReadCreditTransactionEntity>> {\n const { conditions } = this.buildFilters(filters);\n\n return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);\n }\n}\n","export interface CustomerBalance {\n monthly: string;\n rollover: string;\n}\n\nexport const CREDIT_SERVICE_TOKEN = 'ICreditService';\n\n/**\n * Defines the contract for a service that manages the global credit balance.\n */\nexport interface ICreditService {\n /**\n * Deducts a specified amount of credits from the global balance.\n * The deduction is first applied to the monthly balance, then to the rollover balance.\n * @param amount The amount of credits to deduct (as a string to avoid precision loss).\n * @param support_ticket_id Optional ID of the support_ticket item triggering the deduction.\n * @returns A boolean indicating if the deduction was successful (i.e., sufficient credits).\n */\n deductCredits(amount: string, support_ticket_id?: string): Promise<boolean>;\n\n /**\n * Refunds a specified amount of credits to the global rollover balance.\n * @param amount The amount of credits to refund.\n * @param support_ticket_id Optional ID of the support_ticket item triggering the refund.\n */\n refundCredits(amount: string, support_ticket_id?: string): Promise<void>;\n\n /**\n * Retrieves the current global credit balance.\n * @returns An object with the monthly and rollover balances.\n */\n getGlobalBalance(): Promise<CustomerBalance>;\n\n /**\n * Resets the monthly credit balance.\n * Rolls over any remaining monthly credits to the rollover balance.\n */\n resetMonthlyBalance(): Promise<void>;\n\n /**\n * Adds credits to the rollover balance (admin only).\n * Used for purchases, bonuses, or manual adjustments.\n * @param amount The amount of credits to add (as a decimal string).\n * @param reason Reason for adding credits (e.g., \"Purchase\", \"Bonus\", \"Adjustment\").\n */\n addCredits(amount: string, reason?: string): Promise<void>;\n\n /**\n * Sets the monthly credit allocation (admin only).\n * This is the amount that gets reset to each month.\n * @param amount The new monthly allocation amount (as a decimal string).\n */\n setMonthlyAllocation(amount: string): Promise<void>;\n}\n","import { AddCreditsDto, CreditBalanceDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { CREDIT_SERVICE_TOKEN, ICreditService } from '../services/credit_service';\n\n/**\n * Feature interface for adding credits\n */\nexport interface IAddCreditsFeature {\n execute(args: AddCreditsDto): Promise<CreditBalanceDto>;\n}\n\n@injectable()\nexport class AddCreditsFeat implements IAddCreditsFeature {\n constructor(\n @inject(CREDIT_SERVICE_TOKEN)\n private creditService: ICreditService,\n ) {}\n\n async execute(args: AddCreditsDto): Promise<CreditBalanceDto> {\n await this.creditService.addCredits(args.amount, args.reason);\n return await this.creditService.getGlobalBalance();\n }\n}\n","import {\n CreditTransactionFiltersDto,\n CreditTransactionPageDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { CUSTOMER_TOKENS } from '../customer_tokens';\nimport { ICreditTransactionRepo } from '../db/credit_transaction_repo';\n\n/**\n * Feature interface for getting credit transactions\n */\nexport interface IGetCreditTransactionsFeature {\n execute(args: CreditTransactionFiltersDto): Promise<CreditTransactionPageDto>;\n}\n\n@injectable()\nexport class GetCreditTransactionsFeat implements IGetCreditTransactionsFeature {\n constructor(\n @inject(CUSTOMER_TOKENS.ICreditTransactionRepo)\n private repo: ICreditTransactionRepo,\n ) {}\n\n async execute(args: CreditTransactionFiltersDto): Promise<CreditTransactionPageDto> {\n return await this.repo.getAll(args);\n }\n}\n","import { CreditBalanceDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { CREDIT_SERVICE_TOKEN, ICreditService } from '../services/credit_service';\n\n/**\n * Feature interface for resetting monthly balance\n */\nexport interface IResetMonthlyBalanceFeature {\n execute(): Promise<CreditBalanceDto>;\n}\n\n@injectable()\nexport class ResetMonthlyBalanceFeat implements IResetMonthlyBalanceFeature {\n constructor(\n @inject(CREDIT_SERVICE_TOKEN)\n private creditService: ICreditService,\n ) {}\n\n async execute(): Promise<CreditBalanceDto> {\n await this.creditService.resetMonthlyBalance();\n return await this.creditService.getGlobalBalance();\n }\n}\n","import { CreditBalanceDto, SetMonthlyAllocationDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { CREDIT_SERVICE_TOKEN, ICreditService } from '../services/credit_service';\n\n/**\n * Feature interface for setting monthly allocation\n */\nexport interface ISetMonthlyAllocationFeature {\n execute(args: SetMonthlyAllocationDto): Promise<CreditBalanceDto>;\n}\n\n@injectable()\nexport class SetMonthlyAllocationFeat implements ISetMonthlyAllocationFeature {\n constructor(\n @inject(CREDIT_SERVICE_TOKEN)\n private creditService: ICreditService,\n ) {}\n\n async execute(args: SetMonthlyAllocationDto): Promise<CreditBalanceDto> {\n await this.creditService.setMonthlyAllocation(args.amount);\n return await this.creditService.getGlobalBalance();\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { IAppSettingsRepo } from '../../../db/schemas/app_setting/app_settings_repo';\nimport { AppSettingKey } from '../../../db/schemas/app_setting/app_settings_table';\nimport { SessionState } from '../../../db/session_state';\nimport { UserSessionDetails } from '../../../db/user_app_session';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n CREDIT_TRANSACTION_REPO_TOKEN,\n ICreditTransactionRepo,\n} from '../db/credit_transaction_repo';\nimport { InsertCreditTransactionEntity } from '../db/credit_transaction_table';\nimport { CustomerBalance, ICreditService } from './credit_service';\n\nconst CreditMath = {\n PRECISION: 2,\n toBigInt(decimal: string): bigint {\n const [integer, fraction = ''] = decimal.split('.');\n const paddedFraction = fraction.padEnd(this.PRECISION, '0');\n return BigInt(integer + paddedFraction);\n },\n toString(value: bigint): string {\n const s = value.toString().padStart(this.PRECISION + 1, '0');\n const integer = s.slice(0, -this.PRECISION);\n const fraction = s.slice(-this.PRECISION);\n return `${integer}.${fraction}`;\n },\n};\n\n@injectable()\nexport class CreditService implements ICreditService {\n private systemUser: UserSessionDetails;\n\n constructor(\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(TOKENS.SESSION) private session: SessionState,\n @inject(CREDIT_TRANSACTION_REPO_TOKEN)\n private transactionRepo: ICreditTransactionRepo,\n ) {\n if (this.session.type === 'authenticated') {\n this.systemUser = this.session.session.user;\n } else {\n this.systemUser = {\n userId: 'system',\n username: 'system',\n email: 'system@five-quoting.com',\n email_verified: true,\n user_type: 'super_admin',\n };\n }\n }\n\n private async getBalances() {\n const keys = [\n AppSettingKey.CUSTOMER_MONTHLY_BALANCE,\n AppSettingKey.CUSTOMER_ROLLOVER_BALANCE,\n ];\n const settingsMap = await this.appSettingsRepo.readMultipleSettings(keys);\n const monthly = (settingsMap.get('customer_monthly_balance') as string) ?? '0';\n const rollover = (settingsMap.get('customer_rollover_balance') as string) ?? '0';\n return {\n monthlyBalance: BigInt(monthly),\n rolloverBalance: BigInt(rollover),\n };\n }\n\n private async updateBalances(monthly: bigint, rollover: bigint) {\n await Promise.all([\n this.appSettingsRepo.updateSetting(\n 'customer_monthly_balance',\n monthly.toString(),\n this.systemUser,\n ),\n this.appSettingsRepo.updateSetting(\n 'customer_rollover_balance',\n rollover.toString(),\n this.systemUser,\n ),\n ]);\n }\n\n async deductCredits(amount: string, support_ticket_id?: string): Promise<boolean> {\n const { monthlyBalance, rolloverBalance } = await this.getBalances();\n const deductionAmount = CreditMath.toBigInt(amount);\n\n if (monthlyBalance + rolloverBalance < deductionAmount) {\n return false;\n }\n\n const fromMonthly = monthlyBalance < deductionAmount ? monthlyBalance : deductionAmount;\n const newMonthlyBalance = monthlyBalance - fromMonthly;\n const remainingDeduction = deductionAmount - fromMonthly;\n const newRolloverBalance = rolloverBalance - remainingDeduction;\n\n await this.updateBalances(newMonthlyBalance, newRolloverBalance);\n\n const transaction: Omit<InsertCreditTransactionEntity, 'id'> = {\n support_ticket_id: support_ticket_id,\n amount: `-${amount}`,\n type: 'DEDUCTION',\n balance_after: CreditMath.toString(newMonthlyBalance + newRolloverBalance),\n created_at: new Date().toISOString(),\n created_by: this.systemUser.userId,\n };\n await this.transactionRepo.createCreditTransaction(transaction);\n\n return true;\n }\n\n async refundCredits(amount: string, support_ticket_id?: string): Promise<void> {\n const { monthlyBalance, rolloverBalance } = await this.getBalances();\n const refundAmount = CreditMath.toBigInt(amount);\n const newRolloverBalance = rolloverBalance + refundAmount;\n\n await this.updateBalances(monthlyBalance, newRolloverBalance);\n\n const transaction: Omit<InsertCreditTransactionEntity, 'id'> = {\n support_ticket_id: support_ticket_id,\n amount: amount,\n type: 'REFUND',\n balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),\n created_at: new Date().toISOString(),\n created_by: this.systemUser.userId,\n };\n await this.transactionRepo.createCreditTransaction(transaction);\n }\n\n async getGlobalBalance(): Promise<CustomerBalance> {\n // Single customer system - one global balance\n const { monthlyBalance, rolloverBalance } = await this.getBalances();\n return {\n monthly: CreditMath.toString(monthlyBalance),\n rollover: CreditMath.toString(rolloverBalance),\n };\n }\n\n async resetMonthlyBalance(): Promise<void> {\n const keys = [\n AppSettingKey.CUSTOMER_MONTHLY_BALANCE,\n AppSettingKey.CUSTOMER_ROLLOVER_BALANCE,\n AppSettingKey.CUSTOMER_MONTHLY_ALLOCATION,\n ];\n const settingsMap = await this.appSettingsRepo.readMultipleSettings(keys);\n const monthlyBalance = BigInt(\n (settingsMap.get('customer_monthly_balance') as string) ?? '0',\n );\n const rolloverBalance = BigInt(\n (settingsMap.get('customer_rollover_balance') as string) ?? '0',\n );\n const monthlyAllocation = BigInt(\n (settingsMap.get('customer_monthly_allocation') as string) ?? '0',\n );\n\n const newRolloverBalance = rolloverBalance + monthlyBalance;\n const newMonthlyBalance = monthlyAllocation;\n\n await this.updateBalances(newMonthlyBalance, newRolloverBalance);\n }\n\n async addCredits(amount: string, reason: string = 'Manual Addition'): Promise<void> {\n const { monthlyBalance, rolloverBalance } = await this.getBalances();\n const creditsToAdd = CreditMath.toBigInt(amount);\n\n if (creditsToAdd <= BigInt(0)) {\n throw new Error('Credit amount must be positive');\n }\n\n const newRolloverBalance = rolloverBalance + creditsToAdd;\n\n await this.updateBalances(monthlyBalance, newRolloverBalance);\n\n // Record transaction\n const transaction: Omit<InsertCreditTransactionEntity, 'id'> = {\n support_ticket_id: null,\n amount: amount,\n description: reason,\n type: 'PURCHASE_ONETIME',\n balance_after: CreditMath.toString(monthlyBalance + newRolloverBalance),\n created_at: new Date().toISOString(),\n created_by: this.systemUser.userId,\n };\n await this.transactionRepo.createCreditTransaction(transaction);\n }\n\n async setMonthlyAllocation(amount: string): Promise<void> {\n const allocationAmount = CreditMath.toBigInt(amount);\n\n if (allocationAmount < BigInt(0)) {\n throw new Error('Monthly allocation cannot be negative');\n }\n\n await this.appSettingsRepo.updateSetting(\n 'customer_monthly_allocation',\n allocationAmount.toString(),\n this.systemUser,\n );\n }\n}\n","import { container } from 'tsyringe';\nimport { CUSTOMER_TOKENS } from './customer_tokens';\nimport { CreditTransactionRepo, ICreditTransactionRepo } from './db/credit_transaction_repo';\nimport { AddCreditsFeat, IAddCreditsFeature } from './features/add_credits_feat';\nimport {\n GetCreditTransactionsFeat,\n IGetCreditTransactionsFeature,\n} from './features/get_credit_transactions_feat';\nimport {\n IResetMonthlyBalanceFeature,\n ResetMonthlyBalanceFeat,\n} from './features/reset_monthly_balance_feat';\nimport {\n ISetMonthlyAllocationFeature,\n SetMonthlyAllocationFeat,\n} from './features/set_monthly_allocation_feat';\nimport { CREDIT_SERVICE_TOKEN, ICreditService } from './services/credit_service';\nimport { CreditService } from './services/credit_service_impl';\n\n/**\n * Register customer slice dependencies\n */\nexport function registerCustomerDependencies() {\n // Register repositories - singletons\n container.registerSingleton<ICreditTransactionRepo>(\n CUSTOMER_TOKENS.ICreditTransactionRepo,\n CreditTransactionRepo,\n );\n\n // Register services - singleton\n container.registerSingleton<ICreditService>(CREDIT_SERVICE_TOKEN, CreditService);\n\n // Register features - singletons\n container.registerSingleton<IGetCreditTransactionsFeature>(\n CUSTOMER_TOKENS.IGetCreditTransactionsFeature,\n GetCreditTransactionsFeat,\n );\n container.registerSingleton<IAddCreditsFeature>(\n CUSTOMER_TOKENS.IAddCreditsFeature,\n AddCreditsFeat,\n );\n container.registerSingleton<ISetMonthlyAllocationFeature>(\n CUSTOMER_TOKENS.ISetMonthlyAllocationFeature,\n SetMonthlyAllocationFeat,\n );\n container.registerSingleton<IResetMonthlyBalanceFeature>(\n CUSTOMER_TOKENS.IResetMonthlyBalanceFeature,\n ResetMonthlyBalanceFeat,\n );\n}\n","import { RecordConst } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { CreateNoteSupportTicketProcessor } from './support_ticket/create_note_support_ticket_processor';\nimport { DeleteNoteSupportTicketProcessor } from './support_ticket/delete_note_support_ticket_processor';\nimport { GetNotesSupportTicketProcessor } from './support_ticket/get_notes_support_ticket_processor';\nimport { UpdateNoteSupportTicketProcessor } from './support_ticket/update_note_support_ticket_processor';\n\nexport interface IBusinessLogicRouter {\n getCreateProcessor(recordType: string): CreateNoteSupportTicketProcessor | null;\n getUpdateProcessor(recordType: string): UpdateNoteSupportTicketProcessor | null;\n getGetProcessor(recordType: string): GetNotesSupportTicketProcessor | null;\n getDeleteProcessor(recordType: string): DeleteNoteSupportTicketProcessor | null;\n}\n\n@injectable()\nexport class BusinessLogicRouter implements IBusinessLogicRouter {\n constructor(\n @inject('CreateNoteSupportTicketProcessor')\n private createSupportTicketProcessor: CreateNoteSupportTicketProcessor,\n @inject('UpdateNoteSupportTicketProcessor')\n private updateSupportTicketProcessor: UpdateNoteSupportTicketProcessor,\n @inject('GetNotesSupportTicketProcessor')\n private getSupportTicketProcessor: GetNotesSupportTicketProcessor,\n @inject('DeleteNoteSupportTicketProcessor')\n private deleteSupportTicketProcessor: DeleteNoteSupportTicketProcessor,\n ) {}\n\n getCreateProcessor(recordType: string) {\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.createSupportTicketProcessor;\n // FOLLOWUP: Add other record types as needed\n default:\n return null; // No specific business logic for this record type\n }\n }\n\n getUpdateProcessor(recordType: string) {\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.updateSupportTicketProcessor;\n // FOLLOWUP: Add other record types as needed\n default:\n return null;\n }\n }\n\n getGetProcessor(recordType: string) {\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.getSupportTicketProcessor;\n // FOLLOWUP: Add other record types as needed\n default:\n return null;\n }\n }\n\n getDeleteProcessor(recordType: string) {\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.deleteSupportTicketProcessor;\n // FOLLOWUP: Add other record types as needed\n default:\n return null;\n }\n }\n}\n","import { NoteCreateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { ISupportTicketRepo } from '../../../support_ticket/db/support_ticket_repo';\nimport { ReadSupportTicketEntity } from '../../../support_ticket/db/support_ticket_table';\nimport { SUPPORT_TICKET_TOKENS } from '../../../support_ticket/support_ticket_tokens';\n\nexport interface ICreateNoteSupportTicketProcessor {\n process(input: NoteCreateDto): Promise<NoteCreateDto>;\n}\n\n@injectable()\nexport class CreateNoteSupportTicketProcessor implements ICreateNoteSupportTicketProcessor {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n ) {}\n\n async process(input: NoteCreateDto): Promise<NoteCreateDto> {\n // 1. Verify the support ticket exists\n const supportTicket = await this.supportTicketRepo.read(input.record_id);\n if (!supportTicket) {\n throw new Error('Support ticket not found');\n }\n\n // 2. Archived tickets lock comments for both customer and staff\n if (supportTicket.archived_at) {\n throw new Error('Cannot add comments to an archived ticket.');\n }\n\n // 3. Route to appropriate handler based on user type\n const user = this.session.user;\n\n if (user.user_type === 'staff' || user.user_type === 'super_admin') {\n return await this.processStaffRequest(input, supportTicket);\n } else if (user.user_type === 'consumer' || user.user_type === 'lead') {\n return await this.processConsumerRequest(input, supportTicket);\n } else {\n throw new BusinessError('Invalid user type');\n }\n }\n\n private async processStaffRequest(\n input: NoteCreateDto,\n _supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteCreateDto> {\n // Staff can create notes on any support ticket\n // No access restrictions for staff\n\n // Staff can create any type of note with any configuration\n // No additional restrictions for staff\n return input;\n }\n\n private async processConsumerRequest(\n input: NoteCreateDto,\n supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteCreateDto> {\n // Align with GetNotesSupportTicketProcessor and CreateFollowupSupportTicketProcessor:\n // consumers/leads can create notes on any non-internal ticket they can view\n if (supportTicket.approval_status === 'INTERNAL') {\n throw new BusinessError('You do not have access to this support ticket');\n }\n\n // Business rule: Consumers cannot create internal notes\n if (input.is_internal) {\n throw new BusinessError('Consumers cannot create internal notes');\n }\n\n // Force external notes only\n const processedInput = { ...input, is_internal: false };\n return processedInput;\n }\n}\n","/**\n * Tokens for dependency injection\n * Unified features with business logic for permissions\n */\nexport const NOTE_TOKENS = {\n // Repository\n INoteRepo: 'INoteRepo',\n\n // Features\n ICreateNoteFeature: 'ICreateNoteFeature',\n IUpdateNoteFeature: 'IUpdateNoteFeature',\n IGetNotesFeature: 'IGetNotesFeature',\n IDeleteNoteFeature: 'IDeleteNoteFeature',\n\n // Business Logic Router (unique per slice to avoid collision with followup when both are registered)\n IBusinessLogicRouter: 'Note.IBusinessLogicRouter',\n\n // Support Ticket Processors\n CreateNoteSupportTicketProcessor: 'CreateNoteSupportTicketProcessor',\n UpdateNoteSupportTicketProcessor: 'UpdateNoteSupportTicketProcessor',\n GetNotesSupportTicketProcessor: 'GetNotesSupportTicketProcessor',\n DeleteNoteSupportTicketProcessor: 'DeleteNoteSupportTicketProcessor',\n} as const;\n","import { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { ISupportTicketRepo } from '../../../support_ticket/db/support_ticket_repo';\nimport { ReadSupportTicketEntity } from '../../../support_ticket/db/support_ticket_table';\nimport { SUPPORT_TICKET_TOKENS } from '../../../support_ticket/support_ticket_tokens';\nimport { INoteRepo } from '../../db/note_repo';\nimport { ReadNoteEntity } from '../../db/note_table';\nimport { NOTE_TOKENS } from '../../note_tokens';\n\nexport interface IDeleteNoteSupportTicketProcessor {\n process(id: string): Promise<string>;\n}\n\n@injectable()\nexport class DeleteNoteSupportTicketProcessor implements IDeleteNoteSupportTicketProcessor {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n ) {}\n\n async process(id: string): Promise<string> {\n // 1. Verify the note exists and belongs to a support ticket\n const existingNote = await this.noteRepo.read(id);\n if (!existingNote) {\n throw new BusinessError('Note not found');\n }\n\n if (existingNote.record_type !== 'support_ticket') {\n throw new BusinessError('This note does not belong to a support ticket');\n }\n\n // 2. Verify the support ticket still exists\n const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);\n if (!supportTicket) {\n throw new BusinessError('Support ticket not found');\n }\n\n // 3. Route to appropriate handler based on user type\n const user = this.session.user;\n\n if (user.user_type === 'staff' || user.user_type === 'super_admin') {\n return await this.processStaffRequest(id, existingNote, supportTicket);\n } else if (user.user_type === 'consumer' || user.user_type === 'lead') {\n return await this.processConsumerRequest(id, existingNote, supportTicket);\n } else {\n throw new BusinessError('Invalid user type');\n }\n }\n\n private async processStaffRequest(\n id: string,\n existingNote: ReadNoteEntity,\n supportTicket: ReadSupportTicketEntity,\n ): Promise<string> {\n // Only the creator can delete their own notes\n if (existingNote.created_by !== this.session.user.userId) {\n throw new BusinessError('You can only delete notes that you created');\n }\n\n // Business rule: Cannot delete notes on completed or cancelled tickets\n if (\n supportTicket.dev_lifecycle === 'DEPLOYED' ||\n supportTicket.approval_status === 'REJECTED'\n ) {\n throw new BusinessError('Cannot delete notes on completed or cancelled support tickets');\n }\n\n // Archived tickets lock comments for both customer and staff\n if (supportTicket.archived_at) {\n throw new BusinessError('Cannot delete comments on an archived ticket.');\n }\n\n return id;\n }\n\n private async processConsumerRequest(\n id: string,\n existingNote: ReadNoteEntity,\n supportTicket: ReadSupportTicketEntity,\n ): Promise<string> {\n const user = this.session.user;\n\n // Must have ticket access (non-internal)\n if (supportTicket.approval_status === 'INTERNAL') {\n throw new BusinessError('You do not have access to this support ticket');\n }\n\n // Only the creator can delete their own notes\n if (existingNote.created_by !== user.userId) {\n throw new BusinessError('You can only delete notes that you created');\n }\n\n // Archived tickets lock comments\n if (supportTicket.archived_at) {\n throw new BusinessError('Cannot delete comments on an archived ticket.');\n }\n\n return id;\n }\n}\n","import { NoteFiltersDto, OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { ISupportTicketRepo } from '../../../support_ticket/db/support_ticket_repo';\nimport { ReadSupportTicketEntity } from '../../../support_ticket/db/support_ticket_table';\nimport { SUPPORT_TICKET_TOKENS } from '../../../support_ticket/support_ticket_tokens';\n\nexport interface IGetNotesSupportTicketProcessor {\n process(filters: NoteFiltersDto): Promise<NoteFiltersDto>;\n}\n\n@injectable()\nexport class GetNotesSupportTicketProcessor implements IGetNotesSupportTicketProcessor {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n ) {}\n\n async process(filters: NoteFiltersDto): Promise<NoteFiltersDto> {\n const user = this.session.user;\n\n // 1. If record_id is specified, verify user has access to that support ticket\n // Extract value from operator-based filter\n const recordIdValue =\n filters.record_id &&\n typeof filters.record_id === 'object' &&\n 'value' in filters.record_id\n ? filters.record_id.value\n : typeof filters.record_id === 'string'\n ? filters.record_id\n : undefined;\n\n if (recordIdValue && typeof recordIdValue === 'string') {\n const supportTicket = await this.supportTicketRepo.read(recordIdValue);\n if (!supportTicket) {\n throw new Error('Support ticket not found');\n }\n\n // Route to appropriate handler based on user type\n // staff/super_admin: full access; consumer/lead: customer access (non-internal tickets, external only)\n if (user.user_type === 'staff' || user.user_type === 'super_admin') {\n return await this.processStaffRequest(filters, supportTicket);\n } else if (user.user_type === 'consumer' || user.user_type === 'lead') {\n return await this.processConsumerRequest(filters, supportTicket);\n } else {\n throw new BusinessError('Invalid user type');\n }\n }\n\n // 2. Apply visibility rules based on user type for general queries\n return await this.applyVisibilityRules(filters);\n }\n\n private async processStaffRequest(\n filters: NoteFiltersDto,\n _supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteFiltersDto> {\n // Staff can access all notes on any support ticket\n // No access restrictions for staff\n return filters;\n }\n\n private async processConsumerRequest(\n filters: NoteFiltersDto,\n supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteFiltersDto> {\n // Align with GetSupportTicketCustomerFeature: consumers can view notes on any\n // non-internal ticket they can view. The ticket detail page already validated\n // access (approval_status !== 'INTERNAL'), so if they're requesting notes for\n // a ticket, they've already passed that check. Double-check here for safety.\n if (supportTicket.approval_status === 'INTERNAL') {\n throw new BusinessError('You do not have access to this support ticket');\n }\n\n // Business rule: Consumers can only see external notes\n const processedFilters = {\n ...filters,\n is_internal: { operator: OPERATORS.EQUALS, value: false },\n };\n return processedFilters;\n }\n\n private async applyVisibilityRules(filters: NoteFiltersDto): Promise<NoteFiltersDto> {\n const user = this.session.user;\n\n // For consumers and leads (customer-type users), ensure they only see external notes\n if (user.user_type === 'consumer' || user.user_type === 'lead') {\n // Business rule: Consumers can only see external notes\n const processedFilters = {\n ...filters,\n is_internal: { operator: OPERATORS.EQUALS, value: false },\n };\n return processedFilters;\n }\n\n // For staff, they can see all notes for tickets they have access to\n // No additional filtering needed for staff\n\n return filters;\n }\n}\n","import { NoteUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { ISupportTicketRepo } from '../../../support_ticket/db/support_ticket_repo';\nimport { ReadSupportTicketEntity } from '../../../support_ticket/db/support_ticket_table';\nimport { SUPPORT_TICKET_TOKENS } from '../../../support_ticket/support_ticket_tokens';\nimport { INoteRepo } from '../../db/note_repo';\nimport { ReadNoteEntity } from '../../db/note_table';\nimport { NOTE_TOKENS } from '../../note_tokens';\n\nexport interface IUpdateNoteSupportTicketProcessor {\n process(input: NoteUpdateDto): Promise<NoteUpdateDto>;\n}\n\n@injectable()\nexport class UpdateNoteSupportTicketProcessor implements IUpdateNoteSupportTicketProcessor {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n ) {}\n\n async process(input: NoteUpdateDto): Promise<NoteUpdateDto> {\n // 1. Verify the note exists and belongs to a support ticket\n const existingNote = await this.noteRepo.read(input.id);\n if (!existingNote) {\n throw new BusinessError('Note not found');\n }\n\n if (existingNote.record_type !== 'support_ticket') {\n throw new BusinessError('This note does not belong to a support ticket');\n }\n\n // 2. Verify the support ticket still exists\n const supportTicket = await this.supportTicketRepo.read(existingNote.record_id);\n if (!supportTicket) {\n throw new BusinessError('Support ticket not found');\n }\n\n // Archived tickets lock comments for both customer and staff\n if (supportTicket.archived_at) {\n throw new BusinessError('Cannot edit comments on an archived ticket.');\n }\n\n // 3. Route to appropriate handler based on user type\n const user = this.session.user;\n\n if (user.user_type === 'staff' || user.user_type === 'super_admin') {\n return await this.processStaffRequest(input, existingNote, supportTicket);\n } else if (user.user_type === 'consumer' || user.user_type === 'lead') {\n return await this.processConsumerRequest(input, existingNote, supportTicket);\n } else {\n throw new BusinessError('Invalid user type');\n }\n }\n\n private async processStaffRequest(\n input: NoteUpdateDto,\n existingNote: ReadNoteEntity,\n _supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteUpdateDto> {\n if (existingNote.created_by !== this.session.user.userId) {\n throw new BusinessError('You can only update notes that you created');\n }\n return input;\n }\n\n private async processConsumerRequest(\n input: NoteUpdateDto,\n existingNote: ReadNoteEntity,\n supportTicket: ReadSupportTicketEntity,\n ): Promise<NoteUpdateDto> {\n // Only the creator can update; must have ticket access (non-internal)\n const user = this.session.user;\n\n if (supportTicket.approval_status === 'INTERNAL') {\n throw new BusinessError('You do not have access to this support ticket');\n }\n\n if (existingNote.created_by !== user.userId) {\n throw new BusinessError('You can only update notes you created');\n }\n\n // Business rule: Cannot update internal notes\n if (existingNote.is_internal) {\n throw new BusinessError('Consumers cannot update internal notes');\n }\n\n // Business rule: Consumers cannot change visibility\n if (input.is_internal !== undefined && input.is_internal !== existingNote.is_internal) {\n throw new BusinessError('Consumers cannot change note visibility');\n }\n\n return input;\n }\n}\n","import {\n archiveConditions,\n createFilterBuilder,\n deriveColumnMap,\n searchOrCondition,\n type FieldRegistry,\n} from '../../../lib';\nimport { NoteFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { isNull, type SQL } from 'drizzle-orm';\nimport { note_table } from './note_table';\n\n// Field registry - single source of truth for field metadata\nexport const noteFields: FieldRegistry = {\n record_id: {\n column: note_table.record_id,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n record_type: {\n column: note_table.record_type,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n title: {\n column: note_table.title,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n body: {\n column: note_table.body,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n tag: {\n column: note_table.tag,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n created_at: {\n column: note_table.created_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n updated_at: {\n column: note_table.updated_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n is_internal: {\n column: note_table.is_internal,\n type: 'boolean',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n};\n\n// Derive columnMap for pagination from registry\nexport const noteColumnMap = deriveColumnMap(noteFields);\n\n// Create field filter builder with archived_at processed separately\nconst buildFieldFilters = createFilterBuilder({\n fieldRegistry: noteFields,\n processedSeparately: ['archived_at'],\n});\n\n/**\n * Build note query conditions from filters\n */\nexport function buildNoteQuery(\n filters?: NoteFiltersDto,\n): { conditions: SQL[]; skipQuery: boolean } {\n // Build each condition piece\n const softDelete = isNull(note_table.deleted_at);\n const archive = archiveConditions(filters, note_table.archived_at);\n const fields = buildFieldFilters(filters).conditions;\n const search = searchOrCondition(\n filters?.search?.query,\n noteFields,\n filters?.search?.searchableFields,\n );\n\n // Combine all conditions\n const conditions: SQL[] = [\n softDelete,\n ...archive,\n ...fields,\n ...(search ? [search] : []),\n ];\n\n return { conditions, skipQuery: false };\n}\n","import { NoteFiltersDto, RecordType } from '@dragonmastery/dragoncore-shared';\nimport { and, eq, isNotNull, isNull, or, SQL, sql } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { type DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n PaginationUtils,\n type PaginatedResult,\n type PaginationConfig,\n} from '../../../lib/pagination';\nimport { buildNoteQuery, noteColumnMap } from './note_query_config';\nimport { InsertNoteEntity, note_table, ReadNoteEntity, UpdateNoteEntity } from './note_table';\n\n@injectable()\nexport class NoteRepo implements INoteRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n private get paginationConfig(): PaginationConfig<ReadNoteEntity> {\n return {\n table: note_table,\n columnMap: noteColumnMap,\n router: this.router,\n };\n }\n\n /**\n * Create a new note\n */\n async create(entity: InsertNoteEntity): Promise<ReadNoteEntity> {\n const id = await this.router.generateId(RecordConst.NOTE);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(note_table)\n .values([\n {\n id,\n ...entity,\n },\n ])\n .returning(),\n );\n\n return result;\n }\n\n /**\n * Get a note by ID\n */\n async read(id: string): Promise<ReadNoteEntity | null> {\n const [result] = await this.router.queryById(id, (db) =>\n db\n .select()\n .from(note_table)\n .where(and(eq(note_table.id, id), isNull(note_table.deleted_at)))\n .limit(1),\n );\n\n if (!result) {\n return null;\n }\n\n return result;\n }\n\n private buildFilters(filters?: NoteFiltersDto): {\n conditions: SQL[];\n } {\n return buildNoteQuery(filters);\n }\n\n /**\n * Get all notes with breadcrumb pagination\n */\n async read_all(filters?: NoteFiltersDto): Promise<PaginatedResult<ReadNoteEntity>> {\n const { conditions } = this.buildFilters(filters);\n\n // Let the pagination utility handle everything\n return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);\n }\n\n /**\n * Update an existing note\n */\n async update(input: UpdateNoteEntity): Promise<ReadNoteEntity> {\n const result = await this.router.queryById(input.id, (db) =>\n db\n .update(note_table)\n .set({\n ...input,\n })\n .where(eq(note_table.id, input.id))\n .returning(),\n );\n return result[0];\n }\n\n /**\n * Soft delete a note by ID\n */\n async soft_delete(id: string, deleted_by: string): Promise<ReadNoteEntity> {\n const deleted_at = new Date().toISOString();\n const result = await this.router.queryById(id, (db) =>\n db\n .update(note_table)\n .set({\n deleted_at,\n deleted_by,\n })\n .where(and(eq(note_table.id, id), isNull(note_table.deleted_at)))\n .returning(),\n );\n\n if (!result || result.length === 0) {\n throw new Error('Note not found or already deleted');\n }\n\n return result[0];\n }\n\n /**\n * Get all note IDs for records (e.g. tracker + its followups).\n * Used to include note activity in tracker activity timeline.\n */\n async readAllIdsForRecords(\n records: Array<{ record_id: string; record_type: string }>,\n ): Promise<string[]> {\n if (records.length === 0) return [];\n\n const conditions: SQL[] = [\n isNull(note_table.deleted_at),\n or(\n ...records.map((r) =>\n and(\n eq(note_table.record_id, r.record_id),\n eq(note_table.record_type, r.record_type as RecordType),\n ),\n ),\n ) as SQL,\n ];\n\n const results = await this.router.queryLatest((db) =>\n db\n .select({ id: note_table.id })\n .from(note_table)\n .where(and(...conditions)),\n );\n\n return results.map((r) => r.id);\n }\n\n /**\n * Get unique tags for notes\n */\n async getUniqueTags(): Promise<string[]> {\n const conditions: SQL[] = [\n isNotNull(note_table.tag),\n isNull(note_table.deleted_at),\n sql`${note_table.tag} != ''`, // Exclude empty strings\n ];\n\n const results = await this.router.queryLatest((db) =>\n db\n .selectDistinct({ tag: note_table.tag })\n .from(note_table)\n .where(and(...conditions))\n .orderBy(note_table.tag),\n );\n\n return results.map((r) => r.tag).filter((t): t is string => t !== null);\n }\n}\n\nexport type NoteEntityPage = PaginatedResult<ReadNoteEntity>;\n\n/**\n * Interface for note repository\n */\nexport interface INoteRepo {\n create(entity: InsertNoteEntity): Promise<ReadNoteEntity>;\n read(id: string): Promise<ReadNoteEntity | null>;\n read_all(filters?: NoteFiltersDto): Promise<NoteEntityPage>;\n readAllIdsForRecords(\n records: Array<{ record_id: string; record_type: string }>,\n ): Promise<string[]>;\n update(data: UpdateNoteEntity): Promise<ReadNoteEntity>;\n soft_delete(id: string, deleted_by: string): Promise<ReadNoteEntity>;\n getUniqueTags(): Promise<string[]>;\n}\n","import {\n CreateUserDto,\n CreateUserDtoOutput,\n SignupInputDto,\n UserTypeValues,\n} from '@dragonmastery/dragoncore-shared';\nimport { InferInsertModel, InferSelectModel } from 'drizzle-orm';\nimport { CreateUserEntity, ReadUserEntity, UpdateUserEntity } from './db/user_entity';\nimport { user_table } from './db/user_table';\n\n// Re-export ReadUserEntity for use in import strategies\nexport type { ReadUserEntity };\n\nexport const USER_TOKENS = {\n IUserDisplayLookup: Symbol('IUserDisplayLookup'),\n IUsersRepo: Symbol('IUsersRepo'),\n ISignUpUser: Symbol('ISignUpUser'),\n IReadAllUsers: Symbol('IReadAllUsers'),\n IReadUser: Symbol('IReadUser'),\n ICreateUser: Symbol('ICreateUser'),\n IDeleteUser: Symbol('IDeleteUser'),\n IReadConsumers: Symbol('IReadConsumers'),\n IGetAllUsersFeature: 'IGetAllUsersFeature',\n IGetUserFeature: 'IGetUserFeature',\n IUpdateUserFeature: 'IUpdateUserFeature',\n IGetUsersForSelectionFeature: 'IGetUsersForSelectionFeature',\n IGetTriageUsersFeature: 'IGetTriageUsersFeature',\n};\n\nexport interface ReadUserDto extends InferSelectModel<typeof user_table> {}\n\nexport interface SignUpUserOutputDto {\n id: string;\n email: string;\n}\n\nexport interface UpdateUserDto\n extends Omit<InferInsertModel<typeof user_table>, 'created_by' | 'updated_by'> {\n updated_by?: string;\n}\n\nexport interface IUserRepo {\n create_user(user: CreateUserEntity): Promise<ReadUserEntity>;\n read_user(id: string): Promise<ReadUserEntity | undefined>;\n read_users_by_ids(ids: string[]): Promise<ReadUserEntity[]>;\n read_user_by_email(email: string): Promise<ReadUserEntity>;\n read_users_by_emails(emails: string[]): Promise<ReadUserEntity[]>;\n read_users_by_emails_case_insensitive(emails: string[]): Promise<ReadUserEntity[]>;\n read_user_by_username(username: string): Promise<ReadUserEntity>;\n read_user_by_original_id(original_id: string): Promise<ReadUserEntity | undefined>;\n read_users_by_original_ids(original_ids: string[]): Promise<ReadUserEntity[]>;\n read_users_by_usernames(usernames: string[]): Promise<ReadUserEntity[]>;\n read_all_users(): Promise<ReadUserEntity[]>;\n read_users_by_type(user_type: UserTypeValues): Promise<ReadUserEntity[]>;\n read_users_by_types(user_types: UserTypeValues[]): Promise<ReadUserEntity[]>;\n update_user(user: UpdateUserEntity): Promise<UpdateUserEntity>;\n delete_user(id: string): Promise<boolean>;\n change_password_hash(id: string, new_password_hash: string): Promise<boolean>;\n}\n\nexport interface IReadUser {\n execute(id: string): Promise<ReadUserDto | null>;\n}\n\nexport interface IReadAllUsers {\n execute(): Promise<ReadUserDto[]>;\n}\n\nexport interface ICreateUser {\n execute(user: CreateUserDto): Promise<CreateUserDtoOutput>;\n}\n\nexport interface ISignUpUser {\n execute(user: SignupInputDto): Promise<SignUpUserOutputDto>;\n}\n\nexport interface IUpdateUser {\n execute(user: UpdateUserDto): Promise<UpdateUserDto>;\n}\n\nexport interface IDeleteUser {\n execute(id: string): Promise<boolean>;\n}\n\nexport interface IReadConsumers {\n execute(): Promise<string[]>;\n}\n\nexport interface IUserDisplayLookup {\n lookupDisplayNames(userIds: string[]): Promise<Map<string, string>>;\n}\n","import { NoteCreateDto, NoteReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { ISupportTicketRepo } from '../../../slices/support_ticket/db/support_ticket_repo';\nimport type { ISupportTicketNotificationService } from '../../../slices/support_ticket/services/support_ticket_notification_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../../slices/support_ticket/support_ticket_tokens';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { IBusinessLogicRouter } from '../business_logic/business_logic_router';\nimport { INoteRepo } from '../db/note_repo';\nimport { InsertNoteEntity } from '../db/note_table';\nimport { NOTE_TOKENS } from '../note_tokens';\n\nexport interface ICreateNoteFeature {\n execute(input: NoteCreateDto): Promise<NoteReadDto>;\n}\n\n@injectable()\nexport class CreateNoteFeat implements ICreateNoteFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n @inject(NOTE_TOKENS.IBusinessLogicRouter)\n private businessLogicRouter: IBusinessLogicRouter,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo?: ISupportTicketRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService?: ISupportTicketNotificationService,\n ) {}\n\n async execute(input: NoteCreateDto): Promise<NoteReadDto> {\n // Apply business logic based on record type\n const processor = this.businessLogicRouter.getCreateProcessor(input.record_type);\n const processedInput = processor ? await processor.process(input) : input;\n\n const now = new Date().toISOString();\n\n const entity: InsertNoteEntity = {\n record_id: processedInput.record_id,\n record_type: processedInput.record_type,\n tag: processedInput.tag,\n title: processedInput.title,\n body: processedInput.body,\n is_internal: processedInput.is_internal,\n created_by: this.session.user.userId,\n created_at: now,\n updated_at: now, // Same as created_at initially\n updated_by: this.session.user.userId, // Same as created_by initially\n };\n\n // Create note in database\n const noteCreated = await this.noteRepo.create(entity);\n\n // Bump parent record's updated_at to track activity\n await this.bumpParentUpdatedAt(processedInput.record_type, processedInput.record_id);\n\n // Track creation in record versions\n await this.create_record_version.execute({\n record_id: noteCreated.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.NOTE,\n record: noteCreated,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // Notify support ticket subscribers (best-effort, don't fail note creation)\n await this.notifySupportTicketSubscribers(\n processedInput.record_type,\n processedInput.record_id,\n noteCreated.is_internal,\n noteCreated.body ?? '',\n );\n\n const displayMap = await this.userDisplayLookup.lookupDisplayNames([noteCreated.created_by]);\n return {\n ...noteCreated,\n created_by_display_name: displayMap.get(noteCreated.created_by) ?? noteCreated.created_by,\n updated_by_display_name: displayMap.get(noteCreated.updated_by ?? '') ?? noteCreated.updated_by ?? null,\n };\n }\n\n /**\n * Bumps parent record's updated_at timestamp when note is created\n */\n private async bumpParentUpdatedAt(recordType: string, recordId: string): Promise<void> {\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n if (this.supportTicketRepo) {\n // Read the ticket first to get all current fields\n const ticket = await this.supportTicketRepo.read(recordId);\n if (ticket) {\n // Update with all fields plus new updated_at/updated_by\n await this.supportTicketRepo.update({\n ...ticket,\n updated_at: now,\n updated_by: userId,\n });\n }\n }\n break;\n // FOLLOWUP: Add other record types as needed\n default:\n // No parent update needed for this record type\n break;\n }\n }\n\n /**\n * Notify support ticket subscribers when a note is added (best-effort)\n */\n private async notifySupportTicketSubscribers(\n recordType: string,\n recordId: string,\n isInternal: boolean,\n noteBody: string,\n ): Promise<void> {\n if (recordType !== RecordConst.SUPPORT_TICKET || !this.supportTicketRepo || !this.notificationService) {\n return;\n }\n try {\n const ticket = await this.supportTicketRepo.read(recordId);\n if (!ticket) return;\n\n const eventType = isInternal ? 'NEW_INTERNAL_NOTE' : 'NEW_CUSTOMER_NOTE';\n await this.notificationService.notifyFollowers(recordId, ticket, eventType, {\n noteBody: noteBody || undefined,\n actorUserId: this.session.user.userId,\n });\n } catch {\n // Best-effort: don't fail note creation if notification fails\n }\n }\n}\n","import { NoteReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IBusinessLogicRouter } from '../business_logic/business_logic_router';\nimport { INoteRepo } from '../db/note_repo';\nimport { NOTE_TOKENS } from '../note_tokens';\n\nexport interface IDeleteNoteFeature {\n execute(id: string): Promise<NoteReadDto>;\n}\n\n@injectable()\nexport class DeleteNoteFeat implements IDeleteNoteFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n @inject(NOTE_TOKENS.IBusinessLogicRouter)\n private businessLogicRouter: IBusinessLogicRouter,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(id: string): Promise<NoteReadDto> {\n // First, get the existing note to verify it exists\n const existingNote = await this.noteRepo.read(id);\n if (!existingNote) {\n throw new Error('Note not found');\n }\n\n // Apply business logic based on record type\n const processor = this.businessLogicRouter.getDeleteProcessor(existingNote.record_type);\n const processedId = processor ? await processor.process(id) : id;\n\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n // Soft delete the note\n const deletedEntity = await this.noteRepo.soft_delete(processedId, userId);\n\n // Track deletion in record versions\n await this.create_record_version.execute({\n record_id: processedId,\n operation: OperationConst.DELETE,\n recorded_at: now,\n record_type: RecordConst.NOTE,\n record: deletedEntity,\n old_record: existingNote,\n auth_uid: userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n return deletedEntity;\n }\n}\n","import { NoteFiltersDto, NoteReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { type PaginatedResult } from '../../../lib/pagination';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { IBusinessLogicRouter } from '../business_logic/business_logic_router';\nimport { INoteRepo } from '../db/note_repo';\nimport { NOTE_TOKENS } from '../note_tokens';\n\nexport interface IGetNotesFeature {\n execute(filters?: NoteFiltersDto): Promise<PaginatedResult<NoteReadDto>>;\n}\n\nfunction collectUserIdsFromNotes(notes: { created_by?: string | null; updated_by?: string | null }[]): string[] {\n const ids: string[] = [];\n for (const n of notes) {\n if (n.created_by) ids.push(n.created_by);\n if (n.updated_by) ids.push(n.updated_by);\n }\n return ids;\n}\n\n@injectable()\nexport class GetNotesFeat implements IGetNotesFeature {\n constructor(\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n @inject(NOTE_TOKENS.IBusinessLogicRouter)\n private businessLogicRouter: IBusinessLogicRouter,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(filters?: NoteFiltersDto): Promise<PaginatedResult<NoteReadDto>> {\n // Apply business logic based on record type if specified\n let processedFilters = filters;\n\n if (filters?.record_type) {\n // Extract value from operator-based filter\n const recordTypeValue =\n typeof filters.record_type === 'object' && 'value' in filters.record_type\n ? filters.record_type.value\n : typeof filters.record_type === 'string'\n ? filters.record_type\n : undefined;\n if (recordTypeValue && typeof recordTypeValue === 'string') {\n const processor = this.businessLogicRouter.getGetProcessor(recordTypeValue);\n if (processor) {\n processedFilters = await processor.process(filters);\n }\n }\n }\n\n const result = await this.noteRepo.read_all(processedFilters);\n const userIds = collectUserIdsFromNotes(result.items);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n\n const enrichedItems: NoteReadDto[] = result.items.map((n) => ({\n ...n,\n created_by_display_name: n.created_by ? (displayMap.get(n.created_by) ?? n.created_by) : null,\n updated_by_display_name: n.updated_by ? (displayMap.get(n.updated_by) ?? n.updated_by) : null,\n }));\n\n return { ...result, items: enrichedItems };\n }\n}\n","import { NoteReadDto, NoteUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { IBusinessLogicRouter } from '../business_logic/business_logic_router';\nimport { INoteRepo } from '../db/note_repo';\nimport { UpdateNoteEntity } from '../db/note_table';\nimport { NOTE_TOKENS } from '../note_tokens';\n\nexport interface IUpdateNoteFeature {\n execute(input: NoteUpdateDto): Promise<NoteReadDto>;\n}\n\n@injectable()\nexport class UpdateNoteFeat implements IUpdateNoteFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(NOTE_TOKENS.INoteRepo) private noteRepo: INoteRepo,\n @inject(NOTE_TOKENS.IBusinessLogicRouter)\n private businessLogicRouter: IBusinessLogicRouter,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: NoteUpdateDto): Promise<NoteReadDto> {\n // First, get the existing note to verify it exists and get record type\n const existingNote = await this.noteRepo.read(input.id);\n if (!existingNote) {\n throw new Error('Note not found');\n }\n\n // Apply business logic based on record type\n const processor = this.businessLogicRouter.getUpdateProcessor(existingNote.record_type);\n const processedInput = processor ? await processor.process(input) : input;\n\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n const update_entity: UpdateNoteEntity = {\n ...existingNote,\n ...processedInput,\n updated_by: userId,\n updated_at: now,\n };\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(existingNote, update_entity);\n\n // Update note in database\n const noteUpdated = await this.noteRepo.update(update_entity);\n\n // Create record version if there are changes\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: noteUpdated.id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.NOTE,\n record: changed_props,\n old_record: existingNote,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n const userIds = [noteUpdated.created_by, noteUpdated.updated_by].filter(Boolean) as string[];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n return {\n ...noteUpdated,\n created_by_display_name: noteUpdated.created_by\n ? (displayMap.get(noteUpdated.created_by) ?? noteUpdated.created_by)\n : null,\n updated_by_display_name: noteUpdated.updated_by\n ? (displayMap.get(noteUpdated.updated_by) ?? noteUpdated.updated_by)\n : null,\n };\n }\n}\n","import { container } from 'tsyringe';\nimport { BusinessLogicRouter } from './business_logic/business_logic_router';\nimport {\n CreateNoteSupportTicketProcessor,\n DeleteNoteSupportTicketProcessor,\n GetNotesSupportTicketProcessor,\n UpdateNoteSupportTicketProcessor,\n} from './business_logic/support_ticket';\nimport { NoteRepo } from './db/note_repo';\nimport { CreateNoteFeat } from './features/create_note_feat';\nimport { DeleteNoteFeat } from './features/delete_note_feat';\nimport { GetNotesFeat } from './features/get_notes_feat';\nimport { UpdateNoteFeat } from './features/update_note_feat';\nimport { NOTE_TOKENS } from './note_tokens';\n\nexport function registerNoteContainer() {\n // Register Repository - singleton\n container.registerSingleton(NOTE_TOKENS.INoteRepo, NoteRepo);\n\n // Register Features - singletons\n container.registerSingleton(NOTE_TOKENS.ICreateNoteFeature, CreateNoteFeat);\n container.registerSingleton(NOTE_TOKENS.IUpdateNoteFeature, UpdateNoteFeat);\n container.registerSingleton(NOTE_TOKENS.IGetNotesFeature, GetNotesFeat);\n container.registerSingleton(NOTE_TOKENS.IDeleteNoteFeature, DeleteNoteFeat);\n\n // Register Business Logic Router - singleton\n container.registerSingleton(NOTE_TOKENS.IBusinessLogicRouter, BusinessLogicRouter);\n\n // Register Support Ticket Processors - singletons\n container.registerSingleton(\n NOTE_TOKENS.CreateNoteSupportTicketProcessor,\n CreateNoteSupportTicketProcessor,\n );\n container.registerSingleton(\n NOTE_TOKENS.UpdateNoteSupportTicketProcessor,\n UpdateNoteSupportTicketProcessor,\n );\n container.registerSingleton(\n NOTE_TOKENS.GetNotesSupportTicketProcessor,\n GetNotesSupportTicketProcessor,\n );\n container.registerSingleton(\n NOTE_TOKENS.DeleteNoteSupportTicketProcessor,\n DeleteNoteSupportTicketProcessor,\n );\n}\n","// JWT Token Generator & Verifier Interfaces\nimport { UserTypeValues } from '@dragonmastery/dragoncore-shared';\nimport { FrontendSessionDto } from '../user_session_interfaces';\nimport {\n AccessTokenPayload,\n PasswordResetTokenPayload,\n RefreshTokenPayload,\n UserDetailsTokenPayload,\n} from './jwt_types';\n\nexport interface IPasswordResetTokenGenerator {\n generateToken(userId: string, secret: string, expiresIn: number): Promise<string>;\n}\n\nexport interface IPasswordResetTokenVerifier {\n verifyToken<\n T extends\n | AccessTokenPayload\n | RefreshTokenPayload\n | UserDetailsTokenPayload\n | PasswordResetTokenPayload,\n >(\n token: string,\n secret: string,\n ): Promise<T>;\n}\n\nexport interface IAccessTokenGenerator {\n generateToken(\n userId: string,\n userType: UserTypeValues,\n emailVerified: boolean,\n username: string,\n email: string,\n secret: string,\n expiresIn: number,\n family: string,\n ): Promise<string>;\n}\n\nexport interface IRefreshTokenGenerator {\n generateToken(\n userId: string,\n tokenFamily: string,\n secret: string,\n expiresIn: number,\n tokenId: string,\n ): Promise<string>;\n}\n\nexport interface IRefreshTokenVerifier {\n verifyToken<\n T extends\n | AccessTokenPayload\n | RefreshTokenPayload\n | UserDetailsTokenPayload\n | PasswordResetTokenPayload,\n >(\n token: string,\n secret: string,\n ): Promise<T>;\n}\n\nexport interface IUserDetailsTokenGenerator {\n generateToken(\n userId: string,\n userDetails: FrontendSessionDto,\n secret: string,\n expiresIn: number,\n ): Promise<string>;\n}\n\nexport const JWT_TOKENS = {\n IPasswordResetTokenGenerator: 'IPasswordResetTokenGenerator',\n IPasswordResetTokenVerifier: 'IPasswordResetTokenVerifier',\n IAccessTokenGenerator: 'IAccessTokenGenerator',\n IRefreshTokenGenerator: 'IRefreshTokenGenerator',\n IRefreshTokenVerifier: 'IRefreshTokenVerifier',\n IUserDetailsTokenGenerator: 'IUserDetailsTokenGenerator',\n} as const;\n","import { injectable } from 'tsyringe';\nimport { IPasswordResetTokenGenerator } from './jwt_interfaces';\nimport { generatePasswordResetToken } from './jwt_utils';\n\n@injectable()\nexport class PasswordResetTokenGenerator implements IPasswordResetTokenGenerator {\n async generateToken(userId: string, secret: string, expiresIn: number): Promise<string> {\n return generatePasswordResetToken(userId, secret, expiresIn);\n }\n}\n","import { injectable } from 'tsyringe';\nimport { IPasswordResetTokenVerifier } from './jwt_interfaces';\nimport {\n AccessTokenPayload,\n PasswordResetTokenPayload,\n RefreshTokenPayload,\n UserDetailsTokenPayload,\n} from './jwt_types';\nimport { verifyToken } from './jwt_utils';\n\n@injectable()\nexport class PasswordResetTokenVerifier implements IPasswordResetTokenVerifier {\n async verifyToken<\n T extends\n | AccessTokenPayload\n | RefreshTokenPayload\n | UserDetailsTokenPayload\n | PasswordResetTokenPayload,\n >(token: string, secret: string): Promise<T> {\n return verifyToken<T>(token, secret);\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { TOKENS } from '../../../di_tokens';\nimport { IPasswordService } from '../../../lib/password_verifier';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserRepo, USER_TOKENS } from '../../../slices/user/user_interfaces';\nimport { Logger } from '../../../utils/logger';\nimport { ChangePasswordDto, IChangeUserPassword } from '../password_reset_interfaces';\n\n@injectable()\nexport class ChangeUserPassword implements IChangeUserPassword {\n constructor(\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(TOKENS.PASSWORD_SERVICE) private passwordService: IPasswordService,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(user_dto: ChangePasswordDto) {\n const start = performance.now();\n this.logger.debug('ChangeUserPassword.execute: start');\n\n const user = await this.user_repo.read_user(this.session.user.userId);\n if (!user) {\n throw new Error('Invalid user');\n }\n\n const password_validated = await this.passwordService.verifyPassword(\n user_dto.passwords.current_password + user.salt,\n user.hashed_password,\n );\n\n if (!password_validated) {\n throw new Error('Invalid password');\n }\n\n const hashed_password = await this.passwordService.hashPassword(\n user_dto.passwords.new_password_confirm.normalize('NFKC') + user.salt,\n );\n\n await this.user_repo.change_password_hash(user.id, hashed_password);\n\n const record_version: CreateRecordVersionDto = {\n record_id: user.id,\n operation: 'update',\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.USER,\n record: {\n password: 'updated',\n },\n auth_uid: user.id,\n auth_role: 'consumer',\n };\n\n await this.create_record_version.execute(record_version);\n\n const end = performance.now();\n this.logger.perf(`ChangeUserPassword.execute: complete ${Math.round(end - start)}ms`);\n\n return true;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../../../di_tokens';\nimport { IEmailService } from '../../../lib/email_service';\nimport {\n IPasswordResetTokenGenerator,\n JWT_TOKENS,\n} from '../../../slices/user_session/jwt/jwt_interfaces';\nimport { IUserRepo, USER_TOKENS } from '../../user/user_interfaces';\nimport { IForgotPassword } from '../password_reset_interfaces';\n\n@injectable()\nexport class ForgotPassword implements IForgotPassword {\n constructor(\n @inject(TOKENS.ENV) private env: Env,\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @inject(JWT_TOKENS.IPasswordResetTokenGenerator)\n private tokenGenerator: IPasswordResetTokenGenerator,\n @inject(TOKENS.IEmailService) private emailService: IEmailService,\n ) {}\n\n async execute(email_string: string) {\n const user = await this.user_repo.read_user_by_email(email_string);\n\n if (!user) {\n return true;\n }\n\n const expires_seconds = parseInt(this.env.PASSWORD_RESET_TOKEN_LIFETIME);\n\n const reset_token = await this.tokenGenerator.generateToken(\n user.id,\n this.env.PASSWORD_RESET_JWT_SECRET,\n expires_seconds,\n );\n\n const link = `${this.env.WEBSITE_URL}/auth/reset-password/${reset_token}`;\n\n // Use the EmailService instead of direct fetch calls\n await this.emailService.sendEmail({\n to: email_string,\n subject: `FollowZap App Password Reset for ${email_string}`,\n bodyHtml: `<p>The link will expire in ${expires_seconds / 60} minutes. Click this link to reset your password: <a href=\"${link}\">Reset Password</a>.</p>`,\n bodyText: `The link will expire in ${expires_seconds / 60} minutes. Click this link to reset your password: ${link}`,\n from: {\n email: this.env.NO_REPLY_EMAIL,\n name: 'FollowZap App',\n },\n });\n\n return true;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { IUserRepo, USER_TOKENS } from '../../user/user_interfaces';\n\nimport { ResetPasswordDto } from '@dragonmastery/dragoncore-shared';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { IPasswordService } from '../../../lib/password_verifier';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport {\n IPasswordResetTokenVerifier,\n JWT_TOKENS,\n} from '../../../slices/user_session/jwt/jwt_interfaces';\nimport { PasswordResetTokenPayload } from '../../../slices/user_session/jwt/jwt_types';\nimport { Logger } from '../../../utils/logger';\nimport { IResetPassword } from '../password_reset_interfaces';\n\n@injectable()\nexport class ResetPassword implements IResetPassword {\n constructor(\n @inject(TOKENS.ENV) private env: Env,\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.PASSWORD_SERVICE) private passwordService: IPasswordService,\n @inject(JWT_TOKENS.IPasswordResetTokenVerifier)\n private tokenVerifier: IPasswordResetTokenVerifier,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(reset_dto: ResetPasswordDto) {\n const start = performance.now();\n this.logger.debug('ResetPassword.execute: start');\n const validated_token = await this.tokenVerifier.verifyToken<PasswordResetTokenPayload>(\n reset_dto.token,\n this.env.PASSWORD_RESET_JWT_SECRET,\n );\n const verifyTime = performance.now() - start;\n this.logger.perf(`ResetPassword.execute: verifyToken ${Math.round(verifyTime)}ms`);\n\n if (validated_token.exp < Date.now() / 1000) {\n throw new Error('Link expired');\n }\n\n const user = await this.user_repo.read_user(validated_token.sub);\n\n if (!user) {\n throw new Error('Invalid user');\n }\n\n const hashed_password = await this.passwordService.hashPassword(\n reset_dto.passwords.password.normalize('NFKC') + user.salt,\n );\n\n const user_id = user.id;\n\n await this.user_repo.change_password_hash(user_id, hashed_password);\n\n const record_version: CreateRecordVersionDto = {\n record_id: user_id,\n operation: 'insert',\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.USER,\n record: {\n password: 'updated',\n },\n auth_uid: user_id,\n auth_role: 'consumer',\n };\n\n await this.create_record_version.execute(record_version);\n\n const end = performance.now();\n this.logger.perf(`ResetPassword.execute: complete ${Math.round(end - start)}ms`);\n return true;\n }\n}\n","import { changePasswordSchema, ResetPasswordDto } from '@dragonmastery/dragoncore-shared';\nimport { z } from 'zod';\n\nexport const PASSWORD_RESET_TOKENS = {\n IResetPassword: Symbol.for('IResetPassword'),\n IForgotPassword: Symbol.for('IForgotPassword'),\n IChangeUserPassword: Symbol.for('IChangeUserPassword'),\n} as const;\n\nexport interface IForgotPassword {\n execute(email: string): Promise<boolean>;\n}\n\nexport interface IResetPassword {\n execute(reset_dto: ResetPasswordDto): Promise<boolean>;\n}\n\nexport interface IDeletePasswordReset {\n execute(id: string): Promise<boolean>;\n}\n\nexport type ChangePasswordDto = z.infer<typeof changePasswordSchema>;\n\nexport interface IChangeUserPassword {\n execute(dto: ChangePasswordDto): Promise<boolean>;\n}\n","import { container } from 'tsyringe';\nimport {\n IPasswordResetTokenGenerator,\n IPasswordResetTokenVerifier,\n JWT_TOKENS,\n} from '../user_session/jwt/jwt_interfaces';\nimport { PasswordResetTokenGenerator } from '../user_session/jwt/password_reset_token_generator';\nimport { PasswordResetTokenVerifier } from '../user_session/jwt/password_reset_token_verifier';\nimport { ChangeUserPassword } from './features/change_password';\nimport { ForgotPassword } from './features/forgot_password';\nimport { ResetPassword } from './features/reset_password';\nimport {\n IChangeUserPassword,\n IForgotPassword,\n IResetPassword,\n PASSWORD_RESET_TOKENS,\n} from './password_reset_interfaces';\n\nexport function registerPasswordResetContainer() {\n // Register JWT token generator and verifier - singletons\n container.registerSingleton<IPasswordResetTokenGenerator>(\n JWT_TOKENS.IPasswordResetTokenGenerator,\n PasswordResetTokenGenerator,\n );\n container.registerSingleton<IPasswordResetTokenVerifier>(\n JWT_TOKENS.IPasswordResetTokenVerifier,\n PasswordResetTokenVerifier,\n );\n\n // Register password reset features - singletons\n container.registerSingleton<IForgotPassword>(\n PASSWORD_RESET_TOKENS.IForgotPassword,\n ForgotPassword,\n );\n container.registerSingleton<IResetPassword>(\n PASSWORD_RESET_TOKENS.IResetPassword,\n ResetPassword,\n );\n container.registerSingleton<IChangeUserPassword>(\n PASSWORD_RESET_TOKENS.IChangeUserPassword,\n ChangeUserPassword,\n );\n}\n","import type { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { eq, and } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport type { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n InsertRecordSubscriberEntity,\n ReadRecordSubscriberEntity,\n record_subscriber_table,\n} from './record_subscriber_table';\n\nexport interface IRecordSubscriberRepo {\n create(entity: InsertRecordSubscriberEntity): Promise<ReadRecordSubscriberEntity>;\n\n /**\n * Idempotent: ensures a subscriber exists for the given record and user.\n * Returns existing subscriber if already present, otherwise creates and returns.\n */\n ensureSubscriber(entity: InsertRecordSubscriberEntity): Promise<ReadRecordSubscriberEntity>;\n\n readByRecordId(\n recordType: RecordType,\n recordId: string,\n ): Promise<ReadRecordSubscriberEntity[]>;\n readByRecordAndUser(\n recordType: RecordType,\n recordId: string,\n userId: string,\n ): Promise<ReadRecordSubscriberEntity | undefined>;\n delete(id: string): Promise<boolean>;\n updateSubscribedEvents(\n id: string,\n subscribedEvents: string[] | null,\n ): Promise<ReadRecordSubscriberEntity | undefined>;\n}\n\n@injectable()\nexport class RecordSubscriberRepo implements IRecordSubscriberRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create(\n entity: InsertRecordSubscriberEntity,\n ): Promise<ReadRecordSubscriberEntity> {\n const id = await this.router.generateId(RecordConst.RECORD_SUBSCRIBER);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(record_subscriber_table)\n .values({ id, ...entity })\n .returning(),\n );\n return result;\n }\n\n async ensureSubscriber(\n entity: InsertRecordSubscriberEntity,\n ): Promise<ReadRecordSubscriberEntity> {\n const existing = await this.readByRecordAndUser(\n entity.record_type,\n entity.record_id,\n entity.user_id,\n );\n if (existing) return existing;\n return this.create(entity);\n }\n\n async readByRecordId(\n recordType: RecordType,\n recordId: string,\n ): Promise<ReadRecordSubscriberEntity[]> {\n return this.router.queryAll((db) =>\n db\n .select()\n .from(record_subscriber_table)\n .where(\n and(\n eq(record_subscriber_table.record_type, recordType),\n eq(record_subscriber_table.record_id, recordId),\n ),\n ),\n );\n }\n\n async readByRecordAndUser(\n recordType: RecordType,\n recordId: string,\n userId: string,\n ): Promise<ReadRecordSubscriberEntity | undefined> {\n const rows = await this.router.queryAll((db) =>\n db\n .select()\n .from(record_subscriber_table)\n .where(\n and(\n eq(record_subscriber_table.record_type, recordType),\n eq(record_subscriber_table.record_id, recordId),\n eq(record_subscriber_table.user_id, userId),\n ),\n )\n .limit(1),\n );\n return rows[0];\n }\n\n async delete(id: string): Promise<boolean> {\n const result = await this.router.queryLatest((db) =>\n db\n .delete(record_subscriber_table)\n .where(eq(record_subscriber_table.id, id))\n .returning(),\n );\n return result.length > 0;\n }\n\n async updateSubscribedEvents(\n id: string,\n subscribedEvents: string[] | null,\n ): Promise<ReadRecordSubscriberEntity | undefined> {\n const [result] = await this.router.queryLatest((db) =>\n db\n .update(record_subscriber_table)\n .set({ subscribed_events: subscribedEvents })\n .where(eq(record_subscriber_table.id, id))\n .returning(),\n );\n return result;\n }\n}\n","export const RECORD_SUBSCRIBER_TOKENS = {\n IRecordSubscriberRepo: 'IRecordSubscriberRepo',\n} as const;\n","import { container } from 'tsyringe';\nimport {\n IRecordSubscriberRepo,\n RecordSubscriberRepo,\n} from './db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from './record_subscriber_tokens';\n\nexport function registerRecordSubscriberDependencies() {\n container.registerSingleton<IRecordSubscriberRepo>(\n RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo,\n RecordSubscriberRepo,\n );\n}\n","import type {\n PageInfoDto,\n TeamCreateDto,\n TeamFiltersDto,\n TeamPageDto,\n TeamReadDto,\n TeamUpdateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport type {\n InsertTeamEntity,\n ReadTeamEntity,\n TeamEntity,\n UpdateTeamEntity,\n} from './db/team_entity';\n\n// DI Tokens for lazy loading\nexport const TEAM_TOKENS = {\n ITeamRepository: Symbol('ITeamRepository'),\n ICreateTeamFeature: Symbol('ICreateTeamFeature'),\n IReadTeamFeature: Symbol('IReadTeamFeature'),\n IReadAllTeamsFeature: Symbol('IReadAllTeamsFeature'),\n IUpdateTeamFeature: Symbol('IUpdateTeamFeature'),\n IDeleteTeamFeature: Symbol('IDeleteTeamFeature'),\n IToggleArchiveTeamFeature: Symbol('IToggleArchiveTeamFeature'),\n};\n\n// Feature interfaces use DTOs\nexport interface CreateTeamFeature {\n execute(input: TeamCreateDto): Promise<TeamReadDto>;\n}\n\nexport interface ReadTeamFeature {\n execute(id: string): Promise<TeamReadDto | null>;\n}\n\nexport interface ReadAllTeamsFeature {\n execute(filters?: TeamFiltersDto): Promise<TeamPageDto>;\n}\n\nexport interface UpdateTeamFeature {\n execute(input: TeamUpdateDto): Promise<TeamReadDto>;\n}\n\nexport interface DeleteTeamFeature {\n execute(id: string): Promise<void>;\n}\n\nexport interface ToggleArchiveTeamFeature {\n execute(id: string, archive: boolean): Promise<TeamReadDto>;\n}\n\nexport interface TeamEntityPage {\n items: TeamEntity[];\n pageInfo: PageInfoDto;\n}\n\n// Repository interfaces use Entities\nexport interface TeamRepository {\n create(data: InsertTeamEntity): Promise<ReadTeamEntity>;\n read(id: string): Promise<ReadTeamEntity | null>;\n read_all(\n filters?: TeamFiltersDto & { _consumerTeamIds?: string[] },\n ): Promise<TeamEntityPage>;\n update(data: UpdateTeamEntity): Promise<ReadTeamEntity>;\n archive(id: string): Promise<void>;\n unarchive(id: string): Promise<void>;\n delete(id: string): Promise<void>;\n // Helper methods for internal use\n findByUniqueName(uniqueName: string): Promise<ReadTeamEntity | undefined>;\n findByOriginalId(originalId: string): Promise<ReadTeamEntity | undefined>;\n findByOriginalIds(originalIds: string[]): Promise<ReadTeamEntity[]>;\n}\n","export const RECORD_VERSION_VALIDATION_TOKENS = {\n IValidateRecordAccess: Symbol('IValidateRecordAccess'),\n ValidatorRegistry: Symbol('ValidatorRegistry'),\n};\n","import { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { TOKENS } from '../../../di_tokens';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { Logger } from '../../../utils/logger';\nimport { TEAM_TOKENS, TeamRepository } from '../../team/team_interfaces';\nimport { RECORD_VERSION_VALIDATION_TOKENS } from './record_version_validation_tokens';\n\n// Export tokens with the interface\nexport { RECORD_VERSION_VALIDATION_TOKENS };\n\n/**\n * Interface for record access validators\n * Consuming projects can implement this to add custom validation logic\n */\nexport interface IRecordAccessValidator {\n /**\n * Validates that the user has permission to access the specified record.\n * Throws an error if the record doesn't exist or the user doesn't have access.\n *\n * @param record_id - The ID of the record to validate access for\n * @param session - The user session containing user information\n * @throws Error if the record doesn't exist or the user doesn't have permission\n */\n validate(record_id: string, session: UserAppSession): Promise<void>;\n}\n\nexport interface IValidateRecordAccess {\n /**\n * Validates that the user has permission to access the specified record.\n * Throws an error if the record doesn't exist or the user doesn't have access.\n *\n * @param record_id - The ID of the record to validate access for\n * @param record_type - The type of record (e.g., 'tracker', 'team', 'business_profile')\n * @throws Error if the record doesn't exist or the user doesn't have permission\n */\n execute(record_id: string, record_type: RecordType): Promise<void>;\n\n /**\n * Returns the subset of record types the user is allowed to access.\n * Validates each type in parallel; excludes any that would fail execute().\n *\n * @param record_id - The ID of the record to validate access for\n * @param record_types - The record types to check\n * @returns Filtered list of record types the user can access\n * @throws Error if none of the requested record types are accessible\n */\n filterAllowedRecordTypes(\n record_id: string,\n record_types: RecordType[],\n ): Promise<RecordType[]>;\n}\n\n/**\n * Registry for custom record validators\n * Consuming projects can register validators for specific record types\n */\nexport class RecordAccessValidatorRegistry {\n private validators = new Map<RecordType, IRecordAccessValidator>();\n\n /**\n * Register a validator for a specific record type\n */\n register(recordType: RecordType, validator: IRecordAccessValidator): void {\n this.validators.set(recordType, validator);\n }\n\n /**\n * Get a validator for a specific record type\n */\n get(recordType: RecordType): IRecordAccessValidator | undefined {\n return this.validators.get(recordType);\n }\n\n /**\n * Check if a validator exists for a record type\n */\n has(recordType: RecordType): boolean {\n return this.validators.has(recordType);\n }\n}\n\n@injectable()\nexport class ValidateRecordAccess implements IValidateRecordAccess {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @inject(RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry)\n private validatorRegistry: RecordAccessValidatorRegistry,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(record_id: string, record_type: RecordType): Promise<void> {\n const user = this.session.user;\n\n // Check if there's a custom validator registered for this record type\n const customValidator = this.validatorRegistry.get(record_type);\n if (customValidator) {\n await customValidator.validate(record_id, this.session);\n return;\n }\n\n // Use built-in validators for record types in dragoncore-api\n switch (record_type) {\n case 'team':\n await this.validateTeamAccess(record_id, user.user_type);\n break;\n // Add other built-in record types as needed\n default:\n // For unknown record types without custom validators, only allow lead/staff/admin\n if (\n user.user_type !== 'lead' &&\n user.user_type !== 'staff' &&\n user.user_type !== 'super_admin'\n ) {\n this.logger.warn('[ValidateRecordAccess] Access denied', {\n record_id,\n record_type,\n user_id: user.userId,\n user_type: user.user_type,\n reason: 'Record type requires lead/staff/admin; no custom validator registered',\n });\n throw new BusinessError(\n 'Access denied: You do not have permission to view this record.',\n );\n }\n }\n }\n\n async filterAllowedRecordTypes(\n record_id: string,\n record_types: RecordType[],\n ): Promise<RecordType[]> {\n const results = await Promise.all(\n record_types.map(async (rt) => {\n try {\n await this.execute(record_id, rt);\n return { rt, allowed: true };\n } catch {\n return { rt, allowed: false };\n }\n }),\n );\n const allowed = results.filter((r) => r.allowed).map((r) => r.rt);\n if (allowed.length === 0) {\n this.logger.warn('[ValidateRecordAccess] Access denied - no requested record types allowed', {\n record_id,\n requested_record_types: record_types,\n user_id: this.session.user.userId,\n user_type: this.session.user.user_type,\n });\n throw new BusinessError(\n 'Access denied: You do not have permission to access any of the requested record types.',\n );\n }\n return allowed;\n }\n\n /**\n * Validates access to a team record\n * Lead, staff, and admin have full access\n * Consumers cannot access team records\n */\n private async validateTeamAccess(record_id: string, user_type: string): Promise<void> {\n const team = await this.teamRepo.read(record_id);\n\n if (!team) {\n this.logger.warn('[ValidateRecordAccess] Team not found', { record_id });\n throw new BusinessError('Team not found');\n }\n\n // Lead, staff, and admin have full access\n if (user_type === 'lead' || user_type === 'staff' || user_type === 'super_admin') {\n return;\n }\n\n // Consumers cannot access team records\n this.logger.warn('[ValidateRecordAccess] Access denied - team requires lead/staff', {\n record_id,\n user_type,\n });\n throw new BusinessError('Access denied: Team records require lead or staff permissions');\n }\n}\n","import { and, desc, eq, getTableColumns, gte, inArray, lt, lte, SQL } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst, RecordType } from '../../../db/dbTypes';\nimport { record_version_table } from '../../../db/schemas/historical/historical_schema';\nimport { TOKENS } from '../../../di_tokens';\nimport { PaginationUtils, type SortColumn } from '../../../lib/pagination';\nimport { Logger } from '../../../utils/logger';\nimport {\n CreateRecordVersionDto,\n IRecordVersionRepo,\n ReadRecordVersionDto,\n RecordVersionFilters,\n RecordVersionFiltersBreadcrumb,\n RecordVersionsBreadcrumbPage,\n RecordVersionsPage,\n} from '../record_version_interfaces';\nimport { ReadRecordVersionEntity } from './record_version_entity';\n\nconst columnMap: Record<string, SortColumn> = {\n recorded_at: { column: record_version_table.recorded_at, type: 'date' },\n operation: { column: record_version_table.operation, type: 'string' },\n record_type: { column: record_version_table.record_type, type: 'string' },\n};\n\n@injectable()\nexport class RecordVersionRepo implements IRecordVersionRepo {\n constructor(\n @inject(TOKENS.IHistoricalDatabaseRouter) private router: DatabaseRouter,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async create_record_version(\n record_versionRecord: CreateRecordVersionDto,\n ): Promise<ReadRecordVersionDto> {\n const id = await this.router.generateId(RecordConst.RECORD_VERSION);\n const result = await this.router.queryLatest((db) =>\n db\n .insert(record_version_table)\n .values({\n id,\n ...record_versionRecord,\n })\n .returning(),\n );\n return result[0];\n }\n\n async create_record_versions_batch(\n record_versions: CreateRecordVersionDto[],\n ): Promise<ReadRecordVersionDto[]> {\n if (record_versions.length === 0) {\n return [];\n }\n\n // Generate IDs for all record versions\n const ids = await Promise.all(\n record_versions.map(() => this.router.generateId(RecordConst.RECORD_VERSION)),\n );\n\n // Prepare insert queries for batch\n const result = await this.router.queryLatest(async (db) => {\n const insertQueries = record_versions.map((rv, index) =>\n db\n .insert(record_version_table)\n .values({\n id: ids[index],\n ...rv,\n })\n .returning(),\n );\n\n // Execute all inserts in a single batch\n const batchResult = await db.batch(insertQueries as any);\n return batchResult.flat();\n });\n\n return result;\n }\n\n async read_record_version(id: string): Promise<ReadRecordVersionEntity> {\n const { ...rest } = getTableColumns(record_version_table);\n\n const result = await this.router.queryById(id, (db) =>\n db\n .select({\n ...rest,\n // with sharded databases we cannot join with user_table\n // username: user_table.username,\n })\n .from(record_version_table)\n // we will have to find a different way to get the username\n // .leftJoin(user_table, eq(record_version_table.auth_uid, user_table.id))\n .where(eq(record_version_table.id, id))\n .limit(1),\n );\n return result[0];\n }\n\n async read_all_record_versions(\n record_id: string,\n record_type: RecordType,\n filters?: Omit<RecordVersionFilters, 'record_type'>,\n ): Promise<RecordVersionsPage> {\n const { ...rest } = getTableColumns(record_version_table);\n const limit = filters?.limit || 10;\n\n const conditions = [\n eq(record_version_table.record_type, record_type),\n eq(record_version_table.record_id, record_id),\n ];\n\n if (filters?.start_date) {\n conditions.push(gte(record_version_table.recorded_at, filters.start_date));\n }\n\n if (filters?.end_date) {\n conditions.push(lte(record_version_table.recorded_at, filters.end_date));\n }\n\n if (filters?.cursor) {\n try {\n // With universal IDs, we need to use the cursor directly\n // The ID is already ordered correctly for pagination\n conditions.push(lt(record_version_table.id, filters.cursor));\n } catch (error) {\n this.logger.error('Invalid cursor format', { error });\n }\n }\n\n const items = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n // with sharded databases we cannot join with user_table\n // username: user_table.username,\n })\n .from(record_version_table)\n // .leftJoin(user_table, eq(record_version_table.auth_uid, user_table.id))\n .where(and(...conditions))\n // Universal IDs have timestamp as the first component, so sorting by ID sorts by time\n // This works because the timestamp is fixed-width and formatted for lexicographical ordering\n .orderBy(desc(record_version_table.id))\n .limit(limit + 1),\n );\n\n const hasNextPage = items.length > limit;\n const pageItems = hasNextPage ? items.slice(0, -1) : items;\n const endCursor = pageItems.length > 0 ? pageItems[pageItems.length - 1].id : undefined;\n\n return {\n items: pageItems,\n pageInfo: {\n hasNextPage,\n endCursor,\n },\n };\n }\n\n async delete_record_version(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .delete(record_version_table)\n .where(eq(record_version_table.id, id))\n .returning({ id: record_version_table.id }),\n );\n return result.length > 0;\n }\n\n /**\n * NEW: Breadcrumb pagination for record versions\n * Use this for new implementations requiring bidirectional navigation\n * Supports single record_type or multiple record_types (merged results)\n */\n async read_all_record_versions_paginated(\n record_id: string,\n record_type_or_types: RecordType | RecordType[],\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage> {\n const record_types = Array.isArray(record_type_or_types)\n ? record_type_or_types\n : [record_type_or_types];\n const recordTypeCondition: SQL =\n record_types.length === 1\n ? eq(record_version_table.record_type, record_types[0])\n : inArray(record_version_table.record_type, record_types);\n\n const record_ids = filters?.record_ids;\n const recordIdCondition: SQL =\n record_ids && record_ids.length > 0\n ? inArray(record_version_table.record_id, record_ids)\n : eq(record_version_table.record_id, record_id);\n\n const baseConditions: SQL[] = [\n recordTypeCondition,\n recordIdCondition,\n ];\n\n if (filters?.start_date) {\n baseConditions.push(gte(record_version_table.recorded_at, filters.start_date));\n }\n\n if (filters?.end_date) {\n baseConditions.push(lte(record_version_table.recorded_at, filters.end_date));\n }\n\n return PaginationUtils.findAllPaginated(\n {\n table: record_version_table,\n columnMap,\n router: this.router,\n },\n filters || {},\n baseConditions,\n );\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n IRecordVersionRepo,\n ReadRecordVersionDto,\n RECORD_VERSION_TOKENS,\n} from '../record_version_interfaces';\n\n@injectable()\nexport class CreateRecordVersion implements ICreateRecordVersion {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n ) {}\n\n async execute(record_version: CreateRecordVersionDto) {\n const res = await this.record_version_repo.create_record_version(record_version);\n return res as ReadRecordVersionDto;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersionsBatch,\n IRecordVersionRepo,\n ReadRecordVersionDto,\n RECORD_VERSION_TOKENS,\n} from '../record_version_interfaces';\n\n@injectable()\nexport class CreateRecordVersionsBatch implements ICreateRecordVersionsBatch {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n ) {}\n\n async execute(record_versions: CreateRecordVersionDto[]): Promise<ReadRecordVersionDto[]> {\n const res = await this.record_version_repo.create_record_versions_batch(record_versions);\n return res;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { z as val } from 'zod';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n IDeleteRecordVersion,\n IRecordVersionRepo,\n RECORD_VERSION_TOKENS,\n} from '../record_version_interfaces';\n\n@injectable()\nexport class DeleteRecordVersion implements IDeleteRecordVersion {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute(id: string) {\n const user_is_super_admin = (input: string) => {\n return input === 'super_admin';\n };\n const validateUser = val.object({\n user_type: val\n .string()\n .refine(user_is_super_admin, 'You must be a super admin to read all record_versions'),\n });\n\n validateUser.parse(this.session.user);\n\n return await this.record_version_repo.delete_record_version(id);\n }\n}\n","import { RecordType } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n IReadAllRecordVersionsByRecord,\n IRecordVersionRepo,\n RECORD_VERSION_TOKENS,\n RecordVersionFilters,\n} from '../record_version_interfaces';\n\n@injectable()\nexport class ReadAllRecordVersionsByRecord implements IReadAllRecordVersionsByRecord {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute(\n record_id: string,\n record_type: RecordType,\n filters?: Omit<RecordVersionFilters, 'record_type'>,\n ) {\n const records = await this.record_version_repo.read_all_record_versions(\n record_id,\n record_type,\n filters,\n );\n const mappedRecords = records.items.map((record) => {\n return {\n ...record,\n record: this.filterFieldsByRecordType(JSON.stringify(record.record), record_type),\n old_record: this.filterFieldsByRecordType(\n JSON.stringify(record.old_record),\n record_type,\n ),\n };\n });\n records.items = mappedRecords;\n return records;\n }\n\n /**\n * Filter fields based on record type and user permissions\n * Customers should not see staff-only fields\n */\n private filterFieldsByRecordType(\n recordData: string | null | undefined,\n recordType: RecordType,\n ): string | null | undefined {\n if (!recordData) return recordData;\n\n // Staff users see all fields\n if (\n this.session.user.user_type === 'staff' ||\n this.session.user.user_type === 'super_admin'\n ) {\n return recordData;\n }\n\n // Apply record-type specific filtering for customers\n switch (recordType) {\n case 'support_ticket':\n return this.filterSupportTicketFields(recordData);\n // Add other record types as needed\n default:\n return recordData;\n }\n }\n\n /**\n * Filter support ticket fields for customers\n * Remove staff-only fields that customers should not see\n */\n private filterSupportTicketFields(recordData: string): string {\n try {\n const parsed = JSON.parse(recordData);\n const filtered = { ...parsed };\n\n // Remove staff-only fields that customers should not see\n // Note: Database uses snake_case, schema uses camelCase\n delete filtered.approval_status;\n delete filtered.approvalStatus;\n delete filtered.can_delete;\n delete filtered.canDelete;\n delete filtered.dev_lifecycle;\n delete filtered.devLifecycle;\n delete filtered.delivered_value;\n delete filtered.deliveredValue;\n delete filtered.credits_set_at;\n delete filtered.archived_at;\n delete filtered.archived_by;\n delete filtered.deleted_at;\n delete filtered.deleted_by;\n delete filtered.internal_notes;\n delete filtered.staff_only_comments;\n delete filtered.staff_notes;\n delete filtered.internal_status;\n delete filtered.staff_priority;\n\n return JSON.stringify(filtered);\n } catch (error) {\n // If parsing fails, return original data\n return recordData;\n }\n }\n}\n","import { RecordConst, RecordType } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport {\n IRecordVersionRepo,\n RECORD_VERSION_TOKENS,\n RecordVersionFiltersBreadcrumb,\n RecordVersionsBreadcrumbPage,\n} from '../record_version_interfaces';\n\nconst USER_ID_FIELDS = new Set([\n 'assigned_to',\n 'created_by',\n 'updated_by',\n 'archived_by',\n 'deleted_by',\n]);\n\nfunction collectUserIdsFromRecordVersions(\n items: Array<{ record?: string | null; old_record?: string | null }>,\n): string[] {\n const ids = new Set<string>();\n for (const item of items) {\n for (const raw of [item.record, item.old_record]) {\n if (!raw || typeof raw !== 'string') continue;\n try {\n const rec = JSON.parse(raw) as Record<string, unknown>;\n for (const [key, val] of Object.entries(rec)) {\n if (USER_ID_FIELDS.has(key) && typeof val === 'string' && val.trim()) {\n ids.add(val);\n }\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n return [...ids];\n}\n\nexport interface IReadAllRecordVersionsByRecordPaginatedCustomer {\n execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\n@injectable()\nexport class ReadAllRecordVersionsByRecordPaginatedCustomer implements IReadAllRecordVersionsByRecordPaginatedCustomer {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage> {\n // record_types in filters takes priority over record_type (legacy)\n const record_types = filters?.record_types ?? [record_type];\n const records = await this.record_version_repo.read_all_record_versions_paginated(\n record_id,\n record_types,\n filters,\n );\n\n const mappedRecords = records.items.map((record) => {\n const recordType = record.record_type as RecordType;\n return {\n ...record,\n record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),\n old_record: this.filterFieldsByRecordType(\n JSON.stringify(record.old_record),\n recordType,\n ),\n };\n });\n\n records.items = mappedRecords;\n\n // Enrich with user display names for timeline (assigned_to, created_by, etc.) — applies to all record types\n const userIds = collectUserIdsFromRecordVersions(mappedRecords);\n if (userIds.length > 0) {\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n return {\n ...records,\n user_display_map: Object.fromEntries(displayMap),\n };\n }\n\n return records;\n }\n\n /**\n * Filter fields to show only what customers should see\n * This ALWAYS applies customer filtering regardless of user type\n */\n private filterFieldsByRecordType(\n recordData: string | null | undefined,\n recordType: RecordType,\n ): string | null | undefined {\n if (!recordData) return recordData;\n\n // Apply record-type specific filtering for customers\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.filterSupportTicketFields(recordData);\n case RecordConst.SUPPORT_TICKET_ACTIVITY:\n // Activity events (attachment_added/removed, followup_*); no staff-only fields\n return recordData;\n case RecordConst.TRACKER_ACTIVITY:\n // Activity events (followup_*); no staff-only fields\n return recordData;\n // Add other record types as needed\n default:\n return recordData;\n }\n }\n\n /**\n * Filter support ticket fields for customers\n * Remove staff-only fields that customers should not see\n */\n private filterSupportTicketFields(recordData: string): string {\n try {\n const parsed = JSON.parse(recordData);\n const filtered = { ...parsed };\n\n // Remove staff-only fields that customers should not see\n // Note: Database uses snake_case, schema uses camelCase\n delete filtered.approval_status;\n delete filtered.approvalStatus;\n delete filtered.can_delete;\n delete filtered.canDelete;\n delete filtered.dev_lifecycle;\n delete filtered.devLifecycle;\n delete filtered.delivered_value;\n delete filtered.deliveredValue;\n delete filtered.credits_set_at;\n delete filtered.archived_at;\n delete filtered.archived_by;\n delete filtered.deleted_at;\n delete filtered.deleted_by;\n delete filtered.internal_notes;\n delete filtered.staff_only_comments;\n delete filtered.staff_notes;\n delete filtered.internal_status;\n delete filtered.staff_priority;\n\n return JSON.stringify(filtered);\n } catch (error) {\n // If parsing fails, return original data\n return recordData;\n }\n }\n}\n","import { RecordConst, RecordType } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport {\n IValidateRecordAccess,\n RECORD_VERSION_VALIDATION_TOKENS,\n} from '../business_logic/validate_record_access';\nimport {\n IRecordVersionRepo,\n RECORD_VERSION_TOKENS,\n RecordVersionFiltersBreadcrumb,\n RecordVersionsBreadcrumbPage,\n} from '../record_version_interfaces';\n\nconst USER_ID_FIELDS = new Set([\n 'assigned_to',\n 'created_by',\n 'updated_by',\n 'archived_by',\n 'deleted_by',\n]);\n\nfunction collectUserIdsFromRecordVersions(\n items: Array<{ record?: string | null; old_record?: string | null }>,\n): string[] {\n const ids = new Set<string>();\n for (const item of items) {\n for (const raw of [item.record, item.old_record]) {\n if (!raw || typeof raw !== 'string') continue;\n try {\n const rec = JSON.parse(raw) as Record<string, unknown>;\n for (const [key, val] of Object.entries(rec)) {\n if (USER_ID_FIELDS.has(key) && typeof val === 'string' && val.trim()) {\n ids.add(val);\n }\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n return [...ids];\n}\n\nexport interface IReadAllRecordVersionsByRecordPaginated {\n execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage>;\n}\n\n@injectable()\nexport class ReadAllRecordVersionsByRecordPaginated implements IReadAllRecordVersionsByRecordPaginated {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n @inject(RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess)\n private validateRecordAccess: IValidateRecordAccess,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute(\n record_id: string,\n record_type: RecordType,\n filters?: RecordVersionFiltersBreadcrumb,\n ): Promise<RecordVersionsBreadcrumbPage> {\n // record_types in filters takes priority over record_type (legacy)\n const requested_types = filters?.record_types ?? [record_type];\n const allowed_record_types =\n await this.validateRecordAccess.filterAllowedRecordTypes(record_id, requested_types);\n const records = await this.record_version_repo.read_all_record_versions_paginated(\n record_id,\n allowed_record_types,\n filters,\n );\n\n const mappedRecords = records.items.map((record) => {\n // Use record_type from entity for per-version filtering\n const recordType = record.record_type as RecordType;\n return {\n ...record,\n record: this.filterFieldsByRecordType(JSON.stringify(record.record), recordType),\n old_record: this.filterFieldsByRecordType(\n JSON.stringify(record.old_record),\n recordType,\n ),\n };\n });\n\n records.items = mappedRecords;\n\n // Enrich with user display names for timeline (assigned_to, created_by, etc.) — applies to all record types\n const userIds = collectUserIdsFromRecordVersions(mappedRecords);\n if (userIds.length > 0) {\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n return {\n ...records,\n user_display_map: Object.fromEntries(displayMap),\n };\n }\n\n return records;\n }\n\n /**\n * Filter fields based on record type and user permissions\n * Customers should not see staff-only fields\n */\n private filterFieldsByRecordType(\n recordData: string | null | undefined,\n recordType: RecordType,\n ): string | null | undefined {\n if (!recordData) return recordData;\n\n // Staff users see all fields\n if (\n this.session.user.user_type === 'staff' ||\n this.session.user.user_type === 'super_admin'\n ) {\n return recordData;\n }\n\n // Apply record-type specific filtering for customers\n switch (recordType) {\n case RecordConst.SUPPORT_TICKET:\n return this.filterSupportTicketFields(recordData);\n case RecordConst.SUPPORT_TICKET_ACTIVITY:\n // Activity events (attachment_added/removed, followup_*); no staff-only fields\n return recordData;\n case RecordConst.TRACKER_ACTIVITY:\n // Activity events (followup_*); no staff-only fields\n return recordData;\n // Add other record types as needed\n default:\n return recordData;\n }\n }\n\n /**\n * Filter support ticket fields for customers\n * Remove staff-only fields that customers should not see\n */\n private filterSupportTicketFields(recordData: string): string {\n try {\n const parsed = JSON.parse(recordData);\n const filtered = { ...parsed };\n\n // Remove staff-only fields that customers should not see\n // Note: Database uses snake_case, schema uses camelCase\n delete filtered.approval_status;\n delete filtered.approvalStatus;\n delete filtered.can_delete;\n delete filtered.canDelete;\n delete filtered.dev_lifecycle;\n delete filtered.devLifecycle;\n delete filtered.delivered_value;\n delete filtered.deliveredValue;\n delete filtered.credits_set_at;\n delete filtered.archived_at;\n delete filtered.archived_by;\n delete filtered.deleted_at;\n delete filtered.deleted_by;\n delete filtered.internal_notes;\n delete filtered.staff_only_comments;\n delete filtered.staff_notes;\n delete filtered.internal_status;\n delete filtered.staff_priority;\n\n return JSON.stringify(filtered);\n } catch (error) {\n // If parsing fails, return original data\n return recordData;\n }\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport {\n IReadRecordVersion,\n IRecordVersionRepo,\n ReadRecordVersionDto,\n RECORD_VERSION_TOKENS,\n} from '../record_version_interfaces';\n\n@injectable()\nexport class ReadRecordVersion implements IReadRecordVersion {\n constructor(\n @inject(RECORD_VERSION_TOKENS.IRecordVersionsRepo)\n private record_version_repo: IRecordVersionRepo,\n ) {}\n\n async execute(record_version_id: string): Promise<ReadRecordVersionDto> {\n const record = await this.record_version_repo.read_record_version(record_version_id);\n\n const record_dto: ReadRecordVersionDto = {\n ...record,\n record: JSON.stringify(record.record),\n old_record: JSON.stringify(record.old_record),\n };\n\n return record_dto;\n }\n}\n","import { container } from 'tsyringe';\nimport {\n RECORD_VERSION_VALIDATION_TOKENS,\n RecordAccessValidatorRegistry,\n ValidateRecordAccess,\n type IValidateRecordAccess,\n} from './business_logic/validate_record_access';\nimport { RecordVersionRepo } from './db/record_version_repo';\nimport { CreateRecordVersion } from './features/create_record_version';\nimport { CreateRecordVersionsBatch } from './features/create_record_versions_batch';\nimport { DeleteRecordVersion } from './features/delete_record_version';\nimport { ReadAllRecordVersionsByRecord } from './features/read_all_record_versions_by_record_feat';\nimport { ReadAllRecordVersionsByRecordPaginatedCustomer } from './features/read_all_record_versions_by_record_paginated_customer_feat';\nimport { ReadAllRecordVersionsByRecordPaginated } from './features/read_all_record_versions_by_record_paginated_feat';\nimport { ReadRecordVersion } from './features/read_record_version';\nimport {\n ICreateRecordVersion,\n ICreateRecordVersionsBatch,\n IDeleteRecordVersion,\n IReadAllRecordVersionsByRecord,\n IReadAllRecordVersionsByRecordPaginated,\n IReadAllRecordVersionsByRecordPaginatedCustomer,\n IReadRecordVersion,\n IRecordVersionRepo,\n RECORD_VERSION_TOKENS,\n} from './record_version_interfaces';\n\nexport function registerRecordVersionContainer() {\n container.registerSingleton<IRecordVersionRepo>(\n RECORD_VERSION_TOKENS.IRecordVersionsRepo,\n RecordVersionRepo,\n );\n container.registerSingleton<IReadAllRecordVersionsByRecord>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecord,\n ReadAllRecordVersionsByRecord,\n );\n container.registerSingleton<IReadAllRecordVersionsByRecordPaginated>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecordPaginated,\n ReadAllRecordVersionsByRecordPaginated,\n );\n container.registerSingleton<IReadAllRecordVersionsByRecordPaginatedCustomer>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecordPaginatedCustomer,\n ReadAllRecordVersionsByRecordPaginatedCustomer,\n );\n container.registerSingleton<IReadRecordVersion>(\n RECORD_VERSION_TOKENS.IReadRecordVersion,\n ReadRecordVersion,\n );\n container.registerSingleton<ICreateRecordVersion>(\n RECORD_VERSION_TOKENS.ICreateRecordVersion,\n CreateRecordVersion,\n );\n container.registerSingleton<ICreateRecordVersionsBatch>(\n RECORD_VERSION_TOKENS.ICreateRecordVersionsBatch,\n CreateRecordVersionsBatch,\n );\n container.registerSingleton<IDeleteRecordVersion>(\n RECORD_VERSION_TOKENS.IDeleteRecordVersion,\n DeleteRecordVersion,\n );\n\n // Register validator registry as singleton (can be extended by consuming projects)\n container.registerSingleton<RecordAccessValidatorRegistry>(\n RECORD_VERSION_VALIDATION_TOKENS.ValidatorRegistry,\n RecordAccessValidatorRegistry,\n );\n\n // Register ValidateRecordAccess (will use the registry if provided)\n container.registerSingleton<IValidateRecordAccess>(\n RECORD_VERSION_VALIDATION_TOKENS.IValidateRecordAccess,\n ValidateRecordAccess,\n );\n}\n","import type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport type { SavedFilterEntity } from './db/saved_filter_entity';\n\nexport interface SavedFilterInsertEntity {\n user_id: string;\n name: string;\n context: string;\n route_path: string;\n filters: string;\n sort_by?: string | null;\n sort_direction?: string | null;\n}\n\nexport interface SavedFilterUpdateEntity {\n id: string;\n name?: string;\n route_path?: string;\n filters?: string;\n sort_by?: string | null;\n sort_direction?: string | null;\n updated_at: string;\n}\n\nexport const SAVED_FILTER_TOKENS = {\n ISavedFilterRepo: Symbol('ISavedFilterRepo'),\n IListSavedFilters: Symbol('IListSavedFilters'),\n IListAllSavedFilters: Symbol('IListAllSavedFilters'),\n ICreateSavedFilter: Symbol('ICreateSavedFilter'),\n IUpdateSavedFilter: Symbol('IUpdateSavedFilter'),\n IDeleteSavedFilter: Symbol('IDeleteSavedFilter'),\n} as const;\n\nexport interface ISavedFilterRepo {\n create(entity: SavedFilterInsertEntity): Promise<SavedFilterEntity>;\n read(id: string): Promise<SavedFilterEntity | null>;\n readByIds(ids: string[]): Promise<SavedFilterEntity[]>;\n listByUser(userId: string): Promise<SavedFilterEntity[]>;\n listByUserAndContext(userId: string, context: string): Promise<SavedFilterEntity[]>;\n update(entity: SavedFilterUpdateEntity): Promise<SavedFilterEntity>;\n delete(id: string): Promise<boolean>;\n}\n\nexport interface IListSavedFilters {\n execute(context: string): Promise<SavedFilterReadDto[]>;\n}\n\nexport interface IListAllSavedFilters {\n execute(): Promise<SavedFilterReadDto[]>;\n}\n\nexport interface ICreateSavedFilter {\n execute(input: {\n name: string;\n context: string;\n route_path: string;\n filters: Record<string, string | string[]>;\n sort_by?: string;\n sort_direction?: 'asc' | 'desc';\n }): Promise<SavedFilterReadDto>;\n}\n\nexport interface IUpdateSavedFilter {\n execute(input: {\n id: string;\n name?: string;\n route_path?: string;\n filters?: Record<string, string | string[]>;\n sort_by?: string;\n sort_direction?: 'asc' | 'desc';\n }): Promise<SavedFilterReadDto | null>;\n}\n\nexport interface IDeleteSavedFilter {\n execute(id: string): Promise<boolean>;\n}\n","import type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport type { SavedFilterEntity } from './saved_filter_entity';\n\nexport function mapSavedFilterEntityToDto(entity: SavedFilterEntity): SavedFilterReadDto {\n let filters: Record<string, string | string[]>;\n try {\n filters = JSON.parse(entity.filters) as Record<string, string | string[]>;\n } catch {\n filters = {};\n }\n return {\n id: entity.id,\n user_id: entity.user_id,\n name: entity.name,\n context: entity.context,\n route_path: entity.route_path,\n filters,\n sort_by: entity.sort_by ?? undefined,\n sort_direction: (entity.sort_direction as 'asc' | 'desc') ?? undefined,\n created_at: entity.created_at,\n updated_at: entity.updated_at,\n };\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport {\n MAX_PRESETS_PER_CONTEXT,\n type SavedFilterReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type ICreateSavedFilter,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\n\n@injectable()\nexport class CreateSavedFilterFeature implements ICreateSavedFilter {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private repo: ISavedFilterRepo,\n ) {}\n\n async execute(input: {\n name: string;\n context: string;\n route_path: string;\n filters: Record<string, string | string[]>;\n sort_by?: string;\n sort_direction?: 'asc' | 'desc';\n }): Promise<SavedFilterReadDto> {\n const userId = this.session.user.userId;\n const existing = await this.repo.listByUserAndContext(userId, input.context);\n if (existing.length >= MAX_PRESETS_PER_CONTEXT) {\n throw new BusinessError(\n `You can have at most ${MAX_PRESETS_PER_CONTEXT} presets per page. Delete one to save a new preset.`,\n );\n }\n const entity = await this.repo.create({\n user_id: userId,\n name: input.name,\n context: input.context,\n route_path: input.route_path,\n filters: JSON.stringify(input.filters),\n sort_by: input.sort_by ?? null,\n sort_direction: input.sort_direction ?? null,\n });\n return mapSavedFilterEntityToDto(entity);\n }\n}\n","import type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport type { UserPinnedPresetEntity } from './db/user_pinned_preset_entity';\n\nexport const USER_PINNED_PRESET_TOKENS = {\n IUserPinnedPresetRepo: Symbol('IUserPinnedPresetRepo'),\n IListPinnedPresets: Symbol('IListPinnedPresets'),\n IAddPinnedPreset: Symbol('IAddPinnedPreset'),\n IRemovePinnedPreset: Symbol('IRemovePinnedPreset'),\n IReorderPinnedPresets: Symbol('IRearrangePinnedPresets'),\n} as const;\n\nexport interface IUserPinnedPresetRepo {\n create(userId: string, presetId: string, position: number): Promise<UserPinnedPresetEntity>;\n listByUser(userId: string): Promise<UserPinnedPresetEntity[]>;\n findByUserAndPreset(userId: string, presetId: string): Promise<UserPinnedPresetEntity | null>;\n updatePosition(id: string, position: number): Promise<void>;\n delete(id: string): Promise<boolean>;\n deleteByPresetId(presetId: string): Promise<number>;\n}\n\nexport interface IListPinnedPresets {\n execute(): Promise<SavedFilterReadDto[]>;\n}\n\nexport interface IAddPinnedPreset {\n execute(presetId: string): Promise<SavedFilterReadDto | null>;\n}\n\nexport interface IRemovePinnedPreset {\n execute(presetId: string): Promise<boolean>;\n}\n\nexport interface IReorderPinnedPresets {\n execute(presetIds: string[]): Promise<void>;\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type IDeleteSavedFilter,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport {\n USER_PINNED_PRESET_TOKENS,\n type IUserPinnedPresetRepo,\n} from '../user_pinned_preset_interfaces';\n\n@injectable()\nexport class DeleteSavedFilterFeature implements IDeleteSavedFilter {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private repo: ISavedFilterRepo,\n @inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)\n private pinnedRepo: IUserPinnedPresetRepo,\n ) {}\n\n async execute(id: string): Promise<boolean> {\n const existing = await this.repo.read(id);\n if (!existing) return false;\n if (existing.user_id !== this.session.user.userId) {\n throw new BusinessError('You can only delete your own saved filters');\n }\n await this.pinnedRepo.deleteByPresetId(id);\n return await this.repo.delete(id);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type IListSavedFilters,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\n\n@injectable()\nexport class ListSavedFiltersFeature implements IListSavedFilters {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private repo: ISavedFilterRepo,\n ) {}\n\n async execute(context: string): Promise<SavedFilterReadDto[]> {\n const userId = this.session.user.userId;\n const entities = await this.repo.listByUserAndContext(userId, context);\n return entities.map(mapSavedFilterEntityToDto);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type ISavedFilterRepo,\n type IUpdateSavedFilter,\n} from '../saved_filter_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\n\n@injectable()\nexport class UpdateSavedFilterFeature implements IUpdateSavedFilter {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private repo: ISavedFilterRepo,\n ) {}\n\n async execute(input: {\n id: string;\n name?: string;\n route_path?: string;\n filters?: Record<string, string | string[]>;\n sort_by?: string;\n sort_direction?: 'asc' | 'desc';\n }): Promise<SavedFilterReadDto | null> {\n const existing = await this.repo.read(input.id);\n if (!existing) return null;\n if (existing.user_id !== this.session.user.userId) {\n throw new BusinessError('You can only update your own saved filters');\n }\n const now = new Date().toISOString();\n const entity = await this.repo.update({\n id: input.id,\n ...(input.name !== undefined && { name: input.name }),\n ...(input.route_path !== undefined && { route_path: input.route_path }),\n ...(input.filters !== undefined && { filters: JSON.stringify(input.filters) }),\n ...(input.sort_by !== undefined && { sort_by: input.sort_by }),\n ...(input.sort_direction !== undefined && { sort_direction: input.sort_direction }),\n updated_at: now,\n });\n return mapSavedFilterEntityToDto(entity);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport {\n USER_PINNED_PRESET_TOKENS,\n type IAddPinnedPreset,\n type IUserPinnedPresetRepo,\n} from '../user_pinned_preset_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\nimport { MAX_PINNED_PRESETS } from '../db/user_pinned_preset_table';\n\n@injectable()\nexport class AddPinnedPresetFeature implements IAddPinnedPreset {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)\n private pinnedRepo: IUserPinnedPresetRepo,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private savedFilterRepo: ISavedFilterRepo,\n ) {}\n\n async execute(presetId: string): Promise<SavedFilterReadDto | null> {\n const userId = this.session.user.userId;\n\n const preset = await this.savedFilterRepo.read(presetId);\n if (!preset) return null;\n if (preset.user_id !== userId) {\n throw new BusinessError('You can only pin your own presets');\n }\n\n const existing = await this.pinnedRepo.findByUserAndPreset(userId, presetId);\n if (existing) return mapSavedFilterEntityToDto(preset);\n\n const pins = await this.pinnedRepo.listByUser(userId);\n if (pins.length >= MAX_PINNED_PRESETS) {\n throw new BusinessError(\n `You can pin at most ${MAX_PINNED_PRESETS} presets. Unpin one to add another.`,\n );\n }\n\n const position = pins.length;\n await this.pinnedRepo.create(userId, presetId, position);\n return mapSavedFilterEntityToDto(preset);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type IListAllSavedFilters,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\n\n@injectable()\nexport class ListAllSavedFiltersFeature implements IListAllSavedFilters {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private repo: ISavedFilterRepo,\n ) {}\n\n async execute(): Promise<SavedFilterReadDto[]> {\n const userId = this.session.user.userId;\n const entities = await this.repo.listByUser(userId);\n return entities.map(mapSavedFilterEntityToDto);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport type { SavedFilterReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport {\n SAVED_FILTER_TOKENS,\n type ISavedFilterRepo,\n} from '../saved_filter_interfaces';\nimport {\n USER_PINNED_PRESET_TOKENS,\n type IListPinnedPresets,\n type IUserPinnedPresetRepo,\n} from '../user_pinned_preset_interfaces';\nimport { mapSavedFilterEntityToDto } from '../db/saved_filter_mapper';\n\n@injectable()\nexport class ListPinnedPresetsFeature implements IListPinnedPresets {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)\n private pinnedRepo: IUserPinnedPresetRepo,\n @inject(SAVED_FILTER_TOKENS.ISavedFilterRepo)\n private savedFilterRepo: ISavedFilterRepo,\n ) {}\n\n async execute(): Promise<SavedFilterReadDto[]> {\n const userId = this.session.user.userId;\n const pins = await this.pinnedRepo.listByUser(userId);\n if (pins.length === 0) return [];\n\n const presetIds = pins.map((p) => p.preset_id);\n const presets = await this.savedFilterRepo.readByIds(presetIds);\n const presetMap = new Map(presets.map((p) => [p.id, p]));\n\n const result: SavedFilterReadDto[] = [];\n for (const pin of pins) {\n const preset = presetMap.get(pin.preset_id);\n if (preset && preset.user_id === userId) {\n result.push(mapSavedFilterEntityToDto(preset));\n }\n }\n return result;\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { inject, injectable } from 'tsyringe';\nimport {\n USER_PINNED_PRESET_TOKENS,\n type IRemovePinnedPreset,\n type IUserPinnedPresetRepo,\n} from '../user_pinned_preset_interfaces';\n\n@injectable()\nexport class RemovePinnedPresetFeature implements IRemovePinnedPreset {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)\n private pinnedRepo: IUserPinnedPresetRepo,\n ) {}\n\n async execute(presetId: string): Promise<boolean> {\n const userId = this.session.user.userId;\n const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);\n if (!pin) return false;\n return await this.pinnedRepo.delete(pin.id);\n }\n}\n","import type { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { inject, injectable } from 'tsyringe';\nimport {\n USER_PINNED_PRESET_TOKENS,\n type IReorderPinnedPresets,\n type IUserPinnedPresetRepo,\n} from '../user_pinned_preset_interfaces';\n\n@injectable()\nexport class ReorderPinnedPresetsFeature implements IReorderPinnedPresets {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo)\n private pinnedRepo: IUserPinnedPresetRepo,\n ) {}\n\n async execute(presetIds: string[]): Promise<void> {\n const userId = this.session.user.userId;\n for (let i = 0; i < presetIds.length; i++) {\n const presetId = presetIds[i]!;\n const pin = await this.pinnedRepo.findByUserAndPreset(userId, presetId);\n if (pin) {\n await this.pinnedRepo.updatePosition(pin.id, i);\n }\n }\n }\n}\n","import type { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { and, eq, inArray } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport type {\n ISavedFilterRepo,\n SavedFilterInsertEntity,\n SavedFilterUpdateEntity,\n} from '../saved_filter_interfaces';\nimport type { SavedFilterEntity } from './saved_filter_entity';\nimport { saved_filter_table } from './saved_filter_table';\n\n@injectable()\nexport class SavedFilterRepository implements ISavedFilterRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create(entity: SavedFilterInsertEntity): Promise<SavedFilterEntity> {\n const id = await this.router.generateId(RecordConst.SAVED_FILTER);\n const now = new Date().toISOString();\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(saved_filter_table)\n .values({\n id,\n user_id: entity.user_id,\n name: entity.name,\n context: entity.context,\n route_path: entity.route_path,\n filters: entity.filters,\n sort_by: entity.sort_by ?? null,\n sort_direction: entity.sort_direction ?? null,\n created_at: now,\n updated_at: now,\n })\n .returning(),\n );\n return result;\n }\n\n async read(id: string): Promise<SavedFilterEntity | null> {\n const result = await this.router.queryById(id, (db) =>\n db\n .select()\n .from(saved_filter_table)\n .where(eq(saved_filter_table.id, id))\n .limit(1),\n );\n return result[0] ?? null;\n }\n\n async readByIds(ids: string[]): Promise<SavedFilterEntity[]> {\n if (ids.length === 0) return [];\n return this.router.queryByIds(ids, (db, idsForShard) =>\n db\n .select()\n .from(saved_filter_table)\n .where(inArray(saved_filter_table.id, idsForShard)),\n );\n }\n\n async listByUser(userId: string): Promise<SavedFilterEntity[]> {\n const result = await this.router.queryAll((db) =>\n db\n .select()\n .from(saved_filter_table)\n .where(eq(saved_filter_table.user_id, userId)),\n );\n return result;\n }\n\n async listByUserAndContext(userId: string, context: string): Promise<SavedFilterEntity[]> {\n const result = await this.router.queryAll((db) =>\n db\n .select()\n .from(saved_filter_table)\n .where(\n and(\n eq(saved_filter_table.user_id, userId),\n eq(saved_filter_table.context, context),\n ),\n ),\n );\n return result;\n }\n\n async update(entity: SavedFilterUpdateEntity): Promise<SavedFilterEntity> {\n const rows = await this.router.queryById(entity.id, (db) =>\n db\n .update(saved_filter_table)\n .set({\n ...(entity.name !== undefined && { name: entity.name }),\n ...(entity.route_path !== undefined && { route_path: entity.route_path }),\n ...(entity.filters !== undefined && { filters: entity.filters }),\n ...(entity.sort_by !== undefined && { sort_by: entity.sort_by }),\n ...(entity.sort_direction !== undefined && {\n sort_direction: entity.sort_direction,\n }),\n updated_at: entity.updated_at,\n })\n .where(eq(saved_filter_table.id, entity.id))\n .returning(),\n );\n const updated = rows[0];\n if (!updated) {\n throw new Error('Saved filter not found or update failed');\n }\n return updated;\n }\n\n async delete(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .delete(saved_filter_table)\n .where(eq(saved_filter_table.id, id))\n .returning({ id: saved_filter_table.id }),\n );\n return result.length > 0;\n }\n}\n","import type { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { and, eq } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport type { IUserPinnedPresetRepo } from '../user_pinned_preset_interfaces';\nimport type { UserPinnedPresetEntity } from './user_pinned_preset_entity';\nimport { user_pinned_preset_table } from './user_pinned_preset_table';\n\n@injectable()\nexport class UserPinnedPresetRepository implements IUserPinnedPresetRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create(userId: string, presetId: string, position: number): Promise<UserPinnedPresetEntity> {\n const id = await this.router.generateId(RecordConst.USER_PINNED_PRESET);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(user_pinned_preset_table)\n .values({\n id,\n user_id: userId,\n preset_id: presetId,\n position,\n })\n .returning(),\n );\n return result;\n }\n\n async listByUser(userId: string): Promise<UserPinnedPresetEntity[]> {\n const result = await this.router.queryAll((db) =>\n db\n .select()\n .from(user_pinned_preset_table)\n .where(eq(user_pinned_preset_table.user_id, userId)),\n );\n return result.sort((a, b) => a.position - b.position);\n }\n\n async findByUserAndPreset(\n userId: string,\n presetId: string,\n ): Promise<UserPinnedPresetEntity | null> {\n const result = await this.router.queryAll((db) =>\n db\n .select()\n .from(user_pinned_preset_table)\n .where(\n and(\n eq(user_pinned_preset_table.user_id, userId),\n eq(user_pinned_preset_table.preset_id, presetId),\n ),\n )\n .limit(1),\n );\n return result[0] ?? null;\n }\n\n async updatePosition(id: string, position: number): Promise<void> {\n await this.router.queryById(id, (db) =>\n db\n .update(user_pinned_preset_table)\n .set({ position })\n .where(eq(user_pinned_preset_table.id, id))\n .returning({ id: user_pinned_preset_table.id }),\n );\n }\n\n async delete(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .delete(user_pinned_preset_table)\n .where(eq(user_pinned_preset_table.id, id))\n .returning({ id: user_pinned_preset_table.id }),\n );\n return result.length > 0;\n }\n\n async deleteByPresetId(presetId: string): Promise<number> {\n const pins = await this.router.queryAll((db) =>\n db\n .select()\n .from(user_pinned_preset_table)\n .where(eq(user_pinned_preset_table.preset_id, presetId)),\n );\n let deleted = 0;\n for (const pin of pins) {\n const ok = await this.delete(pin.id);\n if (ok) deleted++;\n }\n return deleted;\n }\n}\n","import { container } from 'tsyringe';\nimport { CreateSavedFilterFeature } from './features/create_saved_filter_feat';\nimport { DeleteSavedFilterFeature } from './features/delete_saved_filter_feat';\nimport { ListSavedFiltersFeature } from './features/list_saved_filters_feat';\nimport { UpdateSavedFilterFeature } from './features/update_saved_filter_feat';\nimport { AddPinnedPresetFeature } from './features/add_pinned_preset_feat';\nimport { ListAllSavedFiltersFeature } from './features/list_all_saved_filters_feat';\nimport { ListPinnedPresetsFeature } from './features/list_pinned_presets_feat';\nimport { RemovePinnedPresetFeature } from './features/remove_pinned_preset_feat';\nimport { ReorderPinnedPresetsFeature } from './features/reorder_pinned_presets_feat';\nimport type {\n ICreateSavedFilter,\n IDeleteSavedFilter,\n IListAllSavedFilters,\n IListSavedFilters,\n IUpdateSavedFilter,\n} from './saved_filter_interfaces';\nimport { SAVED_FILTER_TOKENS } from './saved_filter_interfaces';\nimport type {\n IAddPinnedPreset,\n IListPinnedPresets,\n IRemovePinnedPreset,\n IReorderPinnedPresets,\n IUserPinnedPresetRepo,\n} from './user_pinned_preset_interfaces';\nimport { USER_PINNED_PRESET_TOKENS } from './user_pinned_preset_interfaces';\nimport { SavedFilterRepository } from './db/saved_filter_repo';\nimport { UserPinnedPresetRepository } from './db/user_pinned_preset_repo';\nimport type { ISavedFilterRepo } from './saved_filter_interfaces';\n\nexport function registerSavedFilterContainer() {\n container.registerSingleton<ISavedFilterRepo>(\n SAVED_FILTER_TOKENS.ISavedFilterRepo,\n SavedFilterRepository,\n );\n container.registerSingleton<IUserPinnedPresetRepo>(\n USER_PINNED_PRESET_TOKENS.IUserPinnedPresetRepo,\n UserPinnedPresetRepository,\n );\n container.registerSingleton<IListSavedFilters>(\n SAVED_FILTER_TOKENS.IListSavedFilters,\n ListSavedFiltersFeature,\n );\n container.registerSingleton<IListAllSavedFilters>(\n SAVED_FILTER_TOKENS.IListAllSavedFilters,\n ListAllSavedFiltersFeature,\n );\n container.registerSingleton<ICreateSavedFilter>(\n SAVED_FILTER_TOKENS.ICreateSavedFilter,\n CreateSavedFilterFeature,\n );\n container.registerSingleton<IUpdateSavedFilter>(\n SAVED_FILTER_TOKENS.IUpdateSavedFilter,\n UpdateSavedFilterFeature,\n );\n container.registerSingleton<IDeleteSavedFilter>(\n SAVED_FILTER_TOKENS.IDeleteSavedFilter,\n DeleteSavedFilterFeature,\n );\n container.registerSingleton<IListPinnedPresets>(\n USER_PINNED_PRESET_TOKENS.IListPinnedPresets,\n ListPinnedPresetsFeature,\n );\n container.registerSingleton<IAddPinnedPreset>(\n USER_PINNED_PRESET_TOKENS.IAddPinnedPreset,\n AddPinnedPresetFeature,\n );\n container.registerSingleton<IRemovePinnedPreset>(\n USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset,\n RemovePinnedPresetFeature,\n );\n container.registerSingleton<IReorderPinnedPresets>(\n USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets,\n ReorderPinnedPresetsFeature,\n );\n}\n","import { eq } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport type { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { user_table } from '../../user/db/user_table';\nimport {\n InsertSupportStaffEntity,\n ReadSupportStaffEntity,\n support_staff_table,\n} from './support_staff_table';\n\nexport interface SupportStaffUser {\n id: string;\n email: string;\n}\n\nexport interface SupportStaffMemberRow {\n id: string;\n user_id: string;\n email: string;\n created_at: string;\n}\n\nexport interface ISupportStaffRepo {\n readAll(): Promise<ReadSupportStaffEntity[]>;\n readSupportStaffMembers(): Promise<SupportStaffMemberRow[]>;\n readTriageUsers(): Promise<SupportStaffUser[]>;\n add(userId: string, createdBy: string): Promise<ReadSupportStaffEntity>;\n remove(userId: string): Promise<boolean>;\n}\n\n@injectable()\nexport class SupportStaffRepo implements ISupportStaffRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async readAll(): Promise<ReadSupportStaffEntity[]> {\n return this.router.queryAll((db) =>\n db.select().from(support_staff_table).orderBy(support_staff_table.user_id),\n );\n }\n\n async readSupportStaffMembers(): Promise<SupportStaffMemberRow[]> {\n return this.router.queryAll((db) =>\n db\n .select({\n id: support_staff_table.id,\n user_id: support_staff_table.user_id,\n email: user_table.email,\n created_at: support_staff_table.created_at,\n })\n .from(support_staff_table)\n .innerJoin(user_table, eq(support_staff_table.user_id, user_table.id))\n .orderBy(support_staff_table.user_id),\n );\n }\n\n async readTriageUsers(): Promise<SupportStaffUser[]> {\n return this.router.queryAll((db) =>\n db\n .select({\n id: user_table.id,\n email: user_table.email,\n })\n .from(support_staff_table)\n .innerJoin(user_table, eq(support_staff_table.user_id, user_table.id))\n .orderBy(support_staff_table.user_id),\n );\n }\n\n async add(userId: string, createdBy: string): Promise<ReadSupportStaffEntity> {\n const id = await this.router.generateId(RecordConst.SUPPORT_STAFF);\n const now = new Date().toISOString();\n const entity: InsertSupportStaffEntity = {\n user_id: userId,\n created_at: now,\n created_by: createdBy,\n };\n const [result] = await this.router.queryLatest((db) =>\n db.insert(support_staff_table).values({ id, ...entity }).returning(),\n );\n return result;\n }\n\n async remove(userId: string): Promise<boolean> {\n const result = await this.router.queryLatest((db) =>\n db\n .delete(support_staff_table)\n .where(eq(support_staff_table.user_id, userId))\n .returning({ id: support_staff_table.id }),\n );\n return result.length > 0;\n }\n}\n","export const SUPPORT_STAFF_TOKENS = {\n ISupportStaffRepo: Symbol('ISupportStaffRepo'),\n IListSupportStaffFeature: Symbol('IListSupportStaffFeature'),\n IAddSupportStaffFeature: Symbol('IAddSupportStaffFeature'),\n IRemoveSupportStaffFeature: Symbol('IRemoveSupportStaffFeature'),\n} as const;\n","import { inject, injectable } from 'tsyringe';\nimport type { SupportStaffMember } from '@dragonmastery/dragoncore-shared';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { BusinessError } from '../../../middleware/rpc_mid';\nimport { TOKENS } from '../../../di_tokens';\nimport { IUserRepo } from '../../user/user_interfaces';\nimport { USER_TOKENS } from '../../user/user_interfaces';\nimport { ISupportStaffRepo } from '../db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../support_staff_tokens';\n\nexport interface IAddSupportStaffFeature {\n execute(userId: string): Promise<SupportStaffMember>;\n}\n\n@injectable()\nexport class AddSupportStaffFeat implements IAddSupportStaffFeature {\n constructor(\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n @inject(USER_TOKENS.IUsersRepo) private userRepo: IUserRepo,\n @inject(TOKENS.AUTHENTICATED_SESSION) private session: UserAppSession,\n ) {}\n\n async execute(userId: string): Promise<SupportStaffMember> {\n const user = await this.userRepo.read_user(userId);\n if (!user) {\n throw new Error('User not found');\n }\n\n const allowedTypes = ['staff', 'super_admin'] as const;\n if (!allowedTypes.includes(user.user_type as (typeof allowedTypes)[number])) {\n throw new BusinessError('Only staff and super_admin users can be added to support staff');\n }\n\n const existing = await this.supportStaffRepo.readAll();\n if (existing.some((s) => s.user_id === userId)) {\n throw new Error('User is already support staff');\n }\n\n const created = await this.supportStaffRepo.add(userId, this.session.user.userId);\n const members = await this.supportStaffRepo.readSupportStaffMembers();\n const member = members.find((m) => m.id === created.id);\n if (!member) {\n throw new Error('Failed to fetch created support staff member');\n }\n return member;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport type { SupportStaffMember } from '@dragonmastery/dragoncore-shared';\nimport { ISupportStaffRepo } from '../db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../support_staff_tokens';\n\nexport interface IListSupportStaffFeature {\n execute(): Promise<SupportStaffMember[]>;\n}\n\n@injectable()\nexport class ListSupportStaffFeat implements IListSupportStaffFeature {\n constructor(\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n ) {}\n\n async execute(): Promise<SupportStaffMember[]> {\n return this.supportStaffRepo.readSupportStaffMembers();\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { ISupportStaffRepo } from '../db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../support_staff_tokens';\n\nexport interface IRemoveSupportStaffFeature {\n execute(userId: string): Promise<void>;\n}\n\n@injectable()\nexport class RemoveSupportStaffFeat implements IRemoveSupportStaffFeature {\n constructor(\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n ) {}\n\n async execute(userId: string): Promise<void> {\n const removed = await this.supportStaffRepo.remove(userId);\n if (!removed) {\n throw new Error('Support staff member not found');\n }\n }\n}\n","import { container } from 'tsyringe';\nimport {\n ISupportStaffRepo,\n SupportStaffRepo,\n} from './db/support_staff_repo';\nimport { AddSupportStaffFeat, IAddSupportStaffFeature } from './features/add_support_staff_feat';\nimport { IListSupportStaffFeature, ListSupportStaffFeat } from './features/list_support_staff_feat';\nimport { IRemoveSupportStaffFeature, RemoveSupportStaffFeat } from './features/remove_support_staff_feat';\nimport { SUPPORT_STAFF_TOKENS } from './support_staff_tokens';\n\nexport function registerSupportStaffDependencies() {\n container.registerSingleton<ISupportStaffRepo>(\n SUPPORT_STAFF_TOKENS.ISupportStaffRepo,\n SupportStaffRepo,\n );\n container.registerSingleton<IListSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IListSupportStaffFeature,\n ListSupportStaffFeat,\n );\n container.registerSingleton<IAddSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature,\n AddSupportStaffFeat,\n );\n container.registerSingleton<IRemoveSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature,\n RemoveSupportStaffFeat,\n );\n}\n","import {\n applyFilter,\n archiveConditions,\n createFilterBuilder,\n deriveColumnMap,\n searchOrCondition,\n type FieldRegistry,\n} from '../../../lib';\nimport {\n OPERATORS,\n StaffSupportTicketFiltersDto,\n SupportTicketDevLifecycle,\n SupportTicketStatus,\n} from '@dragonmastery/dragoncore-shared';\nimport { eq, inArray, isNull, ne, notInArray, or, type SQL } from 'drizzle-orm';\nimport { support_ticket_table } from './support_ticket_table';\n\n// Field registry - single source of truth for field metadata\n// Note: status and is_locked are computed fields, not included in registry\nexport const supportTicketFields: FieldRegistry = {\n display_id: {\n column: support_ticket_table.display_id,\n type: 'number',\n filterable: false,\n searchable: true,\n sortable: true,\n },\n type: {\n column: support_ticket_table.type,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n priority: {\n column: support_ticket_table.priority,\n type: 'number',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n approval_status: {\n column: support_ticket_table.approval_status,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n dev_lifecycle: {\n column: support_ticket_table.dev_lifecycle,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n created_by: {\n column: support_ticket_table.created_by,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n title: {\n column: support_ticket_table.title,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n credit_value: {\n column: support_ticket_table.credit_value,\n type: 'number',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n delivered_value: {\n column: support_ticket_table.delivered_value,\n type: 'number',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n created_at: {\n column: support_ticket_table.created_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n updated_at: {\n column: support_ticket_table.updated_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n start_at: {\n column: support_ticket_table.start_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n target_at: {\n column: support_ticket_table.target_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n completed_at: {\n column: support_ticket_table.completed_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n // Search field mappings for computed fields\n approvalStatus: {\n column: support_ticket_table.approval_status,\n type: 'string',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n devLifecycle: {\n column: support_ticket_table.dev_lifecycle,\n type: 'string',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n status: {\n column: support_ticket_table.approval_status, // Computed field, search approval_status\n type: 'string',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n isLocked: {\n column: support_ticket_table.locked_approval_at, // Computed field\n type: 'date',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n creditValue: {\n column: support_ticket_table.credit_value,\n type: 'number',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n deliveredValue: {\n column: support_ticket_table.delivered_value,\n type: 'number',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n startAt: {\n column: support_ticket_table.start_at,\n type: 'date',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n targetAt: {\n column: support_ticket_table.target_at,\n type: 'date',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n completedAt: {\n column: support_ticket_table.completed_at,\n type: 'date',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n};\n\n// Derive columnMap for pagination from registry\nexport const supportTicketColumnMap = deriveColumnMap(supportTicketFields);\n\n// Create field filter builder with archived_at, status, is_locked, dev_lifecycle processed separately\nconst buildFieldFilters = createFilterBuilder({\n fieldRegistry: supportTicketFields,\n processedSeparately: ['archived_at', 'status', 'is_locked', 'dev_lifecycle'],\n});\n\n/**\n * Map support ticket status to approval status and dev lifecycle values\n */\nfunction mapSupportTicketStatusToApprovalStatus(statuses: SupportTicketStatus[]) {\n const approvalStatusValues = statuses.map((status) => {\n switch (status) {\n case 'PENDING':\n return 'PENDING';\n case 'CANCELLED':\n return 'REJECTED';\n case 'FOLLOWUP':\n return undefined;\n case 'IN_PROGRESS':\n return undefined;\n case 'VERIFICATION':\n return undefined;\n case 'COMPLETED':\n return undefined;\n default:\n throw new Error(`Invalid support ticket status: ${status}`);\n }\n });\n\n const devLifecycleValues = statuses.map((status) => {\n switch (status) {\n case 'PENDING':\n return undefined;\n case 'CANCELLED':\n return undefined;\n case 'FOLLOWUP':\n return undefined;\n case 'IN_PROGRESS':\n return undefined;\n case 'VERIFICATION':\n return 'VERIFICATION';\n case 'COMPLETED':\n return 'DEPLOYED';\n default:\n throw new Error(`Invalid support ticket status: ${status}`);\n }\n });\n\n return {\n approvalStatusValues,\n devLifecycleValues,\n };\n}\n\n/**\n * Build support ticket query conditions from filters\n */\nexport function buildSupportTicketQuery(\n filters?: StaffSupportTicketFiltersDto,\n): { conditions: SQL[]; skipQuery: boolean } {\n // Build each condition piece\n const softDelete = isNull(support_ticket_table.deleted_at);\n const archive = archiveConditions(filters, support_ticket_table.archived_at);\n const fields = buildFieldFilters(filters).conditions;\n const search = searchOrCondition(\n filters?.search?.query,\n supportTicketFields,\n filters?.search?.searchableFields,\n );\n\n const conditions: SQL[] = [\n softDelete,\n ...archive,\n ...fields,\n ...(search ? [search] : []),\n ];\n\n // Handle computed status field (maps to approval_status + dev_lifecycle)\n if (filters?.status) {\n switch (filters.status.operator) {\n case OPERATORS.IS_NOT_ONE_OF:\n const {\n approvalStatusValues: approvalStatusValuesNotIn,\n devLifecycleValues: devLifecycleValuesNotIn,\n } = mapSupportTicketStatusToApprovalStatus(filters.status.values || []);\n if (approvalStatusValuesNotIn.some((value) => value !== undefined)) {\n conditions.push(\n notInArray(\n support_ticket_table.approval_status,\n approvalStatusValuesNotIn.filter((value) => value !== undefined),\n ),\n );\n }\n\n if (devLifecycleValuesNotIn.some((value) => value !== undefined)) {\n conditions.push(\n or(\n notInArray(\n support_ticket_table.dev_lifecycle,\n devLifecycleValuesNotIn.filter((value) => value !== undefined),\n ),\n isNull(support_ticket_table.dev_lifecycle),\n )!,\n );\n }\n break;\n case OPERATORS.IS_ONE_OF:\n const { approvalStatusValues, devLifecycleValues } =\n mapSupportTicketStatusToApprovalStatus(filters.status.values || []);\n if (approvalStatusValues.some((value) => value !== undefined)) {\n conditions.push(\n inArray(\n support_ticket_table.approval_status,\n approvalStatusValues.filter((value) => value !== undefined),\n ),\n );\n }\n if (devLifecycleValues.some((value) => value !== undefined)) {\n conditions.push(\n inArray(\n support_ticket_table.dev_lifecycle,\n devLifecycleValues.filter((value) => value !== undefined),\n ),\n );\n }\n break;\n }\n\n // Handle single value status filter\n if (filters.status.value) {\n const statusValue =\n typeof filters.status === 'object' ? filters.status.value : filters.status;\n\n if (statusValue === 'PENDING') {\n conditions.push(eq(support_ticket_table.approval_status, 'PENDING'));\n } else if (statusValue === 'CANCELLED') {\n conditions.push(eq(support_ticket_table.approval_status, 'REJECTED'));\n } else if (statusValue === 'FOLLOWUP') {\n conditions.push(eq(support_ticket_table.approval_status, 'APPROVED'));\n conditions.push(\n or(\n eq(support_ticket_table.dev_lifecycle, 'BACKLOG'),\n eq(support_ticket_table.dev_lifecycle, 'PLANNING'),\n )!,\n );\n } else if (statusValue === 'IN_PROGRESS') {\n conditions.push(eq(support_ticket_table.approval_status, 'APPROVED'));\n conditions.push(\n or(\n eq(support_ticket_table.dev_lifecycle, 'DEVELOPMENT'),\n eq(support_ticket_table.dev_lifecycle, 'CODE_REVIEW'),\n eq(support_ticket_table.dev_lifecycle, 'TESTING'),\n eq(support_ticket_table.dev_lifecycle, 'STAGING'),\n eq(support_ticket_table.dev_lifecycle, 'PO_APPROVAL'),\n )!,\n );\n } else if (statusValue === 'VERIFICATION') {\n conditions.push(eq(support_ticket_table.dev_lifecycle, 'VERIFICATION'));\n } else if (statusValue === 'COMPLETED') {\n conditions.push(eq(support_ticket_table.dev_lifecycle, 'DEPLOYED'));\n }\n }\n }\n\n // Handle dev_lifecycle: PENDING is a display fallback for null (never stored)\n if (filters?.dev_lifecycle) {\n const dl = filters.dev_lifecycle as { operator?: string; value?: string; values?: string[] };\n const col = support_ticket_table.dev_lifecycle;\n const isPending = (v: string) => v === 'PENDING';\n\n if (dl.value !== undefined && isPending(dl.value) && dl.operator === OPERATORS.EQUALS) {\n conditions.push(isNull(col));\n } else if (dl.values?.some(isPending) && dl.operator === OPERATORS.IS_ONE_OF) {\n const others = dl.values.filter((v) => !isPending(v)) as SupportTicketDevLifecycle[];\n conditions.push(others.length ? or(isNull(col), inArray(col, others))! : isNull(col));\n } else {\n // All other cases: use standard filter\n const cond = applyFilter(col, dl, 'string');\n if (cond) conditions.push(cond);\n }\n }\n\n // Handle isLocked (derived from approval_status)\n if (filters?.is_locked) {\n const lockedValue =\n typeof filters.is_locked === 'object' ? filters.is_locked.value : filters.is_locked;\n if (lockedValue) {\n // Locked = not pending\n conditions.push(ne(support_ticket_table.approval_status, 'PENDING'));\n } else {\n // Unlocked = pending\n conditions.push(eq(support_ticket_table.approval_status, 'PENDING'));\n }\n }\n\n return { conditions, skipQuery: false };\n}\n","import {\n StaffSupportTicketFiltersDto,\n SupportTicketEnrichmentData,\n} from '@dragonmastery/dragoncore-shared';\nimport { and, eq, inArray, isNull, ne, or, sql, SQL } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { type DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n PaginationUtils,\n type PaginatedResult,\n type PaginationConfig,\n} from '../../../lib/pagination';\nimport {\n buildSupportTicketQuery,\n supportTicketColumnMap,\n} from './support_ticket_query_config';\nimport {\n InsertSupportTicketEntity,\n ReadSupportTicketEntity,\n support_ticket_table,\n UpdateSupportTicketEntity,\n} from './support_ticket_table';\n\n@injectable()\nexport class SupportTicketRepo implements ISupportTicketRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n private get paginationConfig(): PaginationConfig<ReadSupportTicketEntity> {\n return {\n table: support_ticket_table,\n columnMap: supportTicketColumnMap,\n router: this.router,\n };\n }\n\n /**\n * Create a new feature request\n */\n async create(support_ticket: InsertSupportTicketEntity): Promise<ReadSupportTicketEntity> {\n const id = await this.router.generateId(RecordConst.SUPPORT_TICKET);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(support_ticket_table)\n .values([\n {\n id,\n ...support_ticket,\n },\n ])\n .returning(),\n );\n\n return result;\n }\n\n /**\n * Update an existing feature request\n */\n async update(support_ticket: UpdateSupportTicketEntity): Promise<ReadSupportTicketEntity> {\n const now = new Date().toISOString();\n const [result] = await this.router.queryById(support_ticket.id, (db) =>\n db\n .update(support_ticket_table)\n .set({\n ...support_ticket,\n updated_at: now,\n })\n .where(eq(support_ticket_table.id, support_ticket.id))\n .returning(),\n );\n\n return result;\n }\n\n /**\n * Bumps the updated_at timestamp without reading the record first\n */\n async bumpUpdatedAt(id: string, updated_by: string): Promise<void> {\n const now = new Date().toISOString();\n await this.router.queryById(id, async (db) => {\n await db\n .update(support_ticket_table)\n .set({\n updated_at: now,\n updated_by,\n })\n .where(eq(support_ticket_table.id, id));\n return [];\n });\n }\n\n /**\n * Get a feature request by ID\n */\n async read(id: string): Promise<ReadSupportTicketEntity | null> {\n const [result] = await this.router.queryById(id, (db) =>\n db\n .select()\n .from(support_ticket_table)\n .where(and(eq(support_ticket_table.id, id), isNull(support_ticket_table.deleted_at)))\n .limit(1),\n );\n\n if (!result) {\n return null;\n }\n\n return result;\n }\n\n /**\n * Get multiple support tickets by IDs in a single query\n */\n async read_many(ids: string[]): Promise<ReadSupportTicketEntity[]> {\n if (ids.length === 0) {\n return [];\n }\n\n // Use the first ID to route the query (they should all be on the same shard)\n const result = await this.router.queryById(ids[0], (db) =>\n db\n .select()\n .from(support_ticket_table)\n .where(\n and(inArray(support_ticket_table.id, ids), isNull(support_ticket_table.deleted_at)),\n ),\n );\n return result;\n }\n\n /**\n * Read multiple support tickets with only the fields needed for followup enrichment\n * More efficient than read_many when you only need a subset of fields\n */\n async read_many_minimal(ids: string[]): Promise<SupportTicketEnrichmentData[]> {\n if (ids.length === 0) {\n return [];\n }\n\n // Use the first ID to route the query, select only needed fields\n const result = await this.router.queryById(ids[0], (db) =>\n db\n .select({\n id: support_ticket_table.id,\n display_id: support_ticket_table.display_id,\n title: support_ticket_table.title,\n })\n .from(support_ticket_table)\n .where(\n and(inArray(support_ticket_table.id, ids), isNull(support_ticket_table.deleted_at)),\n ),\n );\n return result;\n }\n\n private buildFilters(filters?: StaffSupportTicketFiltersDto): {\n conditions: SQL[];\n } {\n return buildSupportTicketQuery(filters);\n }\n\n /**\n * Get all support_ticket with breadcrumb pagination\n */\n async read_all(\n filters?: StaffSupportTicketFiltersDto,\n ): Promise<PaginatedResult<ReadSupportTicketEntity>> {\n const { conditions } = this.buildFilters(filters);\n\n return PaginationUtils.findAllPaginated(this.paginationConfig, filters || {}, conditions);\n }\n\n /**\n * Get distinct requestor (created_by) user IDs for active support tickets.\n * Active = not archived (archived_at IS NULL).\n * @param excludeInternal - when true, exclude internal tickets (for customer view)\n * Queries all shards.\n */\n async read_distinct_requestors_for_active_tickets(options?: {\n excludeInternal?: boolean;\n }): Promise<string[]> {\n const conditions = [\n isNull(support_ticket_table.deleted_at),\n isNull(support_ticket_table.archived_at),\n ];\n if (options?.excludeInternal) {\n conditions.push(ne(support_ticket_table.approval_status, 'INTERNAL'));\n }\n const rows = await this.router.queryAll((db) =>\n db\n .selectDistinct({ created_by: support_ticket_table.created_by })\n .from(support_ticket_table)\n .where(and(...conditions)),\n );\n const ids = [...new Set(rows.map((r) => r.created_by).filter(Boolean))];\n return ids;\n }\n\n /**\n * Find support tickets with invalid user IDs (e.g. emails instead of 32-char universal IDs).\n * Queries all shards.\n */\n async findWithInvalidUserIds(): Promise<ReadSupportTicketEntity[]> {\n const invalidLengthCondition = or(\n sql`LENGTH(${support_ticket_table.created_by}) != 32`,\n sql`LENGTH(${support_ticket_table.updated_by}) != 32`,\n sql`(${support_ticket_table.assigned_to} IS NOT NULL AND LENGTH(${support_ticket_table.assigned_to}) != 32)`,\n sql`(${support_ticket_table.archived_by} IS NOT NULL AND LENGTH(${support_ticket_table.archived_by}) != 32)`,\n sql`(${support_ticket_table.deleted_by} IS NOT NULL AND LENGTH(${support_ticket_table.deleted_by}) != 32)`,\n );\n return this.router.queryAll((db) =>\n db\n .select()\n .from(support_ticket_table)\n .where(\n and(isNull(support_ticket_table.deleted_at), invalidLengthCondition as SQL<unknown>),\n ),\n );\n }\n\n /**\n * Soft delete a support ticket by ID\n */\n async soft_delete(id: string, deleted_by: string): Promise<ReadSupportTicketEntity> {\n const deleted_at = new Date().toISOString();\n const result = await this.router.queryById(id, (db) =>\n db\n .update(support_ticket_table)\n .set({\n deleted_at,\n deleted_by,\n })\n .where(and(eq(support_ticket_table.id, id), isNull(support_ticket_table.deleted_at)))\n .returning(),\n );\n\n if (!result || result.length === 0) {\n throw new Error('Support ticket not found or already deleted');\n }\n\n return result[0];\n }\n}\n\nexport type SupportTicketEntityPage = PaginatedResult<ReadSupportTicketEntity>;\n\n/**\n * Interface for support_ticket repository\n */\nexport interface ISupportTicketRepo {\n create(support_ticket: InsertSupportTicketEntity): Promise<ReadSupportTicketEntity>;\n update(support_ticket: UpdateSupportTicketEntity): Promise<ReadSupportTicketEntity>;\n read(id: string): Promise<ReadSupportTicketEntity | null>;\n read_many(ids: string[]): Promise<ReadSupportTicketEntity[]>;\n read_many_minimal(ids: string[]): Promise<SupportTicketEnrichmentData[]>;\n read_all(filters?: StaffSupportTicketFiltersDto): Promise<SupportTicketEntityPage>;\n read_distinct_requestors_for_active_tickets(options?: {\n excludeInternal?: boolean;\n }): Promise<string[]>;\n findWithInvalidUserIds(): Promise<ReadSupportTicketEntity[]>;\n soft_delete(id: string, deleted_by: string): Promise<ReadSupportTicketEntity>;\n bumpUpdatedAt(id: string, updated_by: string): Promise<void>;\n}\n","export const USER_PROFILE_TOKENS = {\n IUserProfilesRepo: Symbol('IUserProfilesRepo'),\n\n IReadUserProfilesByUser: Symbol('IReadUserProfilesByUser'),\n IReadUserProfile: Symbol('IReadUserProfile'),\n ICreateUserProfile: Symbol('ICreateUserProfile'),\n IUpdateUserProfile: Symbol('IUpdateUserProfile'),\n};\n","import type { SupportTicketEventType } from '@dragonmastery/dragoncore-shared';\nimport {\n DEFAULT_USER_TYPE,\n RecordConst,\n supportTicketNumberToPriority,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../../../di_tokens';\nimport { IEmailService } from '../../../lib/email_service';\nimport { Logger } from '../../../utils/logger';\nimport { IRecordSubscriberRepo } from '../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../record_subscriber/record_subscriber_tokens';\nimport { IUserRepo, USER_TOKENS } from '../../user/user_interfaces';\nimport { IUserProfileRepo } from '../../user_profile/db/user_profile_repo';\nimport { USER_PROFILE_TOKENS } from '../../user_profile/user_profile_interfaces';\nimport type { ReadSupportTicketEntity } from '../db/support_ticket_table';\n\nconst STAFF_USER_TYPES = ['staff', 'super_admin'] as const;\n\n/** Escape HTML for safe inclusion in email body. */\nfunction escapeHtmlForEmail(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/\\n/g, '<br>');\n}\n\nexport interface SupportTicketNotificationContext {\n changedField?: string;\n oldValue?: unknown;\n newValue?: unknown;\n noteBody?: string;\n actorUserId?: string;\n}\n\nexport interface ISupportTicketNotificationService {\n notifyFollowers(\n supportTicketId: string,\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n context?: SupportTicketNotificationContext,\n ): Promise<void>;\n\n /** Notify the assigned triage person (e.g. on ticket creation or reassignment). */\n notifyAssignee(\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n context?: SupportTicketNotificationContext,\n ): Promise<void>;\n}\n\n@injectable()\nexport class SupportTicketNotificationService implements ISupportTicketNotificationService {\n constructor(\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(USER_TOKENS.IUsersRepo) private userRepo: IUserRepo,\n @inject(USER_PROFILE_TOKENS.IUserProfilesRepo) private userProfileRepo: IUserProfileRepo,\n @inject(TOKENS.IEmailService) private emailService: IEmailService,\n @inject(TOKENS.ENV) private env: Env,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async notifyAssignee(\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n context?: SupportTicketNotificationContext,\n ): Promise<void> {\n try {\n const assigneeId = ticket.assigned_to;\n if (!assigneeId) return;\n if (context?.actorUserId && assigneeId === context.actorUserId) return;\n\n const [email] = await this.getNotificationEmailsForFollowers(\n [assigneeId],\n context?.actorUserId,\n );\n if (!email) return;\n\n const ticketUrl = `${this.env.WEBSITE_URL}/staff/support/${ticket.id}`;\n const { subject, bodyText, bodyHtml } = this.buildAssigneeEmailContent(\n ticket,\n eventType,\n ticketUrl,\n context,\n );\n\n await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);\n this.logger.info(`Sent support ticket ${eventType} notification to assignee`);\n } catch (error) {\n this.logger.error('Failed to send support ticket assignee notification', {\n error,\n supportTicketId: ticket.id,\n eventType,\n });\n }\n }\n\n async notifyFollowers(\n supportTicketId: string,\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n context?: SupportTicketNotificationContext,\n ): Promise<void> {\n try {\n await this.ensureAssigneeIsSubscriber(ticket);\n\n const followers = await this.subscriberRepo.readByRecordId(\n RecordConst.SUPPORT_TICKET,\n supportTicketId,\n );\n if (followers.length === 0) return;\n\n // For NEW_INTERNAL_NOTE, only notify staff followers\n const filteredFollowers =\n eventType === 'NEW_INTERNAL_NOTE'\n ? await this.filterStaffFollowersOnly(followers)\n : followers;\n\n if (filteredFollowers.length === 0) return;\n\n // Filter by subscribed_events (null = all events)\n const subscribed = filteredFollowers.filter((f) => {\n const events = f.subscribed_events;\n if (!events || events.length === 0) return true;\n return events.includes(eventType);\n });\n\n if (subscribed.length === 0) return;\n\n // Exclude assignee when they get the dedicated assignee email (TICKET_CREATED, TICKET_ASSIGNED)\n const assigneeExcluded =\n eventType === 'TICKET_CREATED' || eventType === 'TICKET_ASSIGNED'\n ? subscribed.filter((f) => f.user_id !== ticket.assigned_to)\n : subscribed;\n\n const recipients = await this.getNotificationRecipientsForFollowers(\n assigneeExcluded.map((f) => f.user_id),\n context?.actorUserId,\n );\n\n if (recipients.length === 0) return;\n\n const baseUrl = this.env.WEBSITE_URL;\n for (const { email, userType } of recipients) {\n const isStaff = STAFF_USER_TYPES.includes(\n userType as (typeof STAFF_USER_TYPES)[number],\n );\n const ticketUrl = isStaff\n ? `${baseUrl}/staff/support/${ticket.id}`\n : `${baseUrl}/support/${ticket.id}`;\n const { subject, bodyText, bodyHtml } = this.buildEmailContent(\n ticket,\n eventType,\n ticketUrl,\n context,\n );\n await this.emailService.sendBulkEmail([email], subject, bodyText, bodyHtml);\n }\n this.logger.info(\n `Sent support ticket ${eventType} notification to ${recipients.length} followers`,\n );\n } catch (error) {\n this.logger.error('Failed to send support ticket follower notifications', {\n error,\n supportTicketId,\n eventType,\n });\n // Don't throw - notifications are best-effort\n }\n }\n\n /**\n * Ensures the assigned staff member is a subscriber so they receive the same\n * notifications as other followers (notes, approvals, etc.). Idempotent.\n */\n private async ensureAssigneeIsSubscriber(\n ticket: ReadSupportTicketEntity,\n ): Promise<void> {\n const assigneeId = ticket.assigned_to;\n if (!assigneeId) return;\n\n try {\n const now = new Date().toISOString();\n await this.subscriberRepo.ensureSubscriber({\n record_type: RecordConst.SUPPORT_TICKET,\n record_id: ticket.id,\n user_id: assigneeId,\n subscribed_events: null,\n created_at: now,\n created_by: assigneeId,\n });\n } catch (error) {\n this.logger.error('Failed to ensure assignee is subscriber', {\n error,\n supportTicketId: ticket.id,\n assigneeId,\n });\n }\n }\n\n private async filterStaffFollowersOnly<\n T extends { user_id: string; subscribed_events?: string[] | null },\n >(followers: T[]): Promise<T[]> {\n if (followers.length === 0) return [];\n\n const userIds = followers.map((f) => f.user_id);\n const users = await this.userRepo.read_users_by_ids(userIds);\n const staffUserIds = new Set(\n users\n .filter((u) =>\n STAFF_USER_TYPES.includes(u.user_type as (typeof STAFF_USER_TYPES)[number]),\n )\n .map((u) => u.id),\n );\n return followers.filter((f) => staffUserIds.has(f.user_id));\n }\n\n private async getNotificationEmailsForFollowers(\n userIds: string[],\n excludeUserId?: string,\n ): Promise<string[]> {\n const recipients = await this.getNotificationRecipientsForFollowers(\n userIds,\n excludeUserId,\n );\n return recipients.map((r) => r.email);\n }\n\n private async getNotificationRecipientsForFollowers(\n userIds: string[],\n excludeUserId?: string,\n ): Promise<Array<{ email: string; userType: string }>> {\n const filteredIds = excludeUserId ? userIds.filter((id) => id !== excludeUserId) : userIds;\n if (filteredIds.length === 0) return [];\n\n const [users, profiles] = await Promise.all([\n this.userRepo.read_users_by_ids(filteredIds),\n this.userProfileRepo.read_user_profiles_by_user_ids(filteredIds),\n ]);\n\n const profileByUserId = new Map(profiles.map((p) => [p.user_id, p] as const));\n const seen = new Set<string>();\n const recipients: Array<{ email: string; userType: string }> = [];\n\n for (const user of users) {\n const profile = profileByUserId.get(user.id);\n const email = profile?.notification_email ?? user.email;\n if (!email || seen.has(email)) continue;\n\n seen.add(email);\n recipients.push({ email, userType: user.user_type ?? DEFAULT_USER_TYPE });\n }\n\n return recipients;\n }\n\n private buildEmailContent(\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n ticketUrl: string,\n context?: SupportTicketNotificationContext,\n ): { subject: string; bodyText: string; bodyHtml: string } {\n const eventLabel = eventType.replace(/_/g, ' ').toLowerCase();\n const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;\n\n const details = [\n `Ticket #: ${ticket.display_id || ticket.id}`,\n `Title: ${ticket.title}`,\n `Priority: ${supportTicketNumberToPriority(ticket.priority)}`,\n ];\n if (context?.noteBody) {\n details.push(`\\nNote: ${context.noteBody}`);\n }\n if (eventType === 'TICKET_CREATED' && ticket.description?.trim()) {\n details.push(`\\nDescription:\\n${ticket.description}`);\n }\n\n const bodyText = `A support ticket you're following has been updated.\\n\\n${details.join('\\n')}\\n\\nView ticket: ${ticketUrl}`;\n\n const bodyHtml = `\n<h2>Support Ticket Update</h2>\n<p>A support ticket you're following has been updated (${eventLabel}).</p>\n<ul>\n <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>\n <li><strong>Title:</strong> ${ticket.title}</li>\n <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>\n</ul>\n${eventType === 'TICKET_CREATED' && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ''}\n${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ''}\n<p><a href=\"${ticketUrl}\">View Ticket</a></p>\n `.trim();\n\n return { subject, bodyText, bodyHtml };\n }\n\n private buildAssigneeEmailContent(\n ticket: ReadSupportTicketEntity,\n eventType: SupportTicketEventType,\n ticketUrl: string,\n context?: SupportTicketNotificationContext,\n ): { subject: string; bodyText: string; bodyHtml: string } {\n const eventLabel = eventType.replace(/_/g, ' ').toLowerCase();\n const subject = `Support Ticket #${ticket.display_id || ticket.id}: ${eventLabel}`;\n\n const isAssigned = eventType === 'TICKET_ASSIGNED';\n const intro = isAssigned\n ? 'You have been assigned to a support ticket.'\n : 'A new support ticket has been assigned to you.';\n\n const details = [\n `Ticket #: ${ticket.display_id || ticket.id}`,\n `Title: ${ticket.title}`,\n `Priority: ${supportTicketNumberToPriority(ticket.priority)}`,\n ];\n if (context?.noteBody) {\n details.push(`\\nNote: ${context.noteBody}`);\n }\n if (!isAssigned && ticket.description?.trim()) {\n details.push(`\\nDescription:\\n${ticket.description}`);\n }\n\n const bodyText = `${intro}\\n\\n${details.join('\\n')}\\n\\nView ticket: ${ticketUrl}`;\n\n const bodyHtml = `\n<h2>Support Ticket ${isAssigned ? 'Assigned' : 'Created'}</h2>\n<p>${intro}</p>\n<ul>\n <li><strong>Ticket #:</strong> ${ticket.display_id || ticket.id}</li>\n <li><strong>Title:</strong> ${ticket.title}</li>\n <li><strong>Priority:</strong> ${supportTicketNumberToPriority(ticket.priority)}</li>\n</ul>\n${!isAssigned && ticket.description?.trim() ? `<p><strong>Description:</strong></p><p>${escapeHtmlForEmail(ticket.description)}</p>` : ''}\n${context?.noteBody ? `<p><strong>Note:</strong></p><p>${context.noteBody}</p>` : ''}\n<p><a href=\"${ticketUrl}\">View Ticket</a></p>\n `.trim();\n\n return { subject, bodyText, bodyHtml };\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { UserSessionDetails } from '../../../db/user_app_session';\nimport { AppSettingKey } from '../../../db/schemas/app_setting/app_settings_table';\nimport { IAppSettingsRepo } from '../../../db/schemas/app_setting/app_settings_repo';\nimport { TOKENS } from '../../../di_tokens';\nimport { Logger } from '../../../utils/logger';\nimport { ISupportStaffRepo } from '../../support_staff/db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../../support_staff/support_staff_tokens';\n\nexport interface ISupportTicketTriageService {\n /** Pick next assignee via round-robin among triage users; persist and return user_id. */\n getNextAssignee(updatedBy: UserSessionDetails): Promise<string | null>;\n}\n\n@injectable()\nexport class SupportTicketTriageService implements ISupportTicketTriageService {\n constructor(\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async getNextAssignee(updatedBy: UserSessionDetails): Promise<string | null> {\n try {\n const triageUsers = await this.supportStaffRepo.readTriageUsers();\n if (triageUsers.length === 0) {\n this.logger.debug('No triage users found; skipping assignee');\n return null;\n }\n\n const ids = triageUsers.map((u) => u.id);\n const lastSetting = await this.appSettingsRepo.readSetting(\n AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE,\n );\n const lastId = lastSetting?.value as string | undefined;\n\n const lastIndex = lastId ? ids.indexOf(lastId) : -1;\n const nextIndex = lastIndex < ids.length - 1 ? lastIndex + 1 : 0;\n const nextUserId = ids[nextIndex]!;\n\n await this.appSettingsRepo.updateSetting(\n AppSettingKey.LAST_SUPPORT_TICKET_ASSIGNEE,\n nextUserId,\n updatedBy,\n );\n\n return nextUserId;\n } catch (error) {\n this.logger.error('Failed to get next triage assignee', { error });\n return null;\n }\n }\n}\n","import {\n CustomerSupportTicketReadDto,\n StaffSupportTicketReadDto,\n SupportTicketDevLifecycle,\n supportTicketNumberToPriority,\n SupportTicketStatus,\n} from '@dragonmastery/dragoncore-shared';\nimport { container } from 'tsyringe';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { IDisplayIdPrefixService } from '../../../lib/display_id_prefix_service';\nimport { DISPLAY_ID_PREFIX_TOKENS } from '../../../lib/display_id_prefix_tokens';\nimport { ReadSupportTicketEntity } from '../db/support_ticket_table';\n\n/**\n * Computes customer-facing status from approval_status and dev_lifecycle\n */\nfunction computeStatus(\n approvalStatus: string,\n devLifecycle: SupportTicketDevLifecycle | null | undefined,\n): SupportTicketStatus {\n // Rejected support_ticket shows as CANCELLED\n if (approvalStatus === 'REJECTED') {\n return 'CANCELLED';\n }\n\n // Pending approval shows as PENDING\n if (approvalStatus === 'PENDING') {\n return 'PENDING';\n }\n\n // Approved but no lifecycle yet (shouldn't happen, but default to FOLLOWUP)\n if (!devLifecycle) {\n return 'FOLLOWUP';\n }\n\n // Map dev_lifecycle to customer-facing status\n switch (devLifecycle) {\n case 'BACKLOG':\n case 'PLANNING':\n return 'FOLLOWUP';\n case 'DEVELOPMENT':\n case 'CODE_REVIEW':\n case 'TESTING':\n case 'STAGING':\n case 'PO_APPROVAL':\n return 'IN_PROGRESS';\n case 'VERIFICATION':\n return 'VERIFICATION';\n case 'DEPLOYED':\n return 'COMPLETED';\n default:\n return 'FOLLOWUP';\n }\n}\n\n/**\n * Gets the display ID prefix service, with fallback to default implementation\n */\nfunction getPrefixService(): IDisplayIdPrefixService {\n try {\n return container.resolve<IDisplayIdPrefixService>(\n DISPLAY_ID_PREFIX_TOKENS.IDisplayIdPrefixService,\n );\n } catch {\n // Fallback if service not registered (shouldn't happen in normal usage)\n // Return a minimal implementation\n return {\n getPrefix: () => null,\n };\n }\n}\n\n/**\n * Maps support_ticket entity to customer read DTO (excludes staff-only fields)\n * Note: This function is designed to work with Array.map(), so it only takes the entity parameter\n */\nexport function toCustomerSupportTicketReadDto(\n entity: ReadSupportTicketEntity,\n): CustomerSupportTicketReadDto {\n const prefixSvc = getPrefixService();\n\n // set dates to null if they are invalid\n const startAt = dateIsValid(entity.start_at) ? entity.start_at : null;\n const targetAt = dateIsValid(entity.target_at) ? entity.target_at : null;\n const completedAt = dateIsValid(entity.completed_at) ? entity.completed_at : null;\n const lockedApprovalAt = dateIsValid(entity.locked_approval_at)\n ? entity.locked_approval_at\n : null;\n const createdAt = dateIsValid(entity.created_at) ? entity.created_at : null;\n const updatedAt = dateIsValid(entity.updated_at) ? entity.updated_at : null;\n\n return {\n id: entity.id,\n display_id: entity.display_id,\n display_id_prefix: prefixSvc.getPrefix(RecordConst.SUPPORT_TICKET),\n title: entity.title,\n description: entity.description,\n type: entity.type,\n priority: supportTicketNumberToPriority(entity.priority),\n status: computeStatus(entity.approval_status, entity.dev_lifecycle),\n is_locked: !!entity.approval_status && entity.approval_status !== 'PENDING',\n created_by_display_name: null, // Populated by enrichment from created_by lookup\n // Note: customerNotes are stored in record_versions table\n credit_value: entity.credit_value,\n // deliveredValue is staff-only, not included in customer view\n start_at: startAt,\n target_at: targetAt,\n completed_at: completedAt,\n locked_approval_at: lockedApprovalAt,\n created_at: createdAt,\n created_by: entity.created_by,\n updated_at: updatedAt,\n updated_by: entity.updated_by,\n archived_at: entity.archived_at ?? null,\n };\n}\n\n/**\n * Maps support_ticket entity to staff read DTO (includes ALL fields)\n * Note: This function is designed to work with Array.map(), so it only takes the entity parameter\n */\nexport function toStaffSupportTicketReadDto(\n entity: ReadSupportTicketEntity,\n): StaffSupportTicketReadDto {\n const prefixSvc = getPrefixService();\n\n // set dates to null if they are invalid\n const startAt = dateIsValid(entity.start_at) ? entity.start_at : null;\n const targetAt = dateIsValid(entity.target_at) ? entity.target_at : null;\n const completedAt = dateIsValid(entity.completed_at) ? entity.completed_at : null;\n const lockedApprovalAt = dateIsValid(entity.locked_approval_at)\n ? entity.locked_approval_at\n : null;\n const createdAt = dateIsValid(entity.created_at) ? entity.created_at : null;\n const updatedAt = dateIsValid(entity.updated_at) ? entity.updated_at : null;\n\n // Business rule: Can only delete tickets that haven't been charged to customer\n // APPROVED tickets have been charged, so they cannot be deleted\n const canDelete = entity.approval_status !== 'APPROVED';\n\n return {\n id: entity.id,\n display_id: entity.display_id,\n display_id_prefix: prefixSvc.getPrefix(RecordConst.SUPPORT_TICKET),\n title: entity.title,\n description: entity.description,\n type: entity.type,\n priority: supportTicketNumberToPriority(entity.priority),\n status: computeStatus(entity.approval_status, entity.dev_lifecycle),\n approval_status: entity.approval_status,\n is_locked: !!entity.approval_status && entity.approval_status !== 'PENDING',\n can_delete: canDelete,\n // Note: isInternalOnly removed - check approvalStatus === 'INTERNAL' instead\n created_by_display_name: null, // Populated by enrichment from created_by lookup\n assigned_to: entity.assigned_to ?? null,\n // Note: customerNotes and internalNotes are stored in record_versions table\n // set to PENDING if not set\n dev_lifecycle: entity.dev_lifecycle || 'PENDING',\n credit_value: entity.credit_value,\n delivered_value: entity.delivered_value,\n start_at: startAt,\n target_at: targetAt,\n completed_at: completedAt,\n locked_approval_at: lockedApprovalAt,\n created_at: createdAt,\n created_by: entity.created_by,\n updated_at: updatedAt,\n updated_by: entity.updated_by,\n archived_at: entity.archived_at ?? null,\n archived_by: entity.archived_by ?? null,\n };\n}\n\nfunction dateIsValid(date: string | null): boolean {\n if (!date) {\n return false;\n }\n const parsedDate = new Date(date);\n return !isNaN(parsedDate.getDate());\n}\n","import {\n CustomerSupportTicketCreateDto,\n CustomerSupportTicketReadDto,\n SUPPORT_TICKET_PRIORITY_TO_NUMBER,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { IAppSettingsRepo } from '../../../../db/schemas/app_setting/app_settings_repo';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { TOKENS } from '../../../../di_tokens';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { Logger } from '../../../../utils/logger';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport {\n InsertSupportTicketEntity,\n ReadSupportTicketEntity,\n} from '../../db/support_ticket_table';\nimport { toCustomerSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport type { ISupportTicketNotificationService } from '../../services/support_ticket_notification_service';\nimport type { ISupportTicketTriageService } from '../../services/support_ticket_triage_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for the create feature request feature\n */\nexport interface ICreateSupportTicketFeature {\n execute(input: CustomerSupportTicketCreateDto): Promise<CustomerSupportTicketReadDto>;\n}\n\n/**\n * Feature to create a new feature request and forward it to Jira\n */\n@injectable()\nexport class CreateSupportTicketFeat implements ICreateSupportTicketFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService: ISupportTicketNotificationService,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)\n private triageService: ISupportTicketTriageService,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n /**\n * Validate input and create a new feature request\n */\n async execute(input: CustomerSupportTicketCreateDto): Promise<CustomerSupportTicketReadDto> {\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n const frEntity: InsertSupportTicketEntity = {\n ...input,\n priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,\n description: input.description ?? '', // Default to empty string if not provided\n created_at: now,\n created_by: userId,\n updated_at: now, // Same as created_at initially\n updated_by: userId,\n };\n\n // Create support_ticket in database\n const support_ticketCreated = await this.support_ticketRepo.create(frEntity);\n\n const nextSequentialId = await this.appSettingsRepo.getNextSequentialId(\n RecordConst.SUPPORT_TICKET,\n this.session.user.userId,\n );\n support_ticketCreated.display_id = nextSequentialId;\n\n // Assign via round-robin triage and update\n const assigneeId = await this.triageService.getNextAssignee(this.session.user);\n if (assigneeId) {\n support_ticketCreated.assigned_to = assigneeId;\n await this.support_ticketRepo.update(support_ticketCreated);\n } else {\n await this.support_ticketRepo.update(support_ticketCreated);\n }\n\n // Track creation in record versions\n await this.create_record_version.execute({\n record_id: support_ticketCreated.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET,\n record: support_ticketCreated, // Full new record\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // Auto-add requester as follower (if they have a user account)\n await this.addRequesterAsFollower(support_ticketCreated);\n\n // Notify assignee (triage person)\n await this.notificationService.notifyAssignee(support_ticketCreated, 'TICKET_CREATED', {\n actorUserId: userId,\n });\n\n // Notify followers (e.g. requester if added)\n await this.notificationService.notifyFollowers(\n support_ticketCreated.id,\n support_ticketCreated,\n 'TICKET_CREATED',\n { actorUserId: userId },\n );\n\n const dto = toCustomerSupportTicketReadDto(support_ticketCreated);\n return {\n ...dto,\n created_by_display_name: this.session.user.email,\n };\n }\n\n /**\n * Auto-add requester (created_by) as follower\n */\n private async addRequesterAsFollower(ticket: ReadSupportTicketEntity): Promise<void> {\n try {\n const now = new Date().toISOString();\n await this.subscriberRepo.ensureSubscriber({\n record_type: RecordConst.SUPPORT_TICKET,\n record_id: ticket.id,\n user_id: ticket.created_by,\n subscribed_events: null,\n created_at: now,\n created_by: ticket.created_by,\n });\n } catch (error) {\n this.logger.error('Failed to add requester as follower', { error });\n }\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nexport interface ICustomerToggleSubscriptionFeature {\n execute(supportTicketId: string): Promise<{ subscribed: boolean }>;\n}\n\n@injectable()\nexport class CustomerToggleSubscriptionFeat implements ICustomerToggleSubscriptionFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n ) {}\n\n async execute(supportTicketId: string): Promise<{ subscribed: boolean }> {\n const ticket = await this.supportTicketRepo.read(supportTicketId);\n if (!ticket) {\n throw new BusinessError('Support ticket not found');\n }\n if (ticket.approval_status === 'INTERNAL') {\n throw new BusinessError('Support ticket not found');\n }\n\n const subscription = await this.subscriberRepo.readByRecordAndUser(\n RecordConst.SUPPORT_TICKET,\n supportTicketId,\n this.session.user.userId,\n );\n\n if (subscription) {\n const deleted = await this.subscriberRepo.delete(subscription.id);\n if (!deleted) {\n throw new BusinessError('Failed to unsubscribe');\n }\n return { subscribed: false };\n }\n\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n await this.subscriberRepo.ensureSubscriber({\n record_type: RecordConst.SUPPORT_TICKET,\n record_id: supportTicketId,\n user_id: userId,\n subscribed_events: null,\n created_at: now,\n created_by: userId,\n });\n return { subscribed: true };\n }\n}\n","import type { CustomerSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\n\n/**\n * Enriches a CustomerSupportTicketReadDto with requester info from created_by lookup.\n * Requester name/email are derived from created_by (user display = email).\n */\nexport function enrichCustomerTicket(\n dto: CustomerSupportTicketReadDto,\n displayMap: Map<string, string>,\n): CustomerSupportTicketReadDto {\n const createdByDisplay = dto.created_by\n ? (displayMap.get(dto.created_by) ?? dto.created_by)\n : null;\n return {\n ...dto,\n created_by_display_name: createdByDisplay ?? dto.created_by ?? '—',\n };\n}\n\nexport function collectUserIdsFromCustomerTicket(\n ticket: CustomerSupportTicketReadDto,\n): string[] {\n return [ticket.created_by].filter((id): id is string => Boolean(id));\n}\n","import type { RecordSubscriberReadDto } from '@dragonmastery/dragoncore-shared';\nimport { CustomerSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { RecordConst } from '../../../../db/dbTypes';\nimport { IAppSettingsRepo } from '../../../../db/schemas/app_setting/app_settings_repo';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { TOKENS } from '../../../../di_tokens';\nimport { Logger } from '../../../../utils/logger';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ReadRecordSubscriberEntity } from '../../../record_subscriber/db/record_subscriber_table';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { toCustomerSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport {\n enrichCustomerTicket,\n collectUserIdsFromCustomerTicket,\n} from './enrich_customer_ticket';\n\nfunction toRecordSubscriberReadDto(e: ReadRecordSubscriberEntity): RecordSubscriberReadDto {\n return {\n id: e.id,\n record_type: e.record_type,\n record_id: e.record_id,\n user_id: e.user_id,\n subscribed_events: e.subscribed_events ?? null,\n created_at: e.created_at,\n created_by: e.created_by,\n };\n}\n\nexport interface IGetSupportTicketCustomerFeature {\n execute(id: string): Promise<CustomerSupportTicketReadDto>;\n}\n\n/**\n * Get single support_ticket for customer - throws if internal-only\n * Includes my_subscription for the current user when fetching a single ticket\n */\n@injectable()\nexport class GetSupportTicketCustomerFeature implements IGetSupportTicketCustomerFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(id: string): Promise<CustomerSupportTicketReadDto> {\n this.logger.debug('GetSupportTicketCustomerFeature.execute', { id });\n let support_ticket = await this.support_ticketRepo.read(id);\n\n if (!support_ticket) {\n throw new Error('SupportTicket not found');\n }\n\n // Customers can't see internal support_ticket\n if (support_ticket.approval_status === 'INTERNAL') {\n throw new Error('SupportTicket not found');\n }\n\n // Check if display_id is null and assign one if needed\n if (!support_ticket.display_id) {\n const nextSequentialId = await this.appSettingsRepo.getNextSequentialId(\n RecordConst.SUPPORT_TICKET,\n this.session.user.userId,\n );\n\n // Update the support ticket with the new display_id\n support_ticket = await this.support_ticketRepo.update({\n ...support_ticket,\n display_id: nextSequentialId,\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n });\n }\n\n const base = toCustomerSupportTicketReadDto(support_ticket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromCustomerTicket(base),\n );\n const enriched = enrichCustomerTicket(base, displayMap);\n const subscription = await this.subscriberRepo.readByRecordAndUser(\n RecordConst.SUPPORT_TICKET,\n id,\n this.session.user.userId,\n );\n return {\n ...enriched,\n my_subscription: subscription ? toRecordSubscriberReadDto(subscription) : null,\n };\n }\n}\n","import {\n CustomerSupportTicketFiltersDto,\n CustomerSupportTicketPageDto,\n OPERATORS,\n StaffSupportTicketFiltersDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { toCustomerSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport {\n enrichCustomerTicket,\n collectUserIdsFromCustomerTicket,\n} from './enrich_customer_ticket';\n\nexport interface IGetSupportTicketsCustomerFeature {\n execute(filters: CustomerSupportTicketFiltersDto): Promise<CustomerSupportTicketPageDto>;\n}\n\n/**\n * Get support_tickets for customers - only returns non-internal support_ticket\n * Returns paginated results with breadcrumb navigation\n */\n@injectable()\nexport class GetSupportTicketsCustomerFeature implements IGetSupportTicketsCustomerFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(\n filters: CustomerSupportTicketFiltersDto,\n ): Promise<CustomerSupportTicketPageDto> {\n // Force isInternalOnly to false - customers can never see internal tasks\n // Explicitly set staff-only fields to undefined for security\n const customerFilters: StaffSupportTicketFiltersDto = {\n ...filters,\n approval_status: {\n operator: OPERATORS.NOT_EQUALS,\n value: 'INTERNAL',\n },\n };\n\n const result = await this.support_ticketRepo.read_all(customerFilters);\n const dtos = result.items.map(toCustomerSupportTicketReadDto);\n const userIds = [...new Set(dtos.flatMap(collectUserIdsFromCustomerTicket))];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n\n return {\n items: dtos.map((dto) => enrichCustomerTicket(dto, displayMap)),\n pageInfo: result.pageInfo,\n };\n }\n}\n","import {\n CustomerSupportTicketReadDto,\n CustomerSupportTicketUpdateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { UpdateSupportTicketEntity } from '../../db/support_ticket_table';\nimport { toCustomerSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport {\n enrichCustomerTicket,\n collectUserIdsFromCustomerTicket,\n} from './enrich_customer_ticket';\n\n/**\n * Interface for customer support_ticket update\n */\nexport interface IUpdateSupportTicketCustomerFeature {\n execute(input: CustomerSupportTicketUpdateDto): Promise<CustomerSupportTicketReadDto>;\n}\n\n/**\n * Feature to update support_ticket (customers only - PENDING support_ticket only)\n */\n@injectable()\nexport class UpdateSupportTicketCustomerFeat implements IUpdateSupportTicketCustomerFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n /**\n * Update support_ticket - customers can only edit PENDING (not locked) support_ticket\n */\n async execute(input: CustomerSupportTicketUpdateDto): Promise<CustomerSupportTicketReadDto> {\n const support_ticket = await this.support_ticketRepo.read(input.id);\n if (!support_ticket) {\n throw new BusinessError('SupportTicket not found');\n }\n\n // Customer restrictions: can only edit PENDING support_ticket\n // (INTERNAL support_ticket would also fail this check, so no separate check needed)\n if (support_ticket.approval_status !== 'PENDING') {\n throw new BusinessError('SupportTicket is locked - cannot be edited');\n }\n if (support_ticket.archived_at) {\n throw new BusinessError('Cannot edit an archived support ticket.');\n }\n\n const frEntity: UpdateSupportTicketEntity = {\n ...support_ticket,\n title: input.title,\n description: input.description ?? '',\n type: input.type,\n priority: input.priority,\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n };\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(support_ticket, frEntity);\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: frEntity.id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Update support_ticket in database\n const support_ticketUpdated = await this.support_ticketRepo.update(frEntity);\n\n const base = toCustomerSupportTicketReadDto(support_ticketUpdated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromCustomerTicket(base),\n );\n return enrichCustomerTicket(base, displayMap);\n }\n}\n","import type {\n RecordSubscriberReadDto,\n SupportTicketSubscriberCreateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RecordConst } from '../../../../db/dbTypes';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ReadRecordSubscriberEntity } from '../../../record_subscriber/db/record_subscriber_table';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nfunction toRecordSubscriberReadDto(e: ReadRecordSubscriberEntity): RecordSubscriberReadDto {\n return {\n id: e.id,\n record_type: e.record_type,\n record_id: e.record_id,\n user_id: e.user_id,\n subscribed_events: e.subscribed_events ?? null,\n created_at: e.created_at,\n created_by: e.created_by,\n };\n}\n\nexport interface IAddSupportTicketSubscriberFeature {\n execute(input: SupportTicketSubscriberCreateDto): Promise<RecordSubscriberReadDto>;\n}\n\n@injectable()\nexport class AddSupportTicketSubscriberFeat implements IAddSupportTicketSubscriberFeature {\n constructor(\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: SupportTicketSubscriberCreateDto): Promise<RecordSubscriberReadDto> {\n const ticket = await this.supportTicketRepo.read(input.support_ticket_id);\n if (!ticket) {\n throw new BusinessError('Support ticket not found');\n }\n\n const now = new Date().toISOString();\n const entity = await this.subscriberRepo.ensureSubscriber({\n record_type: RecordConst.SUPPORT_TICKET,\n record_id: input.support_ticket_id,\n user_id: input.user_id,\n subscribed_events: input.subscribed_events ?? null,\n created_at: now,\n created_by: this.session.user.userId,\n });\n const dto = toRecordSubscriberReadDto(entity);\n const userIds = [dto.user_id, dto.created_by].filter(Boolean);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n return {\n ...dto,\n user_id_display_name: dto.user_id ? (displayMap.get(dto.user_id) ?? dto.user_id) : null,\n created_by_display_name: dto.created_by ? (displayMap.get(dto.created_by) ?? dto.created_by) : null,\n };\n }\n}\n","import type { StaffSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\n\n/**\n * Enriches a StaffSupportTicketReadDto with display names from a lookup map.\n * Used by all staff features that return a ticket.\n */\nexport function enrichStaffTicket(\n dto: StaffSupportTicketReadDto,\n displayMap: Map<string, string>,\n): StaffSupportTicketReadDto {\n const createdByDisplay = dto.created_by\n ? (displayMap.get(dto.created_by) ?? dto.created_by)\n : null;\n return {\n ...dto,\n assigned_to_display_name: dto.assigned_to\n ? (displayMap.get(dto.assigned_to) ?? dto.assigned_to)\n : null,\n created_by_display_name: createdByDisplay ?? dto.created_by ?? '—',\n updated_by_display_name: dto.updated_by\n ? (displayMap.get(dto.updated_by) ?? dto.updated_by)\n : null,\n };\n}\n\n/**\n * Collects user IDs from a staff ticket for display name lookup.\n */\nexport function collectUserIdsFromStaffTicket(\n ticket: StaffSupportTicketReadDto,\n): string[] {\n return [ticket.assigned_to, ticket.created_by, ticket.updated_by].filter(\n (id): id is string => Boolean(id),\n );\n}\n","import {\n ApproveSupportTicketDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport {\n CREDIT_SERVICE_TOKEN,\n ICreditService,\n} from '../../../../slices/customer/services/credit_service';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport type { ISupportTicketNotificationService } from '../../services/support_ticket_notification_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n// Re-export for use in registration\nexport { CREDIT_SERVICE_TOKEN };\nexport type { ICreditService };\n\n/**\n * Interface for approve support_ticket feature\n */\nexport interface IApproveSupportTicketFeature {\n execute(args: ApproveSupportTicketDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class ApproveSupportTicketFeat implements IApproveSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private repo: ISupportTicketRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService: ISupportTicketNotificationService,\n @inject(CREDIT_SERVICE_TOKEN) private creditService: ICreditService,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Approves a support_ticket item, deducts credits, and locks it.\n *\n * Workflow:\n * 1. Validates support_ticket is PENDING with credit_value set\n * 2. Deducts credits from customer account\n * 3. Sets approval_status to APPROVED\n * 4. Sets dev_lifecycle to BACKLOG\n * 5. Records locked_approval_at timestamp\n *\n * @param args The ID of the support_ticket to approve.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: ApproveSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n const { id, credit_value: creditValueFromArgs } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new BusinessError(`Support Ticket with ID ${id} not found`);\n }\n\n if (support_ticket.approval_status !== 'PENDING') {\n throw new BusinessError('Support Ticket must be in PENDING status to be approved.');\n }\n\n const creditAmount = parseFloat(creditValueFromArgs);\n if (isNaN(creditAmount) || creditAmount < 0) {\n throw new BusinessError('Invalid credit value amount.');\n }\n\n // Use credit_value from args; update ticket if it differs from current\n const ticketToUpdate = {\n ...support_ticket,\n credit_value: creditValueFromArgs,\n };\n const needsCreditUpdate =\n support_ticket.credit_value?.trim() !== creditValueFromArgs;\n if (needsCreditUpdate) {\n await this.repo.update(ticketToUpdate);\n }\n\n // Deduct credits from the global account\n const creditsDeducted = await this.creditService.deductCredits(\n creditValueFromArgs,\n support_ticket.id,\n );\n\n if (!creditsDeducted) {\n throw new BusinessError(\n 'Insufficient credits. Customer must purchase more credits or wait for monthly reset.',\n );\n }\n\n // Track changes for record versioning\n const lockedAt = new Date().toISOString();\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n approval_status: 'APPROVED',\n dev_lifecycle: 'BACKLOG',\n locked_approval_at: lockedAt,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Update the support_ticket item to approved status\n // Note: is_locked is automatically derived from approval_status !== 'PENDING'\n // Note: status is computed from approval_status + dev_lifecycle in the API layer\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n approval_status: 'APPROVED',\n dev_lifecycle: 'BACKLOG',\n locked_approval_at: lockedAt,\n });\n\n if (!updatedSupportTicket) {\n throw new BusinessError('Failed to approve support_ticket.');\n }\n\n await this.notificationService.notifyFollowers(\n id,\n updatedSupportTicket,\n 'APPROVAL_STATUS_CHANGED',\n { actorUserId: this.session.user.userId },\n );\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { StaffSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for archive support ticket feature\n */\nexport interface IArchiveSupportTicketFeature {\n execute(id: string): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Archives or unarchives a support ticket (toggle).\n * - Archive: completed tickets (dev_lifecycle = DEPLOYED) or rejected tickets can be archived; locks comments\n * - Unarchive: clears archived state; comments are unlocked\n */\n@injectable()\nexport class ArchiveSupportTicketFeat implements IArchiveSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo) private repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(id: string): Promise<StaffSupportTicketReadDto> {\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`Support ticket with id ${id} not found`);\n }\n\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n if (support_ticket.archived_at) {\n // Unarchive\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET,\n record: {\n archived_at: null,\n archived_by: null,\n },\n old_record: support_ticket,\n auth_uid: userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n const updated = await this.repo.update({\n ...support_ticket,\n archived_at: null,\n archived_by: null,\n });\n\n if (!updated) {\n throw new Error('Failed to unarchive support ticket.');\n }\n\n const dto = toStaffSupportTicketReadDto(updated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n\n // Archive: completed tickets (DEPLOYED) or rejected tickets can be archived\n const canArchive =\n support_ticket.dev_lifecycle === 'DEPLOYED' ||\n support_ticket.approval_status === 'REJECTED';\n if (!canArchive) {\n throw new Error(\n 'Only completed or rejected tickets can be archived. Complete or reject the ticket first.',\n );\n }\n\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET,\n record: {\n archived_at: now,\n archived_by: userId,\n },\n old_record: support_ticket,\n auth_uid: userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n const updated = await this.repo.update({\n ...support_ticket,\n archived_at: now,\n archived_by: userId,\n });\n\n if (!updated) {\n throw new Error('Failed to archive support ticket.');\n }\n\n const dto = toStaffSupportTicketReadDto(updated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import {\n CancelInternalTaskDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for cancel internal task feature\n */\nexport interface ICancelInternalTaskFeature {\n execute(args: CancelInternalTaskDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class CancelInternalTaskFeat implements ICancelInternalTaskFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Cancels an internal task by setting dev_lifecycle to CANCELLED.\n *\n * Workflow:\n * 1. Validates support_ticket has approval_status = INTERNAL\n * 2. Sets dev_lifecycle to CANCELLED\n * 3. Sets completed_at timestamp (terminal state)\n *\n * Note: Only internal tasks can be cancelled this way.\n * Customer tickets use the reject workflow which changes approval_status to REJECTED.\n *\n * @param args The ID of the internal task to cancel.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: CancelInternalTaskDto): Promise<StaffSupportTicketReadDto> {\n const { id } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`SupportTicket with ID ${id} not found`);\n }\n\n if (support_ticket.approval_status !== 'INTERNAL') {\n throw new Error(\n 'Only INTERNAL tasks can be cancelled. Use reject workflow for customer tickets.',\n );\n }\n\n if (support_ticket.dev_lifecycle === 'CANCELLED') {\n throw new Error('Internal task is already cancelled.');\n }\n\n const completedAt = new Date().toISOString();\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n dev_lifecycle: 'CANCELLED',\n completed_at: completedAt,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n dev_lifecycle: 'CANCELLED',\n completed_at: completedAt,\n });\n\n if (!updatedSupportTicket) {\n throw new Error('Failed to cancel internal task.');\n }\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import {\n CompleteSupportTicketDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for complete support_ticket feature\n */\nexport interface ICompleteSupportTicketFeature {\n execute(args: CompleteSupportTicketDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class CompleteSupportTicketFeat implements ICompleteSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo) private repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Completes a support_ticket item by setting it to DEPLOYED with delivered value.\n *\n * Workflow:\n * 1. Validates support_ticket is APPROVED\n * 2. Sets dev_lifecycle to DEPLOYED\n * 3. Sets delivered_value from input\n * 4. Auto-sets completed_at timestamp\n *\n * @param args The ID and delivered value of the support_ticket to complete.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: CompleteSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n const { id, delivered_value } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`SupportTicket with ID ${id} not found`);\n }\n\n // Only APPROVED or INTERNAL support_ticket can be completed\n if (!['APPROVED', 'INTERNAL'].includes(support_ticket.approval_status)) {\n throw new Error('SupportTicket must be APPROVED or INTERNAL to be completed.');\n }\n\n // Validate delivered value amount\n const deliveredAmount = parseFloat(delivered_value);\n if (isNaN(deliveredAmount) || deliveredAmount < 0) {\n throw new Error('Invalid delivered value amount.');\n }\n\n // Track changes for record versioning\n const completedAt = new Date().toISOString();\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n dev_lifecycle: 'DEPLOYED',\n delivered_value: delivered_value,\n completed_at: completedAt,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Update the support_ticket to DEPLOYED status\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n dev_lifecycle: 'DEPLOYED',\n delivered_value: delivered_value,\n completed_at: completedAt,\n });\n\n if (!updatedSupportTicket) {\n throw new Error('Failed to complete support_ticket.');\n }\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { StaffSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { UpdateSupportTicketEntity } from '../../db/support_ticket_table';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for convert to customer support_ticket feature\n */\nexport interface IConvertToCustomerFeature {\n execute(support_ticket_id: string): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Feature to convert internal task (INTERNAL) back to customer support_ticket (PENDING)\n *\n * Rules:\n * - Only INTERNAL support_ticket can be converted\n * - Resets to PENDING status (requires approval workflow)\n * - Clears dev_lifecycle (back to planning stage)\n * - Credits remain null until estimate is set\n */\n@injectable()\nexport class ConvertToCustomerFeat implements IConvertToCustomerFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(support_ticket_id: string): Promise<StaffSupportTicketReadDto> {\n // Get existing support_ticket\n const existing = await this.support_ticketRepo.read(support_ticket_id);\n if (!existing) {\n throw new Error('SupportTicket not found');\n }\n\n // Validation: Only INTERNAL can be converted to customer support_ticket\n if (existing.approval_status !== 'INTERNAL') {\n throw new Error(\n `Cannot convert ${existing.approval_status} support_ticket to customer support_ticket. Only INTERNAL tasks can be converted.`,\n );\n }\n\n // Update to PENDING status and clear dev lifecycle\n const updateEntity: UpdateSupportTicketEntity = {\n ...existing,\n approval_status: 'PENDING',\n dev_lifecycle: null, // Clear dev lifecycle - back to planning\n // Keep credit_value as null - staff will need to set estimate\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n };\n\n const updated = await this.support_ticketRepo.update(updateEntity);\n\n // Create audit trail\n await this.create_record_version.execute({\n record_id: support_ticket_id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: updated,\n old_record: existing,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n const dto = toStaffSupportTicketReadDto(updated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { StaffSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { UpdateSupportTicketEntity } from '../../db/support_ticket_table';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for convert to internal feature\n */\nexport interface IConvertToInternalFeature {\n execute(support_ticket_id: string): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Feature to convert customer support_ticket (PENDING) to internal task (INTERNAL)\n *\n * Rules:\n * - Only PENDING support_ticket can be converted\n * - Automatically clears credit_value (internal tasks can't have credits)\n * - Creates audit trail\n */\n@injectable()\nexport class ConvertToInternalFeat implements IConvertToInternalFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(support_ticket_id: string): Promise<StaffSupportTicketReadDto> {\n // Get existing support_ticket\n const existing = await this.support_ticketRepo.read(support_ticket_id);\n if (!existing) {\n throw new Error('SupportTicket not found');\n }\n\n // Validation: Only PENDING can be converted to INTERNAL\n if (existing.approval_status !== 'PENDING') {\n throw new Error(\n `Cannot convert ${existing.approval_status} support_ticket to internal. Only PENDING support_ticket can be converted.`,\n );\n }\n\n // Update to INTERNAL status and clear credits\n const updateEntity: UpdateSupportTicketEntity = {\n ...existing,\n approval_status: 'INTERNAL',\n credit_value: null, // Internal tasks cannot have credits\n dev_lifecycle: 'BACKLOG', // Start in backlog for internal tasks\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n };\n\n const updated = await this.support_ticketRepo.update(updateEntity);\n\n // Create audit trail\n await this.create_record_version.execute({\n record_id: support_ticket_id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: updated,\n old_record: existing,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n const dto = toStaffSupportTicketReadDto(updated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import {\n StaffSupportTicketCreateDto,\n StaffSupportTicketReadDto,\n SUPPORT_TICKET_PRIORITY_TO_NUMBER,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { IAppSettingsRepo } from '../../../../db/schemas/app_setting/app_settings_repo';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { TOKENS } from '../../../../di_tokens';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { formatCreditValue } from '../../../../utils/creditValueFormatter';\nimport { Logger } from '../../../../utils/logger';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ISupportStaffRepo } from '../../../support_staff/db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../../../support_staff/support_staff_tokens';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport {\n InsertSupportTicketEntity,\n ReadSupportTicketEntity,\n} from '../../db/support_ticket_table';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport type { ISupportTicketNotificationService } from '../../services/support_ticket_notification_service';\nimport type { ISupportTicketTriageService } from '../../services/support_ticket_triage_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport { collectUserIdsFromStaffTicket, enrichStaffTicket } from './enrich_staff_ticket';\n\n/**\n * Interface for staff create support_ticket feature\n */\nexport interface ICreateSupportTicketAdminFeature {\n execute(input: StaffSupportTicketCreateDto): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Feature to create support_ticket with staff-only fields\n * Allows staff to create support_ticket with internal fields like creditValue, devLifecycle, etc.\n */\n@injectable()\nexport class CreateSupportTicketAdminFeat implements ICreateSupportTicketAdminFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService: ISupportTicketNotificationService,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketTriageService)\n private triageService: ISupportTicketTriageService,\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n /**\n * Create support_ticket with all staff fields\n */\n async execute(input: StaffSupportTicketCreateDto): Promise<StaffSupportTicketReadDto> {\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n const support_ticketEntity: InsertSupportTicketEntity = {\n title: input.title || '',\n description: input.description || '',\n type: input.type || 'FEATURE_REQUEST',\n priority: input.priority ?? SUPPORT_TICKET_PRIORITY_TO_NUMBER.MEDIUM,\n // Set approval_status: INTERNAL for internal tasks, PENDING for customer support_ticket\n approval_status: input.is_internal ? 'INTERNAL' : 'PENDING',\n dev_lifecycle: input.dev_lifecycle || null,\n // Internal tasks cannot have credits - automatically set to null\n credit_value: input.is_internal ? null : formatCreditValue(input.credit_value),\n delivered_value: input.delivered_value || null,\n start_at: input.start_at || null,\n target_at: input.target_at || null,\n completed_at: input.completed_at || null,\n locked_approval_at: null,\n created_at: now,\n created_by: userId,\n updated_at: now, // Same as created_at initially\n updated_by: userId,\n };\n\n // Create support_ticket in database\n const support_ticketCreated = await this.support_ticketRepo.create(support_ticketEntity);\n\n const nextSequentialId = await this.appSettingsRepo.getNextSequentialId(\n RecordConst.SUPPORT_TICKET,\n this.session.user.userId,\n );\n support_ticketCreated.display_id = nextSequentialId;\n\n // Assign: use staff-selected assignee when provided, otherwise round-robin triage\n let assigneeId: string | null = null;\n if (input.assigned_to?.trim()) {\n const triageUsers = await this.supportStaffRepo.readTriageUsers();\n const isValid = triageUsers.some((u) => u.id === input.assigned_to);\n if (!isValid) {\n throw new BusinessError('Selected assignee is not a support staff member');\n }\n assigneeId = input.assigned_to;\n } else {\n assigneeId = await this.triageService.getNextAssignee(this.session.user);\n }\n if (assigneeId) {\n support_ticketCreated.assigned_to = assigneeId;\n }\n await this.support_ticketRepo.update(support_ticketCreated);\n\n // Track creation in record versions\n await this.create_record_version.execute({\n record_id: support_ticketCreated.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.SUPPORT_TICKET,\n record: support_ticketCreated,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // For non-internal tickets: auto-add requester, notify assignee, notify followers\n if (!input.is_internal) {\n await this.addRequesterAsFollower(support_ticketCreated);\n await this.notificationService.notifyAssignee(support_ticketCreated, 'TICKET_CREATED', {\n actorUserId: userId,\n });\n await this.notificationService.notifyFollowers(\n support_ticketCreated.id,\n support_ticketCreated,\n 'TICKET_CREATED',\n { actorUserId: userId },\n );\n }\n\n const dto = toStaffSupportTicketReadDto(support_ticketCreated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n\n /**\n * Auto-add requester (created_by) as follower\n */\n private async addRequesterAsFollower(ticket: ReadSupportTicketEntity): Promise<void> {\n try {\n const now = new Date().toISOString();\n await this.subscriberRepo.ensureSubscriber({\n record_type: RecordConst.SUPPORT_TICKET,\n record_id: ticket.id,\n user_id: ticket.created_by,\n subscribed_events: null,\n created_at: now,\n created_by: ticket.created_by,\n });\n } catch (error) {\n this.logger.error('Failed to add requester as follower', { error });\n }\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for delete support ticket feature\n */\nexport interface IDeleteSupportTicketFeature {\n execute(id: string): Promise<boolean>;\n}\n\n@injectable()\nexport class DeleteSupportTicketFeat implements IDeleteSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo) private repo: ISupportTicketRepo,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n /**\n * Deletes a support ticket with business rules:\n * - APPROVED tickets (charged to customer): Cannot be deleted - throw error suggesting archive\n * - PENDING, REJECTED, INTERNAL tickets: Can be deleted\n * - Only staff can delete tickets\n */\n async execute(id: string): Promise<boolean> {\n // Get existing support ticket\n const existing = await this.repo.read(id);\n if (!existing) {\n throw new Error(`Support ticket with id ${id} not found`);\n }\n\n // Business rule: Cannot delete APPROVED tickets (charged to customer)\n if (existing.approval_status === 'APPROVED') {\n throw new Error(\n 'Cannot delete approved tickets that have been charged to customers. Use archive instead.',\n );\n }\n\n // Create record version for the delete operation\n const record_version: CreateRecordVersionDto = {\n record_id: id,\n operation: OperationConst.DELETE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: { deleted_at: new Date().toISOString(), deleted_by: this.session.user.userId },\n old_record: existing,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n };\n\n await this.create_record_version.execute(record_version);\n\n // Soft delete the support ticket\n await this.repo.soft_delete(id, this.session.user.userId);\n return true;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport type { IUserRepo } from '../../../user/user_interfaces';\nimport { USER_TOKENS } from '../../../user/user_interfaces';\nimport type { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport type {\n ReadSupportTicketEntity,\n UpdateSupportTicketEntity,\n} from '../../db/support_ticket_table';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nconst UNIVERSAL_ID_LENGTH = 32;\n\nexport interface FixSupportTicketUserIdsResult {\n ticketsScanned: number;\n ticketsFixed: number;\n ticketsSkipped: number; // no matching user found\n}\n\nexport interface IFixSupportTicketUserIdsFeature {\n execute(): Promise<FixSupportTicketUserIdsResult>;\n}\n\n/**\n * Admin feature: fix support_ticket rows where created_by/updated_by/assigned_to/archived_by/deleted_by\n * contain emails instead of user IDs (32-char universal IDs).\n * Matches by user.email and updates to user.id where possible.\n */\n@injectable()\nexport class FixSupportTicketUserIdsFeature implements IFixSupportTicketUserIdsFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUsersRepo)\n private userRepo: IUserRepo,\n ) {}\n\n async execute(): Promise<FixSupportTicketUserIdsResult> {\n const tickets = await this.supportTicketRepo.findWithInvalidUserIds();\n if (tickets.length === 0) {\n return { ticketsScanned: 0, ticketsFixed: 0, ticketsSkipped: 0 };\n }\n\n const emailToUserId = await this.buildEmailToUserIdMap(tickets);\n let ticketsFixed = 0;\n let ticketsSkipped = 0;\n\n for (const ticket of tickets) {\n const updates = this.buildUpdates(ticket, emailToUserId);\n if (Object.keys(updates).length === 0) {\n ticketsSkipped++;\n continue;\n }\n\n const updated: UpdateSupportTicketEntity = {\n ...ticket,\n ...updates,\n };\n await this.supportTicketRepo.update(updated);\n ticketsFixed++;\n }\n\n return {\n ticketsScanned: tickets.length,\n ticketsFixed,\n ticketsSkipped,\n };\n }\n\n private async buildEmailToUserIdMap(\n tickets: ReadSupportTicketEntity[],\n ): Promise<Map<string, string>> {\n const fieldsToFix = [\n 'created_by',\n 'updated_by',\n 'assigned_to',\n 'archived_by',\n 'deleted_by',\n ] as const;\n\n const emails = new Set<string>();\n for (const ticket of tickets) {\n for (const field of fieldsToFix) {\n const value = ticket[field];\n if (value != null && value.length !== UNIVERSAL_ID_LENGTH) {\n emails.add(value);\n }\n }\n }\n\n if (emails.size === 0) return new Map();\n\n const users = await this.userRepo.read_users_by_emails_case_insensitive([...emails]);\n const map = new Map<string, string>();\n for (const u of users) {\n map.set(u.email.toLowerCase(), u.id);\n }\n return map;\n }\n\n private buildUpdates(\n ticket: ReadSupportTicketEntity,\n emailToUserId: Map<string, string>,\n ): Partial<\n Pick<\n ReadSupportTicketEntity,\n 'created_by' | 'updated_by' | 'assigned_to' | 'archived_by' | 'deleted_by'\n >\n > {\n const updates: Partial<ReadSupportTicketEntity> = {};\n const fieldsToFix = [\n 'created_by',\n 'updated_by',\n 'assigned_to',\n 'archived_by',\n 'deleted_by',\n ] as const;\n\n for (const field of fieldsToFix) {\n const value = ticket[field];\n if (value == null || value.length === UNIVERSAL_ID_LENGTH) continue;\n\n const userId = emailToUserId.get(value.toLowerCase());\n if (userId) {\n updates[field] = userId;\n }\n }\n\n return updates;\n }\n}\n","import { StaffSupportTicketReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { RecordConst } from '../../../../db/dbTypes';\nimport { IAppSettingsRepo } from '../../../../db/schemas/app_setting/app_settings_repo';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { TOKENS } from '../../../../di_tokens';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nexport interface IGetSupportTicketAdminFeature {\n execute(id: string): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Get single support_ticket for admin/staff - returns ANY support_ticket including internal\n */\n@injectable()\nexport class GetSupportTicketAdminFeature implements IGetSupportTicketAdminFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(TOKENS.IAppSettingsRepo) private appSettingsRepo: IAppSettingsRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(id: string): Promise<StaffSupportTicketReadDto> {\n let support_ticket = await this.support_ticketRepo.read(id);\n\n if (!support_ticket) {\n throw new Error('SupportTicket not found');\n }\n\n // Check if display_id is null and assign one if needed\n if (!support_ticket.display_id) {\n const nextSequentialId = await this.appSettingsRepo.getNextSequentialId(\n RecordConst.SUPPORT_TICKET,\n this.session.user.userId,\n );\n\n // Update the support ticket with the new display_id\n support_ticket = await this.support_ticketRepo.update({\n ...support_ticket,\n display_id: nextSequentialId,\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n });\n }\n\n const dto = toStaffSupportTicketReadDto(support_ticket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { USER_TOKENS, type IUserRepo } from '../../../user/user_interfaces';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport type { ISupportTicketRepo } from '../../db/support_ticket_repo';\n\nexport interface IGetRequestorsForActiveSupportTicketsFeature {\n execute(options?: { excludeInternal?: boolean }): Promise<Array<{ id: string; email: string }>>;\n}\n\n/**\n * Get requestors (created_by) for active support tickets.\n * Active = not archived (archived_at IS NULL).\n * Used for the Requester filter dropdown in staff and customer support ticket lists.\n * @param excludeInternal - when true, exclude internal tickets (for customer view)\n */\n@injectable()\nexport class GetRequestorsForActiveSupportTicketsFeat implements IGetRequestorsForActiveSupportTicketsFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUsersRepo)\n private userRepo: IUserRepo,\n ) {}\n\n async execute(options?: { excludeInternal?: boolean }): Promise<Array<{ id: string; email: string }>> {\n const requestorIds = await this.supportTicketRepo.read_distinct_requestors_for_active_tickets(options);\n if (requestorIds.length === 0) {\n return [];\n }\n const users = await this.userRepo.read_users_by_ids(requestorIds);\n return users.map((u) => ({ id: u.id, email: u.email }));\n }\n}\n","import {\n StaffSupportTicketFiltersDto,\n StaffSupportTicketPageDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nexport interface IGetSupportTicketsAdminFeature {\n execute(filters: StaffSupportTicketFiltersDto): Promise<StaffSupportTicketPageDto>;\n}\n\n/**\n * Get support_tickets for admin/staff - returns ALL support_ticket including internal tasks\n * Returns paginated results with breadcrumb navigation\n */\n@injectable()\nexport class GetSupportTicketsAdminFeature implements IGetSupportTicketsAdminFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(filters: StaffSupportTicketFiltersDto): Promise<StaffSupportTicketPageDto> {\n // Staff can see everything - pass filters as-is\n const result = await this.support_ticketRepo.read_all(filters);\n const dtos = result.items.map(toStaffSupportTicketReadDto);\n const userIds = [\n ...new Set(dtos.flatMap(collectUserIdsFromStaffTicket)),\n ];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n const enrichedItems = dtos.map((t) => enrichStaffTicket(t, displayMap));\n return { items: enrichedItems, pageInfo: result.pageInfo };\n }\n}\n","import type { RecordSubscriberReadDto } from '@dragonmastery/dragoncore-shared';\nimport { RecordConst } from '../../../../db/dbTypes';\nimport { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\nimport { ReadRecordSubscriberEntity } from '../../../record_subscriber/db/record_subscriber_table';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\nfunction toRecordSubscriberReadDto(e: ReadRecordSubscriberEntity): RecordSubscriberReadDto {\n return {\n id: e.id,\n record_type: e.record_type,\n record_id: e.record_id,\n user_id: e.user_id,\n subscribed_events: e.subscribed_events ?? null,\n created_at: e.created_at,\n created_by: e.created_by,\n };\n}\n\nexport interface IListSupportTicketSubscribersFeature {\n execute(supportTicketId: string): Promise<RecordSubscriberReadDto[]>;\n}\n\n@injectable()\nexport class ListSupportTicketSubscribersFeat implements IListSupportTicketSubscribersFeature {\n constructor(\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private supportTicketRepo: ISupportTicketRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(supportTicketId: string): Promise<RecordSubscriberReadDto[]> {\n const ticket = await this.supportTicketRepo.read(supportTicketId);\n if (!ticket) {\n throw new BusinessError('Support ticket not found');\n }\n\n const subscribers = await this.subscriberRepo.readByRecordId(\n RecordConst.SUPPORT_TICKET,\n supportTicketId,\n );\n const dtos = subscribers.map(toRecordSubscriberReadDto);\n const userIds = [...new Set(dtos.flatMap((s) => [s.user_id, s.created_by].filter(Boolean)))];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n return dtos.map((s) => ({\n ...s,\n user_id_display_name: s.user_id ? (displayMap.get(s.user_id) ?? s.user_id) : null,\n created_by_display_name: s.created_by ? (displayMap.get(s.created_by) ?? s.created_by) : null,\n }));\n }\n}\n","import {\n ReactivateInternalTaskDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for reactivate internal task feature\n */\nexport interface IReactivateInternalTaskFeature {\n execute(args: ReactivateInternalTaskDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class ReactivateInternalTaskFeat implements IReactivateInternalTaskFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private repo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Reactivates a terminal internal task.\n *\n * Workflow:\n * 1. Validates support_ticket has approval_status = INTERNAL\n * 2. Validates support_ticket has dev_lifecycle = CANCELLED or DEPLOYED\n * 3. Sets dev_lifecycle based on previous state:\n * - DEPLOYED → PO_APPROVAL (task needs fixes/tweaks)\n * - CANCELLED → BACKLOG (task restarted from scratch)\n * 4. Clears completed_at timestamp (no longer in terminal state)\n *\n * Note: Only INTERNAL tasks in terminal states (CANCELLED or DEPLOYED) can be reactivated.\n * This allows staff to reopen completed or cancelled tasks for additional work.\n *\n * @param args The ID of the internal task to reactivate.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: ReactivateInternalTaskDto): Promise<StaffSupportTicketReadDto> {\n const { id } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`SupportTicket with ID ${id} not found`);\n }\n\n if (support_ticket.approval_status !== 'INTERNAL') {\n throw new Error('Only INTERNAL tasks can be reactivated.');\n }\n\n const terminalStates = ['CANCELLED', 'DEPLOYED'];\n if (\n !support_ticket.dev_lifecycle ||\n !terminalStates.includes(support_ticket.dev_lifecycle)\n ) {\n throw new Error('Only CANCELLED or DEPLOYED internal tasks can be reactivated.');\n }\n\n // Determine target state based on current state:\n // - DEPLOYED → PO_APPROVAL (task was complete, needs fixes/tweaks)\n // - CANCELLED → BACKLOG (task was stopped, start fresh)\n const targetLifecycle =\n support_ticket.dev_lifecycle === 'DEPLOYED' ? 'PO_APPROVAL' : 'BACKLOG';\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n dev_lifecycle: targetLifecycle,\n completed_at: null,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n dev_lifecycle: targetLifecycle,\n completed_at: null,\n });\n\n if (!updatedSupportTicket) {\n throw new Error('Failed to reactivate internal task.');\n }\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import {\n RejectSupportTicketDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport type { ISupportTicketNotificationService } from '../../services/support_ticket_notification_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for reject support_ticket feature\n */\nexport interface IRejectSupportTicketFeature {\n execute(args: RejectSupportTicketDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class RejectSupportTicketFeat implements IRejectSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private repo: ISupportTicketRepo,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService: ISupportTicketNotificationService,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Rejects a support_ticket item and locks it.\n *\n * Workflow:\n * 1. Validates support_ticket is PENDING\n * 2. Sets approval_status to REJECTED\n * 3. Records locked_approval_at timestamp\n *\n * @param args The ID of the support_ticket to reject.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: RejectSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n const { id } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`SupportTicket with ID ${id} not found`);\n }\n\n if (support_ticket.approval_status !== 'PENDING') {\n throw new Error('SupportTicket must be in PENDING status to be rejected.');\n }\n\n // Track changes for record versioning\n const lockedAt = new Date().toISOString();\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n approval_status: 'REJECTED',\n locked_approval_at: lockedAt,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Note: is_locked is automatically derived from approval_status !== 'PENDING'\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n approval_status: 'REJECTED',\n locked_approval_at: lockedAt,\n });\n\n if (!updatedSupportTicket) {\n throw new Error('Failed to reject support_ticket.');\n }\n\n await this.notificationService.notifyFollowers(\n id,\n updatedSupportTicket,\n 'APPROVAL_STATUS_CHANGED',\n { actorUserId: this.session.user.userId },\n );\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { IRecordSubscriberRepo } from '../../../record_subscriber/db/record_subscriber_repo';\nimport { RECORD_SUBSCRIBER_TOKENS } from '../../../record_subscriber/record_subscriber_tokens';\n\nexport interface IRemoveSupportTicketSubscriberFeature {\n execute(supportTicketId: string, subscriberId: string): Promise<void>;\n}\n\n@injectable()\nexport class RemoveSupportTicketSubscriberFeat implements IRemoveSupportTicketSubscriberFeature {\n constructor(\n @inject(RECORD_SUBSCRIBER_TOKENS.IRecordSubscriberRepo)\n private subscriberRepo: IRecordSubscriberRepo,\n ) {}\n\n async execute(supportTicketId: string, subscriberId: string): Promise<void> {\n const deleted = await this.subscriberRepo.delete(subscriberId);\n if (!deleted) {\n throw new BusinessError('Subscriber not found or already removed');\n }\n }\n}\n","import {\n RevertSupportTicketDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport {\n CREDIT_SERVICE_TOKEN,\n ICreditService,\n} from '../../../../slices/customer/services/credit_service';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\n\n/**\n * Interface for revert support_ticket feature\n */\nexport interface IRevertSupportTicketFeature {\n execute(args: RevertSupportTicketDto): Promise<StaffSupportTicketReadDto>;\n}\n\n@injectable()\nexport class RevertSupportTicketFeat implements IRevertSupportTicketFeature {\n constructor(\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo) private repo: ISupportTicketRepo,\n @inject(CREDIT_SERVICE_TOKEN) private creditService: ICreditService,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @injectSession() private session: UserAppSession,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Reverts a support_ticket item back to PENDING status and refunds credits.\n *\n * Workflow:\n * 1. Validates support_ticket is NOT already PENDING\n * 2. Refunds credits if APPROVED with credit_value\n * 3. Clears approval_status → PENDING\n * 4. Clears credit_value, dev_lifecycle, locked_approval_at\n *\n * @param args The ID of the support_ticket to revert.\n * @returns The updated support_ticket with full staff view.\n */\n async execute(args: RevertSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n const { id } = args;\n\n const support_ticket = await this.repo.read(id);\n if (!support_ticket) {\n throw new Error(`SupportTicket with ID ${id} not found`);\n }\n\n if (support_ticket.approval_status === 'PENDING') {\n throw new Error('SupportTicket is already in PENDING status.');\n }\n\n // Refund credits if they were deducted (only for APPROVED support_ticket)\n if (support_ticket.approval_status === 'APPROVED' && support_ticket.credit_value) {\n const creditAmount = parseFloat(support_ticket.credit_value);\n if (!isNaN(creditAmount) && creditAmount > 0) {\n await this.creditService.refundCredits(support_ticket.credit_value, support_ticket.id);\n }\n }\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(support_ticket, {\n ...support_ticket,\n approval_status: 'PENDING',\n credit_value: null,\n dev_lifecycle: null,\n locked_approval_at: null,\n });\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Revert the support_ticket back to PENDING status\n const updatedSupportTicket = await this.repo.update({\n ...support_ticket,\n approval_status: 'PENDING',\n credit_value: null,\n dev_lifecycle: null,\n locked_approval_at: null,\n });\n\n if (!updatedSupportTicket) {\n throw new Error('Failed to revert support_ticket.');\n }\n\n const dto = toStaffSupportTicketReadDto(updatedSupportTicket);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import {\n StaffSupportTicketInputDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport { UpdateSupportTicketEntity } from '../../db/support_ticket_table';\n\n/**\n * Validate business rules for staff updates based on permissions matrix\n */\nexport const validateBusinessRules = (\n input: StaffSupportTicketInputDto,\n existing: UpdateSupportTicketEntity,\n): void => {\n // Rule: Cannot edit archived support tickets\n if (existing.archived_at) {\n throw new BusinessError('Cannot edit an archived support ticket.');\n }\n\n const currentApproval = existing.approval_status;\n const currentDevLifecycle = existing.dev_lifecycle;\n\n // Rule: INTERNAL status is set at creation, cannot be changed via update\n // (INTERNAL support_ticket are created with approval_status='INTERNAL' instead of using isInternalOnly flag)\n\n // Note: approvalStatus is not in update schema - must use workflow mutations\n // (approve/reject/revert) to change approval status\n\n // Rule: Cannot edit devLifecycle unless APPROVED or INTERNAL\n if (input.dev_lifecycle !== undefined && input.dev_lifecycle !== currentDevLifecycle) {\n const allowedStatuses: string[] = ['APPROVED', 'INTERNAL'];\n if (!currentApproval || !allowedStatuses.includes(currentApproval)) {\n throw new BusinessError(\n 'Can only set dev lifecycle when support_ticket is APPROVED or INTERNAL',\n );\n }\n }\n\n // Rule: Cannot edit creditValue unless PENDING\n // INTERNAL support_ticket should never have credits\n if (input.credit_value !== undefined && input.credit_value !== existing.credit_value) {\n if (currentApproval === 'INTERNAL') {\n throw new BusinessError('Internal support_ticket cannot have credit values');\n }\n if (currentApproval !== 'PENDING') {\n throw new BusinessError('Can only edit credit value when support_ticket is PENDING');\n }\n }\n\n // Rule: Cannot edit deliveredValue unless devLifecycle is DEPLOYED\n if (\n input.delivered_value !== undefined &&\n input.delivered_value !== existing.delivered_value\n ) {\n if (currentDevLifecycle !== 'DEPLOYED') {\n throw new BusinessError('Can only set delivered value when dev lifecycle is DEPLOYED');\n }\n }\n\n // Rule: Cannot edit startAt/targetAt once devLifecycle >= DEVELOPMENT\n const lockedDevStages = [\n 'DEVELOPMENT',\n 'CODE_REVIEW',\n 'TESTING',\n 'STAGING',\n 'PO_APPROVAL',\n 'VERIFICATION',\n 'DEPLOYED',\n ];\n if (input.start_at !== undefined && input.start_at !== existing.start_at) {\n if (currentApproval === 'REJECTED') {\n throw new BusinessError('Cannot edit start date when support_ticket is REJECTED');\n }\n if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) {\n throw new BusinessError(\n 'Cannot edit start date once development has started (DEVELOPMENT or higher)',\n );\n }\n }\n\n if (input.target_at !== undefined && input.target_at !== existing.target_at) {\n if (currentApproval === 'REJECTED') {\n throw new BusinessError('Cannot edit target date when support_ticket is REJECTED');\n }\n if (currentDevLifecycle && lockedDevStages.includes(currentDevLifecycle)) {\n throw new BusinessError(\n 'Cannot edit target date once development has started (DEVELOPMENT or higher)',\n );\n }\n }\n\n // Rule: Cannot edit completed_at unless devLifecycle is DEPLOYED or CANCELLED (terminal states)\n if (input.completed_at !== undefined && input.completed_at !== existing.completed_at) {\n const allowedStatuses: string[] = ['APPROVED', 'INTERNAL'];\n const allowedLifecycles: string[] = ['DEPLOYED', 'CANCELLED'];\n if (\n !currentApproval ||\n !allowedStatuses.includes(currentApproval) ||\n !currentDevLifecycle ||\n !allowedLifecycles.includes(currentDevLifecycle)\n ) {\n throw new BusinessError(\n 'Can only set completed date when dev lifecycle is DEPLOYED or CANCELLED',\n );\n }\n }\n\n // Rule: Cannot edit ANY fields when REJECTED (except via revert workflow)\n if (currentApproval === 'REJECTED') {\n // Check if any field is being changed (excluding approvalStatus which is handled above)\n const hasChanges =\n (input.title !== undefined && input.title !== existing.title) ||\n (input.description !== undefined && input.description !== existing.description) ||\n (input.type !== undefined && input.type !== existing.type) ||\n (input.priority !== undefined && input.priority !== existing.priority) ||\n (input.dev_lifecycle !== undefined && input.dev_lifecycle !== existing.dev_lifecycle) ||\n (input.credit_value !== undefined && input.credit_value !== existing.credit_value) ||\n (input.delivered_value !== undefined &&\n input.delivered_value !== existing.delivered_value);\n\n if (hasChanges) {\n throw new BusinessError('Cannot edit REJECTED support_ticket. Use revert workflow to unlock');\n }\n }\n};\n","import {\n StaffSupportTicketInputDto,\n StaffSupportTicketReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../../db/dbTypes';\nimport { UserAppSession } from '../../../../db/user_app_session';\nimport { injectSession } from '../../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../../lib/getChangedProperties';\nimport { BusinessError } from '../../../../middleware/rpc_mid';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../../user/user_interfaces';\nimport { formatCreditValue, isCreditValueEmpty } from '../../../../utils/creditValueFormatter';\nimport { ISupportTicketRepo } from '../../db/support_ticket_repo';\nimport { UpdateSupportTicketEntity } from '../../db/support_ticket_table';\nimport { enrichStaffTicket, collectUserIdsFromStaffTicket } from './enrich_staff_ticket';\nimport { toStaffSupportTicketReadDto } from '../../mappers/support_ticket_mapper';\nimport type { ISupportTicketNotificationService } from '../../services/support_ticket_notification_service';\nimport { SUPPORT_TICKET_TOKENS } from '../../support_ticket_tokens';\nimport { validateBusinessRules } from './validate_staff_business_rules';\n\n/**\n * Helper function to determine if credits_set_at should be updated\n */\nfunction shouldUpdateCreditsSetAt(\n oldValue: string | null | undefined,\n newValue: string | null | undefined,\n): boolean {\n const oldIsEmpty = isCreditValueEmpty(oldValue);\n const newIsEmpty = isCreditValueEmpty(newValue);\n return oldIsEmpty !== newIsEmpty;\n}\n\n/**\n * Interface for staff support_ticket update feature\n */\nexport interface IUpdateSupportTicketAdminFeature {\n execute(input: StaffSupportTicketInputDto): Promise<StaffSupportTicketReadDto>;\n}\n\n/**\n * Comprehensive staff support_ticket update feature\n *\n * Staff can update ANY field on ANY support_ticket (including locked items):\n * - Basic fields: title, description, type, priority\n * - Staff-only: approvalStatus, isInternalOnly, devLifecycle\n * - Financial: creditValue, deliveredValue\n * - Timeline: startAt, targetAt, completedAt\n *\n * Business rules enforced:\n * - Cannot change isInternalOnly after approval with credits\n * - Approval status transitions have constraints (use workflow mutations for complex transitions)\n *\n * Note: For internal notes, use the dedicated updateSupportTicketNotes feature\n * which provides better audit tracking for chat-like interfaces\n */\n@injectable()\nexport class UpdateSupportTicketAdminFeat implements IUpdateSupportTicketAdminFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketRepo)\n private support_ticketRepo: ISupportTicketRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService)\n private notificationService: ISupportTicketNotificationService,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n /**\n * Update support_ticket - comprehensive update for staff\n */\n async execute(input: StaffSupportTicketInputDto): Promise<StaffSupportTicketReadDto> {\n const support_ticket = await this.support_ticketRepo.read(input.id);\n if (!support_ticket) {\n throw new BusinessError('SupportTicket not found');\n }\n\n // Business rule validations (includes archived check)\n validateBusinessRules(input, support_ticket);\n\n const isoTime = new Date().toISOString();\n\n // Build update entity - only update fields that were provided\n const frEntity: UpdateSupportTicketEntity = {\n ...support_ticket,\n id: input.id,\n updated_at: isoTime,\n updated_by: this.session.user.userId,\n };\n\n // Basic fields\n if (input.title !== undefined && input.title !== null) frEntity.title = input.title;\n if (input.description !== undefined && input.description !== null)\n frEntity.description = input.description;\n if (input.type !== undefined) frEntity.type = input.type;\n if (input.priority !== undefined) frEntity.priority = input.priority;\n\n // Staff-only fields\n // Note: isInternal is set at creation only (via approval_status), cannot be changed after\n if (input.dev_lifecycle !== undefined)\n frEntity.dev_lifecycle = input.dev_lifecycle ?? undefined;\n\n // Financial fields\n if (input.credit_value !== undefined) {\n frEntity.credit_value = formatCreditValue(input.credit_value);\n\n // Update credits_set_at when creditValue transitions between empty and non-empty states\n if (shouldUpdateCreditsSetAt(support_ticket.credit_value, frEntity.credit_value)) {\n frEntity.credits_set_at = isoTime;\n }\n }\n if (input.delivered_value !== undefined)\n frEntity.delivered_value = input.delivered_value ?? undefined;\n\n // Timeline fields\n if (input.start_at !== undefined) frEntity.start_at = input.start_at ?? undefined;\n if (input.target_at !== undefined) frEntity.target_at = input.target_at ?? undefined;\n if (input.completed_at !== undefined)\n frEntity.completed_at = input.completed_at ?? undefined;\n\n // Assigned triage person (null = unassign)\n if (input.assigned_to !== undefined)\n frEntity.assigned_to = input.assigned_to?.trim() || null;\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(support_ticket, frEntity);\n\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: frEntity.id,\n operation: OperationConst.UPDATE,\n recorded_at: isoTime,\n record_type: RecordConst.SUPPORT_TICKET,\n record: changed_props,\n old_record: support_ticket,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n // Update support_ticket in database\n const support_ticketUpdated = await this.support_ticketRepo.update(frEntity);\n\n // Notify new assignee and followers on reassignment\n if (\n input.assigned_to !== undefined &&\n support_ticket.assigned_to !== support_ticketUpdated.assigned_to &&\n support_ticketUpdated.assigned_to\n ) {\n await this.notificationService.notifyAssignee(support_ticketUpdated, 'TICKET_ASSIGNED', {\n actorUserId: this.session.user.userId,\n });\n await this.notificationService.notifyFollowers(\n support_ticketUpdated.id,\n support_ticketUpdated,\n 'TICKET_ASSIGNED',\n { actorUserId: this.session.user.userId },\n );\n }\n\n const dto = toStaffSupportTicketReadDto(support_ticketUpdated);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromStaffTicket(dto),\n );\n return enrichStaffTicket(dto, displayMap);\n }\n}\n","import { container } from 'tsyringe';\nimport { ISupportTicketRepo, SupportTicketRepo } from './db/support_ticket_repo';\nimport {\n ISupportTicketNotificationService,\n SupportTicketNotificationService,\n} from './services/support_ticket_notification_service';\nimport {\n ISupportTicketTriageService,\n SupportTicketTriageService,\n} from './services/support_ticket_triage_service';\n\n// Customer features\nimport {\n CreateSupportTicketFeat,\n ICreateSupportTicketFeature,\n} from './features/customer/create_support_ticket_feat';\nimport {\n CustomerToggleSubscriptionFeat,\n ICustomerToggleSubscriptionFeature,\n} from './features/customer/customer_toggle_subscription_feat';\nimport {\n GetSupportTicketCustomerFeature,\n IGetSupportTicketCustomerFeature,\n} from './features/customer/get_support_ticket_customer_feat';\nimport {\n GetSupportTicketsCustomerFeature,\n IGetSupportTicketsCustomerFeature,\n} from './features/customer/get_support_tickets_customer_feat';\nimport {\n IUpdateSupportTicketCustomerFeature,\n UpdateSupportTicketCustomerFeat,\n} from './features/customer/update_support_ticket_customer_feat';\n\n// Staff features\nimport {\n AddSupportTicketSubscriberFeat,\n IAddSupportTicketSubscriberFeature,\n} from './features/staff/add_support_ticket_subscriber_feat';\nimport {\n ApproveSupportTicketFeat,\n IApproveSupportTicketFeature,\n} from './features/staff/approve_support_ticket_feat';\nimport {\n ArchiveSupportTicketFeat,\n IArchiveSupportTicketFeature,\n} from './features/staff/archive_support_ticket_feat';\nimport {\n CancelInternalTaskFeat,\n ICancelInternalTaskFeature,\n} from './features/staff/cancel_internal_task_feat';\nimport {\n CompleteSupportTicketFeat,\n ICompleteSupportTicketFeature,\n} from './features/staff/complete_support_ticket_feat';\nimport {\n ConvertToCustomerFeat,\n IConvertToCustomerFeature,\n} from './features/staff/convert_to_customer_feat';\nimport {\n ConvertToInternalFeat,\n IConvertToInternalFeature,\n} from './features/staff/convert_to_internal_feat';\nimport {\n CreateSupportTicketAdminFeat,\n ICreateSupportTicketAdminFeature,\n} from './features/staff/create_support_ticket_admin_feat';\nimport {\n DeleteSupportTicketFeat,\n IDeleteSupportTicketFeature,\n} from './features/staff/delete_support_ticket_feat';\nimport {\n FixSupportTicketUserIdsFeature,\n IFixSupportTicketUserIdsFeature,\n} from './features/staff/fix_support_ticket_user_ids_feat';\nimport {\n GetSupportTicketAdminFeature,\n IGetSupportTicketAdminFeature,\n} from './features/staff/get_support_ticket_admin_feat';\nimport {\n GetRequestorsForActiveSupportTicketsFeat,\n IGetRequestorsForActiveSupportTicketsFeature,\n} from './features/staff/get_requestors_for_active_support_tickets_feat';\nimport {\n GetSupportTicketsAdminFeature,\n IGetSupportTicketsAdminFeature,\n} from './features/staff/get_support_tickets_admin_feat';\nimport {\n IListSupportTicketSubscribersFeature,\n ListSupportTicketSubscribersFeat,\n} from './features/staff/list_support_ticket_subscribers_feat';\nimport {\n IReactivateInternalTaskFeature,\n ReactivateInternalTaskFeat,\n} from './features/staff/reactivate_internal_task_feat';\nimport {\n IRejectSupportTicketFeature,\n RejectSupportTicketFeat,\n} from './features/staff/reject_support_ticket_feat';\nimport {\n IRemoveSupportTicketSubscriberFeature,\n RemoveSupportTicketSubscriberFeat,\n} from './features/staff/remove_support_ticket_subscriber_feat';\nimport {\n IRevertSupportTicketFeature,\n RevertSupportTicketFeat,\n} from './features/staff/revert_support_ticket_feat';\nimport {\n IUpdateSupportTicketAdminFeature,\n UpdateSupportTicketAdminFeat,\n} from './features/staff/update_support_ticket_admin_feat';\n\nimport { SUPPORT_TICKET_TOKENS } from './support_ticket_tokens';\n\n/**\n * Register all support_ticket dependencies\n */\nexport function registerSupportTicketDependencies() {\n // Register repositories - singleton\n container.registerSingleton<ISupportTicketRepo>(\n SUPPORT_TICKET_TOKENS.ISupportTicketRepo,\n SupportTicketRepo,\n );\n container.registerSingleton<ISupportTicketNotificationService>(\n SUPPORT_TICKET_TOKENS.ISupportTicketNotificationService,\n SupportTicketNotificationService,\n );\n container.registerSingleton<ISupportTicketTriageService>(\n SUPPORT_TICKET_TOKENS.ISupportTicketTriageService,\n SupportTicketTriageService,\n );\n\n // Note: Credit service is registered in customer_registration.ts\n // It's shared across slices and manages the global credit balance\n\n // ===== Customer Features - singletons =====\n container.registerSingleton<ICreateSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.ICreateSupportTicketFeature,\n CreateSupportTicketFeat,\n );\n container.registerSingleton<IUpdateSupportTicketCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IUpdateSupportTicketCustomerFeature,\n UpdateSupportTicketCustomerFeat,\n );\n container.registerSingleton<IGetSupportTicketCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketCustomerFeature,\n GetSupportTicketCustomerFeature,\n );\n container.registerSingleton<IGetSupportTicketsCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketsCustomerFeature,\n GetSupportTicketsCustomerFeature,\n );\n container.registerSingleton<ICustomerToggleSubscriptionFeature>(\n SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature,\n CustomerToggleSubscriptionFeat,\n );\n\n // ===== Staff Features - singletons =====\n\n // Workflow features\n container.registerSingleton<IApproveSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IApproveSupportTicketFeature,\n ApproveSupportTicketFeat,\n );\n container.registerSingleton<IDeleteSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IDeleteSupportTicketFeature,\n DeleteSupportTicketFeat,\n );\n container.registerSingleton<IArchiveSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature,\n ArchiveSupportTicketFeat,\n );\n container.registerSingleton<IRejectSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IRejectSupportTicketFeature,\n RejectSupportTicketFeat,\n );\n container.registerSingleton<IRevertSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IRevertSupportTicketFeature,\n RevertSupportTicketFeat,\n );\n container.registerSingleton<ICompleteSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.ICompleteSupportTicketFeature,\n CompleteSupportTicketFeat,\n );\n container.registerSingleton<IConvertToInternalFeature>(\n SUPPORT_TICKET_TOKENS.IConvertToInternalFeature,\n ConvertToInternalFeat,\n );\n container.registerSingleton<IConvertToCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IConvertToCustomerFeature,\n ConvertToCustomerFeat,\n );\n container.registerSingleton<ICancelInternalTaskFeature>(\n SUPPORT_TICKET_TOKENS.ICancelInternalTaskFeature,\n CancelInternalTaskFeat,\n );\n container.registerSingleton<IReactivateInternalTaskFeature>(\n SUPPORT_TICKET_TOKENS.IReactivateInternalTaskFeature,\n ReactivateInternalTaskFeat,\n );\n\n // Create/Update features\n container.registerSingleton<ICreateSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.ICreateSupportTicketAdminFeature,\n CreateSupportTicketAdminFeat,\n );\n container.registerSingleton<IUpdateSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.IUpdateSupportTicketAdminFeature,\n UpdateSupportTicketAdminFeat,\n );\n\n // Query features\n container.registerSingleton<IGetSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketAdminFeature,\n GetSupportTicketAdminFeature,\n );\n container.registerSingleton<IGetSupportTicketsAdminFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketsAdminFeature,\n GetSupportTicketsAdminFeature,\n );\n container.registerSingleton<IGetRequestorsForActiveSupportTicketsFeature>(\n SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature,\n GetRequestorsForActiveSupportTicketsFeat,\n );\n\n // Subscriber management\n container.registerSingleton<IAddSupportTicketSubscriberFeature>(\n SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature,\n AddSupportTicketSubscriberFeat,\n );\n container.registerSingleton<IListSupportTicketSubscribersFeature>(\n SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature,\n ListSupportTicketSubscribersFeat,\n );\n container.registerSingleton<IRemoveSupportTicketSubscriberFeature>(\n SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature,\n RemoveSupportTicketSubscriberFeat,\n );\n container.registerSingleton<IFixSupportTicketUserIdsFeature>(\n SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature,\n FixSupportTicketUserIdsFeature,\n );\n}\n","import { TeamFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { and, eq, inArray, sql, type SQL } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { createFilterBuilder } from '../../../lib/filters/filter-builder';\nimport type { FieldRegistry } from '../../../lib/filters/filter-builder';\nimport {\n PaginationUtils,\n type PaginationConfig,\n type SortColumn,\n} from '../../../lib/pagination';\nimport { BreadcrumbUtils } from '../../../lib/pagination/breadcrumb-utils';\nimport { CursorUtils } from '../../../lib/pagination/cursor-utils';\nimport { TeamEntityPage, TeamRepository } from '../team_interfaces';\nimport { InsertTeamEntity, ReadTeamEntity, TeamEntity, UpdateTeamEntity } from './team_entity';\nimport { team_table } from './team_table';\n\nconst columnMap: Record<string, SortColumn> = {\n unique_name: { column: team_table.unique_name, type: 'string' },\n display_name: { column: team_table.display_name, type: 'string' },\n legal_name: { column: team_table.legal_name, type: 'string' },\n contact_email: { column: team_table.contact_email, type: 'string' },\n address_city: { column: team_table.address_city, type: 'string' },\n};\n\n// Field registry - single source of truth for field metadata\n// Note: archived_at is excluded - handled manually with NULLIF due to legacy empty string data\nconst teamFields: FieldRegistry = {\n unique_name: {\n column: team_table.unique_name,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n display_name: {\n column: team_table.display_name,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n legal_name: {\n column: team_table.legal_name,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n contact_email: {\n column: team_table.contact_email,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n address_city: {\n column: team_table.address_city,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n address_zip: {\n column: team_table.address_zip,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n created_at: {\n column: team_table.created_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n updated_at: {\n column: team_table.updated_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n // Search field mappings\n contact_name: {\n column: team_table.contact_name,\n type: 'string',\n filterable: false,\n searchable: true,\n sortable: false,\n },\n};\n\n// Create filter builder at module level (no deleted_at or archived_at - handled manually)\nconst buildTeamFilters = createFilterBuilder({\n fieldRegistry: teamFields,\n});\n\n@injectable()\nexport class TeamRepositoryImpl implements TeamRepository {\n constructor(@inject(TOKENS.IDatabaseRouter) private db: DatabaseRouter) {}\n\n private get paginationConfig(): PaginationConfig<TeamEntity> {\n return {\n table: team_table,\n columnMap,\n router: this.db,\n };\n }\n\n async create(data: InsertTeamEntity): Promise<ReadTeamEntity> {\n const id = await this.db.generateId(RecordConst.TEAM);\n const result = await this.db.queryLatest(async (db) =>\n db\n .insert(team_table)\n .values([{ ...data, id }])\n .returning(),\n );\n\n return result[0];\n }\n\n async read(id: string): Promise<ReadTeamEntity | null> {\n const result = await this.db.queryById(id, (db) =>\n db.select().from(team_table).where(eq(team_table.id, id)).limit(1),\n );\n\n return result[0] || null;\n }\n\n async findById(id: string): Promise<TeamEntity | undefined> {\n return this.read(id) as Promise<TeamEntity | undefined>;\n }\n\n async findByUniqueName(uniqueName: string): Promise<TeamEntity | undefined> {\n const result = await this.db.queryAll((db) =>\n db.select().from(team_table).where(eq(team_table.unique_name, uniqueName)).limit(1),\n );\n\n return result[0];\n }\n\n async findByOriginalId(originalId: string): Promise<TeamEntity | undefined> {\n const result = await this.db.queryAll((db) =>\n db.select().from(team_table).where(eq(team_table.original_id, originalId)).limit(1),\n );\n\n return result[0];\n }\n\n async findByOriginalIds(originalIds: string[]): Promise<ReadTeamEntity[]> {\n if (originalIds.length === 0) {\n return [];\n }\n const result = await this.db.queryAll((db) =>\n db.select().from(team_table).where(inArray(team_table.original_id, originalIds)),\n );\n\n return result;\n }\n\n async read_all(\n filters?: TeamFiltersDto & { _consumerTeamIds?: string[] },\n ): Promise<TeamEntityPage> {\n const consumerTeamIds = (filters as any)?._consumerTeamIds;\n\n // When we have specific team IDs, use queryByIds to route to correct shards\n // This is more efficient than queryAll with inArray across all shards\n if (consumerTeamIds !== undefined && consumerTeamIds.length > 0) {\n return this.readAllByTeamIds(filters, consumerTeamIds);\n }\n\n // Otherwise, use standard pagination\n const baseConditions = this.buildFilters(filters);\n return PaginationUtils.findAllPaginated(\n this.paginationConfig,\n filters || {},\n baseConditions,\n );\n }\n\n /**\n * Read teams by specific team IDs using queryByIds for correct shard routing\n */\n private async readAllByTeamIds(\n filters?: TeamFiltersDto,\n teamIds?: string[],\n ): Promise<TeamEntityPage> {\n if (!teamIds || teamIds.length === 0) {\n return {\n items: [],\n pageInfo: {\n hasNextPage: false,\n hasPreviousPage: false,\n currentPageIndex: 0,\n paginationToken: BreadcrumbUtils.encodeToken(\n BreadcrumbUtils.createInitialState(filters?.sortBy, filters?.sortDirection),\n ),\n },\n };\n }\n\n // Use queryByIds to route to correct shards\n const allTeams = await this.db.queryByIds(teamIds, async (db, shardIds) => {\n // Reuse buildFilters but exclude _consumerTeamIds (we're filtering by shardIds instead)\n const baseConditions = this.buildFilters({ ...filters, _consumerTeamIds: undefined });\n\n // Add shard-specific ID filter\n const conditions = [inArray(team_table.id, shardIds), ...baseConditions];\n\n // Build query\n let query = db.select().from(team_table);\n\n if (conditions.length > 0) {\n query = query.where(and(...conditions)) as any;\n }\n\n return query;\n });\n\n // Apply sorting\n const sortBy = filters?.sortBy || 'created_at';\n const sortDirection = filters?.sortDirection || 'desc';\n const sortColumn = columnMap[sortBy];\n\n if (sortColumn) {\n allTeams.sort((a, b) => {\n const aValue = a[sortBy as keyof ReadTeamEntity];\n const bValue = b[sortBy as keyof ReadTeamEntity];\n\n if (aValue === null || aValue === undefined) return 1;\n if (bValue === null || bValue === undefined) return -1;\n\n let comparison = 0;\n if (sortColumn.type === 'string') {\n comparison = String(aValue).localeCompare(String(bValue));\n } else if (sortColumn.type === 'date' || sortColumn.type === 'number') {\n comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0;\n }\n\n return sortDirection === 'asc' ? comparison : -comparison;\n });\n }\n\n // Apply pagination\n const limit = filters?.first || 50;\n const after = filters?.after;\n\n let startIndex = 0;\n if (after) {\n try {\n const cursorData = CursorUtils.decodeCursor(after);\n startIndex = allTeams.findIndex((team) => team.id === cursorData.id);\n if (startIndex === -1) startIndex = 0;\n else startIndex += 1; // Start after the cursor item\n } catch {\n startIndex = 0;\n }\n }\n\n const paginatedItems = allTeams.slice(startIndex, startIndex + limit);\n const hasMore = startIndex + limit < allTeams.length;\n\n // Build pageInfo\n const endCursor =\n paginatedItems.length > 0\n ? CursorUtils.encodeCursor(\n paginatedItems[paginatedItems.length - 1],\n filters,\n columnMap,\n )\n : undefined;\n\n const paginationState =\n BreadcrumbUtils.decodeToken(filters?.paginationToken) ||\n BreadcrumbUtils.createInitialState(sortBy, sortDirection);\n\n return {\n items: paginatedItems,\n pageInfo: {\n hasNextPage: hasMore,\n hasPreviousPage: startIndex > 0,\n prevPageCursor:\n startIndex > 0 && startIndex - limit >= 0\n ? CursorUtils.encodeCursor(allTeams[startIndex - limit], filters, columnMap)\n : undefined,\n nextPageCursor: hasMore && endCursor ? endCursor : undefined,\n currentPageIndex: paginationState.currentPageIndex,\n paginationToken: BreadcrumbUtils.encodeToken(paginationState),\n },\n };\n }\n\n async findMany(\n filters?: TeamFiltersDto & { _consumerTeamIds?: string[] },\n ): Promise<TeamEntityPage> {\n return this.read_all(filters);\n }\n\n async update(data: UpdateTeamEntity): Promise<ReadTeamEntity> {\n const result = await this.db.queryAll((db) =>\n db.update(team_table).set(data).where(eq(team_table.id, data.id)).returning(),\n );\n\n return result[0];\n }\n\n async archive(id: string): Promise<void> {\n const now = new Date().toISOString();\n await this.db.queryAll((db) =>\n db\n .update(team_table)\n .set({\n archived_at: now,\n updated_at: now,\n })\n .where(eq(team_table.id, id)),\n );\n }\n\n async unarchive(id: string): Promise<void> {\n const now = new Date().toISOString();\n await this.db.queryAll((db) =>\n db\n .update(team_table)\n .set({\n archived_at: null,\n updated_at: now,\n })\n .where(eq(team_table.id, id)),\n );\n }\n\n async delete(id: string): Promise<void> {\n await this.db.queryAll((db) => db.delete(team_table).where(eq(team_table.id, id)));\n }\n\n private buildFilters(\n filters?: TeamFiltersDto & { _consumerTeamIds?: string[] },\n ): SQL<unknown>[] {\n const { conditions } = buildTeamFilters(filters);\n\n // Custom: archived_at handling with NULLIF (legacy empty string data)\n if (!filters?.includeArchived) {\n // Handle both NULL and empty string as \"not archived\" using NULLIF\n // NULLIF converts empty string to NULL, then we check if it's NULL\n conditions.push(sql`NULLIF(${team_table.archived_at}, '') IS NULL` as SQL<unknown>);\n }\n\n // Custom: consumer team access control\n const consumerTeamIds = filters?._consumerTeamIds;\n if (consumerTeamIds !== undefined) {\n if (consumerTeamIds.length === 0) {\n // User is not a member of any teams, return no results\n conditions.push(sql`FALSE` as SQL<unknown>);\n } else {\n // Filter to only include teams the user is a member of\n conditions.push(inArray(team_table.id, consumerTeamIds));\n }\n }\n\n return conditions;\n }\n}\n","/**\n * Tokens for dependency injection\n */\nexport const TEAM_MEMBER_TOKENS = {\n // Repository\n ITeamMemberRepo: 'ITeamMemberRepo',\n\n // Features\n ICreateTeamMemberFeature: 'ICreateTeamMemberFeature',\n IUpdateTeamMemberFeature: 'IUpdateTeamMemberFeature',\n IGetTeamMembersFeature: 'IGetTeamMembersFeature',\n IDeleteTeamMemberFeature: 'IDeleteTeamMemberFeature',\n IGetUserTeamsFeature: 'IGetUserTeamsFeature',\n IGetUserTeamMembersFeature: 'IGetUserTeamMembersFeature',\n} as const;\n","import { TeamReadDto } from '@dragonmastery/dragoncore-shared';\nimport { ReadTeamEntity } from './team_entity';\n\n/**\n * Maps a ReadTeamEntity to TeamReadDto\n * Both use snake_case, so the mapping is straightforward\n */\nexport function mapTeamEntityToDto(entity: ReadTeamEntity): TeamReadDto {\n return {\n id: entity.id,\n original_id: entity.original_id?.toString() || null,\n unique_name: entity.unique_name || null,\n display_name: entity.display_name,\n legal_name: entity.legal_name || null,\n description: entity.description || null,\n contact_name: entity.contact_name || null,\n contact_email: entity.contact_email || null,\n contact_business_phone: entity.contact_business_phone || null,\n contact_mobile_phone: entity.contact_mobile_phone || null,\n contact_time_zone: entity.contact_time_zone || null,\n address_full: entity.address_full || null,\n address_city: entity.address_city || null,\n address_zip: entity.address_zip || null,\n twitter_username: entity.twitter_username || null,\n url: entity.url || null,\n logo: entity.logo || null,\n email_sent_from: entity.email_sent_from || null,\n email_reply_to: entity.email_reply_to || null,\n path: entity.path || null,\n created_at: entity.created_at,\n updated_at: entity.updated_at || null,\n created_by: entity.created_by,\n updated_by: entity.updated_by || null,\n archived_at: entity.archived_at || null,\n archived_by: entity.archived_by || null,\n };\n}\n","import type { TeamReadDto } from '@dragonmastery/dragoncore-shared';\n\n/**\n * Enriches a TeamReadDto with display names from a lookup map.\n */\nexport function enrichTeam(\n dto: TeamReadDto,\n displayMap: Map<string, string>,\n): TeamReadDto {\n return {\n ...dto,\n created_by_display_name: dto.created_by\n ? (displayMap.get(dto.created_by) ?? dto.created_by)\n : null,\n updated_by_display_name: dto.updated_by\n ? (displayMap.get(dto.updated_by) ?? dto.updated_by)\n : null,\n };\n}\n\n/**\n * Collects user IDs from a team for display name lookup.\n */\nexport function collectUserIdsFromTeam(team: TeamReadDto): string[] {\n return [team.created_by, team.updated_by].filter(\n (id): id is string => Boolean(id),\n );\n}\n","import type { TeamCreateDto, TeamReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { ICreateTeamMemberFeature } from '../../../slices/team_member/features/create_team_member_feat';\nimport { TEAM_MEMBER_TOKENS } from '../../../slices/team_member/team_member_tokens';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { mapTeamEntityToDto } from '../db/team_mapper';\nimport { enrichTeam, collectUserIdsFromTeam } from './enrich_team';\nimport { TEAM_TOKENS, type CreateTeamFeature, type TeamRepository } from '../team_interfaces';\n\n@injectable()\nexport class CreateTeamFeatureImpl implements CreateTeamFeature {\n constructor(\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature)\n private createTeamMemberFeature: ICreateTeamMemberFeature,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: TeamCreateDto): Promise<TeamReadDto> {\n // Check if team with unique name already exists\n const existingTeam = await this.teamRepo.findByUniqueName(input.unique_name || '');\n if (existingTeam) {\n throw new Error('Team with this name already exists');\n }\n\n // Create team entity (ID is generated by repository via db.generateId())\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n // Compute path from unique_name\n const path = input.unique_name ? this.computePath(input.unique_name) : null;\n\n const teamEntity = await this.teamRepo.create({\n original_id: null,\n unique_name: input.unique_name || null,\n display_name: input.display_name,\n display_name_lower: input.display_name.toLowerCase(),\n legal_name: input.legal_name || null,\n legal_name_lower: input.legal_name ? input.legal_name.toLowerCase() : null,\n description: input.description || null,\n contact_name: input.contact_name || null,\n contact_email: input.contact_email || null,\n contact_business_phone: input.contact_business_phone || null,\n contact_mobile_phone: input.contact_mobile_phone || null,\n contact_time_zone: input.contact_time_zone || null,\n address_full: input.address_full || null,\n address_city: input.address_city || null,\n address_zip: input.address_zip || null,\n twitter_username: input.twitter_username || null,\n url: input.url || null,\n logo: input.logo || null,\n email_sent_from: input.email_sent_from || null,\n email_reply_to: input.email_reply_to || null,\n path,\n created_at: now,\n created_by: userId,\n updated_at: now, // Same as created_at initially\n updated_by: userId,\n });\n\n // Track creation in record versions\n await this.create_record_version.execute({\n record_id: teamEntity.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.TEAM,\n record: teamEntity, // Full new record\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // Create team member with session user as owner\n await this.createTeamMemberFeature.execute({\n team_id: teamEntity.id,\n user_id: this.session.user.userId,\n role: 'owner',\n display_name: this.session.user.username,\n business_phone: null,\n mobile_phone: null,\n email_address: this.session.user.email,\n website_address: null,\n time_zone: null,\n });\n\n const dto = mapTeamEntityToDto(teamEntity);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromTeam(dto),\n );\n return enrichTeam(dto, displayMap);\n }\n\n /**\n * Compute path from unique name (e.g., \"My Team\" -> \"my-team\")\n */\n private computePath(uniqueName: string): string {\n const parts = uniqueName.split(' ').filter(Boolean);\n return parts.join('-');\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { TEAM_TOKENS, type DeleteTeamFeature, type TeamRepository } from '../team_interfaces';\n\n@injectable()\nexport class DeleteTeamFeatureImpl implements DeleteTeamFeature {\n constructor(\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(id: string): Promise<void> {\n // Check if team exists\n const existingTeam = await this.teamRepo.read(id);\n if (!existingTeam) {\n throw new Error('Team not found');\n }\n\n // Track deletion in record versions\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.DELETE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.TEAM,\n record: null, // No new record for deletion\n old_record: existingTeam, // Full old record\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // Delete team\n await this.teamRepo.delete(id);\n }\n}\n","import type {\n TeamFiltersDto,\n TeamPageDto,\n TeamReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { ITeamMemberRepo } from '../../team_member/db/team_member_repo';\nimport { TEAM_MEMBER_TOKENS } from '../../team_member/team_member_tokens';\nimport { mapTeamEntityToDto } from '../db/team_mapper';\nimport { enrichTeam, collectUserIdsFromTeam } from './enrich_team';\nimport {\n TEAM_TOKENS,\n type ReadAllTeamsFeature,\n type TeamRepository,\n} from '../team_interfaces';\n\n@injectable()\nexport class ReadAllTeamsFeatureImpl implements ReadAllTeamsFeature {\n constructor(\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo)\n private teamMemberRepo: ITeamMemberRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(filters?: TeamFiltersDto): Promise<TeamPageDto> {\n // Apply consumer filtering: consumers can only see teams they are members of\n const processedFilters = await this.applyConsumerFiltering(filters);\n\n const result = await this.teamRepo.read_all(processedFilters);\n\n const dtos = result.items.map((teamEntity) => mapTeamEntityToDto(teamEntity));\n const userIds = [...new Set(dtos.flatMap(collectUserIdsFromTeam))];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n const items: TeamReadDto[] = dtos.map((t) => enrichTeam(t, displayMap));\n\n return {\n items,\n pageInfo: result.pageInfo,\n };\n }\n\n /**\n * Apply filtering for consumers and leads to restrict teams to ones they are members of\n * Staff and admin see all teams (no filtering)\n */\n private async applyConsumerFiltering(\n filters?: TeamFiltersDto,\n ): Promise<TeamFiltersDto & { _consumerTeamIds?: string[] }> {\n const user = this.session.user;\n\n // Staff and admin have full access - no filtering needed\n if (user.user_type === 'staff' || user.user_type === 'super_admin') {\n return filters || {};\n }\n\n // For consumers and leads, get their team memberships and filter teams\n const userId = this.session.user.userId;\n if (!userId) {\n // If no userId, return empty results by filtering to empty team IDs\n return {\n ...filters,\n _consumerTeamIds: [],\n };\n }\n\n // Get all teams the user is a member of\n const userTeamIds = await this.teamMemberRepo.getTeamIdsForUser(userId);\n\n // Return filters with consumer team IDs for repository-level filtering\n return {\n ...filters,\n _consumerTeamIds: userTeamIds,\n };\n }\n}\n","import type { TeamReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { mapTeamEntityToDto } from '../db/team_mapper';\nimport { enrichTeam, collectUserIdsFromTeam } from './enrich_team';\nimport { TEAM_TOKENS, type ReadTeamFeature, type TeamRepository } from '../team_interfaces';\n\n@injectable()\nexport class ReadTeamFeatureImpl implements ReadTeamFeature {\n constructor(\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(id: string): Promise<TeamReadDto | null> {\n const teamEntity = await this.teamRepo.read(id);\n\n if (!teamEntity) {\n return null;\n }\n\n const dto = mapTeamEntityToDto(teamEntity);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromTeam(dto),\n );\n return enrichTeam(dto, displayMap);\n }\n}\n","import type { TeamReadDto, TeamUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { UpdateTeamEntity } from '../db/team_entity';\nimport { mapTeamEntityToDto } from '../db/team_mapper';\nimport { enrichTeam, collectUserIdsFromTeam } from './enrich_team';\nimport { TEAM_TOKENS, type TeamRepository, type UpdateTeamFeature } from '../team_interfaces';\n\n@injectable()\nexport class UpdateTeamFeatureImpl implements UpdateTeamFeature {\n constructor(\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: TeamUpdateDto): Promise<TeamReadDto> {\n // Check if team exists\n const existingTeam = await this.teamRepo.read(input.id);\n if (!existingTeam) {\n throw new Error('Team not found');\n }\n\n // Check if unique name is being changed and conflicts with another team\n if (input.unique_name && input.unique_name !== existingTeam.unique_name) {\n const conflictingTeam = await this.teamRepo.findByUniqueName(input.unique_name);\n if (conflictingTeam && conflictingTeam.id !== input.id) {\n throw new Error('Team with this name already exists');\n }\n }\n\n // Prepare update entity\n const updateData: Omit<UpdateTeamEntity, 'id'> = {\n unique_name: input.unique_name,\n display_name: input.display_name,\n legal_name: input.legal_name,\n description: input.description,\n contact_name: input.contact_name,\n contact_email: input.contact_email,\n contact_business_phone: input.contact_business_phone,\n contact_mobile_phone: input.contact_mobile_phone,\n contact_time_zone: input.contact_time_zone,\n address_full: input.address_full,\n address_city: input.address_city,\n address_zip: input.address_zip,\n twitter_username: input.twitter_username,\n url: input.url,\n logo: input.logo,\n email_sent_from: input.email_sent_from,\n email_reply_to: input.email_reply_to,\n };\n\n // Recompute path if unique_name changed\n if (input.unique_name && input.unique_name !== existingTeam.unique_name) {\n updateData.path = this.computePath(input.unique_name);\n }\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(existingTeam, {\n ...existingTeam,\n ...updateData,\n });\n\n // Update team entity\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n const teamEntity = await this.teamRepo.update({\n ...updateData,\n id: input.id,\n updated_at: now,\n updated_by: userId,\n });\n\n // Create record version if there are changes\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: teamEntity.id,\n operation: OperationConst.UPDATE,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.TEAM,\n record: changed_props,\n old_record: existingTeam,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n const dto = mapTeamEntityToDto(teamEntity);\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromTeam(dto),\n );\n return enrichTeam(dto, displayMap);\n }\n\n /**\n * Compute path from unique name (e.g., \"My Team\" -> \"my-team\")\n */\n private computePath(uniqueName: string): string {\n const parts = uniqueName.split(' ').filter(Boolean);\n return parts.join('-');\n }\n}\n","import { container } from 'tsyringe';\n\n// Repository\nimport { TeamRepositoryImpl } from './db/team_repo';\n\n// Features\nimport { CreateTeamFeatureImpl } from './features/create_team_feat';\nimport { DeleteTeamFeatureImpl } from './features/delete_team_feat';\nimport { ReadAllTeamsFeatureImpl } from './features/read_all_teams_feat';\nimport { ReadTeamFeatureImpl } from './features/read_team_feat';\nimport { UpdateTeamFeatureImpl } from './features/update_team_feat';\n\n// Interfaces and tokens\nimport {\n TEAM_TOKENS,\n type CreateTeamFeature,\n type DeleteTeamFeature,\n type ReadAllTeamsFeature,\n type ReadTeamFeature,\n type TeamRepository,\n type UpdateTeamFeature,\n} from './team_interfaces';\n\nexport function registerTeamDependencies() {\n // Repository - lazy loaded singleton (instantiated on first resolve, then reused)\n container.registerSingleton<TeamRepository>(TEAM_TOKENS.ITeamRepository, TeamRepositoryImpl);\n\n // Features - lazy loaded singletons (only instantiated when first resolved, then reused)\n container.registerSingleton<CreateTeamFeature>(\n TEAM_TOKENS.ICreateTeamFeature,\n CreateTeamFeatureImpl,\n );\n\n container.registerSingleton<ReadTeamFeature>(\n TEAM_TOKENS.IReadTeamFeature,\n ReadTeamFeatureImpl,\n );\n\n container.registerSingleton<ReadAllTeamsFeature>(\n TEAM_TOKENS.IReadAllTeamsFeature,\n ReadAllTeamsFeatureImpl,\n );\n\n container.registerSingleton<UpdateTeamFeature>(\n TEAM_TOKENS.IUpdateTeamFeature,\n UpdateTeamFeatureImpl,\n );\n\n container.registerSingleton<DeleteTeamFeature>(\n TEAM_TOKENS.IDeleteTeamFeature,\n DeleteTeamFeatureImpl,\n );\n}\n","import {\n createFilterBuilder,\n deriveColumnMap,\n searchOrCondition,\n type FieldRegistry,\n} from '../../../lib';\nimport { TeamMemberFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { isNull, type SQL } from 'drizzle-orm';\nimport { team_member_table } from './team_member_table';\n\n// Field registry - single source of truth for field metadata\nexport const teamMemberFields: FieldRegistry = {\n original_id: {\n column: team_member_table.original_id,\n type: 'number',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n team_id: {\n column: team_member_table.team_id,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n original_team_id: {\n column: team_member_table.original_team_id,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n user_id: {\n column: team_member_table.user_id,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n original_user_id: {\n column: team_member_table.original_user_id,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n role: {\n column: team_member_table.role,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n display_name: {\n column: team_member_table.display_name,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: true,\n },\n business_phone: {\n column: team_member_table.business_phone,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n mobile_phone: {\n column: team_member_table.mobile_phone,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n email_address: {\n column: team_member_table.email_address,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n website_address: {\n column: team_member_table.website_address,\n type: 'string',\n filterable: true,\n searchable: false,\n sortable: false,\n },\n time_zone: {\n column: team_member_table.time_zone,\n type: 'string',\n filterable: true,\n searchable: true,\n sortable: false,\n },\n created_at: {\n column: team_member_table.created_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n updated_at: {\n column: team_member_table.updated_at,\n type: 'date',\n filterable: true,\n searchable: false,\n sortable: true,\n },\n};\n\n// Derive columnMap for pagination from registry\nexport const teamMemberColumnMap = deriveColumnMap(teamMemberFields);\n\n// Create field filter builder\nconst buildFieldFilters = createFilterBuilder({\n fieldRegistry: teamMemberFields,\n});\n\n/**\n * Build team member query conditions from filters\n */\nexport function buildTeamMemberQuery(\n filters?: TeamMemberFiltersDto,\n): { conditions: SQL[]; skipQuery: boolean } {\n // Build each condition piece\n const softDelete = isNull(team_member_table.deleted_at);\n const fields = buildFieldFilters(filters).conditions;\n const search = searchOrCondition(\n filters?.search?.query,\n teamMemberFields,\n filters?.search?.searchableFields,\n );\n\n // Combine all conditions\n const conditions: SQL[] = [\n softDelete,\n ...fields,\n ...(search ? [search] : []),\n ];\n\n return { conditions, skipQuery: false };\n}\n","import { TeamMemberFiltersDto } from '@dragonmastery/dragoncore-shared';\nimport { and, eq, inArray, isNull, SQL } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { type DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n PaginationUtils,\n type PaginatedResult,\n type PaginationConfig,\n} from '../../../lib/pagination';\nimport {\n InsertTeamMemberEntity,\n ReadTeamMemberEntity,\n team_member_table,\n UpdateTeamMemberEntity,\n} from './team_member_table';\nimport { buildTeamMemberQuery, teamMemberColumnMap } from './team_member_query_config';\n\n@injectable()\nexport class TeamMemberRepo implements ITeamMemberRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n private get paginationConfig(): PaginationConfig<ReadTeamMemberEntity> {\n return {\n table: team_member_table,\n columnMap: teamMemberColumnMap,\n router: this.router,\n };\n }\n\n /**\n * Create a new team member\n */\n async create(entity: InsertTeamMemberEntity): Promise<ReadTeamMemberEntity> {\n const id = await this.router.generateId(RecordConst.TEAM_MEMBER);\n const [result] = await this.router.queryLatest((db) =>\n db\n .insert(team_member_table)\n .values([\n {\n id,\n ...entity,\n },\n ])\n .returning(),\n );\n\n return result;\n }\n\n /**\n * Update an existing team member\n */\n async update(entity: UpdateTeamMemberEntity): Promise<ReadTeamMemberEntity> {\n const { id, ...updateData } = entity;\n const [result] = await this.router.queryById(id, (db) =>\n db\n .update(team_member_table)\n .set(updateData)\n .where(eq(team_member_table.id, id))\n .returning(),\n );\n\n return result;\n }\n\n /**\n * Get a team member by ID\n */\n async getById(id: string): Promise<ReadTeamMemberEntity | null> {\n const [result] = await this.router.queryById(id, (db) =>\n db\n .select()\n .from(team_member_table)\n .where(and(eq(team_member_table.id, id), isNull(team_member_table.deleted_at)))\n .limit(1),\n );\n\n if (!result) {\n return null;\n }\n\n return result;\n }\n\n private buildFilters(filters?: TeamMemberFiltersDto): {\n conditions: SQL[];\n } {\n return buildTeamMemberQuery(filters);\n }\n\n /**\n * Get all team members with breadcrumb pagination\n */\n async getAll(\n filters?: TeamMemberFiltersDto,\n ): Promise<PaginatedResult<ReadTeamMemberEntity>> {\n const { conditions } = this.buildFilters(filters);\n\n // Let the pagination utility handle everything\n return PaginationUtils.findAllPaginated(\n this.paginationConfig,\n filters || {},\n conditions,\n );\n }\n\n /**\n * Soft delete a team member by ID\n */\n async delete(id: string, deleted_by: string): Promise<ReadTeamMemberEntity> {\n const deleted_at = new Date().toISOString();\n const result = await this.router.queryById(id, (db) =>\n db\n .update(team_member_table)\n .set({\n deleted_at,\n deleted_by,\n })\n .where(and(eq(team_member_table.id, id), isNull(team_member_table.deleted_at)))\n .returning(),\n );\n\n if (!result || result.length === 0) {\n throw new Error('Team member not found or already deleted');\n }\n\n return result[0];\n }\n\n /**\n * Get all team IDs for a user (for filtering purposes)\n * Returns all team IDs where the user is an active member\n */\n async getTeamIdsForUser(userId: string): Promise<string[]> {\n const results = await this.router.queryAll((db) =>\n db\n .select({\n team_id: team_member_table.team_id,\n })\n .from(team_member_table)\n .where(\n and(eq(team_member_table.user_id, userId), isNull(team_member_table.deleted_at)),\n ),\n );\n\n return results.map((r) => r.team_id);\n }\n\n /**\n * Get all team members for the given team IDs\n * Returns all active (non-deleted) team members from the specified teams\n * @param teamIds - Array of team IDs to fetch team members for\n * @param limit - Maximum number of results to return (default: 10000)\n * @returns Array of team member entities\n */\n async getByTeamIds(\n teamIds: string[],\n limit: number = 10000,\n ): Promise<ReadTeamMemberEntity[]> {\n if (teamIds.length === 0) {\n return [];\n }\n\n const results = await this.router.queryAll((db) =>\n db\n .select()\n .from(team_member_table)\n .where(\n and(\n inArray(team_member_table.team_id, teamIds),\n isNull(team_member_table.deleted_at),\n ),\n )\n .limit(limit),\n );\n\n return results;\n }\n}\n\nexport type TeamMemberEntityPage = PaginatedResult<ReadTeamMemberEntity>;\n\n/**\n * Interface for team member repository\n */\nexport interface ITeamMemberRepo {\n create(entity: InsertTeamMemberEntity): Promise<ReadTeamMemberEntity>;\n update(entity: UpdateTeamMemberEntity): Promise<ReadTeamMemberEntity>;\n getById(id: string): Promise<ReadTeamMemberEntity | null>;\n getAll(filters?: TeamMemberFiltersDto): Promise<TeamMemberEntityPage>;\n delete(id: string, deleted_by: string): Promise<ReadTeamMemberEntity>;\n getTeamIdsForUser(userId: string): Promise<string[]>;\n getByTeamIds(teamIds: string[], limit?: number): Promise<ReadTeamMemberEntity[]>;\n}\n","import type { TeamMemberReadDto } from '@dragonmastery/dragoncore-shared';\n\n/**\n * Enriches a TeamMemberReadDto with display names from a lookup map.\n */\nexport function enrichTeamMember(\n dto: TeamMemberReadDto,\n displayMap: Map<string, string>,\n): TeamMemberReadDto {\n return {\n ...dto,\n created_by_display_name: dto.created_by\n ? (displayMap.get(dto.created_by) ?? dto.created_by)\n : null,\n updated_by_display_name: dto.updated_by\n ? (displayMap.get(dto.updated_by) ?? dto.updated_by)\n : null,\n };\n}\n\n/**\n * Collects user IDs from a team member for display name lookup.\n */\nexport function collectUserIdsFromTeamMember(\n member: TeamMemberReadDto | { created_by?: string | null; updated_by?: string | null },\n): string[] {\n return [member.created_by, member.updated_by].filter(\n (id): id is string => Boolean(id),\n );\n}\n","import { TeamMemberCreateDto, TeamMemberReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { InsertTeamMemberEntity } from '../db/team_member_table';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\nimport { enrichTeamMember, collectUserIdsFromTeamMember } from './enrich_team_member';\n\nexport interface ICreateTeamMemberFeature {\n execute(input: TeamMemberCreateDto): Promise<TeamMemberReadDto>;\n}\n\n@injectable()\nexport class CreateTeamMemberFeat implements ICreateTeamMemberFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: TeamMemberCreateDto): Promise<TeamMemberReadDto> {\n const now = new Date().toISOString();\n const userId = this.session.user.userId;\n\n const entity: InsertTeamMemberEntity = {\n team_id: input.team_id,\n user_id: input.user_id,\n role: input.role,\n display_name: input.display_name,\n display_name_lower: input.display_name.toLowerCase(),\n business_phone: input.business_phone,\n mobile_phone: input.mobile_phone,\n email_address: input.email_address,\n website_address: input.website_address,\n time_zone: input.time_zone,\n created_at: now,\n created_by: userId,\n updated_at: now, // Same as created_at initially\n updated_by: userId,\n };\n\n // Create team member in database\n const teamMemberCreated = await this.teamMemberRepo.create(entity);\n\n // Track creation in record versions\n await this.create_record_version.execute({\n record_id: teamMemberCreated.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.TEAM_MEMBER,\n record: teamMemberCreated,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n const dto = teamMemberCreated as TeamMemberReadDto;\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromTeamMember(dto),\n );\n return enrichTeamMember(dto, displayMap);\n }\n}\n","import { OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport { TeamMemberReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\n\nexport interface IDeleteTeamMemberFeature {\n execute(id: string): Promise<TeamMemberReadDto>;\n}\n\n@injectable()\nexport class DeleteTeamMemberFeat implements IDeleteTeamMemberFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(id: string): Promise<TeamMemberReadDto> {\n // Get existing team member before deletion\n const existingTeamMember = await this.teamMemberRepo.getById(id);\n if (!existingTeamMember) {\n throw new Error('Team member not found');\n }\n\n // Check permissions: Owner can delete any team member, others can only delete themselves\n const currentUserId = this.session.user.userId;\n const isDeletingSelf = existingTeamMember.user_id === currentUserId;\n\n if (!isDeletingSelf) {\n // Get current user's team member record for this team\n const currentUserTeamMembers = await this.teamMemberRepo.getAll({\n team_id: { operator: OPERATORS.EQUALS, value: existingTeamMember.team_id },\n user_id: { operator: OPERATORS.EQUALS, value: currentUserId },\n });\n\n const currentUserTeamMember = currentUserTeamMembers.items.find(\n (tm) => tm.user_id === currentUserId && tm.team_id === existingTeamMember.team_id,\n );\n\n if (!currentUserTeamMember) {\n throw new Error('You are not a member of this team');\n }\n\n // Only owners can delete other team members\n if (currentUserTeamMember.role !== 'owner') {\n throw new Error('Only owners can delete other team members');\n }\n }\n\n const now = new Date().toISOString();\n\n // Track deletion in record versions (before deletion)\n await this.create_record_version.execute({\n record_id: id,\n operation: OperationConst.DELETE,\n recorded_at: now,\n record_type: RecordConst.TEAM_MEMBER,\n record: null, // No new record for deletion\n old_record: existingTeamMember, // Full old record\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n\n // Soft delete team member\n const teamMemberDeleted = await this.teamMemberRepo.delete(id, this.session.user.userId);\n\n return teamMemberDeleted as TeamMemberReadDto;\n }\n}\n","import { TeamMemberFiltersDto, TeamMemberReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { type PaginatedResult } from '../../../lib/pagination';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\nimport { enrichTeamMember, collectUserIdsFromTeamMember } from './enrich_team_member';\n\nexport interface IGetTeamMembersFeature {\n execute(filters?: TeamMemberFiltersDto): Promise<PaginatedResult<TeamMemberReadDto>>;\n}\n\n@injectable()\nexport class GetTeamMembersFeat implements IGetTeamMembersFeature {\n constructor(\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(filters?: TeamMemberFiltersDto): Promise<PaginatedResult<TeamMemberReadDto>> {\n const result = await this.teamMemberRepo.getAll(filters);\n const items = result.items as TeamMemberReadDto[];\n const userIds = [...new Set(items.flatMap(collectUserIdsFromTeamMember))];\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(userIds);\n const enrichedItems = items.map((m) => enrichTeamMember(m, displayMap));\n return { ...result, items: enrichedItems };\n }\n}\n","import { TeamMemberOptionDto, UserTeamMembersDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { ReadTeamMemberEntity } from '../db/team_member_table';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\n\nexport interface IGetUserTeamMembersFeature {\n execute(): Promise<UserTeamMembersDto>;\n}\n\n@injectable()\nexport class GetUserTeamMembersFeat implements IGetUserTeamMembersFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n ) {}\n\n async execute(): Promise<UserTeamMembersDto> {\n const userId = this.session.user.userId;\n\n if (!userId) {\n return { items: [] };\n }\n\n let teamMembers: ReadTeamMemberEntity[];\n\n // Super admins see all team members across all teams\n if (this.session.user.user_type === 'super_admin') {\n // For super admins, get all team members (no team filtering)\n const allTeamMembersPage = await this.teamMemberRepo.getAll({\n first: 10000, // Large limit to get all team members\n });\n teamMembers = allTeamMembersPage.items;\n } else {\n // Get all team IDs for the user\n const teamIds = await this.teamMemberRepo.getTeamIdsForUser(userId);\n\n if (teamIds.length === 0) {\n return { items: [] };\n }\n\n // Get all team members from those teams using dedicated method\n teamMembers = await this.teamMemberRepo.getByTeamIds(teamIds, 10000);\n }\n\n // Transform entities to DTOs - TeamMemberOptionDto matches TeamMemberReadDto\n // Deduplicate by user_id to avoid showing the same user multiple times\n // (a user can be a member of multiple teams)\n const userMap = new Map<string, TeamMemberOptionDto>();\n teamMembers.forEach((teamMemberEntity) => {\n const member = teamMemberEntity as TeamMemberOptionDto;\n // Use user_id as the key for deduplication\n // If a user appears in multiple teams, keep the first occurrence\n if (member.user_id && !userMap.has(member.user_id)) {\n userMap.set(member.user_id, member);\n }\n });\n\n const items: TeamMemberOptionDto[] = Array.from(userMap.values());\n\n return {\n items,\n };\n }\n}\n","import { TeamOptionDto, UserTeamsDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { mapTeamEntityToDto } from '../../team/db/team_mapper';\nimport { TEAM_TOKENS, type TeamRepository } from '../../team/team_interfaces';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\n\nexport interface IGetUserTeamsFeature {\n execute(): Promise<UserTeamsDto>;\n}\n\n@injectable()\nexport class GetUserTeamsFeat implements IGetUserTeamsFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n @inject(TEAM_TOKENS.ITeamRepository) private teamRepo: TeamRepository,\n ) {}\n\n async execute(): Promise<UserTeamsDto> {\n const userId = this.session.user.userId;\n\n if (!userId) {\n return { items: [] };\n }\n\n // Super admins see all teams\n if (this.session.user.user_type === 'super_admin') {\n const teamPage = await this.teamRepo.read_all({\n first: 1000, // Large limit to get all teams\n });\n\n // Transform entities to DTOs - TeamOptionDto now matches TeamReadDto\n const items: TeamOptionDto[] = teamPage.items.map((teamEntity) => {\n return mapTeamEntityToDto(teamEntity);\n });\n\n return {\n items,\n };\n }\n\n // Get all team IDs for the user\n const teamIds = await this.teamMemberRepo.getTeamIdsForUser(userId);\n\n if (teamIds.length === 0) {\n return { items: [] };\n }\n\n // Get team details for those team IDs using read_all with _consumerTeamIds filter\n // Use a large limit to get all teams (user teams should be a small list)\n const teamPage = await this.teamRepo.read_all({\n _consumerTeamIds: teamIds,\n first: 1000, // Large limit to get all user teams\n });\n\n // Transform entities to DTOs - TeamOptionDto now matches TeamReadDto\n const items: TeamOptionDto[] = teamPage.items.map((teamEntity) => {\n return mapTeamEntityToDto(teamEntity);\n });\n\n return {\n items,\n };\n }\n}\n","import { OPERATORS } from '@dragonmastery/dragoncore-shared';\nimport { TeamMemberReadDto, TeamMemberUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../lib/getChangedProperties';\nimport {\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserDisplayLookup, USER_TOKENS } from '../../user/user_interfaces';\nimport { ITeamMemberRepo } from '../db/team_member_repo';\nimport { UpdateTeamMemberEntity } from '../db/team_member_table';\nimport { TEAM_MEMBER_TOKENS } from '../team_member_tokens';\nimport { enrichTeamMember, collectUserIdsFromTeamMember } from './enrich_team_member';\n\nexport interface IUpdateTeamMemberFeature {\n execute(input: TeamMemberUpdateDto): Promise<TeamMemberReadDto>;\n}\n\n@injectable()\nexport class UpdateTeamMemberFeat implements IUpdateTeamMemberFeature {\n constructor(\n @injectSession() private session: UserAppSession,\n @inject(TEAM_MEMBER_TOKENS.ITeamMemberRepo) private teamMemberRepo: ITeamMemberRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(USER_TOKENS.IUserDisplayLookup)\n private userDisplayLookup: IUserDisplayLookup,\n ) {}\n\n async execute(input: TeamMemberUpdateDto): Promise<TeamMemberReadDto> {\n // Get existing team member to track changes\n const existingTeamMember = await this.teamMemberRepo.getById(input.id);\n if (!existingTeamMember) {\n throw new Error('Team member not found');\n }\n\n // Check permissions: Owner can update any team member, others can only update themselves\n const currentUserId = this.session.user.userId;\n const isUpdatingSelf = existingTeamMember.user_id === currentUserId;\n\n if (!isUpdatingSelf) {\n // Get current user's team member record for this team\n const currentUserTeamMembers = await this.teamMemberRepo.getAll({\n team_id: { operator: OPERATORS.EQUALS, value: existingTeamMember.team_id },\n user_id: { operator: OPERATORS.EQUALS, value: currentUserId },\n });\n\n const currentUserTeamMember = currentUserTeamMembers.items.find(\n (tm) => tm.user_id === currentUserId && tm.team_id === existingTeamMember.team_id,\n );\n\n if (!currentUserTeamMember) {\n throw new Error('You are not a member of this team');\n }\n\n // Only owners can update other team members\n if (currentUserTeamMember.role !== 'owner') {\n throw new Error('Only owners can update other team members');\n }\n }\n\n const now = new Date().toISOString();\n\n // Compute display_name_lower if display_name is being updated\n const updateData: UpdateTeamMemberEntity = {\n id: input.id,\n team_id: input.team_id,\n role: input.role,\n display_name: input.display_name,\n display_name_lower: input.display_name ? input.display_name.toLowerCase() : undefined,\n business_phone: input.business_phone,\n mobile_phone: input.mobile_phone,\n email_address: input.email_address,\n website_address: input.website_address,\n time_zone: input.time_zone,\n updated_at: now,\n updated_by: this.session.user.userId,\n };\n\n // Track changes for record versioning\n const changed_props = getChangedProperties(existingTeamMember, {\n ...existingTeamMember,\n ...updateData,\n });\n\n // Update team member in database\n const teamMemberUpdated = await this.teamMemberRepo.update(updateData);\n\n // Create record version if there are changes\n if (Object.keys(changed_props).length > 0) {\n await this.create_record_version.execute({\n record_id: teamMemberUpdated.id,\n operation: OperationConst.UPDATE,\n recorded_at: now,\n record_type: RecordConst.TEAM_MEMBER,\n record: changed_props,\n old_record: existingTeamMember,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n auth_username: this.session.user.username,\n });\n }\n\n const dto = teamMemberUpdated as TeamMemberReadDto;\n const displayMap = await this.userDisplayLookup.lookupDisplayNames(\n collectUserIdsFromTeamMember(dto),\n );\n return enrichTeamMember(dto, displayMap);\n }\n}\n","import { container } from 'tsyringe';\nimport { TeamMemberRepo } from './db/team_member_repo';\nimport { CreateTeamMemberFeat } from './features/create_team_member_feat';\nimport { DeleteTeamMemberFeat } from './features/delete_team_member_feat';\nimport { GetTeamMembersFeat } from './features/get_team_members_feat';\nimport { GetUserTeamMembersFeat } from './features/get_user_team_members_feat';\nimport { GetUserTeamsFeat } from './features/get_user_teams_feat';\nimport { UpdateTeamMemberFeat } from './features/update_team_member_feat';\nimport { TEAM_MEMBER_TOKENS } from './team_member_tokens';\n\nexport function registerTeamMemberContainer() {\n // Register Repository - singleton\n container.registerSingleton(TEAM_MEMBER_TOKENS.ITeamMemberRepo, TeamMemberRepo);\n\n // Register Features - singletons\n container.registerSingleton(\n TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature,\n CreateTeamMemberFeat,\n );\n container.registerSingleton(\n TEAM_MEMBER_TOKENS.IUpdateTeamMemberFeature,\n UpdateTeamMemberFeat,\n );\n container.registerSingleton(TEAM_MEMBER_TOKENS.IGetTeamMembersFeature, GetTeamMembersFeat);\n container.registerSingleton(\n TEAM_MEMBER_TOKENS.IDeleteTeamMemberFeature,\n DeleteTeamMemberFeat,\n );\n container.registerSingleton(TEAM_MEMBER_TOKENS.IGetUserTeamsFeature, GetUserTeamsFeat);\n container.registerSingleton(\n TEAM_MEMBER_TOKENS.IGetUserTeamMembersFeature,\n GetUserTeamMembersFeat,\n );\n}\n","import { RecordConst, UserTypeValues } from '@dragonmastery/dragoncore-shared';\nimport { and, eq, getTableColumns, inArray, or, sql } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { user_profile_table, user_table } from '../../../db/schemas/primary/primary_schema';\nimport { TOKENS } from '../../../di_tokens';\nimport { Logger } from '../../../utils/logger';\nimport { IUserRepo } from '../user_interfaces';\nimport { CreateUserEntity, ReadUserEntity, UpdateUserEntity } from './user_entity';\n\n@injectable()\nexport class UserRepo implements IUserRepo {\n constructor(\n @inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async create_user(userRecord: CreateUserEntity): Promise<ReadUserEntity> {\n // check if user with the same username or email already exists\n const existing_user = await this.read_user_by_username_or_email(\n userRecord.username,\n userRecord.email,\n );\n if (existing_user) {\n throw new Error('User already exists');\n }\n\n const user_id = await this.router.generateId(RecordConst.USER);\n const profile_id = await this.router.generateId(RecordConst.USER_PROFILE);\n\n const result = await this.router.queryLatest(async (db) => {\n const user_insert = db\n .insert(user_table)\n .values({\n id: user_id,\n ...userRecord,\n })\n .returning();\n\n const createdAt = userRecord.created_at || new Date().toISOString();\n const profile_insert = db\n .insert(user_profile_table)\n .values({\n id: profile_id,\n user_id: user_id,\n created_at: createdAt,\n created_by: user_id,\n updated_at: createdAt, // Same as created_at initially\n updated_by: user_id, // Same as created_by initially\n })\n .returning();\n\n const res = await db.batch([user_insert, profile_insert]);\n return res[0];\n });\n const user = result[0];\n\n return user;\n }\n\n async read_user(id: string): Promise<ReadUserEntity> {\n const startTime = performance.now();\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryById(id, (db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(eq(user_table.id, id))\n .limit(1),\n );\n const queryTime = performance.now() - startTime;\n this.logger.perf(`UserRepo.read_user: ${queryTime.toFixed(2)}ms for id ${id}`);\n return result[0];\n }\n\n async read_users_by_ids(ids: string[]): Promise<ReadUserEntity[]> {\n if (ids.length === 0) {\n return [];\n }\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryByIds(ids, (db, shardIds) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(inArray(user_table.id, shardIds)),\n );\n return result;\n }\n\n async read_user_by_email(email: string): Promise<ReadUserEntity> {\n const startTime = performance.now();\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(eq(user_table.email, email))\n .limit(1),\n );\n const queryTime = performance.now() - startTime;\n this.logger.perf(\n `UserRepo.read_user_by_email: ${queryTime.toFixed(2)}ms for email ${email}`,\n );\n return result[0];\n }\n\n async read_users_by_emails(emails: string[]): Promise<ReadUserEntity[]> {\n if (emails.length === 0) {\n return [];\n }\n const { ...rest } = getTableColumns(user_table);\n\n return this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(inArray(user_table.email, emails)),\n );\n }\n\n /**\n * Case-insensitive email lookup. Use when matching against values that may\n * differ in casing (e.g. emails stored in other tables).\n */\n async read_users_by_emails_case_insensitive(emails: string[]): Promise<ReadUserEntity[]> {\n if (emails.length === 0) {\n return [];\n }\n const { ...rest } = getTableColumns(user_table);\n const lowerEmails = [...new Set(emails.map((e) => e.toLowerCase()))];\n\n return this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(sql`LOWER(${user_table.email}) IN (${sql.join(lowerEmails.map((e) => sql`${e}`), sql`, `)})`),\n );\n }\n\n async read_user_by_username(username: string): Promise<ReadUserEntity> {\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(eq(user_table.username, username))\n .limit(1),\n );\n return result[0];\n }\n\n async read_user_by_original_id(original_id: string): Promise<ReadUserEntity | undefined> {\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(eq(user_table.original_id, original_id))\n .limit(1),\n );\n return result[0];\n }\n\n async read_users_by_original_ids(original_ids: string[]): Promise<ReadUserEntity[]> {\n if (original_ids.length === 0) {\n return [];\n }\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(inArray(user_table.original_id, original_ids)),\n );\n return result;\n }\n\n async read_users_by_usernames(usernames: string[]): Promise<ReadUserEntity[]> {\n if (usernames.length === 0) {\n return [];\n }\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(inArray(user_table.username, usernames)),\n );\n return result;\n }\n\n async read_user_by_username_or_email(\n username: string,\n email: string,\n ): Promise<ReadUserEntity> {\n const { ...rest } = getTableColumns(user_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(or(eq(user_table.username, username), eq(user_table.email, email)))\n .limit(1),\n );\n return result[0];\n }\n\n async read_all_users(): Promise<ReadUserEntity[]> {\n const { ...rest } = getTableColumns(user_table);\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table),\n );\n return result;\n }\n\n async read_users_by_type(user_type: UserTypeValues): Promise<ReadUserEntity[]> {\n const { ...rest } = getTableColumns(user_table);\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(eq(user_table.user_type, user_type)),\n );\n return result;\n }\n\n async read_users_by_types(user_types: UserTypeValues[]): Promise<ReadUserEntity[]> {\n const { ...rest } = getTableColumns(user_table);\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_table)\n .where(inArray(user_table.user_type, user_types)),\n );\n return result;\n }\n\n async update_user(userRecord: UpdateUserEntity): Promise<UpdateUserEntity> {\n userRecord.updated_at = new Date().toISOString();\n // exclude unique columns\n const { username, email, ...rest } = userRecord;\n const result = await this.router.queryAll((db) =>\n db.update(user_table).set(rest).where(eq(user_table.id, userRecord.id)).returning(),\n );\n return result[0];\n }\n\n async change_user_username(id: string, new_username: string): Promise<ReadUserEntity> {\n // check if user with the same username already exists\n const existing_user = await this.read_user_by_username(new_username);\n if (existing_user) {\n throw new Error('User already exists');\n }\n const result = await this.router.queryById(id, (db) =>\n db\n .update(user_table)\n .set({\n username: new_username,\n })\n .where(eq(user_table.id, id))\n .returning(),\n );\n return result[0];\n }\n\n async change_user_email(id: string, new_email: string): Promise<ReadUserEntity> {\n // check if user with the same email already exists\n const existing_user = await this.read_user_by_email(new_email);\n if (existing_user) {\n throw new Error('User already exists');\n }\n const result = await this.router.queryById(id, (db) =>\n db\n .update(user_table)\n .set({\n email: new_email,\n })\n .where(eq(user_table.id, id))\n .returning(),\n );\n return result[0];\n }\n\n async change_password_hash(id: string, new_password_hash: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db\n .update(user_table)\n .set({\n hashed_password: new_password_hash,\n })\n .where(eq(user_table.id, id))\n .returning(),\n );\n return result.length > 0;\n }\n\n async delete_user(id: string): Promise<boolean> {\n const result = await this.router.queryById(id, (db) =>\n db.delete(user_table).where(eq(user_table.id, id)).returning(),\n );\n return result.length > 0;\n }\n}\n","import { CreateUserDto, CreateUserDtoOutput } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { custom_long_nanoid } from '../../../db/db_utils';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { IPasswordService } from '../../../lib/password_verifier';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { Logger } from '../../../utils/logger';\nimport { CreateUserEntity } from '../db/user_entity';\nimport { ICreateUser, IUserRepo, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class CreateUser implements ICreateUser {\n constructor(\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.PASSWORD_SERVICE) private passwordService: IPasswordService,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(user_dto: CreateUserDto) {\n const user_salt = custom_long_nanoid();\n\n // Use provided password or generate random one\n const password = user_dto.password || custom_long_nanoid();\n\n const hashed_password = await this.passwordService.hashPassword(\n password.normalize('NFKC') + user_salt,\n );\n\n let user_entity: CreateUserEntity = {\n username: user_dto.email,\n salt: user_salt,\n hashed_password: hashed_password,\n email: user_dto.email,\n email_verified: false,\n user_type: user_dto.user_type,\n created_at: new Date().toISOString(),\n };\n\n const user_created = await this.user_repo.create_user(user_entity);\n\n const record_version: CreateRecordVersionDto = {\n record_id: user_created.id,\n operation: OperationConst.INSERT,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.USER,\n record: user_created,\n auth_uid: user_created.id,\n auth_role: 'super_admin',\n };\n\n await this.create_record_version.execute(record_version);\n\n const created_dto: CreateUserDtoOutput = {\n id: user_created.id,\n email: user_created.email,\n };\n this.logger.debug('[CreateUser] execute', { created_dto });\n return created_dto;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport type { IUserDisplayLookup, IUserRepo } from '../user_interfaces';\nimport { USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class UserDisplayLookupFeat implements IUserDisplayLookup {\n constructor(\n @inject(USER_TOKENS.IUsersRepo)\n private userRepo: IUserRepo,\n ) {}\n\n async lookupDisplayNames(userIds: string[]): Promise<Map<string, string>> {\n const uniqueIds = [...new Set(userIds)].filter((id): id is string => !!id?.trim());\n // Only look up IDs that match universal ID format (32 chars) - skip emails/legacy values\n const validIds = uniqueIds.filter((id) => id.length === 32);\n if (validIds.length === 0) {\n return new Map();\n }\n\n const users = await this.userRepo.read_users_by_ids(validIds);\n const map = new Map<string, string>();\n for (const u of users) {\n const display = u.email ?? u.id;\n map.set(u.id, display);\n }\n return map;\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { z as val } from 'zod';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IDeleteUser, IUserRepo, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class DeleteUser implements IDeleteUser {\n constructor(\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(id: string) {\n const user_is_super_admin = (input: string) => {\n return input === 'super_admin';\n };\n const validateUser = val.object({\n user_type: val\n .string()\n .refine(user_is_super_admin, 'You must be a super admin to create a user'),\n });\n\n validateUser.parse(this.session.user);\n\n const res = await this.user_repo.delete_user(id);\n\n if (!res) {\n return false;\n }\n\n const record_version: CreateRecordVersionDto = {\n record_id: id,\n operation: 'delete',\n recorded_at: new Date().toISOString(),\n record_type: 'user',\n old_record: res,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n };\n\n await this.create_record_version.execute(record_version);\n return true;\n }\n}\n","import { UserReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserRepo, USER_TOKENS } from '../user_interfaces';\n\nexport interface IGetAllUsersFeature {\n execute(): Promise<UserReadDto[]>;\n}\n\n@injectable()\nexport class GetAllUsersFeat implements IGetAllUsersFeature {\n constructor(@inject(USER_TOKENS.IUsersRepo) private repo: IUserRepo) {}\n\n async execute(): Promise<UserReadDto[]> {\n const users = await this.repo.read_all_users();\n return users.map((user) => ({\n id: user.id,\n username: user.username,\n email: user.email,\n email_verified: user.email_verified,\n user_type: user.user_type,\n created_at: user.created_at,\n updated_at: user.updated_at,\n }));\n }\n}\n","import { UserReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserRepo, USER_TOKENS } from '../user_interfaces';\n\nexport interface IGetUserFeature {\n execute(id: string): Promise<UserReadDto>;\n}\n\n@injectable()\nexport class GetUserFeat implements IGetUserFeature {\n constructor(@inject(USER_TOKENS.IUsersRepo) private repo: IUserRepo) {}\n\n async execute(id: string): Promise<UserReadDto> {\n const user = await this.repo.read_user(id);\n\n if (!user) {\n throw new Error('User not found');\n }\n\n return {\n id: user.id,\n username: user.username,\n email: user.email,\n email_verified: user.email_verified,\n user_type: user.user_type,\n created_at: user.created_at,\n updated_at: user.updated_at,\n };\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { ISupportStaffRepo } from '../../support_staff/db/support_staff_repo';\nimport { SUPPORT_STAFF_TOKENS } from '../../support_staff/support_staff_tokens';\n\nexport interface IGetTriageUsersFeature {\n execute(): Promise<Array<{ id: string; email: string }>>;\n}\n\n@injectable()\nexport class GetTriageUsersFeat implements IGetTriageUsersFeature {\n constructor(\n @inject(SUPPORT_STAFF_TOKENS.ISupportStaffRepo)\n private supportStaffRepo: ISupportStaffRepo,\n ) {}\n\n async execute(): Promise<Array<{ id: string; email: string }>> {\n return this.supportStaffRepo.readTriageUsers();\n }\n}\n","import { USER_TYPES } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { IUserRepo, USER_TOKENS } from '../user_interfaces';\n\nexport interface IGetUsersForSelectionFeature {\n execute(): Promise<Array<{ id: string; email: string }>>;\n}\n\n@injectable()\nexport class GetUsersForSelectionFeat implements IGetUsersForSelectionFeature {\n constructor(@inject(USER_TOKENS.IUsersRepo) private repo: IUserRepo) {}\n\n async execute(): Promise<Array<{ id: string; email: string }>> {\n const users = await this.repo.read_users_by_types([...USER_TYPES]);\n return users.map((user) => ({\n id: user.id,\n email: user.email,\n }));\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { z as val } from 'zod';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { IReadAllUsers, IUserRepo, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class ReadAllUsers implements IReadAllUsers {\n constructor(\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute() {\n const user_is_super_admin = (input: string) => {\n return input === 'super_admin';\n };\n\n const validateUser = val\n .string()\n .refine(user_is_super_admin, 'You must be a super admin to read all users');\n\n validateUser.parse(this.session.user.user_type);\n\n return await this.user_repo.read_all_users();\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { IReadConsumers, IUserRepo, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class ReadConsumers implements IReadConsumers {\n constructor(@inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo) {}\n\n async execute() {\n const users = await this.user_repo.read_users_by_type('consumer');\n return users.map((user) => user.email);\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { IReadUser, IUserRepo, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class ReadUser implements IReadUser {\n constructor(@inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo) {}\n\n async execute(id: string) {\n const record = await this.user_repo.read_user(id);\n\n if (!record) {\n return null;\n }\n\n return record;\n }\n}\n","import { SignupInputDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { custom_long_nanoid } from '../../../db/db_utils';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { IPasswordService } from '../../../lib/password_verifier';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { Logger } from '../../../utils/logger';\nimport { CreateUserEntity } from '../db/user_entity';\nimport { ISignUpUser, IUserRepo, SignUpUserOutputDto, USER_TOKENS } from '../user_interfaces';\n\n@injectable()\nexport class SignUpUser implements ISignUpUser {\n constructor(\n @inject(USER_TOKENS.IUsersRepo) private user_repo: IUserRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n @inject(TOKENS.PASSWORD_SERVICE) private passwordService: IPasswordService,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(user_dto: SignupInputDto) {\n const user_salt = custom_long_nanoid();\n\n const hashed_password = await this.passwordService.hashPassword(\n user_dto.passwords.password.normalize('NFKC') + user_salt,\n );\n\n let user_entity: CreateUserEntity = {\n username: user_dto.email,\n salt: user_salt,\n hashed_password: hashed_password,\n email: user_dto.email,\n email_verified: false,\n created_at: new Date().toISOString(),\n };\n\n const user_created = await this.user_repo.create_user(user_entity);\n\n const record_version: CreateRecordVersionDto = {\n record_id: user_created.id,\n operation: OperationConst.INSERT,\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.USER,\n record: user_created,\n auth_uid: user_created.id,\n auth_role: 'consumer',\n };\n\n await this.create_record_version.execute(record_version);\n\n const created_dto: SignUpUserOutputDto = {\n id: user_created.id,\n email: user_created.email,\n };\n this.logger.debug('[SignUpUser] execute', { created_dto });\n return created_dto;\n }\n}\n","import { UserReadDto, UserUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { getChangedProperties } from '../../../lib/getChangedProperties';\nimport { IUserRepo, USER_TOKENS } from '../user_interfaces';\n\nexport interface IUpdateUserFeature {\n execute(input: UserUpdateDto): Promise<UserReadDto>;\n}\n\n@injectable()\nexport class UpdateUserFeat implements IUpdateUserFeature {\n constructor(@inject(USER_TOKENS.IUsersRepo) private repo: IUserRepo) {}\n\n async execute(input: UserUpdateDto): Promise<UserReadDto> {\n //get old record\n const old_record = await this.repo.read_user(input.id);\n if (!old_record) {\n throw new Error('User not found');\n }\n //get changed properties\n const changed_props = getChangedProperties(old_record, input);\n if (Object.keys(changed_props).length === 0) {\n return {\n id: old_record.id,\n username: old_record.username,\n email: old_record.email,\n email_verified: old_record.email_verified,\n user_type: old_record.user_type,\n created_at: old_record.created_at,\n updated_at: old_record.updated_at,\n };\n }\n //update user\n const updated = await this.repo.update_user({\n ...old_record,\n ...input,\n updated_at: new Date().toISOString(),\n });\n\n if (!updated) {\n throw new Error('User not found');\n }\n\n return {\n id: updated.id,\n username: updated.username || '',\n email: updated.email || '',\n email_verified: updated.email_verified || false,\n user_type: updated.user_type || 'consumer',\n created_at: old_record.created_at,\n updated_at: updated.updated_at || null,\n };\n }\n}\n","import { container } from 'tsyringe';\nimport { UserRepo } from './db/user_repo';\nimport { CreateUser } from './features/create_user';\nimport { UserDisplayLookupFeat } from './features/user_display_lookup_feat';\nimport { DeleteUser } from './features/delete_user';\nimport { GetAllUsersFeat, IGetAllUsersFeature } from './features/get_all_users_feat';\nimport { GetUserFeat, IGetUserFeature } from './features/get_user_feat';\nimport {\n GetTriageUsersFeat,\n IGetTriageUsersFeature,\n} from './features/get_triage_users_feat';\nimport {\n GetUsersForSelectionFeat,\n IGetUsersForSelectionFeature,\n} from './features/get_users_for_selection_feat';\nimport { ReadAllUsers } from './features/read_all_users';\nimport { ReadConsumers } from './features/read_consumers';\nimport { ReadUser } from './features/read_user';\nimport { SignUpUser } from './features/sign_up_user';\nimport { IUpdateUserFeature, UpdateUserFeat } from './features/update_user_feat';\nimport {\n ICreateUser,\n IDeleteUser,\n IReadAllUsers,\n IReadConsumers,\n IReadUser,\n ISignUpUser,\n IUserDisplayLookup,\n IUserRepo,\n USER_TOKENS,\n} from './user_interfaces';\n\nexport function registerUserContainer() {\n container.registerSingleton<IUserRepo>(USER_TOKENS.IUsersRepo, UserRepo);\n container.registerSingleton<IUserDisplayLookup>(\n USER_TOKENS.IUserDisplayLookup,\n UserDisplayLookupFeat,\n );\n container.registerSingleton<IReadAllUsers>(USER_TOKENS.IReadAllUsers, ReadAllUsers);\n container.registerSingleton<ISignUpUser>(USER_TOKENS.ISignUpUser, SignUpUser);\n container.registerSingleton<IDeleteUser>(USER_TOKENS.IDeleteUser, DeleteUser);\n container.registerSingleton<ICreateUser>(USER_TOKENS.ICreateUser, CreateUser);\n container.registerSingleton<IReadConsumers>(USER_TOKENS.IReadConsumers, ReadConsumers);\n container.registerSingleton<IReadUser>(USER_TOKENS.IReadUser, ReadUser);\n container.registerSingleton<IGetAllUsersFeature>(\n USER_TOKENS.IGetAllUsersFeature,\n GetAllUsersFeat,\n );\n container.registerSingleton<IGetUserFeature>(USER_TOKENS.IGetUserFeature, GetUserFeat);\n container.registerSingleton<IUpdateUserFeature>(\n USER_TOKENS.IUpdateUserFeature,\n UpdateUserFeat,\n );\n container.registerSingleton<IGetUsersForSelectionFeature>(\n USER_TOKENS.IGetUsersForSelectionFeature,\n GetUsersForSelectionFeat,\n );\n container.registerSingleton<IGetTriageUsersFeature>(\n USER_TOKENS.IGetTriageUsersFeature,\n GetTriageUsersFeat,\n );\n}\n","import { eq, getTableColumns, inArray } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { user_profile_table } from '../../../db/schemas/primary/primary_schema';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n CreateUserProfileEntity,\n ReadUserProfileEntity,\n UpdateUserProfileEntity,\n} from './user_profile_entity';\n\n@injectable()\nexport class UserProfileRepo implements IUserProfileRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n async create_user_profile(\n user_profileRecord: CreateUserProfileEntity,\n ): Promise<ReadUserProfileEntity> {\n const id = await this.router.generateId(RecordConst.USER_PROFILE);\n const result = await this.router.queryLatest((db) =>\n db\n .insert(user_profile_table)\n .values({\n id,\n ...user_profileRecord,\n })\n .returning(),\n );\n return result[0];\n }\n\n async read_user_profile(user_id: string): Promise<ReadUserProfileEntity> {\n const { ...rest } = getTableColumns(user_profile_table);\n\n const result = await this.router.queryAll((db) =>\n db\n .select({\n ...rest,\n })\n .from(user_profile_table)\n .where(eq(user_profile_table.user_id, user_id))\n .limit(1),\n );\n return result[0];\n }\n\n async read_user_profiles_by_user_ids(\n user_ids: string[],\n ): Promise<ReadUserProfileEntity[]> {\n if (user_ids.length === 0) return [];\n\n const { ...rest } = getTableColumns(user_profile_table);\n\n return this.router.queryAll((db) =>\n db\n .select({ ...rest })\n .from(user_profile_table)\n .where(inArray(user_profile_table.user_id, user_ids)),\n );\n }\n\n async update_user_profile(\n user_profileRecord: UpdateUserProfileEntity,\n ): Promise<ReadUserProfileEntity> {\n user_profileRecord.updated_at = new Date().toISOString();\n const { created_at, ...rest } = user_profileRecord;\n const result = await this.router.queryLatest((db) =>\n db\n .update(user_profile_table)\n .set({\n ...rest,\n })\n .where(eq(user_profile_table.id, user_profileRecord.id))\n .returning(),\n );\n return result[0];\n }\n}\n\nexport interface IUserProfileRepo {\n create_user_profile(user_profile: CreateUserProfileEntity): Promise<ReadUserProfileEntity>;\n read_user_profile(user_id: string): Promise<ReadUserProfileEntity>;\n read_user_profiles_by_user_ids(user_ids: string[]): Promise<ReadUserProfileEntity[]>;\n update_user_profile(user_profile: UpdateUserProfileEntity): Promise<ReadUserProfileEntity>;\n}\n","import { UserProfileReadDto } from '@dragonmastery/dragoncore-shared';\nimport { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { IUserProfileRepo } from '../db/user_profile_repo';\nimport { USER_PROFILE_TOKENS } from '../user_profile_interfaces';\n\n@injectable()\nexport class ReadUserProfile implements IReadUserProfile {\n constructor(\n @inject(USER_PROFILE_TOKENS.IUserProfilesRepo)\n private user_profile_repo: IUserProfileRepo,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute(user_id: string) {\n if (this.session.user.userId !== user_id) {\n throw new Error('You do not have permission to read this user profile');\n }\n\n const record = await this.user_profile_repo.read_user_profile(user_id);\n\n return {\n id: record.id,\n user_id: record.user_id,\n created_at: record.created_at,\n created_by: record.created_by,\n updated_at: record.updated_at,\n updated_by: record.updated_by,\n first_name: record.first_name,\n last_name: record.last_name,\n bio: record.bio,\n } satisfies UserProfileReadDto;\n }\n}\n\nexport interface IReadUserProfile {\n execute(user_id: string): Promise<UserProfileReadDto>;\n}\n","import { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { USER_PROFILE_TOKENS } from '../user_profile_interfaces';\n\nimport { UserProfileReadDto, UserProfileUpdateDto } from '@dragonmastery/dragoncore-shared';\nimport { RecordConst } from '../../../db/dbTypes';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { getChangedProperties } from '../../../lib/getChangedProperties';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { UpdateUserProfileEntity } from '../db/user_profile_entity';\nimport { IUserProfileRepo } from '../db/user_profile_repo';\n\n@injectable()\nexport class UpdateUserProfile implements IUpdateUserProfile {\n constructor(\n @inject(USER_PROFILE_TOKENS.IUserProfilesRepo)\n private user_profile_repo: IUserProfileRepo,\n @injectSession() private session: UserAppSession,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private create_record_version: ICreateRecordVersion,\n ) {}\n\n async execute(user_profile_dto: UserProfileUpdateDto) {\n let old_record = await this.user_profile_repo.read_user_profile(user_profile_dto.user_id);\n\n // if record does not exist then we need to create it\n if (!old_record) {\n const now = new Date().toISOString();\n old_record = await this.user_profile_repo.create_user_profile({\n user_id: this.session.user.userId,\n first_name: user_profile_dto.first_name,\n last_name: user_profile_dto.last_name,\n bio: user_profile_dto.bio,\n created_at: now,\n created_by: this.session.user.userId,\n updated_at: now, // Same as created_at initially\n updated_by: this.session.user.userId, // Same as created_by initially\n });\n }\n\n if (old_record.user_id !== this.session.user.userId) {\n throw new Error('You do not have permission to update this user profile');\n }\n\n let user_profile_entity: UpdateUserProfileEntity = {\n id: old_record.id,\n first_name: user_profile_dto.first_name,\n last_name: user_profile_dto.last_name,\n bio: user_profile_dto.bio,\n updated_at: new Date().toISOString(),\n updated_by: this.session.user.userId,\n };\n\n const changed_props = getChangedProperties(old_record, user_profile_entity);\n\n if (Object.keys(changed_props).length === 0) {\n return {\n ...old_record,\n } satisfies UserProfileReadDto;\n }\n\n const new_record = await this.user_profile_repo.update_user_profile(user_profile_entity);\n\n const record_version: CreateRecordVersionDto = {\n record_id: new_record.id,\n operation: 'update',\n recorded_at: new Date().toISOString(),\n record_type: RecordConst.USER_PROFILE,\n record: changed_props,\n old_record: old_record,\n auth_uid: this.session.user.userId,\n auth_role: this.session.user.user_type,\n };\n\n await this.create_record_version.execute(record_version);\n\n return {\n ...new_record,\n } satisfies UserProfileReadDto;\n }\n}\n\nexport interface IUpdateUserProfile {\n execute(user_profile: UserProfileUpdateDto): Promise<UserProfileReadDto>;\n}\n","import { container } from 'tsyringe';\nimport { IUserProfileRepo, UserProfileRepo } from './db/user_profile_repo';\nimport { IReadUserProfile, ReadUserProfile } from './features/read_user_profile';\nimport { IUpdateUserProfile, UpdateUserProfile } from './features/update_user_profile';\nimport { USER_PROFILE_TOKENS } from './user_profile_interfaces';\n\nexport function registerUserProfileContainer() {\n container.registerSingleton<IUserProfileRepo>(\n USER_PROFILE_TOKENS.IUserProfilesRepo,\n UserProfileRepo,\n );\n container.registerSingleton<IReadUserProfile>(\n USER_PROFILE_TOKENS.IReadUserProfile,\n ReadUserProfile,\n );\n container.registerSingleton<IUpdateUserProfile>(\n USER_PROFILE_TOKENS.IUpdateUserProfile,\n UpdateUserProfile,\n );\n}\n","import { and, desc, eq, sql } from 'drizzle-orm';\nimport { inject, injectable } from 'tsyringe';\nimport { DatabaseRouter } from '../../../db/database_router';\nimport { RecordConst } from '../../../db/dbTypes';\nimport {\n refresh_token_table,\n user_profile_table,\n user_table,\n} from '../../../db/schemas/primary/primary_schema';\nimport {\n createAuthenticatedState,\n createUnauthenticatedState,\n SessionState,\n} from '../../../db/session_state';\nimport { UserAppSession, UserSessionDetails } from '../../../db/user_app_session';\nimport { TOKENS } from '../../../di_tokens';\nimport {\n CreateRefreshTokenEntity,\n ReadRefreshTokenEntity,\n UpdateRefreshTokenEntity,\n} from './refresh_token_entity';\n\nexport interface IRefreshTokenRepo {\n // Token creation and retrieval\n createRefreshToken(refreshToken: CreateRefreshTokenEntity): Promise<ReadRefreshTokenEntity>;\n getRefreshToken(id: string): Promise<ReadRefreshTokenEntity | null>;\n getActiveTokenByFamily(family: string): Promise<ReadRefreshTokenEntity | null>;\n getAllActiveTokens(): Promise<ReadRefreshTokenEntity[]>;\n\n // Token management\n revokeRefreshToken(id: string, reason: string): Promise<UpdateRefreshTokenEntity>;\n revokeTokenFamily(family: string, reason: string): Promise<UpdateRefreshTokenEntity[]>;\n expireRefreshToken(id: string): Promise<UpdateRefreshTokenEntity>;\n\n // User-related operations\n getUserActiveRefreshTokens(userId: string): Promise<ReadRefreshTokenEntity[]>;\n getAdditionalUserData(userId: string): Promise<UserSessionDetails>;\n\n // Maintenance\n cleanupExpiredTokens(): Promise<void>;\n\n getLatestRefreshToken(userId: string): Promise<SessionState>;\n getLatestRefreshTokenByFamily(family: string): Promise<SessionState>;\n}\n\n@injectable()\nexport class RefreshTokenRepo implements IRefreshTokenRepo {\n constructor(@inject(TOKENS.IDatabaseRouter) private router: DatabaseRouter) {}\n\n // Token Creation and Retrieval\n async createRefreshToken(\n refreshToken: CreateRefreshTokenEntity,\n ): Promise<ReadRefreshTokenEntity> {\n const id = await this.router.generateId(RecordConst.REFRESH_TOKEN);\n let { token_family, ...rest } = refreshToken;\n if (!token_family) {\n token_family = await this.router.generateId(RecordConst.REFRESH_TOKEN_FAMILY);\n }\n const result = await this.router.queryLatest((db) =>\n db\n .insert(refresh_token_table)\n .values({\n id,\n token_family,\n ...rest,\n status: 'active',\n })\n .returning(),\n );\n\n return result[0];\n }\n\n async getRefreshToken(id: string): Promise<ReadRefreshTokenEntity | null> {\n const result = await this.router.queryById(id, (db) =>\n db.select().from(refresh_token_table).where(eq(refresh_token_table.id, id)).limit(1),\n );\n return result[0] || null;\n }\n\n async getActiveTokenByFamily(family: string): Promise<ReadRefreshTokenEntity | null> {\n const result = await this.router.queryAll((db) =>\n db\n .select()\n .from(refresh_token_table)\n .where(\n and(\n eq(refresh_token_table.token_family, family),\n eq(refresh_token_table.status, 'active'),\n ),\n )\n .limit(1),\n );\n return result[0] || null;\n }\n\n // Token Management\n async revokeRefreshToken(id: string, reason: string): Promise<UpdateRefreshTokenEntity> {\n const now = new Date().toISOString();\n const result = await this.router.queryById(id, (db) =>\n db\n .update(refresh_token_table)\n .set({\n status: 'revoked',\n revoked_at: now,\n revocation_reason: reason,\n })\n .where(eq(refresh_token_table.id, id))\n .returning(),\n );\n return result[0];\n }\n\n async revokeTokenFamily(\n family: string,\n reason: string,\n ): Promise<UpdateRefreshTokenEntity[]> {\n const now = new Date().toISOString();\n const result = await this.router.queryAll((db) =>\n db\n .update(refresh_token_table)\n .set({\n status: 'revoked',\n revoked_at: now,\n revocation_reason: reason,\n })\n .where(eq(refresh_token_table.token_family, family))\n .returning(),\n );\n return result;\n }\n\n async expireRefreshToken(id: string): Promise<UpdateRefreshTokenEntity> {\n const result = await this.router.queryById(id, (db) =>\n db\n .update(refresh_token_table)\n .set({\n status: 'expired',\n })\n .where(eq(refresh_token_table.id, id))\n .returning(),\n );\n return result[0];\n }\n\n // User-related Operations\n async getUserActiveRefreshTokens(userId: string): Promise<ReadRefreshTokenEntity[]> {\n return await this.router.queryAll((db) =>\n db\n .select()\n .from(refresh_token_table)\n .where(\n and(\n eq(refresh_token_table.user_id, userId),\n eq(refresh_token_table.status, 'active'),\n ),\n ),\n );\n }\n\n async getAdditionalUserData(userId: string): Promise<UserSessionDetails> {\n // console.log('getAdditionalUserData', userId);\n const db = await this.router.getConnectionForId(userId);\n\n const user_record_query = db\n .select()\n .from(user_table)\n .where(eq(user_table.id, userId))\n .limit(1);\n\n const user_profile_query = db\n .select()\n .from(user_profile_table)\n .where(eq(user_profile_table.id, userId))\n .limit(1);\n\n const [user_record, user_profile] = await Promise.all([\n user_record_query.then((results) => results[0]),\n user_profile_query.then((results) => results[0]),\n ]);\n\n return {\n userId: userId,\n username: user_record.username,\n email: user_record.email,\n email_verified: user_record.email_verified,\n user_type: user_record.user_type,\n first_name: user_profile?.first_name,\n last_name: user_profile?.last_name,\n avatar_url: user_profile?.avatar_url,\n };\n }\n\n async getAllActiveTokens(): Promise<ReadRefreshTokenEntity[]> {\n const tokens = await this.router.queryAll((db) =>\n db\n .select()\n .from(refresh_token_table)\n .where(eq(refresh_token_table.status, 'active'))\n .orderBy(desc(refresh_token_table.created_at)),\n );\n\n return tokens;\n }\n\n // Maintenance\n async cleanupExpiredTokens(): Promise<void> {\n const now = new Date().toISOString();\n await this.router.queryAll((db) =>\n db\n .update(refresh_token_table)\n .set({\n status: 'expired',\n })\n .where(\n and(\n eq(refresh_token_table.status, 'active'),\n sql`${refresh_token_table.expires_at} < ${now}`,\n ),\n ),\n );\n }\n\n async getLatestRefreshToken(userId: string) {\n const sessions = await this.router.queryAll((db) =>\n db\n .select()\n .from(refresh_token_table)\n .where(eq(refresh_token_table.user_id, userId))\n .orderBy(desc(refresh_token_table.created_at))\n .limit(1),\n );\n\n const session = sessions[0];\n\n // console.log(`get session ${performance.now() - start} ms`);\n\n if (!session || session.status !== 'active') {\n return createUnauthenticatedState();\n }\n\n const user_details = await this.getAdditionalUserData(session.user_id);\n\n // console.log(`get user details ${performance.now() - start} ms`);\n\n const session_with_details: UserAppSession = {\n id: session.id,\n created_at: session.created_at,\n expires_at: session.expires_at,\n status: session.status,\n user_agent: session.user_agent ?? undefined,\n ip_address: session.ip_address ?? undefined,\n user: user_details,\n };\n\n return createAuthenticatedState(session_with_details);\n }\n\n async getLatestRefreshTokenByFamily(family: string): Promise<any> {\n const sessions = await this.router.queryAll((db) =>\n db\n .select()\n .from(refresh_token_table)\n .where(eq(refresh_token_table.token_family, family))\n .orderBy(desc(refresh_token_table.created_at))\n .limit(1),\n );\n\n const session = sessions[0];\n\n // console.log(`get session ${performance.now() - start} ms`);\n\n if (!session || session.status !== 'active') {\n return createUnauthenticatedState();\n }\n\n const user_details = await this.getAdditionalUserData(session.user_id);\n\n // console.log(`get user details ${performance.now() - start} ms`);\n\n const session_with_details: UserAppSession = {\n id: session.id,\n created_at: session.created_at,\n expires_at: session.expires_at,\n status: session.status,\n user_agent: session.user_agent ?? undefined,\n ip_address: session.ip_address ?? undefined,\n user: user_details,\n };\n\n return createAuthenticatedState(session_with_details);\n }\n}\n","import { loginResponseSchema, userSessionSchema } from '@dragonmastery/dragoncore-shared';\nimport { z } from 'zod';\n\nexport const USER_SESSION_TOKENS = {\n IRefreshTokenRepo: Symbol('IRefreshTokenRepo'),\n ILoginUserSession: Symbol('ILoginUserSession'),\n IRefreshTokenSession: Symbol('IRefreshTokenSession'),\n IRevokeRefreshToken: Symbol('IDeleteUserSession'),\n IReadAllUserSessions: Symbol('IReadAllUserSessions'),\n};\n\n// Feature Interfaces\nexport interface ILoginUserSession {\n execute(loginDto: LoginUserSessionDto): Promise<LoginResponse>;\n}\n\nexport interface IRefreshTokenSession {\n execute(refreshToken: string): Promise<LoginResponse>;\n}\n\nexport interface IRevokeRefreshToken {\n execute(tokenId: string): Promise<void>;\n}\n\nexport interface IReadAllUserSessions {\n execute(): Promise<FrontendSessionDto[]>;\n}\n\n// DTO Interfaces\nexport interface LoginUserSessionDto {\n email: string;\n password: string;\n}\n\nexport type FrontendSessionDto = z.infer<typeof userSessionSchema>;\n\n// Response Types\nexport type LoginResponse = z.infer<typeof loginResponseSchema>;\n\nexport interface LoginReturnType {\n access_token: string;\n user_details_token: string;\n}\n\nexport interface RefreshTokenResponse {\n access_token: string;\n refresh_token: string;\n user_details_token: string;\n}\n\n// Error Types\nexport class InvalidCredentialsError extends Error {\n constructor() {\n super('Invalid email or password');\n this.name = 'InvalidCredentialsError';\n }\n}\n\nexport class InvalidRefreshTokenError extends Error {\n constructor() {\n super('Invalid or expired refresh token');\n this.name = 'InvalidRefreshTokenError';\n }\n}\n\nexport class TokenFamilyReusedError extends Error {\n constructor() {\n super('Token family has been revoked due to reuse');\n this.name = 'TokenFamilyReusedError';\n }\n}\n\nexport class UserNotFoundError extends Error {\n constructor() {\n super('User not found');\n this.name = 'UserNotFoundError';\n }\n}\n\nexport class SessionNotFoundError extends Error {\n constructor() {\n super('Session not found');\n this.name = 'SessionNotFoundError';\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { z as val } from 'zod';\nimport { OperationConst, RecordConst } from '../../../db/dbTypes';\nimport { TOKENS } from '../../../di_tokens';\nimport { IPasswordService } from '../../../lib/password_verifier';\nimport {\n CreateRecordVersionDto,\n ICreateRecordVersion,\n RECORD_VERSION_TOKENS,\n} from '../../../slices/record_version/record_version_interfaces';\nimport { IUserRepo, USER_TOKENS } from '../../../slices/user/user_interfaces';\nimport { CreateRefreshTokenEntity } from '../db/refresh_token_entity';\nimport { IRefreshTokenRepo } from '../db/refresh_token_repo';\nimport {\n IAccessTokenGenerator,\n IRefreshTokenGenerator,\n IUserDetailsTokenGenerator,\n JWT_TOKENS,\n} from '../jwt/jwt_interfaces';\nimport {\n FrontendSessionDto,\n ILoginUserSession,\n InvalidCredentialsError,\n LoginResponse,\n LoginUserSessionDto,\n USER_SESSION_TOKENS,\n} from '../user_session_interfaces';\n\n@injectable()\nexport class LoginUserSession implements ILoginUserSession {\n constructor(\n @inject(USER_SESSION_TOKENS.IRefreshTokenRepo)\n private refreshTokenRepo: IRefreshTokenRepo,\n @inject(USER_TOKENS.IUsersRepo) private userRepo: IUserRepo,\n @inject(RECORD_VERSION_TOKENS.ICreateRecordVersion)\n private createRecordVersion: ICreateRecordVersion,\n @inject(TOKENS.REQUEST) private request: Request,\n @inject(TOKENS.ENV) private env: Env,\n @inject(TOKENS.PASSWORD_SERVICE) private passwordService: IPasswordService,\n @inject(JWT_TOKENS.IAccessTokenGenerator)\n private accessTokenGenerator: IAccessTokenGenerator,\n @inject(JWT_TOKENS.IRefreshTokenGenerator)\n private refreshTokenGenerator: IRefreshTokenGenerator,\n @inject(JWT_TOKENS.IUserDetailsTokenGenerator)\n private userDetailsTokenGenerator: IUserDetailsTokenGenerator,\n ) {}\n\n async execute(loginDto: LoginUserSessionDto): Promise<LoginResponse> {\n const emailValidate = val\n .string()\n .trim()\n .toLowerCase()\n .min(3, 'Please enter your email.')\n .email('The email address is badly formatted.');\n\n const email = emailValidate.parse(loginDto.email);\n\n // Get user by email\n const user = await this.userRepo.read_user_by_email(email);\n if (!user) {\n throw new InvalidCredentialsError();\n }\n\n const passwordValidate = val\n .string()\n .min(8, 'Your password must have 8 characters or more.')\n .max(64, 'Your password must have less than 64 characters.');\n\n await passwordValidate.parseAsync(loginDto.password);\n\n const isValidPassword = await this.passwordService.verifyPassword(\n loginDto.password.normalize('NFKC') + user.salt,\n user.hashed_password,\n );\n\n if (!isValidPassword) {\n throw new InvalidCredentialsError();\n }\n\n // Create refresh token\n const now = new Date().toISOString();\n const expiresAt = new Date(\n Date.now() + parseInt(this.env.REFRESH_TOKEN_LIFETIME) * 1000,\n ).toISOString();\n\n const refreshTokenEntity: CreateRefreshTokenEntity = {\n user_id: user.id,\n status: 'active',\n created_at: now,\n expires_at: expiresAt,\n user_agent: this.request.headers.get('user-agent') || undefined,\n ip_address: this.request.headers.get('cf-connecting-ip') || undefined,\n };\n\n // Create refresh token record\n const createdToken = await this.refreshTokenRepo.createRefreshToken(refreshTokenEntity);\n\n // Create record version\n const recordVersion: CreateRecordVersionDto = {\n record_id: createdToken.id,\n operation: OperationConst.INSERT,\n recorded_at: now,\n record_type: RecordConst.REFRESH_TOKEN,\n record: createdToken,\n auth_uid: user.id,\n auth_role: user.user_type,\n };\n\n await this.createRecordVersion.execute(recordVersion);\n\n // Get user details for tokens\n const userDetails = await this.refreshTokenRepo.getAdditionalUserData(user.id);\n\n // Create frontend session\n const frontendSession: FrontendSessionDto = {\n created_at: createdToken.created_at,\n expires_at: createdToken.expires_at,\n status: createdToken.status!,\n user_agent: createdToken.user_agent,\n ip_address: createdToken.ip_address,\n user: userDetails,\n };\n\n // Generate JWT tokens\n const [accessToken, refreshToken, userDetailsToken] = await Promise.all([\n this.accessTokenGenerator.generateToken(\n user.id,\n userDetails.user_type,\n userDetails.email_verified,\n userDetails.username,\n userDetails.email,\n this.env.ACCESS_JWT_SECRET,\n parseInt(this.env.ACCESS_TOKEN_LIFETIME),\n createdToken.token_family,\n ),\n this.refreshTokenGenerator.generateToken(\n user.id,\n createdToken.token_family,\n this.env.REFRESH_JWT_SECRET,\n parseInt(this.env.REFRESH_TOKEN_LIFETIME),\n createdToken.id,\n ),\n this.userDetailsTokenGenerator.generateToken(\n user.id,\n frontendSession,\n this.env.USER_DETAILS_JWT_SECRET,\n parseInt(this.env.USER_DETAILS_TOKEN_LIFETIME),\n ),\n ]);\n\n return {\n frontend_session: frontendSession,\n access_token: accessToken,\n refresh_token: refreshToken,\n user_details_token: userDetailsToken,\n };\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { UserAppSession } from '../../../db/user_app_session';\nimport { injectSession } from '../../../decorators/inject_session';\nimport { IRefreshTokenRepo } from '../db/refresh_token_repo';\nimport {\n FrontendSessionDto,\n IReadAllUserSessions,\n USER_SESSION_TOKENS,\n} from '../user_session_interfaces';\n\n@injectable()\nexport class ReadAllUserSessions implements IReadAllUserSessions {\n constructor(\n @inject(USER_SESSION_TOKENS.IRefreshTokenRepo)\n private refreshTokenRepo: IRefreshTokenRepo,\n @injectSession() private session: UserAppSession,\n ) {}\n\n async execute(): Promise<FrontendSessionDto[]> {\n // Get user's active refresh tokens\n const activeTokens = await this.refreshTokenRepo.getUserActiveRefreshTokens(\n this.session.user.userId,\n );\n\n // Get user details once since it's the same user\n const userDetails = await this.refreshTokenRepo.getAdditionalUserData(\n this.session.user.userId,\n );\n\n // Map tokens to frontend sessions\n return activeTokens.map((token) => ({\n created_at: token.created_at,\n expires_at: token.expires_at,\n status: token.status!,\n user_agent: token.user_agent,\n ip_address: token.ip_address,\n user: userDetails,\n }));\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../../../di_tokens';\nimport { Logger } from '../../../utils/logger';\nimport { CreateRefreshTokenEntity } from '../db/refresh_token_entity';\nimport { IRefreshTokenRepo } from '../db/refresh_token_repo';\nimport {\n IAccessTokenGenerator,\n IRefreshTokenGenerator,\n IRefreshTokenVerifier,\n IUserDetailsTokenGenerator,\n JWT_TOKENS,\n} from '../jwt/jwt_interfaces';\nimport { RefreshTokenPayload } from '../jwt/jwt_types';\nimport {\n FrontendSessionDto,\n IRefreshTokenSession,\n InvalidRefreshTokenError,\n LoginResponse,\n TokenFamilyReusedError,\n USER_SESSION_TOKENS,\n} from '../user_session_interfaces';\n\n@injectable()\nexport class RefreshTokenSession implements IRefreshTokenSession {\n constructor(\n @inject(USER_SESSION_TOKENS.IRefreshTokenRepo)\n private refreshTokenRepo: IRefreshTokenRepo,\n @inject(TOKENS.ENV) private env: Env,\n @inject(TOKENS.LOGGER) private logger: Logger,\n @inject(JWT_TOKENS.IRefreshTokenVerifier) private tokenVerifier: IRefreshTokenVerifier,\n @inject(JWT_TOKENS.IAccessTokenGenerator)\n private accessTokenGenerator: IAccessTokenGenerator,\n @inject(JWT_TOKENS.IRefreshTokenGenerator)\n private refreshTokenGenerator: IRefreshTokenGenerator,\n @inject(JWT_TOKENS.IUserDetailsTokenGenerator)\n private userDetailsTokenGenerator: IUserDetailsTokenGenerator,\n ) {}\n\n async execute(refreshToken: string): Promise<LoginResponse> {\n this.logger.debug('[RefreshTokenSession] Starting token refresh process');\n try {\n // Verify the refresh token\n let payload;\n try {\n payload = await this.tokenVerifier.verifyToken<RefreshTokenPayload>(\n refreshToken,\n this.env.REFRESH_JWT_SECRET,\n );\n this.logger.trace('[RefreshTokenSession] Token verified successfully, payload:', {\n jti: payload.jti,\n family: payload.family,\n sub: payload.sub,\n exp: payload.exp,\n iat: payload.iat,\n });\n } catch (verifyError) {\n this.logger.error(\n '[RefreshTokenSession] Token verification failed:',\n verifyError instanceof Error\n ? { message: verifyError.message, stack: verifyError.stack }\n : { error: String(verifyError) },\n );\n throw new InvalidRefreshTokenError();\n }\n\n // Get the token from the database\n this.logger.debug(\n `[RefreshTokenSession] Retrieving token from database with ID: ${payload.jti}`,\n );\n const token = await this.refreshTokenRepo.getRefreshToken(payload.jti);\n if (!token) {\n this.logger.error('[RefreshTokenSession] Token not found in database');\n throw new InvalidRefreshTokenError();\n }\n if (token.status !== 'active') {\n this.logger.error(\n `[RefreshTokenSession] Token found but status is not active: ${token.status}`,\n );\n throw new InvalidRefreshTokenError();\n }\n this.logger.trace('[RefreshTokenSession] Token found and active:', {\n id: token.id,\n user_id: token.user_id,\n status: token.status,\n expires_at: token.expires_at,\n });\n\n // Check if there's a newer token in the family\n this.logger.debug(\n `[RefreshTokenSession] Checking for newer tokens in family: ${payload.family}`,\n );\n const activeToken = await this.refreshTokenRepo.getActiveTokenByFamily(payload.family);\n if (activeToken && activeToken.id !== token.id) {\n this.logger.error(\n '[RefreshTokenSession] Token reuse detected - newer token exists in family',\n );\n this.logger.trace('[RefreshTokenSession] Active token:', {\n id: activeToken.id,\n user_id: activeToken.user_id,\n created_at: activeToken.created_at,\n });\n this.logger.trace('[RefreshTokenSession] Current token:', {\n id: token.id,\n user_id: token.user_id,\n created_at: token.created_at,\n });\n\n // Token reuse detected - revoke the entire family\n this.logger.debug(`[RefreshTokenSession] Revoking token family: ${payload.family}`);\n await this.refreshTokenRepo.revokeTokenFamily(payload.family, 'Token reuse detected');\n throw new TokenFamilyReusedError();\n }\n\n // Get user details for new tokens\n this.logger.debug(\n `[RefreshTokenSession] Getting user details for user ID: ${token.user_id}`,\n );\n const userDetails = await this.refreshTokenRepo.getAdditionalUserData(token.user_id);\n this.logger.debug('[RefreshTokenSession] User details retrieved');\n\n // Create new refresh token\n const now = new Date().toISOString();\n const refreshTokenLifetime = parseInt(this.env.REFRESH_TOKEN_LIFETIME);\n const refreshTokenExpiresAt = new Date(\n Date.now() + refreshTokenLifetime * 1000,\n ).toISOString();\n\n this.logger.debug(\n `[RefreshTokenSession] Creating new refresh token with lifetime: ${refreshTokenLifetime}s`,\n );\n this.logger.debug(\n `[RefreshTokenSession] New token expires at: ${refreshTokenExpiresAt}`,\n );\n\n const newRefreshTokenRecord: CreateRefreshTokenEntity = {\n user_id: token.user_id,\n token_family: token.token_family,\n previous_token_id: token.id,\n status: 'active',\n created_at: now,\n expires_at: refreshTokenExpiresAt,\n user_agent: token.user_agent,\n ip_address: token.ip_address,\n };\n\n const FrontendSessionDto: FrontendSessionDto = {\n created_at: now,\n expires_at: newRefreshTokenRecord.expires_at,\n status: newRefreshTokenRecord.status!,\n user_agent: newRefreshTokenRecord.user_agent,\n ip_address: newRefreshTokenRecord.ip_address,\n user: userDetails,\n };\n\n // Create new token and expire old one atomically\n this.logger.debug(\n '[RefreshTokenSession] Creating new token and expiring old one atomically',\n );\n const createdTokens = await Promise.all([\n this.refreshTokenRepo.createRefreshToken(newRefreshTokenRecord),\n this.refreshTokenRepo.expireRefreshToken(token.id),\n ]);\n this.logger.debug(\n `[RefreshTokenSession] New token created with ID: ${createdTokens[0].id}`,\n );\n this.logger.debug(`[RefreshTokenSession] Old token expired with ID: ${token.id}`);\n\n // Generate new tokens\n this.logger.debug('[RefreshTokenSession] Generating new JWT tokens');\n const accessTokenLifetime = parseInt(this.env.ACCESS_TOKEN_LIFETIME);\n const userDetailsTokenLifetime = parseInt(this.env.USER_DETAILS_TOKEN_LIFETIME);\n\n this.logger.debug(\n `[RefreshTokenSession] Access token lifetime: ${accessTokenLifetime}s`,\n );\n this.logger.debug(\n `[RefreshTokenSession] User details token lifetime: ${userDetailsTokenLifetime}s`,\n );\n\n const [accessToken, newRefreshToken, userDetailsToken] = await Promise.all([\n this.accessTokenGenerator.generateToken(\n token.user_id,\n userDetails.user_type,\n userDetails.email_verified,\n userDetails.username,\n userDetails.email,\n this.env.ACCESS_JWT_SECRET,\n accessTokenLifetime,\n token.token_family,\n ),\n this.refreshTokenGenerator.generateToken(\n token.user_id,\n token.token_family,\n this.env.REFRESH_JWT_SECRET,\n refreshTokenLifetime,\n createdTokens[0].id,\n ),\n this.userDetailsTokenGenerator.generateToken(\n token.user_id,\n FrontendSessionDto,\n this.env.USER_DETAILS_JWT_SECRET,\n userDetailsTokenLifetime,\n ),\n ]);\n this.logger.debug('[RefreshTokenSession] All tokens generated successfully');\n\n this.logger.debug('[RefreshTokenSession] Token refresh process completed successfully');\n return {\n frontend_session: FrontendSessionDto,\n access_token: accessToken,\n refresh_token: newRefreshToken,\n user_details_token: userDetailsToken,\n };\n } catch (error) {\n this.logger.error(\n '[RefreshTokenSession] Error during token refresh:',\n error instanceof Error\n ? { message: error.message, stack: error.stack }\n : { error: String(error) },\n );\n if (\n error instanceof InvalidRefreshTokenError ||\n error instanceof TokenFamilyReusedError\n ) {\n throw error;\n }\n this.logger.error(\n '[RefreshTokenSession] Unexpected error, wrapping as InvalidRefreshTokenError',\n );\n throw new InvalidRefreshTokenError();\n }\n }\n}\n","import { inject, injectable } from 'tsyringe';\nimport { TOKENS } from '../../../di_tokens';\nimport { Logger } from '../../../utils/logger';\nimport { IRefreshTokenRepo } from '../db/refresh_token_repo';\nimport { IRefreshTokenVerifier, JWT_TOKENS } from '../jwt/jwt_interfaces';\nimport { RefreshTokenPayload } from '../jwt/jwt_types';\nimport { IRevokeRefreshToken, USER_SESSION_TOKENS } from '../user_session_interfaces';\n\n@injectable()\nexport class RevokeRefreshToken implements IRevokeRefreshToken {\n constructor(\n @inject(USER_SESSION_TOKENS.IRefreshTokenRepo)\n private refreshTokenRepo: IRefreshTokenRepo,\n @inject(TOKENS.ENV) private env: Env,\n @inject(JWT_TOKENS.IRefreshTokenVerifier) private tokenVerifier: IRefreshTokenVerifier,\n @inject(TOKENS.LOGGER) private logger: Logger,\n ) {}\n\n async execute(refreshToken: string): Promise<void> {\n try {\n // Verify and decode the refresh token\n const payload = await this.tokenVerifier.verifyToken<RefreshTokenPayload>(\n refreshToken,\n this.env.REFRESH_JWT_SECRET,\n );\n\n // Revoke the entire family to invalidate all related tokens\n await this.refreshTokenRepo.revokeTokenFamily(payload.family, 'User logged out');\n } catch (error) {\n this.logger.error('Error revoking refresh token', { error });\n }\n }\n}\n","import { UserTypeValues } from '@dragonmastery/dragoncore-shared';\nimport { injectable } from 'tsyringe';\nimport { IAccessTokenGenerator } from './jwt_interfaces';\nimport { generateAccessToken } from './jwt_utils';\n\n@injectable()\nexport class AccessTokenGenerator implements IAccessTokenGenerator {\n async generateToken(\n userId: string,\n userType: UserTypeValues,\n emailVerified: boolean,\n username: string,\n email: string,\n secret: string,\n expiresIn: number,\n family: string,\n ): Promise<string> {\n return generateAccessToken(\n userId,\n userType,\n emailVerified,\n username,\n email,\n secret,\n expiresIn,\n family,\n );\n }\n}\n","import { injectable } from 'tsyringe';\nimport { IRefreshTokenGenerator } from './jwt_interfaces';\nimport { generateRefreshToken } from './jwt_utils';\n\n@injectable()\nexport class RefreshTokenGenerator implements IRefreshTokenGenerator {\n async generateToken(\n userId: string,\n tokenFamily: string,\n secret: string,\n expiresIn: number,\n tokenId: string,\n ): Promise<string> {\n return generateRefreshToken(userId, tokenFamily, secret, expiresIn, tokenId);\n }\n}\n","import { injectable } from 'tsyringe';\nimport { IRefreshTokenVerifier } from './jwt_interfaces';\nimport {\n AccessTokenPayload,\n PasswordResetTokenPayload,\n RefreshTokenPayload,\n UserDetailsTokenPayload,\n} from './jwt_types';\nimport { verifyToken } from './jwt_utils';\n\n@injectable()\nexport class RefreshTokenVerifier implements IRefreshTokenVerifier {\n async verifyToken<\n T extends\n | AccessTokenPayload\n | RefreshTokenPayload\n | UserDetailsTokenPayload\n | PasswordResetTokenPayload,\n >(token: string, secret: string): Promise<T> {\n return verifyToken<T>(token, secret);\n }\n}\n","import { injectable } from 'tsyringe';\nimport { FrontendSessionDto } from '../user_session_interfaces';\nimport { IUserDetailsTokenGenerator } from './jwt_interfaces';\nimport { generateUserDetailsToken } from './jwt_utils';\n\n@injectable()\nexport class UserDetailsTokenGenerator implements IUserDetailsTokenGenerator {\n async generateToken(\n userId: string,\n userDetails: FrontendSessionDto,\n secret: string,\n expiresIn: number,\n ): Promise<string> {\n return generateUserDetailsToken(userId, userDetails, secret, expiresIn);\n }\n}\n","import { container } from 'tsyringe';\nimport { IRefreshTokenRepo, RefreshTokenRepo } from './db/refresh_token_repo';\nimport { LoginUserSession } from './features/login_user_session_feat';\nimport { ReadAllUserSessions } from './features/read_all_user_sessions_feat';\nimport { RefreshTokenSession } from './features/refresh_token_feat';\nimport { RevokeRefreshToken } from './features/revoke_refresh_token_feat';\nimport { AccessTokenGenerator } from './jwt/access_token_generator';\nimport {\n IAccessTokenGenerator,\n IRefreshTokenGenerator,\n IRefreshTokenVerifier,\n IUserDetailsTokenGenerator,\n JWT_TOKENS,\n} from './jwt/jwt_interfaces';\nimport { RefreshTokenGenerator } from './jwt/refresh_token_generator';\nimport { RefreshTokenVerifier } from './jwt/refresh_token_verifier';\nimport { UserDetailsTokenGenerator } from './jwt/user_details_token_generator';\nimport {\n ILoginUserSession,\n IReadAllUserSessions,\n IRefreshTokenSession,\n IRevokeRefreshToken,\n USER_SESSION_TOKENS,\n} from './user_session_interfaces';\n\nexport function registerUserSessionContainer() {\n // Register JWT services - singletons\n container.registerSingleton<IAccessTokenGenerator>(\n JWT_TOKENS.IAccessTokenGenerator,\n AccessTokenGenerator,\n );\n container.registerSingleton<IRefreshTokenGenerator>(\n JWT_TOKENS.IRefreshTokenGenerator,\n RefreshTokenGenerator,\n );\n container.registerSingleton<IRefreshTokenVerifier>(\n JWT_TOKENS.IRefreshTokenVerifier,\n RefreshTokenVerifier,\n );\n container.registerSingleton<IUserDetailsTokenGenerator>(\n JWT_TOKENS.IUserDetailsTokenGenerator,\n UserDetailsTokenGenerator,\n );\n\n // Register repositories - singleton\n container.registerSingleton<IRefreshTokenRepo>(\n USER_SESSION_TOKENS.IRefreshTokenRepo,\n RefreshTokenRepo,\n );\n\n // Register features - singletons\n container.registerSingleton<ILoginUserSession>(\n USER_SESSION_TOKENS.ILoginUserSession,\n LoginUserSession,\n );\n container.registerSingleton<IRevokeRefreshToken>(\n USER_SESSION_TOKENS.IRevokeRefreshToken,\n RevokeRefreshToken,\n );\n container.registerSingleton<IRefreshTokenSession>(\n USER_SESSION_TOKENS.IRefreshTokenSession,\n RefreshTokenSession,\n );\n container.registerSingleton<IReadAllUserSessions>(\n USER_SESSION_TOKENS.IReadAllUserSessions,\n ReadAllUserSessions,\n );\n}\n","import { container } from 'tsyringe';\n\nimport { AppSettingsRepo, IAppSettingsRepo } from './db/schemas/app_setting/app_settings_repo';\nimport { registerAuthenticatedSession } from './decorators/inject_session';\nimport { TOKENS } from './di_tokens';\nimport { CookieService, ICookieService } from './lib/cookie_service';\nimport {\n DisplayIdPrefixRegistry,\n DisplayIdPrefixService,\n IDisplayIdPrefixService,\n} from './lib/display_id_prefix_service';\nimport { DISPLAY_ID_PREFIX_TOKENS } from './lib/display_id_prefix_tokens';\nimport { EmailService, IEmailService } from './lib/email_service';\nimport { IPasswordService, PasswordService } from './lib/password_verifier';\nimport { registerAppSettingsContainer } from './slices/app_settings/app_settings_container';\nimport { registerAttachmentContainer } from './slices/attachment/attachment_container';\nimport { registerCustomerDependencies } from './slices/customer/customer_registration';\nimport { registerNoteContainer } from './slices/note/note_container';\nimport { registerPasswordResetContainer } from './slices/password_reset/password_reset_container';\nimport { registerRecordSubscriberDependencies } from './slices/record_subscriber/record_subscriber_registration';\nimport { registerRecordVersionContainer } from './slices/record_version/record_version_container';\nimport { registerSavedFilterContainer } from './slices/saved_filter/saved_filter_container';\nimport { registerSupportStaffDependencies } from './slices/support_staff/support_staff_registration';\nimport { registerSupportTicketDependencies } from './slices/support_ticket/support_ticket_registration';\nimport { registerTeamDependencies } from './slices/team/team_container';\nimport { registerTeamMemberContainer } from './slices/team_member/team_member_container';\nimport { registerUserContainer } from './slices/user/user_container';\nimport { registerUserProfileContainer } from './slices/user_profile/user_profile_container';\nimport { registerUserSessionContainer } from './slices/user_session/user_session_container';\n\nexport type Factory<T = any> = (dependencyContainer: any) => T;\n\nexport interface ContainerInstances {\n instances?: Record<symbol, any>;\n factories?: Record<symbol, Factory>;\n}\n\n/**\n * Registers core services and dragoncore-api slice containers\n * This is the shared container setup that can be used by both\n * dragoncore-api and the main API worker\n */\nexport function registerCoreContainer() {\n registerAuthenticatedSession();\n\n // Core services - singletons for performance\n container.registerSingleton<IPasswordService>(TOKENS.PASSWORD_SERVICE, PasswordService);\n container.registerSingleton<IAppSettingsRepo>(TOKENS.IAppSettingsRepo, AppSettingsRepo);\n container.registerSingleton<ICookieService>(TOKENS.COOKIE_SERVICE, CookieService);\n container.registerSingleton<IEmailService>(TOKENS.IEmailService, EmailService);\n\n // Register display ID prefix registry and service\n container.registerSingleton<DisplayIdPrefixRegistry>(\n DISPLAY_ID_PREFIX_TOKENS.PrefixRegistry,\n DisplayIdPrefixRegistry,\n );\n container.registerSingleton<IDisplayIdPrefixService>(\n DISPLAY_ID_PREFIX_TOKENS.IDisplayIdPrefixService,\n DisplayIdPrefixService,\n );\n\n // Register dragoncore-api slice dependencies\n registerPasswordResetContainer();\n registerRecordSubscriberDependencies();\n registerRecordVersionContainer();\n registerSavedFilterContainer();\n registerSupportStaffDependencies();\n registerUserContainer();\n registerUserProfileContainer();\n registerUserSessionContainer();\n registerCustomerDependencies();\n registerSupportTicketDependencies();\n registerAttachmentContainer();\n registerAppSettingsContainer();\n registerNoteContainer();\n registerTeamDependencies();\n registerTeamMemberContainer();\n}\n\n/**\n * Creates a request-scoped child container with the core registrations\n * and any additional instances/factories provided in the config\n *\n * @param config - Container configuration with instances and/or factories\n * @param additionalRegistrations - Optional function to register additional slice containers\n */\nexport function createRequestContainer(\n config: ContainerInstances | Record<symbol, any>,\n additionalRegistrations?: () => void,\n) {\n // Register core services and slices\n registerCoreContainer();\n\n // Register any additional slices (e.g., worker-specific slices)\n if (additionalRegistrations) {\n additionalRegistrations();\n }\n\n const childContainer = container.createChildContainer();\n\n // Support both old format (just instances) and new format (with factories)\n const isContainerInstances = (\n c: ContainerInstances | Record<symbol, any>,\n ): c is ContainerInstances => {\n return 'instances' in c || 'factories' in c;\n };\n\n if (isContainerInstances(config)) {\n // New format with factories support\n const instances = config.instances;\n const factories = config.factories;\n\n // Register instances (eager)\n if (instances) {\n for (const token of Object.getOwnPropertySymbols(instances)) {\n childContainer.registerInstance(token, instances[token]);\n }\n }\n\n // Register factories (lazy) - using tsyringe's register with useFactory\n // Cache instances to ensure singleton behavior per request\n if (factories) {\n const factoryCache = new Map<symbol, any>();\n for (const token of Object.getOwnPropertySymbols(factories)) {\n childContainer.register(token, {\n useFactory: (container) => {\n if (!factoryCache.has(token)) {\n factoryCache.set(token, factories[token](container));\n }\n return factoryCache.get(token);\n },\n });\n }\n }\n } else {\n // Old format - just instances\n for (const token of Object.getOwnPropertySymbols(config)) {\n childContainer.registerInstance(token, config[token]);\n }\n }\n\n return childContainer;\n}\n","import type { AppSettingsApi } from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\n\nexport class AppSettingsApiServer extends RpcTarget implements AppSettingsApi {\n constructor(_container: DependencyContainer) {\n super();\n }\n\n // Notification emails removed - use assignee-based triage instead\n}\n","// App settings tokens - reserved for future features (e.g. LAST_SUPPORT_TICKET_ASSIGNEE)\nexport const APP_SETTINGS_TOKENS = {} as const;\n","import {\n AttachmentFiltersSchema,\n AttachmentFolderReadSchema,\n AttachmentPageSchema,\n type AttachmentApi,\n type AttachmentFiltersDto,\n type AttachmentFolderReadDto,\n type AttachmentPageDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod } from '../../middleware/rpc_mid';\nimport {\n ATTACHMENT_FOLDER_TOKENS,\n ATTACHMENT_TOKENS,\n type ICreateAttachmentFolder,\n type IDeleteAttachment,\n type IReadAllAttachmentsByRecord,\n} from './attachment_interfaces';\n\nconst CreateFolderInputSchema = z.object({\n record_id: z.string(),\n record_type: z.string(),\n folder_name: z.string(),\n parent_folder_id: z.string().optional().nullable(),\n});\n\nexport class AttachmentApiServer extends RpcTarget implements AttachmentApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async createFolder(input: {\n record_id: string;\n record_type: string;\n folder_name: string;\n parent_folder_id?: string | null;\n }): Promise<AttachmentFolderReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'AttachmentApiServer.createFolder',\n input,\n inputSchema: CreateFolderInputSchema,\n outputSchema: AttachmentFolderReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateAttachmentFolder>(\n ATTACHMENT_FOLDER_TOKENS.ICreateAttachmentFolder,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async listAttachments(filters: AttachmentFiltersDto): Promise<AttachmentPageDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'AttachmentApiServer.listAttachments',\n input: filters,\n inputSchema: AttachmentFiltersSchema,\n outputSchema: AttachmentPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IReadAllAttachmentsByRecord>(\n ATTACHMENT_TOKENS.IReadAllAttachmentsByRecord,\n );\n // Zod applies default for include_folders, so it's always boolean at runtime\n return await feature.execute(validated as AttachmentFiltersDto);\n },\n });\n }\n\n async deleteAttachment(id: string): Promise<boolean> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'AttachmentApiServer.deleteAttachment',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.boolean(),\n execute: async (id, container) => {\n const feature = container.resolve<IDeleteAttachment>(\n ATTACHMENT_TOKENS.IDeleteAttachment,\n );\n return await feature.execute(id);\n },\n });\n }\n}\n","import {\n AddCreditsSchema,\n CreditBalanceSchema,\n CreditTransactionFiltersSchema,\n CreditTransactionPageSchema,\n SetMonthlyAllocationSchema,\n type AddCreditsDto,\n type CreditBalanceDto,\n type CreditTransactionFiltersDto,\n type CreditTransactionReadDto,\n type CustomerApi,\n type PagedResult,\n type SetMonthlyAllocationDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport { CUSTOMER_TOKENS } from './customer_tokens';\nimport type { IAddCreditsFeature } from './features/add_credits_feat';\nimport type { IGetCreditTransactionsFeature } from './features/get_credit_transactions_feat';\nimport type { IResetMonthlyBalanceFeature } from './features/reset_monthly_balance_feat';\nimport type { ISetMonthlyAllocationFeature } from './features/set_monthly_allocation_feat';\nimport { CREDIT_SERVICE_TOKEN, type ICreditService } from './services/credit_service';\n\nexport class CustomerApiServer extends RpcTarget implements CustomerApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async getCreditBalance(): Promise<CreditBalanceDto> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'CustomerApiServer.getCreditBalance',\n input: undefined,\n outputSchema: CreditBalanceSchema,\n execute: async (_, container) => {\n const creditService = container.resolve<ICreditService>(CREDIT_SERVICE_TOKEN);\n return await creditService.getGlobalBalance();\n },\n });\n }\n\n async getCreditTransactions(\n filters?: CreditTransactionFiltersDto,\n ): Promise<PagedResult<CreditTransactionReadDto>> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'CustomerApiServer.getCreditTransactions',\n input: filters,\n inputSchema: CreditTransactionFiltersSchema.optional(),\n outputSchema: CreditTransactionPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IGetCreditTransactionsFeature>(\n CUSTOMER_TOKENS.IGetCreditTransactionsFeature,\n );\n return await feature.execute(validated ?? {});\n },\n });\n }\n\n async addCredits(input: AddCreditsDto): Promise<CreditBalanceDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'CustomerApiServer.addCredits',\n input,\n inputSchema: AddCreditsSchema,\n outputSchema: CreditBalanceSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IAddCreditsFeature>(\n CUSTOMER_TOKENS.IAddCreditsFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async setMonthlyAllocation(input: SetMonthlyAllocationDto): Promise<CreditBalanceDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'CustomerApiServer.setMonthlyAllocation',\n input,\n inputSchema: SetMonthlyAllocationSchema,\n outputSchema: CreditBalanceSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ISetMonthlyAllocationFeature>(\n CUSTOMER_TOKENS.ISetMonthlyAllocationFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async resetMonthlyBalance(): Promise<CreditBalanceDto> {\n return rpcMethodPartial({\n auth: 'admin',\n container: this.container,\n context: 'CustomerApiServer.resetMonthlyBalance',\n input: undefined,\n outputSchema: CreditBalanceSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IResetMonthlyBalanceFeature>(\n CUSTOMER_TOKENS.IResetMonthlyBalanceFeature,\n );\n return await feature.execute();\n },\n });\n }\n}\n","import {\n createPaginatedSchema,\n NoteCreateSchema,\n NoteFiltersSchema,\n NoteReadSchema,\n NoteUpdateSchema,\n type NoteApi,\n type NoteCreateDto,\n type NoteFiltersDto,\n type NoteReadDto,\n type NoteUpdateDto,\n type PagedResult,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport type { INoteRepo } from './db/note_repo';\nimport type { ICreateNoteFeature } from './features/create_note_feat';\nimport type { IDeleteNoteFeature } from './features/delete_note_feat';\nimport type { IGetNotesFeature } from './features/get_notes_feat';\nimport type { IUpdateNoteFeature } from './features/update_note_feat';\nimport { NOTE_TOKENS } from './note_tokens';\n\nconst NotePageSchema = createPaginatedSchema(NoteReadSchema);\n\nexport class NoteApiServer extends RpcTarget implements NoteApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async createNote(input: NoteCreateDto): Promise<NoteReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'NoteApiServer.createNote',\n input,\n inputSchema: NoteCreateSchema,\n outputSchema: NoteReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateNoteFeature>(NOTE_TOKENS.ICreateNoteFeature);\n // Zod applies default for is_internal, so it's always boolean at runtime\n return await feature.execute(input as NoteCreateDto);\n },\n });\n }\n\n async updateNote(input: NoteUpdateDto): Promise<NoteReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'NoteApiServer.updateNote',\n input,\n inputSchema: NoteUpdateSchema,\n outputSchema: NoteReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateNoteFeature>(NOTE_TOKENS.IUpdateNoteFeature);\n return await feature.execute(input);\n },\n });\n }\n\n async deleteNote(id: string): Promise<void> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'NoteApiServer.deleteNote',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.void(),\n execute: async (id, container) => {\n const feature = container.resolve<IDeleteNoteFeature>(NOTE_TOKENS.IDeleteNoteFeature);\n await feature.execute(id);\n },\n });\n }\n\n async getNotes(filters: NoteFiltersDto): Promise<PagedResult<NoteReadDto>> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'NoteApiServer.getNotes',\n input: filters,\n inputSchema: NoteFiltersSchema,\n outputSchema: NotePageSchema,\n execute: async (filters, container) => {\n const feature = container.resolve<IGetNotesFeature>(NOTE_TOKENS.IGetNotesFeature);\n return await feature.execute(filters);\n },\n });\n }\n\n async getNoteTags(): Promise<string[]> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'NoteApiServer.getNoteTags',\n input: undefined,\n outputSchema: z.array(z.string()),\n execute: async (_, container) => {\n // Direct repo access is fine for simple read queries like this\n const repo = container.resolve<INoteRepo>(NOTE_TOKENS.INoteRepo);\n return await repo.getUniqueTags();\n },\n });\n }\n}\n","import {\n changePasswordSchema,\n resetPasswordSchema,\n type ChangePasswordInputDto,\n type PasswordResetApi,\n type ResetPasswordDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod } from '../../middleware/rpc_mid';\nimport {\n PASSWORD_RESET_TOKENS,\n type IChangeUserPassword,\n type IForgotPassword,\n type IResetPassword,\n} from './password_reset_interfaces';\n\nconst OkResponseSchema = z.object({ ok: z.boolean() });\n\nexport class PasswordResetApiServer extends RpcTarget implements PasswordResetApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async changePassword(input: ChangePasswordInputDto): Promise<{ ok: boolean }> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'PasswordResetApiServer.changePassword',\n input,\n inputSchema: changePasswordSchema,\n outputSchema: OkResponseSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IChangeUserPassword>(\n PASSWORD_RESET_TOKENS.IChangeUserPassword,\n );\n await feature.execute(input);\n return { ok: true };\n },\n });\n }\n\n async forgotPassword(email: string): Promise<{ ok: boolean }> {\n return rpcMethod({\n auth: 'public',\n container: this.container,\n context: 'PasswordResetApiServer.forgotPassword',\n input: email,\n inputSchema: z.string().email(),\n outputSchema: OkResponseSchema,\n execute: async (email, container) => {\n const feature = container.resolve<IForgotPassword>(\n PASSWORD_RESET_TOKENS.IForgotPassword,\n );\n await feature.execute(email);\n return { ok: true };\n },\n });\n }\n\n async resetPassword(input: ResetPasswordDto): Promise<{ ok: boolean }> {\n return rpcMethod({\n auth: 'public',\n container: this.container,\n context: 'PasswordResetApiServer.resetPassword',\n input,\n inputSchema: resetPasswordSchema,\n outputSchema: OkResponseSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IResetPassword>(\n PASSWORD_RESET_TOKENS.IResetPassword,\n );\n await feature.execute(input);\n return { ok: true };\n },\n });\n }\n}\n","import {\n recordVersionFiltersInputBreadcrumbSchema,\n recordVersionFiltersInputSchema,\n recordVersionPageBreadcrumbSchema,\n recordVersionPageSchema,\n recordVersionSchema,\n recordVersionTrackerActivityInputSchema,\n type RecordType,\n type RecordVersionApi,\n type RecordVersionFiltersBreadcrumbDto,\n type RecordVersionFiltersDto,\n type RecordVersionPageBreadcrumbDto,\n type RecordVersionPageDto,\n type RecordVersionReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod } from '../../middleware/rpc_mid';\nimport {\n RECORD_VERSION_TOKENS,\n type IReadAllRecordVersionsByRecord,\n type IReadAllRecordVersionsByRecordPaginated,\n type IReadAllRecordVersionsByRecordPaginatedCustomer,\n type IReadRecordVersion,\n type IReadTrackerActivityVersions,\n} from './record_version_interfaces';\n\nexport class RecordVersionApiServer extends RpcTarget implements RecordVersionApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async getRecordVersion(id: string): Promise<RecordVersionReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'RecordVersionApiServer.getRecordVersion',\n input: id,\n inputSchema: z.string(),\n outputSchema: recordVersionSchema,\n execute: async (id, container) => {\n const feature = container.resolve<IReadRecordVersion>(\n RECORD_VERSION_TOKENS.IReadRecordVersion,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async listRecordVersions(\n recordId: string,\n recordType: RecordType,\n filters?: RecordVersionFiltersDto,\n ): Promise<RecordVersionPageDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'RecordVersionApiServer.listRecordVersions',\n input: { record_id: recordId, record_type: recordType, filters },\n inputSchema: recordVersionFiltersInputSchema,\n outputSchema: recordVersionPageSchema,\n execute: async ({ record_id, record_type, filters }, container) => {\n const feature = container.resolve<IReadAllRecordVersionsByRecord>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecord,\n );\n return await feature.execute(record_id, record_type, filters || {});\n },\n });\n }\n\n async listRecordVersionsPaginated(\n recordId: string,\n recordType: RecordType,\n filters?: RecordVersionFiltersBreadcrumbDto,\n ): Promise<RecordVersionPageBreadcrumbDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'RecordVersionApiServer.listRecordVersionsPaginated',\n input: { record_id: recordId, record_type: recordType, filters },\n inputSchema: recordVersionFiltersInputBreadcrumbSchema,\n outputSchema: recordVersionPageBreadcrumbSchema,\n execute: async ({ record_id, record_type, filters }, container) => {\n const feature = container.resolve<IReadAllRecordVersionsByRecordPaginated>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecordPaginated,\n );\n return await feature.execute(record_id, record_type, filters || {});\n },\n });\n }\n\n async listRecordVersionsCustomer(\n recordId: string,\n recordType: RecordType,\n filters?: RecordVersionFiltersBreadcrumbDto,\n ): Promise<RecordVersionPageBreadcrumbDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'RecordVersionApiServer.listRecordVersionsCustomer',\n input: { record_id: recordId, record_type: recordType, filters },\n inputSchema: recordVersionFiltersInputBreadcrumbSchema,\n outputSchema: recordVersionPageBreadcrumbSchema,\n execute: async ({ record_id, record_type, filters }, container) => {\n const feature = container.resolve<IReadAllRecordVersionsByRecordPaginatedCustomer>(\n RECORD_VERSION_TOKENS.IReadAllRecordVersionsByRecordPaginatedCustomer,\n );\n return await feature.execute(record_id, record_type, filters || {});\n },\n });\n }\n\n async listTrackerActivityVersions(\n trackerId: string,\n filters?: RecordVersionFiltersBreadcrumbDto,\n ): Promise<RecordVersionPageBreadcrumbDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'RecordVersionApiServer.listTrackerActivityVersions',\n input: { tracker_id: trackerId, filters },\n inputSchema: recordVersionTrackerActivityInputSchema,\n outputSchema: recordVersionPageBreadcrumbSchema,\n execute: async ({ tracker_id, filters: f }, container) => {\n const feature = container.resolve<IReadTrackerActivityVersions>(\n RECORD_VERSION_TOKENS.IReadTrackerActivityVersions,\n );\n return await feature.execute(tracker_id, f || {});\n },\n });\n }\n}\n","import type { SupportStaffApi, SupportStaffMember } from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport { IAddSupportStaffFeature } from './features/add_support_staff_feat';\nimport { IListSupportStaffFeature } from './features/list_support_staff_feat';\nimport { IRemoveSupportStaffFeature } from './features/remove_support_staff_feat';\nimport { SUPPORT_STAFF_TOKENS } from './support_staff_tokens';\n\nconst SupportStaffMemberSchema = z.object({\n id: z.string(),\n user_id: z.string(),\n email: z.string(),\n created_at: z.string(),\n});\n\nexport class SupportStaffApiServer extends RpcTarget implements SupportStaffApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async listSupportStaff(): Promise<SupportStaffMember[]> {\n return rpcMethodPartial({\n auth: 'admin',\n container: this.container,\n context: 'SupportStaffApiServer.listSupportStaff',\n input: undefined,\n outputSchema: z.array(SupportStaffMemberSchema),\n execute: async (_, container) => {\n const feature = container.resolve<IListSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IListSupportStaffFeature,\n );\n return feature.execute();\n },\n });\n }\n\n async addSupportStaff(userId: string): Promise<SupportStaffMember> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportStaffApiServer.addSupportStaff',\n input: userId,\n inputSchema: z.string(),\n outputSchema: SupportStaffMemberSchema,\n execute: async (userId, container) => {\n const feature = container.resolve<IAddSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IAddSupportStaffFeature,\n );\n return feature.execute(userId);\n },\n });\n }\n\n async removeSupportStaff(userId: string): Promise<void> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportStaffApiServer.removeSupportStaff',\n input: userId,\n inputSchema: z.string(),\n outputSchema: z.void(),\n execute: async (userId, container) => {\n const feature = container.resolve<IRemoveSupportStaffFeature>(\n SUPPORT_STAFF_TOKENS.IRemoveSupportStaffFeature,\n );\n await feature.execute(userId);\n },\n });\n }\n}\n","import { rpcMethod } from '../../middleware/rpc_mid';\nimport {\n SavedFilterCreateSchema,\n SavedFilterReadSchema,\n SavedFilterUpdateSchema,\n type SavedFilterApi,\n type SavedFilterCreateDto,\n type SavedFilterReadDto,\n type SavedFilterUpdateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport type {\n ICreateSavedFilter,\n IDeleteSavedFilter,\n IListAllSavedFilters,\n IListSavedFilters,\n IUpdateSavedFilter,\n} from './saved_filter_interfaces';\nimport { SAVED_FILTER_TOKENS } from './saved_filter_interfaces';\nimport type {\n IAddPinnedPreset,\n IListPinnedPresets,\n IRemovePinnedPreset,\n IReorderPinnedPresets,\n} from './user_pinned_preset_interfaces';\nimport { USER_PINNED_PRESET_TOKENS } from './user_pinned_preset_interfaces';\n\nexport class SavedFilterApiServer extends RpcTarget implements SavedFilterApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async listSavedFilters(context: string): Promise<SavedFilterReadDto[]> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.listSavedFilters',\n input: context,\n inputSchema: z.string(),\n outputSchema: z.array(SavedFilterReadSchema),\n execute: async (context, container) => {\n const feature = container.resolve<IListSavedFilters>(\n SAVED_FILTER_TOKENS.IListSavedFilters,\n );\n return await feature.execute(context);\n },\n });\n }\n\n async listAllSavedFilters(): Promise<SavedFilterReadDto[]> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.listAllSavedFilters',\n input: undefined,\n inputSchema: z.undefined(),\n outputSchema: z.array(SavedFilterReadSchema),\n execute: async (_input, container) => {\n const feature = container.resolve<IListAllSavedFilters>(\n SAVED_FILTER_TOKENS.IListAllSavedFilters,\n );\n return await feature.execute();\n },\n });\n }\n\n async createSavedFilter(input: SavedFilterCreateDto): Promise<SavedFilterReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.createSavedFilter',\n input,\n inputSchema: SavedFilterCreateSchema,\n outputSchema: SavedFilterReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateSavedFilter>(\n SAVED_FILTER_TOKENS.ICreateSavedFilter,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async updateSavedFilter(input: SavedFilterUpdateDto): Promise<SavedFilterReadDto | null> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.updateSavedFilter',\n input,\n inputSchema: SavedFilterUpdateSchema,\n outputSchema: SavedFilterReadSchema.nullable(),\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateSavedFilter>(\n SAVED_FILTER_TOKENS.IUpdateSavedFilter,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async deleteSavedFilter(id: string): Promise<boolean> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.deleteSavedFilter',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.boolean(),\n execute: async (id, container) => {\n const feature = container.resolve<IDeleteSavedFilter>(\n SAVED_FILTER_TOKENS.IDeleteSavedFilter,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async listPinnedPresets(): Promise<SavedFilterReadDto[]> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.listPinnedPresets',\n input: undefined,\n inputSchema: z.undefined(),\n outputSchema: z.array(SavedFilterReadSchema),\n execute: async (_input, container) => {\n const feature = container.resolve<IListPinnedPresets>(\n USER_PINNED_PRESET_TOKENS.IListPinnedPresets,\n );\n return await feature.execute();\n },\n });\n }\n\n async addPinnedPreset(presetId: string): Promise<SavedFilterReadDto | null> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.addPinnedPreset',\n input: presetId,\n inputSchema: z.string(),\n outputSchema: SavedFilterReadSchema.nullable(),\n execute: async (presetId, container) => {\n const feature = container.resolve<IAddPinnedPreset>(\n USER_PINNED_PRESET_TOKENS.IAddPinnedPreset,\n );\n return await feature.execute(presetId);\n },\n });\n }\n\n async removePinnedPreset(presetId: string): Promise<boolean> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.removePinnedPreset',\n input: presetId,\n inputSchema: z.string(),\n outputSchema: z.boolean(),\n execute: async (presetId, container) => {\n const feature = container.resolve<IRemovePinnedPreset>(\n USER_PINNED_PRESET_TOKENS.IRemovePinnedPreset,\n );\n return await feature.execute(presetId);\n },\n });\n }\n\n async reorderPinnedPresets(presetIds: string[]): Promise<void> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SavedFilterApiServer.reorderPinnedPresets',\n input: presetIds,\n inputSchema: z.array(z.string()),\n outputSchema: z.void(),\n execute: async (presetIds, container) => {\n const feature = container.resolve<IReorderPinnedPresets>(\n USER_PINNED_PRESET_TOKENS.IReorderPinnedPresets,\n );\n return await feature.execute(presetIds);\n },\n });\n }\n}\n","import {\n ApproveSupportTicketSchema,\n ArchiveSupportTicketSchema,\n CompleteSupportTicketSchema,\n CustomerSupportTicketCreateSchema,\n CustomerSupportTicketFiltersSchema,\n CustomerSupportTicketPageSchema,\n CustomerSupportTicketReadSchema,\n CustomerSupportTicketUpdateSchema,\n RecordSubscriberReadSchema,\n RejectSupportTicketSchema,\n RevertSupportTicketSchema,\n StaffSupportTicketCreateSchema,\n StaffSupportTicketFiltersSchema,\n StaffSupportTicketPageSchema,\n StaffSupportTicketReadSchema,\n StaffSupportTicketUpdateSchema,\n SupportTicketSubscriberCreateSchema,\n type ApproveSupportTicketDto,\n type CompleteSupportTicketDto,\n type CustomerSupportTicketCreateDto,\n type CustomerSupportTicketFiltersDto,\n type CustomerSupportTicketPageDto,\n type CustomerSupportTicketReadDto,\n type CustomerSupportTicketUpdateDto,\n type RecordSubscriberReadDto,\n type RejectSupportTicketDto,\n type RevertSupportTicketDto,\n type StaffSupportTicketCreateDto,\n type StaffSupportTicketFiltersDto,\n type StaffSupportTicketPageDto,\n type StaffSupportTicketReadDto,\n type StaffSupportTicketUpdateDto,\n type SupportTicketApi,\n type SupportTicketSubscriberCreateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport type { ICreateSupportTicketFeature } from './features/customer/create_support_ticket_feat';\nimport type { ICustomerToggleSubscriptionFeature } from './features/customer/customer_toggle_subscription_feat';\nimport type { IGetSupportTicketCustomerFeature } from './features/customer/get_support_ticket_customer_feat';\nimport type { IGetSupportTicketsCustomerFeature } from './features/customer/get_support_tickets_customer_feat';\nimport type { IUpdateSupportTicketCustomerFeature } from './features/customer/update_support_ticket_customer_feat';\nimport type { IApproveSupportTicketFeature } from './features/staff/approve_support_ticket_feat';\nimport type { ICancelInternalTaskFeature } from './features/staff/cancel_internal_task_feat';\nimport type { ICompleteSupportTicketFeature } from './features/staff/complete_support_ticket_feat';\nimport type { IConvertToCustomerFeature } from './features/staff/convert_to_customer_feat';\nimport type { IConvertToInternalFeature } from './features/staff/convert_to_internal_feat';\nimport type { IArchiveSupportTicketFeature } from './features/staff/archive_support_ticket_feat';\nimport type { ICreateSupportTicketAdminFeature } from './features/staff/create_support_ticket_admin_feat';\nimport type { IDeleteSupportTicketFeature } from './features/staff/delete_support_ticket_feat';\nimport type { IGetSupportTicketAdminFeature } from './features/staff/get_support_ticket_admin_feat';\nimport type { IGetSupportTicketsAdminFeature } from './features/staff/get_support_tickets_admin_feat';\nimport type { IReactivateInternalTaskFeature } from './features/staff/reactivate_internal_task_feat';\nimport type { IRejectSupportTicketFeature } from './features/staff/reject_support_ticket_feat';\nimport type { IRevertSupportTicketFeature } from './features/staff/revert_support_ticket_feat';\nimport type { IUpdateSupportTicketAdminFeature } from './features/staff/update_support_ticket_admin_feat';\nimport type { IAddSupportTicketSubscriberFeature } from './features/staff/add_support_ticket_subscriber_feat';\nimport type { IListSupportTicketSubscribersFeature } from './features/staff/list_support_ticket_subscribers_feat';\nimport type { IRemoveSupportTicketSubscriberFeature } from './features/staff/remove_support_ticket_subscriber_feat';\nimport type { IFixSupportTicketUserIdsFeature } from './features/staff/fix_support_ticket_user_ids_feat';\nimport type { IGetRequestorsForActiveSupportTicketsFeature } from './features/staff/get_requestors_for_active_support_tickets_feat';\nimport { SUPPORT_TICKET_TOKENS } from './support_ticket_tokens';\n\nconst CancelInternalTaskInputSchema = z.object({ id: z.string() });\nconst ReactivateInternalTaskInputSchema = z.object({ id: z.string() });\nconst UsersForSelectionSchema = z.array(z.object({ id: z.string(), email: z.string() }));\n\nexport class SupportTicketApiServer extends RpcTarget implements SupportTicketApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n // ===== Customer Operations =====\n\n async createTicket(\n input: CustomerSupportTicketCreateDto,\n ): Promise<CustomerSupportTicketReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.createTicket',\n input,\n inputSchema: CustomerSupportTicketCreateSchema,\n outputSchema: CustomerSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.ICreateSupportTicketFeature,\n );\n // Zod applies defaults for type and priority, so they're always present at runtime\n return await feature.execute(input as CustomerSupportTicketCreateDto);\n },\n });\n }\n\n async updateTicket(\n input: CustomerSupportTicketUpdateDto,\n ): Promise<CustomerSupportTicketReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.updateTicket',\n input,\n inputSchema: CustomerSupportTicketUpdateSchema,\n outputSchema: CustomerSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateSupportTicketCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IUpdateSupportTicketCustomerFeature,\n );\n // Zod applies defaults for type and priority, so they're always present at runtime\n return await feature.execute(input as CustomerSupportTicketUpdateDto);\n },\n });\n }\n\n async getTicket(id: string): Promise<CustomerSupportTicketReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.getTicket',\n input: id,\n inputSchema: z.string(),\n outputSchema: CustomerSupportTicketReadSchema,\n execute: async (id, container) => {\n const feature = container.resolve<IGetSupportTicketCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketCustomerFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async listTickets(\n filters: CustomerSupportTicketFiltersDto,\n ): Promise<CustomerSupportTicketPageDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.listTickets',\n input: filters,\n inputSchema: CustomerSupportTicketFiltersSchema,\n outputSchema: CustomerSupportTicketPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IGetSupportTicketsCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketsCustomerFeature,\n );\n return await feature.execute(validated);\n },\n });\n }\n\n async toggleSubscription(supportTicketId: string): Promise<{ subscribed: boolean }> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.toggleSubscription',\n input: supportTicketId,\n inputSchema: z.string(),\n outputSchema: z.object({ subscribed: z.boolean() }),\n execute: async (id, container) => {\n const feature = container.resolve<ICustomerToggleSubscriptionFeature>(\n SUPPORT_TICKET_TOKENS.ICustomerToggleSubscriptionFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async getRequestorsForActiveTickets(): Promise<Array<{ id: string; email: string }>> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'SupportTicketApiServer.getRequestorsForActiveTickets',\n input: undefined,\n outputSchema: UsersForSelectionSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetRequestorsForActiveSupportTicketsFeature>(\n SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature,\n );\n return await feature.execute({ excludeInternal: true });\n },\n });\n }\n\n // ===== Staff Operations =====\n\n async staffCreateTicket(\n input: StaffSupportTicketCreateDto,\n ): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffCreateTicket',\n input,\n inputSchema: StaffSupportTicketCreateSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.ICreateSupportTicketAdminFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async staffUpdateTicket(\n input: StaffSupportTicketUpdateDto,\n ): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffUpdateTicket',\n input,\n inputSchema: StaffSupportTicketUpdateSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.IUpdateSupportTicketAdminFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async staffGetTicket(id: string): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffGetTicket',\n input: id,\n inputSchema: z.string(),\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (id, container) => {\n const feature = container.resolve<IGetSupportTicketAdminFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketAdminFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async staffListTickets(\n filters: StaffSupportTicketFiltersDto,\n ): Promise<StaffSupportTicketPageDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffListTickets',\n input: filters,\n inputSchema: StaffSupportTicketFiltersSchema,\n outputSchema: StaffSupportTicketPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IGetSupportTicketsAdminFeature>(\n SUPPORT_TICKET_TOKENS.IGetSupportTicketsAdminFeature,\n );\n return await feature.execute(validated);\n },\n });\n }\n\n async staffGetRequestorsForActiveTickets(): Promise<\n Array<{ id: string; email: string }>\n > {\n return rpcMethodPartial({\n auth: 'staff',\n container: this.container,\n context: 'SupportTicketApiServer.staffGetRequestorsForActiveTickets',\n input: undefined,\n outputSchema: UsersForSelectionSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetRequestorsForActiveSupportTicketsFeature>(\n SUPPORT_TICKET_TOKENS.IGetRequestorsForActiveSupportTicketsFeature,\n );\n return await feature.execute({ excludeInternal: false });\n },\n });\n }\n\n async approveTicket(input: ApproveSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.approveTicket',\n input,\n inputSchema: ApproveSupportTicketSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IApproveSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IApproveSupportTicketFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async rejectTicket(input: RejectSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.rejectTicket',\n input,\n inputSchema: RejectSupportTicketSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IRejectSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IRejectSupportTicketFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async revertTicket(input: RevertSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.revertTicket',\n input,\n inputSchema: RevertSupportTicketSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IRevertSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IRevertSupportTicketFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async completeTicket(input: CompleteSupportTicketDto): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.completeTicket',\n input,\n inputSchema: CompleteSupportTicketSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICompleteSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.ICompleteSupportTicketFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async convertToInternal(id: string): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.convertToInternal',\n input: id,\n inputSchema: z.string(),\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (id, container) => {\n const feature = container.resolve<IConvertToInternalFeature>(\n SUPPORT_TICKET_TOKENS.IConvertToInternalFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async convertToCustomer(id: string): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.convertToCustomer',\n input: id,\n inputSchema: z.string(),\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (id, container) => {\n const feature = container.resolve<IConvertToCustomerFeature>(\n SUPPORT_TICKET_TOKENS.IConvertToCustomerFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async cancelInternalTask(input: { id: string }): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.cancelInternalTask',\n input,\n inputSchema: CancelInternalTaskInputSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICancelInternalTaskFeature>(\n SUPPORT_TICKET_TOKENS.ICancelInternalTaskFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async reactivateInternalTask(input: { id: string }): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.reactivateInternalTask',\n input,\n inputSchema: ReactivateInternalTaskInputSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IReactivateInternalTaskFeature>(\n SUPPORT_TICKET_TOKENS.IReactivateInternalTaskFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async deleteTicket(id: string): Promise<void> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.deleteTicket',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.void(),\n execute: async (id, container) => {\n const feature = container.resolve<IDeleteSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IDeleteSupportTicketFeature,\n );\n await feature.execute(id);\n },\n });\n }\n\n async archiveTicket(input: { id: string }): Promise<StaffSupportTicketReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.archiveTicket',\n input,\n inputSchema: ArchiveSupportTicketSchema,\n outputSchema: StaffSupportTicketReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IArchiveSupportTicketFeature>(\n SUPPORT_TICKET_TOKENS.IArchiveSupportTicketFeature,\n );\n return await feature.execute(input.id);\n },\n });\n }\n\n async staffListSubscribers(supportTicketId: string): Promise<RecordSubscriberReadDto[]> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffListSubscribers',\n input: supportTicketId,\n inputSchema: z.string(),\n outputSchema: z.array(RecordSubscriberReadSchema),\n execute: async (id, container) => {\n const feature = container.resolve<IListSupportTicketSubscribersFeature>(\n SUPPORT_TICKET_TOKENS.IListSupportTicketSubscribersFeature,\n );\n return await feature.execute(id);\n },\n });\n }\n\n async staffAddSubscriber(\n input: SupportTicketSubscriberCreateDto,\n ): Promise<RecordSubscriberReadDto> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffAddSubscriber',\n input,\n inputSchema: SupportTicketSubscriberCreateSchema,\n outputSchema: RecordSubscriberReadSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IAddSupportTicketSubscriberFeature>(\n SUPPORT_TICKET_TOKENS.IAddSupportTicketSubscriberFeature,\n );\n return await feature.execute(validated);\n },\n });\n }\n\n async staffRemoveSubscriber(input: {\n supportTicketId: string;\n subscriberId: string;\n }): Promise<void> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffRemoveSubscriber',\n input,\n inputSchema: z.object({\n supportTicketId: z.string(),\n subscriberId: z.string(),\n }),\n outputSchema: z.void(),\n execute: async (validated, container) => {\n const feature = container.resolve<IRemoveSupportTicketSubscriberFeature>(\n SUPPORT_TICKET_TOKENS.IRemoveSupportTicketSubscriberFeature,\n );\n await feature.execute(validated.supportTicketId, validated.subscriberId);\n },\n });\n }\n\n async staffFixSupportTicketUserIds(): Promise<{\n ticketsScanned: number;\n ticketsFixed: number;\n ticketsSkipped: number;\n }> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'SupportTicketApiServer.staffFixSupportTicketUserIds',\n input: undefined,\n inputSchema: z.undefined(),\n outputSchema: z.object({\n ticketsScanned: z.number(),\n ticketsFixed: z.number(),\n ticketsSkipped: z.number(),\n }),\n execute: async (_input, container) => {\n const feature = container.resolve<IFixSupportTicketUserIdsFeature>(\n SUPPORT_TICKET_TOKENS.IFixSupportTicketUserIdsFeature,\n );\n return await feature.execute();\n },\n });\n }\n}\n","import {\n TeamCreateSchema,\n TeamFiltersSchema,\n TeamPageSchema,\n TeamReadSchema,\n TeamUpdateSchema,\n UserTeamsSchema,\n type TeamApi,\n type TeamCreateDto,\n type TeamFiltersDto,\n type TeamPageDto,\n type TeamReadDto,\n type TeamUpdateDto,\n type UserTeamsDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport type { IGetUserTeamsFeature } from '../team_member/features/get_user_teams_feat';\nimport { TEAM_MEMBER_TOKENS } from '../team_member/team_member_tokens';\nimport {\n TEAM_TOKENS,\n type CreateTeamFeature,\n type DeleteTeamFeature,\n type ReadAllTeamsFeature,\n type ReadTeamFeature,\n type ToggleArchiveTeamFeature,\n type UpdateTeamFeature,\n} from './team_interfaces';\n\nexport class TeamApiServer extends RpcTarget implements TeamApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async getTeam(id: string): Promise<TeamReadDto | null> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.getTeam',\n input: id,\n inputSchema: z.string(),\n outputSchema: TeamReadSchema.nullable(),\n execute: async (id, container) => {\n const feature = container.resolve<ReadTeamFeature>(TEAM_TOKENS.IReadTeamFeature);\n try {\n return await feature.execute(id);\n } catch (error) {\n // Return null if not found instead of throwing\n if (error instanceof Error && error.message === 'Team not found') {\n return null;\n }\n throw error;\n }\n },\n });\n }\n\n async createTeam(input: TeamCreateDto): Promise<TeamReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.createTeam',\n input,\n inputSchema: TeamCreateSchema,\n outputSchema: TeamReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<CreateTeamFeature>(TEAM_TOKENS.ICreateTeamFeature);\n return await feature.execute(input);\n },\n });\n }\n\n async updateTeam(input: TeamUpdateDto): Promise<TeamReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.updateTeam',\n input,\n inputSchema: TeamUpdateSchema,\n outputSchema: TeamReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<UpdateTeamFeature>(TEAM_TOKENS.IUpdateTeamFeature);\n return await feature.execute(input);\n },\n });\n }\n\n async deleteTeam(id: string): Promise<void> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.deleteTeam',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.void(),\n execute: async (id, container) => {\n const feature = container.resolve<DeleteTeamFeature>(TEAM_TOKENS.IDeleteTeamFeature);\n await feature.execute(id);\n },\n });\n }\n\n async listTeams(filters?: TeamFiltersDto): Promise<TeamPageDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.listTeams',\n input: filters,\n inputSchema: TeamFiltersSchema.optional(),\n outputSchema: TeamPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<ReadAllTeamsFeature>(\n TEAM_TOKENS.IReadAllTeamsFeature,\n );\n return await feature.execute(validated);\n },\n });\n }\n\n async toggleArchiveTeam(id: string, archive: boolean): Promise<TeamReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.toggleArchiveTeam',\n input: { id, archive },\n inputSchema: z.object({ id: z.string(), archive: z.boolean() }),\n outputSchema: TeamReadSchema,\n execute: async ({ id, archive }, container) => {\n const feature = container.resolve<ToggleArchiveTeamFeature>(\n TEAM_TOKENS.IToggleArchiveTeamFeature,\n );\n return await feature.execute(id, archive);\n },\n });\n }\n\n async getUserTeams(): Promise<UserTeamsDto> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'TeamApiServer.getUserTeams',\n input: undefined,\n outputSchema: UserTeamsSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetUserTeamsFeature>(\n TEAM_MEMBER_TOKENS.IGetUserTeamsFeature,\n );\n return await feature.execute();\n },\n });\n }\n}\n","import {\n createPaginatedSchema,\n TeamMemberCreateSchema,\n TeamMemberFiltersSchema,\n TeamMemberReadSchema,\n TeamMemberUpdateSchema,\n UserTeamMembersSchema,\n type PagedResult,\n type TeamMemberApi,\n type TeamMemberCreateDto,\n type TeamMemberFiltersDto,\n type TeamMemberReadDto,\n type TeamMemberUpdateDto,\n type UserTeamMembersDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport type { ICreateTeamMemberFeature } from './features/create_team_member_feat';\nimport type { IDeleteTeamMemberFeature } from './features/delete_team_member_feat';\nimport type { IGetTeamMembersFeature } from './features/get_team_members_feat';\nimport type { IGetUserTeamMembersFeature } from './features/get_user_team_members_feat';\nimport type { IUpdateTeamMemberFeature } from './features/update_team_member_feat';\nimport { TEAM_MEMBER_TOKENS } from './team_member_tokens';\n\nconst TeamMemberPageSchema = createPaginatedSchema(TeamMemberReadSchema);\n\nexport class TeamMemberApiServer extends RpcTarget implements TeamMemberApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async createTeamMember(input: TeamMemberCreateDto): Promise<TeamMemberReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamMemberApiServer.createTeamMember',\n input,\n inputSchema: TeamMemberCreateSchema,\n outputSchema: TeamMemberReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateTeamMemberFeature>(\n TEAM_MEMBER_TOKENS.ICreateTeamMemberFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async updateTeamMember(input: TeamMemberUpdateDto): Promise<TeamMemberReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamMemberApiServer.updateTeamMember',\n input,\n inputSchema: TeamMemberUpdateSchema,\n outputSchema: TeamMemberReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateTeamMemberFeature>(\n TEAM_MEMBER_TOKENS.IUpdateTeamMemberFeature,\n );\n return await feature.execute(input);\n },\n });\n }\n\n async deleteTeamMember(id: string): Promise<void> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamMemberApiServer.deleteTeamMember',\n input: id,\n inputSchema: z.string(),\n outputSchema: z.void(),\n execute: async (id, container) => {\n const feature = container.resolve<IDeleteTeamMemberFeature>(\n TEAM_MEMBER_TOKENS.IDeleteTeamMemberFeature,\n );\n await feature.execute(id);\n },\n });\n }\n\n async listTeamMembers(\n filters?: TeamMemberFiltersDto,\n ): Promise<PagedResult<TeamMemberReadDto>> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'TeamMemberApiServer.listTeamMembers',\n input: filters,\n inputSchema: TeamMemberFiltersSchema.optional(),\n outputSchema: TeamMemberPageSchema,\n execute: async (validated, container) => {\n const feature = container.resolve<IGetTeamMembersFeature>(\n TEAM_MEMBER_TOKENS.IGetTeamMembersFeature,\n );\n return await feature.execute(validated || {});\n },\n });\n }\n\n async getUserTeamMembers(): Promise<UserTeamMembersDto> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'TeamMemberApiServer.getUserTeamMembers',\n input: undefined,\n outputSchema: UserTeamMembersSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetUserTeamMembersFeature>(\n TEAM_MEMBER_TOKENS.IGetUserTeamMembersFeature,\n );\n return await feature.execute();\n },\n });\n }\n}\n","import {\n createUserSchema,\n createUserSchemaOutput,\n signupSchema,\n UserReadSchema,\n UserUpdateSchema,\n type CreateUserDto,\n type CreateUserDtoOutput,\n type SignupInputDto,\n type UserApi,\n type UserReadDto,\n type UserUpdateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { UserAppSession } from '../../db/user_app_session';\nimport { TOKENS } from '../../di_tokens';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport { IGetAllUsersFeature } from './features/get_all_users_feat';\nimport { IGetUserFeature } from './features/get_user_feat';\nimport { IGetTriageUsersFeature } from './features/get_triage_users_feat';\nimport { IGetUsersForSelectionFeature } from './features/get_users_for_selection_feat';\nimport { IUpdateUserFeature } from './features/update_user_feat';\nimport { ICreateUser, ISignUpUser, SignUpUserOutputDto, USER_TOKENS } from './user_interfaces';\n\nconst UsersForSelectionSchema = z.array(\n z.object({\n id: z.string(),\n email: z.string(),\n }),\n);\n\nexport class UserApiServer extends RpcTarget implements UserApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async getUser(id: string): Promise<UserReadDto | null> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'UserApiServer.getUser',\n input: id,\n inputSchema: z.string(),\n outputSchema: UserReadSchema.nullable(),\n execute: async (id, container) => {\n const feature = container.resolve<IGetUserFeature>(USER_TOKENS.IGetUserFeature);\n try {\n return await feature.execute(id);\n } catch (error) {\n // Return null if not found instead of throwing\n if (error instanceof Error && error.message === 'User not found') {\n return null;\n }\n throw error;\n }\n },\n });\n }\n\n async getCurrentUser(): Promise<UserReadDto | null> {\n const user = this.container.resolve<UserAppSession>(TOKENS.AUTHENTICATED_SESSION);\n if (!user) {\n return null;\n }\n return this.getUser(user.user.userId);\n }\n\n async signupUser(input: SignupInputDto): Promise<SignUpUserOutputDto> {\n return rpcMethod({\n auth: 'public',\n container: this.container,\n context: 'UserApiServer.signupUser',\n input,\n inputSchema: signupSchema,\n outputSchema: createUserSchemaOutput,\n execute: async (input, container) => {\n const feature = container.resolve<ISignUpUser>(USER_TOKENS.ISignUpUser);\n return await feature.execute(input);\n },\n });\n }\n\n async createUser(input: CreateUserDto): Promise<CreateUserDtoOutput> {\n return rpcMethod({\n auth: 'admin',\n container: this.container,\n context: 'UserApiServer.createUser',\n input,\n inputSchema: createUserSchema,\n outputSchema: createUserSchemaOutput,\n execute: async (input, container) => {\n const feature = container.resolve<ICreateUser>(USER_TOKENS.ICreateUser);\n return await feature.execute(input);\n },\n });\n }\n\n async updateUser(input: UserUpdateDto): Promise<UserReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'UserApiServer.updateUser',\n input,\n inputSchema: UserUpdateSchema,\n outputSchema: UserReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateUserFeature>(USER_TOKENS.IUpdateUserFeature);\n return await feature.execute(input);\n },\n });\n }\n\n async deleteUser(_id: string): Promise<boolean> {\n // FOLLOWUP: Implement soft delete feature\n // For now, throw not implemented\n throw new Error('Delete user not yet implemented via RPC');\n }\n\n async listUsers(): Promise<UserReadDto[]> {\n return rpcMethodPartial({\n auth: 'admin',\n container: this.container,\n context: 'UserApiServer.listUsers',\n input: undefined,\n outputSchema: z.array(UserReadSchema),\n execute: async (_, container) => {\n const feature = container.resolve<IGetAllUsersFeature>(\n USER_TOKENS.IGetAllUsersFeature,\n );\n return await feature.execute();\n },\n });\n }\n\n async getUsersForSelection(): Promise<Array<{ id: string; email: string }>> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'UserApiServer.getUsersForSelection',\n input: undefined,\n outputSchema: UsersForSelectionSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetUsersForSelectionFeature>(\n USER_TOKENS.IGetUsersForSelectionFeature,\n );\n return await feature.execute();\n },\n });\n }\n\n async getTriageUsers(): Promise<Array<{ id: string; email: string }>> {\n return rpcMethodPartial({\n auth: 'admin',\n container: this.container,\n context: 'UserApiServer.getTriageUsers',\n input: undefined,\n outputSchema: UsersForSelectionSchema,\n execute: async (_, container) => {\n const feature = container.resolve<IGetTriageUsersFeature>(\n USER_TOKENS.IGetTriageUsersFeature,\n );\n return await feature.execute();\n },\n });\n }\n}\n","import {\n UserProfileReadSchema,\n UserProfileUpdateSchema,\n type UserProfileApi,\n type UserProfileReadDto,\n type UserProfileUpdateDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { UserAppSession } from '../../db/user_app_session';\nimport { TOKENS } from '../../di_tokens';\nimport { rpcMethod, rpcMethodPartial } from '../../middleware/rpc_mid';\nimport type { IReadUserProfile } from './features/read_user_profile';\nimport type { IUpdateUserProfile } from './features/update_user_profile';\nimport { USER_PROFILE_TOKENS } from './user_profile_interfaces';\n\nexport class UserProfileApiServer extends RpcTarget implements UserProfileApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async getCurrentUserProfile(): Promise<UserProfileReadDto | null> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'UserProfileApiServer.getCurrentUserProfile',\n input: undefined,\n outputSchema: UserProfileReadSchema.nullable(),\n execute: async (_, container) => {\n const feature = container.resolve<IReadUserProfile>(\n USER_PROFILE_TOKENS.IReadUserProfile,\n );\n const user = container.resolve<UserAppSession>(TOKENS.AUTHENTICATED_SESSION);\n return await feature.execute(user.user.userId);\n },\n });\n }\n\n async getUserProfile(userId: string): Promise<UserProfileReadDto | null> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'UserProfileApiServer.getUserProfile',\n input: userId,\n inputSchema: z.string(),\n outputSchema: UserProfileReadSchema.nullable(),\n execute: async (userId, container) => {\n const feature = container.resolve<IReadUserProfile>(\n USER_PROFILE_TOKENS.IReadUserProfile,\n );\n return await feature.execute(userId);\n },\n });\n }\n\n async updateUserProfile(input: UserProfileUpdateDto): Promise<UserProfileReadDto> {\n return rpcMethod({\n auth: 'protected',\n container: this.container,\n context: 'UserProfileApiServer.updateUserProfile',\n input,\n inputSchema: UserProfileUpdateSchema,\n outputSchema: UserProfileReadSchema,\n execute: async (input, container) => {\n const feature = container.resolve<IUpdateUserProfile>(\n USER_PROFILE_TOKENS.IUpdateUserProfile,\n );\n return await feature.execute(input);\n },\n });\n }\n}\n","import type { LoginResponse } from '@dragonmastery/dragoncore-shared';\nimport { CookieOptions } from 'hono/utils/cookie';\nimport { MAX_SESSION_LIFETIME_MS } from '../../db/db_utils';\nimport { ICookieService } from '../../lib/cookie_service';\nimport type { HonoContext } from '../../types/hono_context';\n\n/**\n * Get the environment/tenant-specific cookie name for refresh token\n */\nexport function getRefreshTokenCookieName(env: Env): string {\n // @ts-ignore\n if (env.ENVIRONMENT === 'prod') {\n // In production, use tenant name to avoid conflicts between tenants\n return `${env.TENANT}_refresh_token`;\n } else if (env.ENVIRONMENT === 'local') {\n // For local environment, include the project name from COOKIE_DOMAIN to avoid conflicts with other projects\n // Strip the leading dot and convert domain separator to underscore\n const projectId = env.COOKIE_DOMAIN.replace(/^\\./g, '').replace(/\\./g, '_');\n return `local_${projectId}_refresh_token`;\n } else {\n // In dev/staging, use environment prefix\n return `${env.ENVIRONMENT}_refresh_token`;\n }\n}\n\n/**\n * Get cookie options for refresh token based on environment\n */\nexport function getRefreshTokenCookieOptions(\n env: Env,\n expiresAt: string,\n): CookieOptions & { prefix?: 'secure' } {\n const cookieOptions: CookieOptions & { prefix?: 'secure' } = {\n path: '/',\n prefix: 'secure',\n secure: true,\n httpOnly: true,\n maxAge: MAX_SESSION_LIFETIME_MS / 1000,\n expires: new Date(expiresAt),\n sameSite: env.ENVIRONMENT === 'local' ? 'None' : 'Lax',\n };\n\n // For local environment, don't set the domain to make cookies work on both localhost and 127.0.0.1\n if (env.ENVIRONMENT !== 'local') {\n cookieOptions.domain = env.COOKIE_DOMAIN;\n }\n\n return cookieOptions;\n}\n\n/**\n * Set the refresh token as an HTTP-only signed cookie\n */\nexport async function setRefreshTokenCookie(\n ctx: HonoContext,\n env: Env,\n cookieService: ICookieService,\n loginResponse: LoginResponse,\n): Promise<void> {\n const cookieName = getRefreshTokenCookieName(env);\n const cookieOptions = getRefreshTokenCookieOptions(\n env,\n loginResponse.frontend_session.expires_at,\n );\n\n await cookieService.setSignedCookie(\n ctx,\n cookieName,\n loginResponse.refresh_token,\n env.REFRESH_JWT_SECRET,\n cookieOptions,\n );\n}\n\n/**\n * Clear the refresh token cookie\n */\nexport async function clearRefreshTokenCookie(\n ctx: HonoContext,\n env: Env,\n cookieService: ICookieService,\n): Promise<void> {\n const cookieName = getRefreshTokenCookieName(env);\n const cookieOptions: CookieOptions & { prefix?: 'secure' } = {\n path: '/',\n prefix: 'secure',\n secure: true,\n httpOnly: true,\n sameSite: env.ENVIRONMENT === 'local' ? 'None' : 'Lax',\n };\n\n // For local environment, don't set the domain to make cookies work on both localhost and 127.0.0.1\n if (env.ENVIRONMENT !== 'local') {\n cookieOptions.domain = env.COOKIE_DOMAIN;\n }\n\n // Remove the cookie by setting it to empty with maxAge 0\n await cookieService.setSignedCookie(ctx as any, cookieName, '', env.REFRESH_JWT_SECRET, {\n ...cookieOptions,\n maxAge: 0,\n });\n}\n","import {\n loginResponseSchema,\n loginSchema,\n userSessionSchema,\n type LoginInputDto,\n type LoginResponse,\n type UserSessionApi,\n type UserSessionReadDto,\n} from '@dragonmastery/dragoncore-shared';\nimport { RpcTarget } from 'capnweb';\nimport { DependencyContainer } from 'tsyringe';\nimport { z } from 'zod';\nimport { TOKENS } from '../../di_tokens';\nimport { ICookieService } from '../../lib/cookie_service';\nimport {\n AuthenticationError,\n rpcMethod,\n rpcMethodPartial,\n rpcMethodSimple,\n} from '../../middleware/rpc_mid';\nimport type { HonoContext } from '../../types/hono_context';\nimport { Logger } from '../../utils/logger';\nimport {\n clearRefreshTokenCookie,\n getRefreshTokenCookieName,\n setRefreshTokenCookie,\n} from './user_session_cookie_utils';\nimport {\n USER_SESSION_TOKENS,\n type ILoginUserSession,\n type IReadAllUserSessions,\n type IRefreshTokenSession,\n type IRevokeRefreshToken,\n} from './user_session_interfaces';\n\n/**\n * UserSession API Server\n * Note: Cookie management is handled by REST endpoints and middleware\n * This RPC interface provides the core session operations\n */\nexport class UserSessionApiServer extends RpcTarget implements UserSessionApi {\n constructor(private container: DependencyContainer) {\n super();\n }\n\n async login(input: LoginInputDto): Promise<LoginResponse> {\n return rpcMethod({\n auth: 'public',\n container: this.container,\n context: 'UserSessionApiServer.login',\n input,\n inputSchema: loginSchema,\n outputSchema: loginResponseSchema,\n execute: async (input, container) => {\n const feature = container.resolve<ILoginUserSession>(\n USER_SESSION_TOKENS.ILoginUserSession,\n );\n const result = await feature.execute(input);\n\n // Set refresh token as HTTP-only signed cookie\n const ctx = container.resolve<HonoContext>(TOKENS.HONO_CONTEXT);\n const env = container.resolve<Env>(TOKENS.ENV);\n const cookieService = container.resolve<ICookieService>(TOKENS.COOKIE_SERVICE);\n\n await setRefreshTokenCookie(ctx, env, cookieService, result);\n\n return result;\n },\n });\n }\n\n async refreshToken(): Promise<LoginResponse> {\n return rpcMethodSimple({\n auth: 'public', // Allow refresh even if access token is expired\n container: this.container,\n context: 'UserSessionApiServer.refreshToken',\n execute: async (container) => {\n // Get the request and environment from container\n const ctx = container.resolve<HonoContext>(TOKENS.HONO_CONTEXT);\n const env = container.resolve<Env>(TOKENS.ENV);\n const cookieService = container.resolve<ICookieService>(TOKENS.COOKIE_SERVICE);\n const refreshFeature = container.resolve<IRefreshTokenSession>(\n USER_SESSION_TOKENS.IRefreshTokenSession,\n );\n\n // Use environment and tenant-specific cookie name to prevent conflicts between environments\n const cookieName = getRefreshTokenCookieName(env);\n\n // Get refresh token from cookie\n const refreshToken = await cookieService.getSignedCookie(\n ctx,\n env.REFRESH_JWT_SECRET, // secret\n cookieName, // key (cookie name)\n 'secure', // prefix\n );\n\n if (!refreshToken || typeof refreshToken !== 'string') {\n throw new AuthenticationError('No refresh token found', 'NO_REFRESH_TOKEN');\n }\n\n // Execute refresh token session\n const result = await refreshFeature.execute(refreshToken);\n\n // Set new refresh token as HTTP-only signed cookie\n await setRefreshTokenCookie(ctx, env, cookieService, result);\n\n return result;\n },\n });\n }\n\n async logout(): Promise<{ ok: boolean }> {\n return rpcMethodSimple({\n auth: 'public', // Allow logout even if token is expired\n container: this.container,\n context: 'UserSessionApiServer.logout',\n execute: async (container) => {\n // Get the request and environment from container\n const ctx = container.resolve<HonoContext>(TOKENS.HONO_CONTEXT);\n const env = container.resolve<Env>(TOKENS.ENV);\n const cookieService = container.resolve<ICookieService>(TOKENS.COOKIE_SERVICE);\n const revokeFeature = container.resolve<IRevokeRefreshToken>(\n USER_SESSION_TOKENS.IRevokeRefreshToken,\n );\n\n // Use environment and tenant-specific cookie name to prevent conflicts between environments\n const cookieName = getRefreshTokenCookieName(env);\n\n // Get refresh token from cookie\n const refreshToken = await cookieService.getSignedCookie(\n ctx,\n env.REFRESH_JWT_SECRET, // secret\n cookieName, // key (cookie name)\n 'secure', // prefix\n );\n\n if (!refreshToken || typeof refreshToken !== 'string') {\n // No token found, but still clear the cookie and return success\n await clearRefreshTokenCookie(ctx, env, cookieService);\n return { ok: true };\n }\n\n // Revoke the refresh token\n try {\n await revokeFeature.execute(refreshToken);\n } catch (error) {\n // Log error but don't fail - token might already be revoked\n const logger = container.resolve<Logger>(TOKENS.LOGGER);\n logger.error('Error revoking refresh token', { error });\n }\n\n // Clear the cookie\n await clearRefreshTokenCookie(ctx, env, cookieService);\n\n return { ok: true };\n },\n });\n }\n\n async getSessions(): Promise<UserSessionReadDto[]> {\n return rpcMethodPartial({\n auth: 'protected',\n container: this.container,\n context: 'UserSessionApiServer.getSessions',\n input: undefined,\n outputSchema: z.array(userSessionSchema),\n execute: async (_, container) => {\n const feature = container.resolve<IReadAllUserSessions>(\n USER_SESSION_TOKENS.IReadAllUserSessions,\n );\n return await feature.execute();\n },\n });\n }\n}\n"],"mappings":"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"}