@hotmeshio/long-tail 0.16.4 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/users.d.ts +31 -1
- package/build/api/users.js +54 -1
- package/build/bin/ltc.js +7 -0
- package/build/lib/cli/commands/users.d.ts +11 -0
- package/build/lib/cli/commands/users.js +59 -0
- package/build/routes/users.js +26 -0
- package/build/sdk/index.d.ts +2 -0
- package/build/sdk/index.js +2 -0
- package/build/services/user/crud.d.ts +32 -1
- package/build/services/user/crud.js +108 -0
- package/build/services/user/index.d.ts +2 -2
- package/build/services/user/index.js +5 -1
- package/build/services/user/sql.d.ts +16 -0
- package/build/services/user/sql.js +33 -1
- package/build/services/user/types.d.ts +11 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +30 -14
- package/build/system/mcp-servers/admin/schemas.js +9 -3
- package/build/system/mcp-servers/admin/users.js +26 -0
- package/build/system/seed/tool-manifests-admin.d.ts +445 -0
- package/build/system/seed/tool-manifests-admin.js +1 -0
- package/dashboard/dist/assets/{AdminDashboard-Csq-oxmY.js → AdminDashboard-Bx5G6Qb_.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Csq-oxmY.js.map → AdminDashboard-Bx5G6Qb_.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-Bnr5zbyJ.js → AgentConfigPage-S-YfPwCG.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-Bnr5zbyJ.js.map → AgentConfigPage-S-YfPwCG.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-DgRu-Fhg.js → AgentDetailPage-pFVqLY4c.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-DgRu-Fhg.js.map → AgentDetailPage-pFVqLY4c.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-BuyXKkP3.js → AgentsPage-DjAluFkM.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-BuyXKkP3.js.map → AgentsPage-DjAluFkM.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-BIzTjUMG.js → AvailableEscalationsPage-BHsRZk4v.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-BIzTjUMG.js.map → AvailableEscalationsPage-BHsRZk4v.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-xpPjRnkf.js → BotPicker-h2YrxQS2.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-xpPjRnkf.js.map → BotPicker-h2YrxQS2.js.map} +1 -1
- package/dashboard/dist/assets/{BulkAssignModal-CQ3k-7rO.js → BulkAssignModal-CNKZBgK5.js} +2 -2
- package/dashboard/dist/assets/{BulkAssignModal-CQ3k-7rO.js.map → BulkAssignModal-CNKZBgK5.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-DCkEJ0In.js → CapabilitiesPage-DGsnIiJi.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-DCkEJ0In.js.map → CapabilitiesPage-DGsnIiJi.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-10sJSNNF.js → CollapsibleSection-ptEsXSl6.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-10sJSNNF.js.map → CollapsibleSection-ptEsXSl6.js.map} +1 -1
- package/dashboard/dist/assets/{CountdownTimer-BFy9Joxb.js → CountdownTimer-Cvif0QtN.js} +2 -2
- package/dashboard/dist/assets/{CountdownTimer-BFy9Joxb.js.map → CountdownTimer-Cvif0QtN.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-DIR-fUoF.js → CredentialsPage-CwKUAtAR.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-DIR-fUoF.js.map → CredentialsPage-CwKUAtAR.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-C66ASvgE.js → CronLabel-B9RbqMR-.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-C66ASvgE.js.map → CronLabel-B9RbqMR-.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-RWvrCHn7.js → ElapsedCell-BirS3yZq.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-RWvrCHn7.js.map → ElapsedCell-BirS3yZq.js.map} +1 -1
- package/dashboard/dist/assets/{EntityTimelinePanel-CNaYs3nA.js → EntityTimelinePanel-D3QNqDiL.js} +2 -2
- package/dashboard/dist/assets/{EntityTimelinePanel-CNaYs3nA.js.map → EntityTimelinePanel-D3QNqDiL.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-BsRQtDDc.js → EscalationListSchemaPage-Bke-30FY.js} +2 -2
- package/dashboard/dist/assets/{EscalationListSchemaPage-BsRQtDDc.js.map → EscalationListSchemaPage-Bke-30FY.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-BMYQmwGA.js → EscalationSchemaPage-CTFE9vko.js} +2 -2
- package/dashboard/dist/assets/{EscalationSchemaPage-BMYQmwGA.js.map → EscalationSchemaPage-CTFE9vko.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-yv4Yn6zl.js → EscalationsOverview-C2FaSmXk.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-yv4Yn6zl.js.map → EscalationsOverview-C2FaSmXk.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-ebZK7vXl.js → EventTable-zFqzbEdC.js} +2 -2
- package/dashboard/dist/assets/{EventTable-ebZK7vXl.js.map → EventTable-zFqzbEdC.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-D6JDMSHi.js → FilterBar-fwUqeM-5.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-D6JDMSHi.js.map → FilterBar-fwUqeM-5.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-BFntNMop.js → GraphInvokePage-CA_XYI2W.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-BFntNMop.js.map → GraphInvokePage-CA_XYI2W.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-B24GH9Ga.js → HomePage-C2HnqdFX.js} +2 -2
- package/dashboard/dist/assets/{HomePage-B24GH9Ga.js.map → HomePage-C2HnqdFX.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-DlGreUpP.js → ListToolbar-BEogxu1f.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-DlGreUpP.js.map → ListToolbar-BEogxu1f.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-D0G2vACW.js → McpOverview-CdMR-5qh.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-D0G2vACW.js.map → McpOverview-CdMR-5qh.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-LZO04PEB.js → McpQueryDetailPage-BXo4UjJF.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-LZO04PEB.js.map → McpQueryDetailPage-BXo4UjJF.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-BIwSDHgB.js → McpQueryPage-BvvM0Ya1.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-BIwSDHgB.js.map → McpQueryPage-BvvM0Ya1.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-DjYWJLWh.js → McpRunDetailPage-5eaJeupC.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-DjYWJLWh.js.map → McpRunDetailPage-5eaJeupC.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CTT7B-pf.js → McpRunsPage-B5_-3NUF.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CTT7B-pf.js.map → McpRunsPage-B5_-3NUF.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-Dd0WxAwH.js → OperationsPage-CJXTw02Z.js} +2 -2
- package/dashboard/dist/assets/{OperationsPage-Dd0WxAwH.js.map → OperationsPage-CJXTw02Z.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-BfKMaVzV.js → OperatorDashboard-Dx7639os.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-BfKMaVzV.js.map → OperatorDashboard-Dx7639os.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-B48MX4lq.js → PersonaDetailPage-BHQ44UIu.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-B48MX4lq.js.map → PersonaDetailPage-BHQ44UIu.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-dGX6E96g.js → PersonasPage-Cnmb4uox.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-dGX6E96g.js.map → PersonasPage-Cnmb4uox.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-Co5oCg31.js → ProcessDetailPage-CZ09xiWf.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-Co5oCg31.js.map → ProcessDetailPage-CZ09xiWf.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-CrB4VAS6.js → ProcessesListPage-BT76_7bW.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-CrB4VAS6.js.map → ProcessesListPage-BT76_7bW.js.map} +1 -1
- package/dashboard/dist/assets/{RoleDetailPage-C4UTNx0p.js → RoleDetailPage-CDsJKPvV.js} +2 -2
- package/dashboard/dist/assets/{RoleDetailPage-C4UTNx0p.js.map → RoleDetailPage-CDsJKPvV.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-BXo5RirX.js → RolesPage-D2cV92bL.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-BXo5RirX.js.map → RolesPage-D2cV92bL.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-BRQiGv2-.js → RunAsSelector-Cvi8x8xx.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-BRQiGv2-.js.map → RunAsSelector-Cvi8x8xx.js.map} +1 -1
- package/dashboard/dist/assets/{ScanCodesPage-BUT5wjzC.js → ScanCodesPage-BWt_iRCu.js} +2 -2
- package/dashboard/dist/assets/{ScanCodesPage-BUT5wjzC.js.map → ScanCodesPage-BWt_iRCu.js.map} +1 -1
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CkzoOyjd.js → ScanSchemeDetailPage-C6mRYkxL.js} +2 -2
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CkzoOyjd.js.map → ScanSchemeDetailPage-C6mRYkxL.js.map} +1 -1
- package/dashboard/dist/assets/{ScanStationPage-DYamACNZ.js → ScanStationPage-YQiijAKy.js} +2 -2
- package/dashboard/dist/assets/{ScanStationPage-DYamACNZ.js.map → ScanStationPage-YQiijAKy.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-B3yiAcrs.js → StickyPagination-BsbSade1.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-B3yiAcrs.js.map → StickyPagination-BsbSade1.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-C56PtZSb.js → StreamMessageDetail-jczXwuBw.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-C56PtZSb.js.map → StreamMessageDetail-jczXwuBw.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-D64ep5KY.js → SwimlaneTimeline-DSO6QXmX.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-D64ep5KY.js.map → SwimlaneTimeline-DSO6QXmX.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-C2V7BcgB.js → TaskDetailPage-DWLfcAF4.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-C2V7BcgB.js.map → TaskDetailPage-DWLfcAF4.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-C4ctoAa0.js → TasksListPage-B0at3vJ3.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-C4ctoAa0.js.map → TasksListPage-B0at3vJ3.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-BpJ7In27.js → TimeAgo-poxwErGl.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-BpJ7In27.js.map → TimeAgo-poxwErGl.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-BKrIaU_R.js → TimestampCell-Be6tvFQd.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-BKrIaU_R.js.map → TimestampCell-Be6tvFQd.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-CdTXWPqn.js → ToolTestPanel-CYOqnqMz.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-CdTXWPqn.js.map → ToolTestPanel-CYOqnqMz.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-B6tb1RG4.js → TopicDetailPage-C82WIKaU.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-B6tb1RG4.js.map → TopicDetailPage-C82WIKaU.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-DgdOKjwE.js → TopicsPage-COH547ZD.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-DgdOKjwE.js.map → TopicsPage-COH547ZD.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-A-ET2GnX.js → UserName-D2SoVbe9.js} +2 -2
- package/dashboard/dist/assets/{UserName-A-ET2GnX.js.map → UserName-D2SoVbe9.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-CzhoRsis.js → WorkflowExecutionPage-C82Q-TYC.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-CzhoRsis.js.map → WorkflowExecutionPage-C82Q-TYC.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-B_tCYLei.js → WorkflowsDashboard-DDWwCT-v.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-B_tCYLei.js.map → WorkflowsDashboard-DDWwCT-v.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-B4Bh8MIo.js → WorkflowsOverview-DdKRtWdK.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-B4Bh8MIo.js.map → WorkflowsOverview-DdKRtWdK.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-zRrcxk0e.js → YamlWorkflowDetailPage-D3qvyy_4.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-zRrcxk0e.js.map → YamlWorkflowDetailPage-D3qvyy_4.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-DMK3ojp3.js → YamlWorkflowsPage-BJsxZOBC.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-DMK3ojp3.js.map → YamlWorkflowsPage-BJsxZOBC.js.map} +1 -1
- package/dashboard/dist/assets/{agents-B67yyE1z.js → agents-OQHCkIfJ.js} +2 -2
- package/dashboard/dist/assets/{agents-B67yyE1z.js.map → agents-OQHCkIfJ.js.map} +1 -1
- package/dashboard/dist/assets/{bots-pSLFdOm-.js → bots-e8jsNOf4.js} +2 -2
- package/dashboard/dist/assets/{bots-pSLFdOm-.js.map → bots-e8jsNOf4.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-B2Dem6zw.js → capabilities-CEo0AB79.js} +2 -2
- package/dashboard/dist/assets/{capabilities-B2Dem6zw.js.map → capabilities-CEo0AB79.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-DGdaM9f3.js → controlplane-CD6ti-QB.js} +2 -2
- package/dashboard/dist/assets/{controlplane-DGdaM9f3.js.map → controlplane-CD6ti-QB.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-BM7vp-hY.js → escalation-columns-CUtDSbxl.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-BM7vp-hY.js.map → escalation-columns-CUtDSbxl.js.map} +1 -1
- package/dashboard/dist/assets/{index-CunrdEfT.js → index-B0C6wSrQ.js} +2 -2
- package/dashboard/dist/assets/{index-CunrdEfT.js.map → index-B0C6wSrQ.js.map} +1 -1
- package/dashboard/dist/assets/index-B2aiEKtM.js +2 -0
- package/dashboard/dist/assets/index-B2aiEKtM.js.map +1 -0
- package/dashboard/dist/assets/{index-Cd2RdpOx.js → index-B76fjEom.js} +2 -2
- package/dashboard/dist/assets/{index-Cd2RdpOx.js.map → index-B76fjEom.js.map} +1 -1
- package/dashboard/dist/assets/{index-Baf-tOnA.js → index-BYIN0l5M.js} +2 -2
- package/dashboard/dist/assets/{index-Baf-tOnA.js.map → index-BYIN0l5M.js.map} +1 -1
- package/dashboard/dist/assets/{index-CGoXyPlO.js → index-Bf4IPu7I.js} +2 -2
- package/dashboard/dist/assets/{index-CGoXyPlO.js.map → index-Bf4IPu7I.js.map} +1 -1
- package/dashboard/dist/assets/{index-CLq228E2.js → index-BpwkKV5f.js} +2 -2
- package/dashboard/dist/assets/{index-CLq228E2.js.map → index-BpwkKV5f.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cn__VdSf.js → index-BsP7vujM.js} +2 -2
- package/dashboard/dist/assets/{index-Cn__VdSf.js.map → index-BsP7vujM.js.map} +1 -1
- package/dashboard/dist/assets/{index-DbLOvfv5.js → index-C8VhbKFU.js} +2 -2
- package/dashboard/dist/assets/{index-DbLOvfv5.js.map → index-C8VhbKFU.js.map} +1 -1
- package/dashboard/dist/assets/{index-DajlugVw.js → index-COUj4v_W.js} +2 -2
- package/dashboard/dist/assets/{index-DajlugVw.js.map → index-COUj4v_W.js.map} +1 -1
- package/dashboard/dist/assets/{index-Brf_fpE4.js → index-CTZEzzN3.js} +2 -2
- package/dashboard/dist/assets/{index-Brf_fpE4.js.map → index-CTZEzzN3.js.map} +1 -1
- package/dashboard/dist/assets/{index-CKt5YK2z.js → index-DIHr2CvI.js} +2 -2
- package/dashboard/dist/assets/{index-CKt5YK2z.js.map → index-DIHr2CvI.js.map} +1 -1
- package/dashboard/dist/assets/{index-zqoZDL5j.js → index-DSdqso-_.js} +3 -3
- package/dashboard/dist/assets/{index-zqoZDL5j.js.map → index-DSdqso-_.js.map} +1 -1
- package/dashboard/dist/assets/{index-BD6nhSOu.js → index-VsZyCFz8.js} +2 -2
- package/dashboard/dist/assets/{index-BD6nhSOu.js.map → index-VsZyCFz8.js.map} +1 -1
- package/dashboard/dist/assets/{index-CJV2pWNH.css → index-fUqvRHOe.css} +1 -1
- package/dashboard/dist/assets/{knowledge-BExXJ0Ib.js → knowledge-C89zT4Qe.js} +2 -2
- package/dashboard/dist/assets/{knowledge-BExXJ0Ib.js.map → knowledge-C89zT4Qe.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-D2WA-SHc.js → mcp-BLKHykZS.js} +2 -2
- package/dashboard/dist/assets/{mcp-D2WA-SHc.js.map → mcp-BLKHykZS.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-4Tsojk-z.js → mcp-query-CfiqUNDV.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-4Tsojk-z.js.map → mcp-query-CfiqUNDV.js.map} +1 -1
- package/dashboard/dist/assets/{personas-Dy_XSYfJ.js → personas-B1HzC94M.js} +2 -2
- package/dashboard/dist/assets/{personas-Dy_XSYfJ.js.map → personas-B1HzC94M.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-yDHgNtdx.js → pipelines-C73X50ES.js} +2 -2
- package/dashboard/dist/assets/{pipelines-yDHgNtdx.js.map → pipelines-C73X50ES.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-CGoPhQyw.js → tasks-DHbFpQk4.js} +2 -2
- package/dashboard/dist/assets/{tasks-CGoPhQyw.js.map → tasks-DHbFpQk4.js.map} +1 -1
- package/dashboard/dist/assets/{topics-DA1MHDwJ.js → topics-Fu8qCUOd.js} +2 -2
- package/dashboard/dist/assets/{topics-DA1MHDwJ.js.map → topics-Fu8qCUOd.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-onx-Z6IY.js → useEventHooks-DL3Vhz4g.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-onx-Z6IY.js.map → useEventHooks-DL3Vhz4g.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BkuU0SeK.js → useNamespace-CpO5Hogb.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BkuU0SeK.js.map → useNamespace-CpO5Hogb.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-XXS3wg6-.js → useYamlActivityEvents-BwIl6_d2.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-XXS3wg6-.js.map → useYamlActivityEvents-BwIl6_d2.js.map} +1 -1
- package/dashboard/dist/assets/users-C0ke1Z-8.js +2 -0
- package/dashboard/dist/assets/users-C0ke1Z-8.js.map +1 -0
- package/dashboard/dist/assets/{workflows-5sds1Ko8.js → workflows-DR9OQcuq.js} +2 -2
- package/dashboard/dist/assets/{workflows-5sds1Ko8.js.map → workflows-DR9OQcuq.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-CjNa2u3I.js → yaml-workflows-DjEHh9Wy.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-CjNa2u3I.js.map → yaml-workflows-DjEHh9Wy.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/api/http/users.md +53 -1
- package/docs/api/mcp/admin.md +17 -0
- package/docs/api/sdk/users.md +24 -0
- package/docs/dashboard.md +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-5eRsj758.js +0 -2
- package/dashboard/dist/assets/index-5eRsj758.js.map +0 -1
- package/dashboard/dist/assets/users-B_9mC6D_.js +0 -2
- package/dashboard/dist/assets/users-B_9mC6D_.js.map +0 -1
package/build/api/users.d.ts
CHANGED
|
@@ -63,7 +63,8 @@ export declare function createUser(input: {
|
|
|
63
63
|
* @param input.email — new email address
|
|
64
64
|
* @param input.display_name — new display name
|
|
65
65
|
* @param input.status — new user status
|
|
66
|
-
* @param input.metadata —
|
|
66
|
+
* @param input.metadata — REPLACES the whole properties dictionary; for
|
|
67
|
+
* per-key edits use patchUserProperties (never clobbers siblings)
|
|
67
68
|
* @returns `{ status: 200, data: User }` on success, or `{ status: 404 }` if not found
|
|
68
69
|
*/
|
|
69
70
|
export declare function updateUser(input: {
|
|
@@ -74,6 +75,35 @@ export declare function updateUser(input: {
|
|
|
74
75
|
status?: string;
|
|
75
76
|
metadata?: Record<string, any>;
|
|
76
77
|
}): Promise<LTApiResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Atomically patch the user's properties dictionary (lt_users.metadata) —
|
|
80
|
+
* one statement, never read-merge-write. Deleting a key is explicit
|
|
81
|
+
* (`remove`); a key absent from the patch is kept. `rename` preserves the
|
|
82
|
+
* value with no key-absent window. Identity-binding keys (badge scan
|
|
83
|
+
* scheme target facets) assert uniqueness among active users in the same
|
|
84
|
+
* statement.
|
|
85
|
+
*
|
|
86
|
+
* @param input.id — the user's unique identifier
|
|
87
|
+
* @param input.set — properties to set (typed JSON values)
|
|
88
|
+
* @param input.remove — property keys to delete
|
|
89
|
+
* @param input.rename — `{ oldKey: newKey }` renames, values preserved
|
|
90
|
+
* @returns `{ status: 200, data: User }`; 400 malformed patch; 404 unknown
|
|
91
|
+
* user; 409 when an identity value already belongs to another active user
|
|
92
|
+
*/
|
|
93
|
+
export declare function patchUserProperties(input: {
|
|
94
|
+
id: string;
|
|
95
|
+
set?: Record<string, unknown>;
|
|
96
|
+
remove?: string[];
|
|
97
|
+
rename?: Record<string, string>;
|
|
98
|
+
}): Promise<LTApiResult>;
|
|
99
|
+
/**
|
|
100
|
+
* The property keys the platform itself resolves identities against — every
|
|
101
|
+
* enabled identity scan scheme's target facet. The dashboard marks these as
|
|
102
|
+
* system properties.
|
|
103
|
+
*
|
|
104
|
+
* @returns `{ status: 200, data: { keys: string[] } }`
|
|
105
|
+
*/
|
|
106
|
+
export declare function getSystemPropertyKeys(): Promise<LTApiResult>;
|
|
77
107
|
/**
|
|
78
108
|
* Delete a user by ID. Requires admin privileges.
|
|
79
109
|
*
|
package/build/api/users.js
CHANGED
|
@@ -37,6 +37,8 @@ exports.listUsers = listUsers;
|
|
|
37
37
|
exports.getUser = getUser;
|
|
38
38
|
exports.createUser = createUser;
|
|
39
39
|
exports.updateUser = updateUser;
|
|
40
|
+
exports.patchUserProperties = patchUserProperties;
|
|
41
|
+
exports.getSystemPropertyKeys = getSystemPropertyKeys;
|
|
40
42
|
exports.deleteUser = deleteUser;
|
|
41
43
|
exports.getUserRoles = getUserRoles;
|
|
42
44
|
exports.addUserRole = addUserRole;
|
|
@@ -145,7 +147,8 @@ async function createUser(input) {
|
|
|
145
147
|
* @param input.email — new email address
|
|
146
148
|
* @param input.display_name — new display name
|
|
147
149
|
* @param input.status — new user status
|
|
148
|
-
* @param input.metadata —
|
|
150
|
+
* @param input.metadata — REPLACES the whole properties dictionary; for
|
|
151
|
+
* per-key edits use patchUserProperties (never clobbers siblings)
|
|
149
152
|
* @returns `{ status: 200, data: User }` on success, or `{ status: 404 }` if not found
|
|
150
153
|
*/
|
|
151
154
|
async function updateUser(input) {
|
|
@@ -161,6 +164,56 @@ async function updateUser(input) {
|
|
|
161
164
|
return { status: 500, error: err.message };
|
|
162
165
|
}
|
|
163
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Atomically patch the user's properties dictionary (lt_users.metadata) —
|
|
169
|
+
* one statement, never read-merge-write. Deleting a key is explicit
|
|
170
|
+
* (`remove`); a key absent from the patch is kept. `rename` preserves the
|
|
171
|
+
* value with no key-absent window. Identity-binding keys (badge scan
|
|
172
|
+
* scheme target facets) assert uniqueness among active users in the same
|
|
173
|
+
* statement.
|
|
174
|
+
*
|
|
175
|
+
* @param input.id — the user's unique identifier
|
|
176
|
+
* @param input.set — properties to set (typed JSON values)
|
|
177
|
+
* @param input.remove — property keys to delete
|
|
178
|
+
* @param input.rename — `{ oldKey: newKey }` renames, values preserved
|
|
179
|
+
* @returns `{ status: 200, data: User }`; 400 malformed patch; 404 unknown
|
|
180
|
+
* user; 409 when an identity value already belongs to another active user
|
|
181
|
+
*/
|
|
182
|
+
async function patchUserProperties(input) {
|
|
183
|
+
try {
|
|
184
|
+
const user = await userService.patchUserProperties(input.id, {
|
|
185
|
+
set: input.set,
|
|
186
|
+
remove: input.remove,
|
|
187
|
+
rename: input.rename,
|
|
188
|
+
});
|
|
189
|
+
if (!user) {
|
|
190
|
+
return { status: 404, error: 'User not found' };
|
|
191
|
+
}
|
|
192
|
+
return { status: 200, data: user };
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
if (err.status === 400 || err.status === 409) {
|
|
196
|
+
return { status: err.status, error: err.message };
|
|
197
|
+
}
|
|
198
|
+
return { status: 500, error: err.message };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The property keys the platform itself resolves identities against — every
|
|
203
|
+
* enabled identity scan scheme's target facet. The dashboard marks these as
|
|
204
|
+
* system properties.
|
|
205
|
+
*
|
|
206
|
+
* @returns `{ status: 200, data: { keys: string[] } }`
|
|
207
|
+
*/
|
|
208
|
+
async function getSystemPropertyKeys() {
|
|
209
|
+
try {
|
|
210
|
+
const keys = await userService.getIdentityPropertyKeys();
|
|
211
|
+
return { status: 200, data: { keys } };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return { status: 500, error: err.message };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
164
217
|
/**
|
|
165
218
|
* Delete a user by ID. Requires admin privileges.
|
|
166
219
|
*
|
package/build/bin/ltc.js
CHANGED
|
@@ -335,6 +335,13 @@ usrCmd.command('list')
|
|
|
335
335
|
usrCmd.command('get <id>')
|
|
336
336
|
.option('--json', 'JSON output')
|
|
337
337
|
.action(wrap(usr.getUser));
|
|
338
|
+
usrCmd.command('props <id>')
|
|
339
|
+
.description('View or atomically patch the user\'s properties dictionary')
|
|
340
|
+
.option('--set <key=value...>', 'Set a property (repeatable; value JSON-parsed, string fallback)')
|
|
341
|
+
.option('--remove <key...>', 'Delete a property (repeatable)')
|
|
342
|
+
.option('--rename <old=new...>', 'Rename a property, value preserved (repeatable)')
|
|
343
|
+
.option('--json', 'JSON output')
|
|
344
|
+
.action(wrap(usr.userProps));
|
|
338
345
|
// ── Roles ────────────────────────────────────────────────────────────────
|
|
339
346
|
const rolesCmd = commander_1.program.command('roles').description('Roles — the queue-backed work surfaces where workflows hand off to people');
|
|
340
347
|
rolesCmd.command('list')
|
|
@@ -5,3 +5,14 @@ export declare function listUsers(opts: {
|
|
|
5
5
|
export declare function getUser(id: string, opts: {
|
|
6
6
|
json?: boolean;
|
|
7
7
|
}): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* View or atomically patch a user's properties dictionary. With no flags,
|
|
10
|
+
* prints the dictionary; with --set/--remove/--rename, applies ONE atomic
|
|
11
|
+
* patch (never read-merge-write) and prints the result.
|
|
12
|
+
*/
|
|
13
|
+
export declare function userProps(id: string, opts: {
|
|
14
|
+
set?: string[];
|
|
15
|
+
remove?: string[];
|
|
16
|
+
rename?: string[];
|
|
17
|
+
json?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.listUsers = listUsers;
|
|
4
7
|
exports.getUser = getUser;
|
|
8
|
+
exports.userProps = userProps;
|
|
9
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
5
10
|
const client_1 = require("../client");
|
|
6
11
|
const format_1 = require("../format");
|
|
7
12
|
const COLUMNS = [
|
|
@@ -23,3 +28,57 @@ async function getUser(id, opts) {
|
|
|
23
28
|
}
|
|
24
29
|
console.log(JSON.stringify(data, null, 2));
|
|
25
30
|
}
|
|
31
|
+
/** Parse a repeatable `key=value` flag; values JSON-parse with string fallback. */
|
|
32
|
+
function parsePairs(pairs) {
|
|
33
|
+
if (!pairs?.length)
|
|
34
|
+
return undefined;
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const pair of pairs) {
|
|
37
|
+
const eq = pair.indexOf('=');
|
|
38
|
+
if (eq <= 0)
|
|
39
|
+
throw new Error(`Expected key=value, got "${pair}"`);
|
|
40
|
+
const key = pair.slice(0, eq);
|
|
41
|
+
const raw = pair.slice(eq + 1);
|
|
42
|
+
try {
|
|
43
|
+
out[key] = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
out[key] = raw;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* View or atomically patch a user's properties dictionary. With no flags,
|
|
53
|
+
* prints the dictionary; with --set/--remove/--rename, applies ONE atomic
|
|
54
|
+
* patch (never read-merge-write) and prints the result.
|
|
55
|
+
*/
|
|
56
|
+
async function userProps(id, opts) {
|
|
57
|
+
const set = parsePairs(opts.set);
|
|
58
|
+
const rename = parsePairs(opts.rename);
|
|
59
|
+
const remove = opts.remove?.length ? opts.remove : undefined;
|
|
60
|
+
let user;
|
|
61
|
+
if (set || remove || rename) {
|
|
62
|
+
user = await (0, client_1.apiFetch)(`/users/${id}/properties`, {
|
|
63
|
+
method: 'PATCH',
|
|
64
|
+
body: JSON.stringify({ set, remove, rename }),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
user = await (0, client_1.apiFetch)(`/users/${id}`);
|
|
69
|
+
}
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
console.log(JSON.stringify(user.metadata ?? {}, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const props = Object.entries(user.metadata ?? {});
|
|
75
|
+
console.log(`\n ${picocolors_1.default.bold(user.display_name ?? user.external_id ?? id)} ${picocolors_1.default.dim(user.id ?? id)}`);
|
|
76
|
+
if (props.length === 0) {
|
|
77
|
+
console.log(picocolors_1.default.dim(' (no properties)\n'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const [key, value] of props) {
|
|
81
|
+
console.log(` ${picocolors_1.default.bold(key)} = ${typeof value === 'string' ? value : JSON.stringify(value)}`);
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
package/build/routes/users.js
CHANGED
|
@@ -55,6 +55,16 @@ router.get('/', async (req, res) => {
|
|
|
55
55
|
});
|
|
56
56
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
57
57
|
});
|
|
58
|
+
/**
|
|
59
|
+
* GET /api/users/system-property-keys
|
|
60
|
+
* The property keys the platform resolves identities against (enabled
|
|
61
|
+
* identity scan schemes' target facets) — marked as system properties in
|
|
62
|
+
* the dashboard editor. Registered before /:id so the literal wins.
|
|
63
|
+
*/
|
|
64
|
+
router.get('/system-property-keys', async (_req, res) => {
|
|
65
|
+
const result = await api.getSystemPropertyKeys();
|
|
66
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
67
|
+
});
|
|
58
68
|
/**
|
|
59
69
|
* GET /api/users/:id
|
|
60
70
|
* Get a single user by ID.
|
|
@@ -81,6 +91,22 @@ router.put('/:id', auth_1.requireBuilder, async (req, res) => {
|
|
|
81
91
|
const result = await api.updateUser({ id: req.params.id, ...(req.body || {}) });
|
|
82
92
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
83
93
|
});
|
|
94
|
+
/**
|
|
95
|
+
* PATCH /api/users/:id/properties
|
|
96
|
+
* Atomically patch the user's properties dictionary. Builder only — the
|
|
97
|
+
* same gate as every other user write.
|
|
98
|
+
* Body: { set?: { key: value }, remove?: [key], rename?: { old: new } }
|
|
99
|
+
*/
|
|
100
|
+
router.patch('/:id/properties', auth_1.requireBuilder, async (req, res) => {
|
|
101
|
+
const { set, remove, rename } = req.body || {};
|
|
102
|
+
const result = await api.patchUserProperties({
|
|
103
|
+
id: req.params.id,
|
|
104
|
+
set,
|
|
105
|
+
remove,
|
|
106
|
+
rename,
|
|
107
|
+
});
|
|
108
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
109
|
+
});
|
|
84
110
|
/**
|
|
85
111
|
* DELETE /api/users/:id
|
|
86
112
|
* Delete a user. Builder only.
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -290,6 +290,8 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
290
290
|
get: typeof usersApi.getUser;
|
|
291
291
|
create: typeof usersApi.createUser;
|
|
292
292
|
update: typeof usersApi.updateUser;
|
|
293
|
+
patchProperties: typeof usersApi.patchUserProperties;
|
|
294
|
+
getSystemPropertyKeys: typeof usersApi.getSystemPropertyKeys;
|
|
293
295
|
delete: typeof usersApi.deleteUser;
|
|
294
296
|
getRoles: typeof usersApi.getUserRoles;
|
|
295
297
|
addRole: typeof usersApi.addUserRole;
|
package/build/sdk/index.js
CHANGED
|
@@ -194,6 +194,8 @@ function createClient(options = {}) {
|
|
|
194
194
|
get: usersApi.getUser,
|
|
195
195
|
create: usersApi.createUser,
|
|
196
196
|
update: usersApi.updateUser,
|
|
197
|
+
patchProperties: usersApi.patchUserProperties,
|
|
198
|
+
getSystemPropertyKeys: usersApi.getSystemPropertyKeys,
|
|
197
199
|
delete: usersApi.deleteUser,
|
|
198
200
|
getRoles: usersApi.getUserRoles,
|
|
199
201
|
addRole: usersApi.addUserRole,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LTUserRecord, LTRoleType, LTUserStatus } from '../../types';
|
|
2
|
-
import type { CreateUserInput, UpdateUserInput } from './types';
|
|
2
|
+
import type { CreateUserInput, UpdateUserInput, UserPropertyOps } from './types';
|
|
3
3
|
export declare function attachRoles(user: any): Promise<LTUserRecord>;
|
|
4
4
|
export declare function createUser(input: CreateUserInput): Promise<LTUserRecord>;
|
|
5
5
|
export declare function getUser(id: string): Promise<LTUserRecord | null>;
|
|
@@ -12,6 +12,37 @@ export declare function getUserByEmail(email: string): Promise<LTUserRecord | nu
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function getUserByMetadataValue(key: string, value: string): Promise<LTUserRecord | null>;
|
|
14
14
|
export declare function updateUser(id: string, input: UpdateUserInput): Promise<LTUserRecord | null>;
|
|
15
|
+
/** A property write that would create an ambiguous identity binding. */
|
|
16
|
+
export declare class UserPropertyConflictError extends Error {
|
|
17
|
+
status: number;
|
|
18
|
+
constructor(key: string);
|
|
19
|
+
}
|
|
20
|
+
/** A malformed property patch — rejected before any write. */
|
|
21
|
+
export declare class UserPropertyValidationError extends Error {
|
|
22
|
+
status: number;
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The lt_users.metadata keys the platform itself resolves identities against:
|
|
27
|
+
* every enabled identity scan scheme's target_facet (e.g. badge_id). Writes
|
|
28
|
+
* to these keys carry a uniqueness guard; the dashboard marks them as system
|
|
29
|
+
* properties.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getIdentityPropertyKeys(): Promise<string[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Atomic per-key patch of the user's properties dictionary (lt_users.metadata).
|
|
34
|
+
* One statement — never read-merge-write: concurrent patches of different
|
|
35
|
+
* keys both land, a rename never opens a key-absent window, and deleting is
|
|
36
|
+
* explicit (`remove`) — an absent key means keep. Precedence on collision:
|
|
37
|
+
* set > rename > existing. Values are raw JSON, so numbers/booleans/objects
|
|
38
|
+
* round-trip typed.
|
|
39
|
+
*
|
|
40
|
+
* Identity-binding keys (getIdentityPropertyKeys) additionally assert, in the
|
|
41
|
+
* same statement, that no OTHER active user carries the value — the
|
|
42
|
+
* write-side counterpart of getUserByMetadataValue's ambiguity throw.
|
|
43
|
+
* A tripped guard raises UserPropertyConflictError (409).
|
|
44
|
+
*/
|
|
45
|
+
export declare function patchUserProperties(id: string, ops: UserPropertyOps): Promise<LTUserRecord | null>;
|
|
15
46
|
export declare function deleteUser(id: string): Promise<boolean>;
|
|
16
47
|
export declare function listUsers(filters: {
|
|
17
48
|
role?: string;
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UserPropertyValidationError = exports.UserPropertyConflictError = void 0;
|
|
6
7
|
exports.attachRoles = attachRoles;
|
|
7
8
|
exports.createUser = createUser;
|
|
8
9
|
exports.getUser = getUser;
|
|
@@ -10,11 +11,15 @@ exports.getUserByExternalId = getUserByExternalId;
|
|
|
10
11
|
exports.getUserByEmail = getUserByEmail;
|
|
11
12
|
exports.getUserByMetadataValue = getUserByMetadataValue;
|
|
12
13
|
exports.updateUser = updateUser;
|
|
14
|
+
exports.getIdentityPropertyKeys = getIdentityPropertyKeys;
|
|
15
|
+
exports.patchUserProperties = patchUserProperties;
|
|
13
16
|
exports.deleteUser = deleteUser;
|
|
14
17
|
exports.listUsers = listUsers;
|
|
15
18
|
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
16
19
|
const db_1 = require("../../lib/db");
|
|
17
20
|
const sql_1 = require("./sql");
|
|
21
|
+
const scan_code_1 = require("../scan-code");
|
|
22
|
+
const scan_code_2 = require("../../types/scan-code");
|
|
18
23
|
const scope_1 = require("./scope");
|
|
19
24
|
// ─── Private helpers (exported for internal use by auth.ts) ──────────────────
|
|
20
25
|
async function attachRoles(user) {
|
|
@@ -143,6 +148,109 @@ async function updateUser(id, input) {
|
|
|
143
148
|
return null;
|
|
144
149
|
return attachRoles(rows[0]);
|
|
145
150
|
}
|
|
151
|
+
/** A property write that would create an ambiguous identity binding. */
|
|
152
|
+
class UserPropertyConflictError extends Error {
|
|
153
|
+
constructor(key) {
|
|
154
|
+
super(`"${key}" value is already bound to another active user`);
|
|
155
|
+
this.status = 409;
|
|
156
|
+
this.name = 'UserPropertyConflictError';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.UserPropertyConflictError = UserPropertyConflictError;
|
|
160
|
+
/** A malformed property patch — rejected before any write. */
|
|
161
|
+
class UserPropertyValidationError extends Error {
|
|
162
|
+
constructor(message) {
|
|
163
|
+
super(message);
|
|
164
|
+
this.status = 400;
|
|
165
|
+
this.name = 'UserPropertyValidationError';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.UserPropertyValidationError = UserPropertyValidationError;
|
|
169
|
+
/**
|
|
170
|
+
* The lt_users.metadata keys the platform itself resolves identities against:
|
|
171
|
+
* every enabled identity scan scheme's target_facet (e.g. badge_id). Writes
|
|
172
|
+
* to these keys carry a uniqueness guard; the dashboard marks them as system
|
|
173
|
+
* properties.
|
|
174
|
+
*/
|
|
175
|
+
async function getIdentityPropertyKeys() {
|
|
176
|
+
const schemes = await (0, scan_code_1.listScanSchemes)();
|
|
177
|
+
return [...new Set(schemes
|
|
178
|
+
.filter((s) => s.enabled && s.kind === scan_code_2.SCAN_SCHEME_KINDS.IDENTITY)
|
|
179
|
+
.map((s) => s.target_facet))];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Atomic per-key patch of the user's properties dictionary (lt_users.metadata).
|
|
183
|
+
* One statement — never read-merge-write: concurrent patches of different
|
|
184
|
+
* keys both land, a rename never opens a key-absent window, and deleting is
|
|
185
|
+
* explicit (`remove`) — an absent key means keep. Precedence on collision:
|
|
186
|
+
* set > rename > existing. Values are raw JSON, so numbers/booleans/objects
|
|
187
|
+
* round-trip typed.
|
|
188
|
+
*
|
|
189
|
+
* Identity-binding keys (getIdentityPropertyKeys) additionally assert, in the
|
|
190
|
+
* same statement, that no OTHER active user carries the value — the
|
|
191
|
+
* write-side counterpart of getUserByMetadataValue's ambiguity throw.
|
|
192
|
+
* A tripped guard raises UserPropertyConflictError (409).
|
|
193
|
+
*/
|
|
194
|
+
async function patchUserProperties(id, ops) {
|
|
195
|
+
const set = ops.set ?? {};
|
|
196
|
+
const remove = ops.remove ?? [];
|
|
197
|
+
const rename = ops.rename ?? {};
|
|
198
|
+
const setKeys = Object.keys(set);
|
|
199
|
+
const renameEntries = Object.entries(rename);
|
|
200
|
+
if (setKeys.length === 0 && remove.length === 0 && renameEntries.length === 0) {
|
|
201
|
+
throw new UserPropertyValidationError('A property patch requires at least one set, remove, or rename');
|
|
202
|
+
}
|
|
203
|
+
for (const key of [...setKeys, ...remove, ...renameEntries.flat()]) {
|
|
204
|
+
if (typeof key !== 'string' || key.trim().length === 0) {
|
|
205
|
+
throw new UserPropertyValidationError('Property keys must be non-empty strings');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const key of setKeys) {
|
|
209
|
+
if (remove.includes(key)) {
|
|
210
|
+
throw new UserPropertyValidationError(`"${key}" cannot be both set and removed in one patch`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const [prev, next] of renameEntries) {
|
|
214
|
+
if (prev === next) {
|
|
215
|
+
throw new UserPropertyValidationError(`Renaming "${prev}" to itself is not a change`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const renameTargets = renameEntries.map(([, next]) => next);
|
|
219
|
+
if (new Set(renameTargets).size !== renameTargets.length) {
|
|
220
|
+
throw new UserPropertyValidationError('Two properties cannot be renamed to the same key');
|
|
221
|
+
}
|
|
222
|
+
// Identity bindings resolve people — their values must be scalar and, for
|
|
223
|
+
// ACTIVE users, unique. The guard rides the same statement.
|
|
224
|
+
const identityKeys = await getIdentityPropertyKeys();
|
|
225
|
+
const guard = {};
|
|
226
|
+
for (const key of identityKeys) {
|
|
227
|
+
if (key in set) {
|
|
228
|
+
const value = set[key];
|
|
229
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
230
|
+
throw new UserPropertyValidationError(`"${key}" is an identity binding — its value must be a string or number`);
|
|
231
|
+
}
|
|
232
|
+
guard[key] = String(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const removeAll = [...new Set([...remove, ...renameEntries.map(([prev]) => prev)])];
|
|
236
|
+
const renamePairs = renameEntries.map(([prev, next]) => ({ prev, next }));
|
|
237
|
+
const pool = (0, db_1.getPool)();
|
|
238
|
+
const { rows } = await pool.query(sql_1.PATCH_USER_PROPERTIES, [
|
|
239
|
+
id,
|
|
240
|
+
removeAll,
|
|
241
|
+
JSON.stringify(renamePairs),
|
|
242
|
+
JSON.stringify(set),
|
|
243
|
+
JSON.stringify(guard),
|
|
244
|
+
]);
|
|
245
|
+
if (rows[0])
|
|
246
|
+
return attachRoles(rows[0]);
|
|
247
|
+
const exists = await pool.query(sql_1.VERIFY_USER_BY_ID, [id]);
|
|
248
|
+
if (!exists.rows[0])
|
|
249
|
+
return null;
|
|
250
|
+
// The user exists, so the identity guard is what blocked the write. Name
|
|
251
|
+
// the first guarded key for the caller; the row was not touched.
|
|
252
|
+
throw new UserPropertyConflictError(Object.keys(guard)[0] ?? 'identity binding');
|
|
253
|
+
}
|
|
146
254
|
async function deleteUser(id) {
|
|
147
255
|
const pool = (0, db_1.getPool)();
|
|
148
256
|
const { rowCount } = await pool.query(sql_1.DELETE_USER_BY_ID, [id]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CreateUserInput, UpdateUserInput, VALID_ROLE_TYPES } from './types';
|
|
2
|
-
export { createUser, getUser, getUserByExternalId, getUserByEmail, getUserByMetadataValue, updateUser, deleteUser, listUsers, } from './crud';
|
|
1
|
+
export { CreateUserInput, UpdateUserInput, UserPropertyOps, VALID_ROLE_TYPES } from './types';
|
|
2
|
+
export { createUser, getUser, getUserByExternalId, getUserByEmail, getUserByMetadataValue, updateUser, patchUserProperties, getIdentityPropertyKeys, UserPropertyConflictError, UserPropertyValidationError, deleteUser, listUsers, } from './crud';
|
|
3
3
|
export { isValidRoleType, addUserRole, grantRoleIfAbsent, removeUserRole, getUserRoles, hasRole, hasRoleType, } from './roles';
|
|
4
4
|
export { isSuperAdmin, isGroupAdmin, canManageRole, hasGlobalEscalationAccess, hasRolesAsAdmin, getRoleScope, getRoleWriteScope } from './rbac';
|
|
5
5
|
export { READ_SCOPES, WRITE_SCOPES, DEFAULT_READ_SCOPE, DEFAULT_WRITE_SCOPE, isValidReadScope, isValidWriteScope, isValidScopePair, effectiveScope, } from './scope';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = exports.seedAdmin = exports.verifyPassword = exports.effectiveScope = exports.isValidScopePair = exports.isValidWriteScope = exports.isValidReadScope = exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = exports.getRoleWriteScope = exports.getRoleScope = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.grantRoleIfAbsent = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByMetadataValue = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
|
|
3
|
+
exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = exports.seedAdmin = exports.verifyPassword = exports.effectiveScope = exports.isValidScopePair = exports.isValidWriteScope = exports.isValidReadScope = exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = exports.getRoleWriteScope = exports.getRoleScope = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.grantRoleIfAbsent = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.UserPropertyValidationError = exports.UserPropertyConflictError = exports.getIdentityPropertyKeys = exports.patchUserProperties = exports.updateUser = exports.getUserByMetadataValue = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
|
|
4
4
|
var types_1 = require("./types");
|
|
5
5
|
Object.defineProperty(exports, "VALID_ROLE_TYPES", { enumerable: true, get: function () { return types_1.VALID_ROLE_TYPES; } });
|
|
6
6
|
var crud_1 = require("./crud");
|
|
@@ -10,6 +10,10 @@ Object.defineProperty(exports, "getUserByExternalId", { enumerable: true, get: f
|
|
|
10
10
|
Object.defineProperty(exports, "getUserByEmail", { enumerable: true, get: function () { return crud_1.getUserByEmail; } });
|
|
11
11
|
Object.defineProperty(exports, "getUserByMetadataValue", { enumerable: true, get: function () { return crud_1.getUserByMetadataValue; } });
|
|
12
12
|
Object.defineProperty(exports, "updateUser", { enumerable: true, get: function () { return crud_1.updateUser; } });
|
|
13
|
+
Object.defineProperty(exports, "patchUserProperties", { enumerable: true, get: function () { return crud_1.patchUserProperties; } });
|
|
14
|
+
Object.defineProperty(exports, "getIdentityPropertyKeys", { enumerable: true, get: function () { return crud_1.getIdentityPropertyKeys; } });
|
|
15
|
+
Object.defineProperty(exports, "UserPropertyConflictError", { enumerable: true, get: function () { return crud_1.UserPropertyConflictError; } });
|
|
16
|
+
Object.defineProperty(exports, "UserPropertyValidationError", { enumerable: true, get: function () { return crud_1.UserPropertyValidationError; } });
|
|
13
17
|
Object.defineProperty(exports, "deleteUser", { enumerable: true, get: function () { return crud_1.deleteUser; } });
|
|
14
18
|
Object.defineProperty(exports, "listUsers", { enumerable: true, get: function () { return crud_1.listUsers; } });
|
|
15
19
|
var roles_1 = require("./roles");
|
|
@@ -28,6 +28,22 @@ export declare const GET_USER_BY_ID = "SELECT * FROM lt_users WHERE id = $1";
|
|
|
28
28
|
export declare const GET_USERS_BY_METADATA_VALUE = "SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2";
|
|
29
29
|
/** Check if a user exists by id. Lightweight — returns only the id column. */
|
|
30
30
|
export declare const VERIFY_USER_BY_ID = "SELECT id FROM lt_users WHERE id = $1 LIMIT 1";
|
|
31
|
+
/**
|
|
32
|
+
* Atomic per-key patch of the user's properties dictionary — ONE statement,
|
|
33
|
+
* never read-merge-write. Composition (later wins on key collision):
|
|
34
|
+
*
|
|
35
|
+
* base minus ($2: removed keys ∪ rename old-names)
|
|
36
|
+
* || renames ($3: [{prev,next}] — each carries the OLD value, skipped when
|
|
37
|
+
* the source key is absent)
|
|
38
|
+
* || sets ($4: typed JSON values)
|
|
39
|
+
*
|
|
40
|
+
* $5 is the identity-binding guard: {key: value} pairs that must not already
|
|
41
|
+
* belong to ANOTHER active user (the write-side counterpart of the
|
|
42
|
+
* resolver's ambiguity throw in GET_USERS_BY_METADATA_VALUE). Zero rows
|
|
43
|
+
* back means the user is missing OR the guard tripped — the caller
|
|
44
|
+
* distinguishes with VERIFY_USER_BY_ID.
|
|
45
|
+
*/
|
|
46
|
+
export declare const PATCH_USER_PROPERTIES = "\n UPDATE lt_users SET metadata = (\n (COALESCE(metadata, '{}'::jsonb) - $2::text[])\n || COALESCE((\n SELECT jsonb_object_agg(r.next, COALESCE(lt_users.metadata, '{}'::jsonb) -> r.prev)\n FROM jsonb_to_recordset($3::jsonb) AS r(prev text, next text)\n WHERE COALESCE(lt_users.metadata, '{}'::jsonb) ? r.prev\n ), '{}'::jsonb)\n || $4::jsonb\n )\n WHERE id = $1\n AND NOT EXISTS (\n SELECT 1\n FROM lt_users u2, jsonb_each_text($5::jsonb) AS g(k, v)\n WHERE u2.id <> $1 AND u2.status = 'active' AND u2.metadata ->> g.k = g.v\n )\n RETURNING *";
|
|
31
47
|
/** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
|
|
32
48
|
export declare const GET_USER_WITH_ROLES = "SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,\n r.role, r.type AS role_type, r.read_scope, r.write_scope\n FROM lt_users u\n LEFT JOIN lt_user_roles r ON r.user_id = u.id\n WHERE u.external_id = $1\n ORDER BY r.created_at";
|
|
33
49
|
export declare const DELETE_USER_BY_ID = "DELETE FROM lt_users WHERE id = $1";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ─── Shared queries (used by multiple modules) ──────────────────────────────
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.INSERT_USER_ROLE_IF_ABSENT = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.VERIFY_USER_BY_ID = exports.GET_USERS_BY_METADATA_VALUE = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
|
|
4
|
+
exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.INSERT_USER_ROLE_IF_ABSENT = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.PATCH_USER_PROPERTIES = exports.VERIFY_USER_BY_ID = exports.GET_USERS_BY_METADATA_VALUE = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
|
|
5
5
|
/** Ensure a role name exists in the lt_roles lookup table (FK target). */
|
|
6
6
|
exports.ENSURE_ROLE_EXISTS = 'INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING';
|
|
7
7
|
/** Fetch roles for a single user, ordered by creation time. granted_by_persona
|
|
@@ -50,6 +50,38 @@ exports.GET_USER_BY_ID = 'SELECT * FROM lt_users WHERE id = $1';
|
|
|
50
50
|
exports.GET_USERS_BY_METADATA_VALUE = `SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2`;
|
|
51
51
|
/** Check if a user exists by id. Lightweight — returns only the id column. */
|
|
52
52
|
exports.VERIFY_USER_BY_ID = 'SELECT id FROM lt_users WHERE id = $1 LIMIT 1';
|
|
53
|
+
/**
|
|
54
|
+
* Atomic per-key patch of the user's properties dictionary — ONE statement,
|
|
55
|
+
* never read-merge-write. Composition (later wins on key collision):
|
|
56
|
+
*
|
|
57
|
+
* base minus ($2: removed keys ∪ rename old-names)
|
|
58
|
+
* || renames ($3: [{prev,next}] — each carries the OLD value, skipped when
|
|
59
|
+
* the source key is absent)
|
|
60
|
+
* || sets ($4: typed JSON values)
|
|
61
|
+
*
|
|
62
|
+
* $5 is the identity-binding guard: {key: value} pairs that must not already
|
|
63
|
+
* belong to ANOTHER active user (the write-side counterpart of the
|
|
64
|
+
* resolver's ambiguity throw in GET_USERS_BY_METADATA_VALUE). Zero rows
|
|
65
|
+
* back means the user is missing OR the guard tripped — the caller
|
|
66
|
+
* distinguishes with VERIFY_USER_BY_ID.
|
|
67
|
+
*/
|
|
68
|
+
exports.PATCH_USER_PROPERTIES = `
|
|
69
|
+
UPDATE lt_users SET metadata = (
|
|
70
|
+
(COALESCE(metadata, '{}'::jsonb) - $2::text[])
|
|
71
|
+
|| COALESCE((
|
|
72
|
+
SELECT jsonb_object_agg(r.next, COALESCE(lt_users.metadata, '{}'::jsonb) -> r.prev)
|
|
73
|
+
FROM jsonb_to_recordset($3::jsonb) AS r(prev text, next text)
|
|
74
|
+
WHERE COALESCE(lt_users.metadata, '{}'::jsonb) ? r.prev
|
|
75
|
+
), '{}'::jsonb)
|
|
76
|
+
|| $4::jsonb
|
|
77
|
+
)
|
|
78
|
+
WHERE id = $1
|
|
79
|
+
AND NOT EXISTS (
|
|
80
|
+
SELECT 1
|
|
81
|
+
FROM lt_users u2, jsonb_each_text($5::jsonb) AS g(k, v)
|
|
82
|
+
WHERE u2.id <> $1 AND u2.status = 'active' AND u2.metadata ->> g.k = g.v
|
|
83
|
+
)
|
|
84
|
+
RETURNING *`;
|
|
53
85
|
/** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
|
|
54
86
|
exports.GET_USER_WITH_ROLES = `SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,
|
|
55
87
|
r.role, r.type AS role_type, r.read_scope, r.write_scope
|
|
@@ -24,5 +24,16 @@ export interface UpdateUserInput {
|
|
|
24
24
|
display_name?: string;
|
|
25
25
|
password?: string;
|
|
26
26
|
status?: LTUserStatus;
|
|
27
|
+
/** Wholesale REPLACE of the properties dictionary — for per-key edits use patchUserProperties. */
|
|
27
28
|
metadata?: Record<string, any>;
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* One atomic patch of the user's properties dictionary. Deleting is explicit
|
|
32
|
+
* (`remove`) — a key absent from the patch is kept. `rename` preserves the
|
|
33
|
+
* value. Precedence on collision: set > rename > existing.
|
|
34
|
+
*/
|
|
35
|
+
export interface UserPropertyOps {
|
|
36
|
+
set?: Record<string, unknown>;
|
|
37
|
+
remove?: string[];
|
|
38
|
+
rename?: Record<string, string>;
|
|
39
|
+
}
|