circuschief 0.1.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/package.json +33 -0
- package/packages/server/bin/cli.js +4 -0
- package/packages/server/src/agents/AgentGateway.js +64 -0
- package/packages/server/src/agents/BaseAgent.js +41 -0
- package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
- package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
- package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
- package/packages/server/src/agents/types.js +43 -0
- package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
- package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
- package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
- package/packages/server/src/api/canvas-helpers.js +249 -0
- package/packages/server/src/api/canvas-trash-routes.js +205 -0
- package/packages/server/src/api/canvas.js +331 -0
- package/packages/server/src/api/commandButtons.js +312 -0
- package/packages/server/src/api/commands.js +169 -0
- package/packages/server/src/api/filesystem.js +62 -0
- package/packages/server/src/api/git.js +85 -0
- package/packages/server/src/api/index.js +44 -0
- package/packages/server/src/api/kanban.js +342 -0
- package/packages/server/src/api/metrics.js +194 -0
- package/packages/server/src/api/projects-helpers.js +43 -0
- package/packages/server/src/api/projects-session-helpers.js +295 -0
- package/packages/server/src/api/projects.js +384 -0
- package/packages/server/src/api/providers.js +249 -0
- package/packages/server/src/api/quickResponses.js +129 -0
- package/packages/server/src/api/sessions-archive.js +69 -0
- package/packages/server/src/api/sessions-commands.js +220 -0
- package/packages/server/src/api/sessions-conversations.js +168 -0
- package/packages/server/src/api/sessions-draft.js +72 -0
- package/packages/server/src/api/sessions-lifecycle.js +190 -0
- package/packages/server/src/api/sessions-messages.js +141 -0
- package/packages/server/src/api/sessions-notes.js +51 -0
- package/packages/server/src/api/sessions-patch.js +252 -0
- package/packages/server/src/api/sessions-streaming.js +86 -0
- package/packages/server/src/api/sessions.js +269 -0
- package/packages/server/src/api/settings.js +194 -0
- package/packages/server/src/api/templates.js +63 -0
- package/packages/server/src/app.js +51 -0
- package/packages/server/src/database.js +58 -0
- package/packages/server/src/db/AgentCallLogRepository.js +322 -0
- package/packages/server/src/db/AttachmentRepository.js +191 -0
- package/packages/server/src/db/BaseRepository.js +39 -0
- package/packages/server/src/db/CanvasItemRepository.js +315 -0
- package/packages/server/src/db/CommandButtonRepository.js +75 -0
- package/packages/server/src/db/CommandRunRepository.js +219 -0
- package/packages/server/src/db/ConversationRepository.js +379 -0
- package/packages/server/src/db/DatabaseManager.js +91 -0
- package/packages/server/src/db/KanbanBoardRepository.js +92 -0
- package/packages/server/src/db/KanbanCardRepository.js +286 -0
- package/packages/server/src/db/KanbanLaneRepository.js +279 -0
- package/packages/server/src/db/MessageRepository.js +156 -0
- package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
- package/packages/server/src/db/ProjectRepository.js +110 -0
- package/packages/server/src/db/ProviderRepository.js +307 -0
- package/packages/server/src/db/QuickResponseRepository.js +186 -0
- package/packages/server/src/db/SessionNoteRepository.js +60 -0
- package/packages/server/src/db/SessionRepository.js +314 -0
- package/packages/server/src/db/SessionSummaryRepository.js +200 -0
- package/packages/server/src/db/SessionTemplateRepository.js +171 -0
- package/packages/server/src/db/SettingsRepository.js +211 -0
- package/packages/server/src/db/TodoRepository.js +132 -0
- package/packages/server/src/db/WorkLogRepository.js +122 -0
- package/packages/server/src/db/conversation-helpers.js +119 -0
- package/packages/server/src/db/index.js +100 -0
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
- package/packages/server/src/db/migrations/index.js +199 -0
- package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
- package/packages/server/src/db/migrations/migrationUtils.js +55 -0
- package/packages/server/src/db/migrations/miscMigrations.js +242 -0
- package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
- package/packages/server/src/db/session-helpers.js +150 -0
- package/packages/server/src/index.js +106 -0
- package/packages/server/src/logger.js +22 -0
- package/packages/server/src/middleware/sessionLookup.js +57 -0
- package/packages/server/src/middleware/upload.js +94 -0
- package/packages/server/src/schema.sql +363 -0
- package/packages/server/src/services/agentCallLogger.js +116 -0
- package/packages/server/src/services/canvasStore.js +56 -0
- package/packages/server/src/services/childSessionContext.js +61 -0
- package/packages/server/src/services/commandRunner.js +422 -0
- package/packages/server/src/services/conversationContext.js +72 -0
- package/packages/server/src/services/diffService.js +172 -0
- package/packages/server/src/services/draftSessionService.js +181 -0
- package/packages/server/src/services/encryption.js +134 -0
- package/packages/server/src/services/ghService.js +169 -0
- package/packages/server/src/services/gitService.js +520 -0
- package/packages/server/src/services/gitSessionSetup.js +48 -0
- package/packages/server/src/services/hookService.js +60 -0
- package/packages/server/src/services/kanbanService.js +262 -0
- package/packages/server/src/services/kanbanTriggers.js +273 -0
- package/packages/server/src/services/nodeSpawnHelper.js +63 -0
- package/packages/server/src/services/prStatusService.js +204 -0
- package/packages/server/src/services/prUrlService.js +224 -0
- package/packages/server/src/services/providerTestService.js +81 -0
- package/packages/server/src/services/scheduleService.js +110 -0
- package/packages/server/src/services/schedulerService.js +281 -0
- package/packages/server/src/services/sessionDuplicator.js +63 -0
- package/packages/server/src/services/sessionErrors.js +173 -0
- package/packages/server/src/services/sessionExecution.js +378 -0
- package/packages/server/src/services/sessionManager.js +356 -0
- package/packages/server/src/services/sessionPrompts.js +427 -0
- package/packages/server/src/services/sessionProvider.js +107 -0
- package/packages/server/src/services/slashCommandDiscovery.js +258 -0
- package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
- package/packages/server/src/services/slashCommandService.js +306 -0
- package/packages/server/src/services/streamEventCallbacks.js +170 -0
- package/packages/server/src/services/streamEventHandler.js +488 -0
- package/packages/server/src/services/streamUsageHandler.js +228 -0
- package/packages/server/src/services/summaryBroadcast.js +61 -0
- package/packages/server/src/services/summaryClaudeClient.js +180 -0
- package/packages/server/src/services/summaryPrompts.js +169 -0
- package/packages/server/src/services/summaryService.js +552 -0
- package/packages/server/src/services/summaryStaleCheck.js +35 -0
- package/packages/server/src/services/systemMonitor.js +281 -0
- package/packages/server/src/services/templateTriggerService.js +197 -0
- package/packages/server/src/services/terminalOutput.js +160 -0
- package/packages/server/src/services/todoStore.js +58 -0
- package/packages/server/src/services/usageTracker.js +69 -0
- package/packages/server/src/services/withConcurrencyGuard.js +110 -0
- package/packages/server/src/websocket.js +10 -0
- package/packages/server/src/ws/WebSocketManager.js +240 -0
- package/packages/server/src/ws/index.js +50 -0
- package/packages/shared/package.json +27 -0
- package/packages/shared/src/constants.js +44 -0
- package/packages/shared/src/contracts/canvas.js +25 -0
- package/packages/shared/src/contracts/commandButtons.js +36 -0
- package/packages/shared/src/contracts/kanban.js +142 -0
- package/packages/shared/src/contracts/projects.js +63 -0
- package/packages/shared/src/contracts/providers.js +81 -0
- package/packages/shared/src/contracts/quickResponses.js +44 -0
- package/packages/shared/src/contracts/sessions.js +112 -0
- package/packages/shared/src/contracts/templates.js +51 -0
- package/packages/shared/src/index.js +5 -0
- package/packages/shared/src/protocol.js +76 -0
- package/packages/shared/src/routeParams.js +36 -0
- package/packages/shared/src/types.js +167 -0
- package/packages/shared/src/utils.js +101 -0
- package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
- package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
- package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
- package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
- package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
- package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
- package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
- package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
- package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
- package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
- package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
- package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
- package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
- package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
- package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
- package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
- package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
- package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
- package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
- package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
- package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
- package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
- package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
- package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
- package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
- package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
- package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
- package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
- package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
- package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
- package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
- package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
- package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
- package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
- package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
- package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
- package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
- package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
- package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
- package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
- package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
- package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
- package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
- package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
- package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
- package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
- package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
- package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
- package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
- package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
- package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
- package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
- package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
- package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
- package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
- package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
- package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
- package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
- package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
- package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
- package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
- package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
- package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
- package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
- package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
- package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
- package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
- package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
- package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
- package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
- package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
- package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
- package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
- package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
- package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
- package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
- package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
- package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
- package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
- package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
- package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
- package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
- package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
- package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
- package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
- package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
- package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
- package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
- package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
- package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
- package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
- package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
- package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
- package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
- package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
- package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
- package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/packages/web/dist/favicon.png +0 -0
- package/packages/web/dist/index.html +17 -0
- package/packages/web/dist/logo.png +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { extname } from 'path';
|
|
2
|
+
import { broadcastToSession } from '../websocket.js';
|
|
3
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
|
+
|
|
5
|
+
// Common MIME type constants
|
|
6
|
+
const MIME_TEXT_PLAIN = 'text/plain';
|
|
7
|
+
const MIME_TEXT_MARKDOWN = 'text/markdown';
|
|
8
|
+
|
|
9
|
+
// Map file extensions to MIME types for binary files (images/PDF)
|
|
10
|
+
export const MIME_TYPES = {
|
|
11
|
+
'.png': 'image/png',
|
|
12
|
+
'.jpg': 'image/jpeg',
|
|
13
|
+
'.jpeg': 'image/jpeg',
|
|
14
|
+
'.gif': 'image/gif',
|
|
15
|
+
'.webp': 'image/webp',
|
|
16
|
+
'.svg': 'image/svg+xml',
|
|
17
|
+
'.bmp': 'image/bmp',
|
|
18
|
+
'.ico': 'image/x-icon',
|
|
19
|
+
'.pdf': 'application/pdf',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Text-based file extensions mapped to MIME types
|
|
23
|
+
export const TEXT_EXTENSIONS = {
|
|
24
|
+
// Code files
|
|
25
|
+
'.js': 'text/javascript',
|
|
26
|
+
'.mjs': 'text/javascript',
|
|
27
|
+
'.cjs': 'text/javascript',
|
|
28
|
+
'.ts': 'text/typescript',
|
|
29
|
+
'.mts': 'text/typescript',
|
|
30
|
+
'.cts': 'text/typescript',
|
|
31
|
+
'.jsx': 'text/javascript',
|
|
32
|
+
'.tsx': 'text/typescript',
|
|
33
|
+
'.py': 'text/x-python',
|
|
34
|
+
'.rb': 'text/x-ruby',
|
|
35
|
+
'.go': 'text/x-go',
|
|
36
|
+
'.rs': 'text/x-rust',
|
|
37
|
+
'.java': 'text/x-java',
|
|
38
|
+
'.c': 'text/x-c',
|
|
39
|
+
'.cpp': 'text/x-c++',
|
|
40
|
+
'.h': 'text/x-c',
|
|
41
|
+
'.hpp': 'text/x-c++',
|
|
42
|
+
'.cs': 'text/x-csharp',
|
|
43
|
+
'.php': 'text/x-php',
|
|
44
|
+
'.swift': 'text/x-swift',
|
|
45
|
+
'.kt': 'text/x-kotlin',
|
|
46
|
+
'.scala': 'text/x-scala',
|
|
47
|
+
'.sh': 'text/x-shellscript',
|
|
48
|
+
'.bash': 'text/x-shellscript',
|
|
49
|
+
'.zsh': 'text/x-shellscript',
|
|
50
|
+
'.sql': 'text/x-sql',
|
|
51
|
+
'.html': 'text/html',
|
|
52
|
+
'.htm': 'text/html',
|
|
53
|
+
'.css': 'text/css',
|
|
54
|
+
'.scss': 'text/x-scss',
|
|
55
|
+
'.sass': 'text/x-sass',
|
|
56
|
+
'.less': 'text/x-less',
|
|
57
|
+
'.vue': 'text/x-vue',
|
|
58
|
+
'.svelte': 'text/x-svelte',
|
|
59
|
+
// Config/data files
|
|
60
|
+
'.yaml': 'text/yaml',
|
|
61
|
+
'.yml': 'text/yaml',
|
|
62
|
+
'.toml': 'text/x-toml',
|
|
63
|
+
'.xml': 'text/xml',
|
|
64
|
+
'.ini': MIME_TEXT_PLAIN,
|
|
65
|
+
'.cfg': MIME_TEXT_PLAIN,
|
|
66
|
+
'.conf': MIME_TEXT_PLAIN,
|
|
67
|
+
'.env': MIME_TEXT_PLAIN,
|
|
68
|
+
'.properties': MIME_TEXT_PLAIN,
|
|
69
|
+
// Special files
|
|
70
|
+
'.gitignore': MIME_TEXT_PLAIN,
|
|
71
|
+
'.dockerignore': MIME_TEXT_PLAIN,
|
|
72
|
+
'.editorconfig': MIME_TEXT_PLAIN,
|
|
73
|
+
'.prettierrc': MIME_TEXT_PLAIN,
|
|
74
|
+
'.eslintrc': MIME_TEXT_PLAIN,
|
|
75
|
+
// Markdown
|
|
76
|
+
'.md': MIME_TEXT_MARKDOWN,
|
|
77
|
+
'.mdx': MIME_TEXT_MARKDOWN,
|
|
78
|
+
'.markdown': MIME_TEXT_MARKDOWN,
|
|
79
|
+
// Plain text
|
|
80
|
+
'.txt': MIME_TEXT_PLAIN,
|
|
81
|
+
'.log': MIME_TEXT_PLAIN,
|
|
82
|
+
'.csv': 'text/csv',
|
|
83
|
+
// JSON
|
|
84
|
+
'.json': 'application/json',
|
|
85
|
+
'.jsonc': 'application/json',
|
|
86
|
+
'.json5': 'application/json',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if a buffer contains binary content by looking for null bytes
|
|
91
|
+
* @param {Buffer} buffer - The buffer to check
|
|
92
|
+
* @returns {boolean} True if binary content detected
|
|
93
|
+
*/
|
|
94
|
+
export function isBinaryContent(buffer) {
|
|
95
|
+
// Check first 8KB for null bytes (common indicator of binary content)
|
|
96
|
+
const checkLength = Math.min(buffer.length, 8192);
|
|
97
|
+
for (let i = 0; i < checkLength; i++) {
|
|
98
|
+
if (buffer[i] === 0) return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Determine canvas type from file extension
|
|
105
|
+
* @param {string} ext - The file extension (with leading dot)
|
|
106
|
+
* @returns {string|null} The canvas type or null if unknown
|
|
107
|
+
*/
|
|
108
|
+
export function getTypeFromExtension(ext) {
|
|
109
|
+
if (MIME_TYPES[ext]) {
|
|
110
|
+
return ext === '.pdf' ? 'pdf' : 'image';
|
|
111
|
+
}
|
|
112
|
+
if (ext === '.json' || ext === '.jsonc' || ext === '.json5') {
|
|
113
|
+
return 'json';
|
|
114
|
+
}
|
|
115
|
+
if (ext === '.md' || ext === '.mdx' || ext === '.markdown') {
|
|
116
|
+
return 'markdown';
|
|
117
|
+
}
|
|
118
|
+
if (TEXT_EXTENSIONS[ext]) {
|
|
119
|
+
return 'code';
|
|
120
|
+
}
|
|
121
|
+
return null; // Unknown, will try binary detection
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get MIME type for a canvas type
|
|
126
|
+
* @param {string} type - The canvas type (text, markdown, code, json)
|
|
127
|
+
* @returns {string} The MIME type
|
|
128
|
+
*/
|
|
129
|
+
function getMimeTypeForType(type) {
|
|
130
|
+
switch (type) {
|
|
131
|
+
case 'text':
|
|
132
|
+
return MIME_TEXT_PLAIN;
|
|
133
|
+
case 'markdown':
|
|
134
|
+
return MIME_TEXT_MARKDOWN;
|
|
135
|
+
case 'code':
|
|
136
|
+
return MIME_TEXT_PLAIN;
|
|
137
|
+
case 'json':
|
|
138
|
+
return 'application/json';
|
|
139
|
+
default:
|
|
140
|
+
return MIME_TEXT_PLAIN;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Process a file buffer and build canvas item data.
|
|
146
|
+
* Handles type detection, binary vs text classification, and data encoding.
|
|
147
|
+
* @param {Buffer} fileBuffer - The file contents as a buffer
|
|
148
|
+
* @param {string} itemFilename - The filename for the canvas item
|
|
149
|
+
* @returns {{ error: string } | { itemData: object }} Result with either error or itemData
|
|
150
|
+
*/
|
|
151
|
+
export function processFileBuffer(fileBuffer, itemFilename) {
|
|
152
|
+
const ext = extname(itemFilename).toLowerCase();
|
|
153
|
+
let detectedType = getTypeFromExtension(ext);
|
|
154
|
+
let detectedMimeType = MIME_TYPES[ext] || TEXT_EXTENSIONS[ext];
|
|
155
|
+
|
|
156
|
+
// For unknown extensions, detect if binary or text
|
|
157
|
+
if (!detectedType) {
|
|
158
|
+
if (isBinaryContent(fileBuffer)) {
|
|
159
|
+
return {
|
|
160
|
+
error: `Unsupported binary file format: ${ext}. Supported binary formats: ${Object.keys(MIME_TYPES).join(', ')}`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// It's a text file with unknown extension, treat as code
|
|
164
|
+
detectedType = 'code';
|
|
165
|
+
detectedMimeType = MIME_TEXT_PLAIN;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle binary types (image, pdf)
|
|
169
|
+
if (detectedType === 'image' || detectedType === 'pdf') {
|
|
170
|
+
const base64 = fileBuffer.toString('base64');
|
|
171
|
+
return {
|
|
172
|
+
itemData: {
|
|
173
|
+
type: detectedType,
|
|
174
|
+
data: base64,
|
|
175
|
+
mimeType: detectedMimeType,
|
|
176
|
+
filename: itemFilename,
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle text-based types (code, markdown, json)
|
|
182
|
+
const textContent = fileBuffer.toString('utf-8');
|
|
183
|
+
return {
|
|
184
|
+
itemData: {
|
|
185
|
+
type: detectedType,
|
|
186
|
+
content: detectedType === 'json' ? null : textContent,
|
|
187
|
+
data: detectedType === 'json' ? textContent : null,
|
|
188
|
+
mimeType: detectedMimeType,
|
|
189
|
+
filename: itemFilename,
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const VALID_INLINE_TYPES = ['text', 'markdown', 'code', 'json'];
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validate that the given type is valid for inline canvas content.
|
|
198
|
+
* @param {string} type - The canvas type to validate
|
|
199
|
+
* @returns {string|null} Error message if invalid, null if valid
|
|
200
|
+
*/
|
|
201
|
+
export function validateInlineType(type) {
|
|
202
|
+
if (!VALID_INLINE_TYPES.includes(type)) {
|
|
203
|
+
return `Invalid type for inline content: ${type}. Valid types: ${VALID_INLINE_TYPES.join(', ')}`;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Build canvas item data from inline content.
|
|
210
|
+
* @param {string} type - The canvas type
|
|
211
|
+
* @param {string} content - The content string
|
|
212
|
+
* @param {string} itemFilename - The filename
|
|
213
|
+
* @returns {object} The canvas item data
|
|
214
|
+
*/
|
|
215
|
+
export function buildInlineItemData(type, content, itemFilename) {
|
|
216
|
+
return {
|
|
217
|
+
type,
|
|
218
|
+
content: type === 'json' ? null : content,
|
|
219
|
+
data: type === 'json' ? content : null,
|
|
220
|
+
mimeType: getMimeTypeForType(type),
|
|
221
|
+
filename: itemFilename,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Broadcast a canvas update to all session subscribers.
|
|
227
|
+
* @param {string} sessionId - The session ID
|
|
228
|
+
* @param {object} item - The canvas item to broadcast
|
|
229
|
+
*/
|
|
230
|
+
export function broadcastCanvasUpdate(sessionId, item) {
|
|
231
|
+
broadcastToSession(sessionId, WS_MESSAGE_TYPES.CANVAS_ADD, { item });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Write a canvas item to a file based on its type.
|
|
236
|
+
* @param {Object} item - The canvas item
|
|
237
|
+
* @param {string} filePath - The file path to write to
|
|
238
|
+
*/
|
|
239
|
+
export async function writeCanvasItemToFile(item, filePath) {
|
|
240
|
+
const { writeFile } = await import('fs/promises');
|
|
241
|
+
if (item.type === 'image' || item.type === 'pdf') {
|
|
242
|
+
const buffer = Buffer.from(item.data, 'base64');
|
|
243
|
+
await writeFile(filePath, buffer);
|
|
244
|
+
} else if (item.type === 'json') {
|
|
245
|
+
await writeFile(filePath, item.data || item.content || '{}');
|
|
246
|
+
} else {
|
|
247
|
+
await writeFile(filePath, item.content || '');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { sessions, canvasItems } from '../database.js';
|
|
3
|
+
import { broadcastToSession } from '../websocket.js';
|
|
4
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
5
|
+
|
|
6
|
+
// Error message constants
|
|
7
|
+
const ERR_SESSION_NOT_FOUND = 'Session not found';
|
|
8
|
+
|
|
9
|
+
const router = Router();
|
|
10
|
+
|
|
11
|
+
// DELETE /api/sessions/:id/canvas/bulk-delete-permanent - Permanently delete multiple items from trash
|
|
12
|
+
// NOTE: This must be defined BEFORE /:id/canvas/:itemId to avoid Express matching it as :itemId = "bulk-delete-permanent"
|
|
13
|
+
router.delete('/:id/canvas/bulk-delete-permanent', (req, res) => {
|
|
14
|
+
const session = sessions.getById(req.params.id);
|
|
15
|
+
if (!session) {
|
|
16
|
+
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { itemIds } = req.body;
|
|
20
|
+
if (!Array.isArray(itemIds)) {
|
|
21
|
+
return res.status(400).json({ error: 'itemIds must be an array' });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (itemIds.length === 0) {
|
|
25
|
+
return res.json({ deletedCount: 0 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Verify all items are in trash and belong to this session
|
|
29
|
+
for (const itemId of itemIds) {
|
|
30
|
+
const item = canvasItems.getById(itemId);
|
|
31
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
32
|
+
return res.status(404).json({ error: `Item ${itemId} not found in session` });
|
|
33
|
+
}
|
|
34
|
+
if (!item.deletedAt) {
|
|
35
|
+
return res.status(400).json({ error: `Item ${itemId} is not in trash (cannot permanently delete active items)` });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Expand to include all versions of each file
|
|
40
|
+
const expandedIds = canvasItems.expandToAllVersions(itemIds, { deletedOnly: true });
|
|
41
|
+
const deletedCount = canvasItems.permanentDeleteBatch(expandedIds);
|
|
42
|
+
|
|
43
|
+
// Broadcast each deleted item to session subscribers
|
|
44
|
+
expandedIds.forEach(itemId => {
|
|
45
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_REMOVE, { sessionId: req.params.id, itemId });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
res.json({ deletedCount, deletedIds: expandedIds });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// DELETE /api/sessions/:id/canvas/:itemId - Soft delete canvas item (move to trash)
|
|
52
|
+
router.delete('/:id/canvas/:itemId', (req, res) => {
|
|
53
|
+
const item = canvasItems.getById(req.params.itemId);
|
|
54
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
55
|
+
return res.status(404).json({ error: 'Canvas item not found' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Soft delete instead of hard delete
|
|
59
|
+
const deletedItem = canvasItems.softDelete(req.params.itemId);
|
|
60
|
+
|
|
61
|
+
// Broadcast to session subscribers
|
|
62
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_REMOVE, { sessionId: req.params.id, itemId: req.params.itemId });
|
|
63
|
+
|
|
64
|
+
res.json(deletedItem);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// GET /api/sessions/:id/canvas-trash - List deleted items in trash
|
|
68
|
+
router.get('/:id/canvas-trash', (req, res) => {
|
|
69
|
+
const session = sessions.getById(req.params.id);
|
|
70
|
+
if (!session) {
|
|
71
|
+
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const items = canvasItems.getDeletedBySessionId(req.params.id);
|
|
75
|
+
// Strip content/data from list responses to reduce payload size.
|
|
76
|
+
res.json(items.map(({ content: _content, data: _data, ...meta }) => meta));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// POST /api/sessions/:id/canvas/:itemId/recover - Recover a single item from trash
|
|
80
|
+
router.post('/:id/canvas/:itemId/recover', (req, res) => {
|
|
81
|
+
const item = canvasItems.getById(req.params.itemId);
|
|
82
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
83
|
+
return res.status(404).json({ error: 'Canvas item not found' });
|
|
84
|
+
}
|
|
85
|
+
if (!item.deletedAt) {
|
|
86
|
+
return res.status(400).json({ error: 'Item is not deleted' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const recoveredItem = canvasItems.recover(req.params.itemId);
|
|
90
|
+
|
|
91
|
+
// Broadcast recovery - same as add
|
|
92
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_ADD, { item: recoveredItem });
|
|
93
|
+
|
|
94
|
+
res.json(recoveredItem);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// POST /api/sessions/:id/canvas-trash/recover-file/:filename - Recover all versions of a file
|
|
98
|
+
router.post('/:id/canvas-trash/recover-file/:filename', (req, res) => {
|
|
99
|
+
const session = sessions.getById(req.params.id);
|
|
100
|
+
if (!session) {
|
|
101
|
+
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { filename } = req.params;
|
|
105
|
+
canvasItems.recoverByFilename(req.params.id, filename);
|
|
106
|
+
const items = canvasItems.getAllVersionsByFilename(req.params.id, filename);
|
|
107
|
+
|
|
108
|
+
// Broadcast each recovered item
|
|
109
|
+
items.forEach(item => {
|
|
110
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_ADD, { item });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
res.json({ recovered: items.length });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// DELETE /api/sessions/:id/canvas/:itemId/permanent - Permanently delete from trash
|
|
117
|
+
router.delete('/:id/canvas/:itemId/permanent', (req, res) => {
|
|
118
|
+
const item = canvasItems.getById(req.params.itemId);
|
|
119
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
120
|
+
return res.status(404).json({ error: 'Canvas item not found' });
|
|
121
|
+
}
|
|
122
|
+
if (!item.deletedAt) {
|
|
123
|
+
return res.status(400).json({ error: 'Item must be in trash before permanent deletion' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
canvasItems.permanentDelete(req.params.itemId);
|
|
127
|
+
res.status(204).send();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// POST /api/sessions/:id/canvas/bulk-delete - Soft delete multiple items
|
|
131
|
+
router.post('/:id/canvas/bulk-delete', (req, res) => {
|
|
132
|
+
const session = sessions.getById(req.params.id);
|
|
133
|
+
if (!session) {
|
|
134
|
+
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { itemIds } = req.body;
|
|
138
|
+
if (!Array.isArray(itemIds)) {
|
|
139
|
+
return res.status(400).json({ error: 'itemIds must be an array' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (itemIds.length === 0) {
|
|
143
|
+
return res.json({ deletedCount: 0 });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Verify all items belong to this session
|
|
147
|
+
for (const itemId of itemIds) {
|
|
148
|
+
const item = canvasItems.getById(itemId);
|
|
149
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
150
|
+
return res.status(404).json({ error: `Item ${itemId} not found in session` });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Expand to include all versions of each file
|
|
155
|
+
const expandedIds = canvasItems.expandToAllVersions(itemIds, { activeOnly: true });
|
|
156
|
+
const deletedCount = canvasItems.softDeleteBatch(expandedIds);
|
|
157
|
+
|
|
158
|
+
// Broadcast each deleted item to session subscribers
|
|
159
|
+
expandedIds.forEach(itemId => {
|
|
160
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_REMOVE, { sessionId: req.params.id, itemId });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.json({ deletedCount, deletedIds: expandedIds });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// POST /api/sessions/:id/canvas/bulk-recover - Recover multiple items from trash
|
|
167
|
+
router.post('/:id/canvas/bulk-recover', (req, res) => {
|
|
168
|
+
const session = sessions.getById(req.params.id);
|
|
169
|
+
if (!session) {
|
|
170
|
+
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { itemIds } = req.body;
|
|
174
|
+
if (!Array.isArray(itemIds)) {
|
|
175
|
+
return res.status(400).json({ error: 'itemIds must be an array' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (itemIds.length === 0) {
|
|
179
|
+
return res.json({ recoveredCount: 0 });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate all items belong to this session
|
|
183
|
+
for (const itemId of itemIds) {
|
|
184
|
+
const item = canvasItems.getById(itemId);
|
|
185
|
+
if (!item || item.sessionId !== req.params.id) {
|
|
186
|
+
return res.status(404).json({ error: `Item ${itemId} not found in session` });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Expand to include all versions of each file
|
|
191
|
+
const expandedIds = canvasItems.expandToAllVersions(itemIds, { deletedOnly: true });
|
|
192
|
+
const recoveredCount = canvasItems.recoverBatch(expandedIds);
|
|
193
|
+
|
|
194
|
+
// Broadcast each recovered item to session subscribers
|
|
195
|
+
expandedIds.forEach(itemId => {
|
|
196
|
+
const item = canvasItems.getById(itemId);
|
|
197
|
+
if (item) {
|
|
198
|
+
broadcastToSession(req.params.id, WS_MESSAGE_TYPES.CANVAS_ADD, { item });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
res.json({ recoveredCount, recoveredIds: expandedIds });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
export default router;
|