@rubytech/create-maxy-code 0.1.489 → 0.1.491
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 +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +1716 -0
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +103 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +29 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.js +25 -2
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/audit.ts +59 -1
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +48 -0
- package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +34 -5
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +28 -8
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +133 -35
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +95 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +141 -9
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +83 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +48 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +62 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +103 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +211 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +168 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +138 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +111 -4
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +14 -13
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +52 -14
- package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +70 -7
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +74 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +91 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +23 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +45 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +59 -4
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +58 -9
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +358 -16
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +45 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +216 -20
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +5 -122
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/agents/passive/IDENTITY.md +6 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
- package/payload/platform/templates/specialists/agents/citation-auditor.md +6 -0
- package/payload/platform/templates/specialists/agents/coding-assistant.md +6 -0
- package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +6 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
- package/payload/platform/templates/specialists/agents/data-manager.md +6 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
- package/payload/platform/templates/specialists/agents/librarian.md +6 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +6 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +7 -1
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +6 -0
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +6 -0
- package/payload/server/{chunk-UFXRUKXO.js → chunk-KZFE3MOY.js} +49 -3
- package/payload/server/public/activity.html +5 -6
- package/payload/server/public/assets/AdminLoginScreens-DgVFsHtd.js +1 -0
- package/payload/server/public/assets/AdminShell-BwNqwb4k.js +2 -0
- package/payload/server/public/assets/activity-2DL2DwLr.js +1 -0
- package/payload/server/public/assets/admin-COe0dT2U.js +1 -0
- package/payload/server/public/assets/{arc-D-QzyndF.js → arc-CFMJyMm-.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-W7f0Us4S.js → architectureDiagram-Q4EWVU46-CiSSgdL8.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CfPS4FXg.js → blockDiagram-DXYQGD6D-_sXtIW5c.js} +1 -1
- package/payload/server/public/assets/{bot-DnuyRsLS.js → bot-CpBuDQlF.js} +1 -1
- package/payload/server/public/assets/{browser-q4EFqKvR.js → browser-CSApuZrv.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dq60cLVI.js → c4Diagram-AHTNJAMY-Bh7OAMXs.js} +1 -1
- package/payload/server/public/assets/{calendar-C-q9kWmA.js → calendar-DlvwZnS7.js} +1 -1
- package/payload/server/public/assets/channel-BMyXE4zc.js +1 -0
- package/payload/server/public/assets/chat-DWvYv9jq.js +1 -0
- package/payload/server/public/assets/chevron-down-Dnuuw6Q2.js +1 -0
- package/payload/server/public/assets/chevron-left-CC4zmWOr.js +1 -0
- package/payload/server/public/assets/chevron-right-CG_lHTx_.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-CXDjuDN0.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-926ox0tj.js → chunk-336JU56O-CcQoKH2G.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-C2nrPCfN.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-CuW3FOR6.js → chunk-4TB4RGXK-BL48D7gN.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-B4_-In90.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-DnYGr9CP.js → chunk-5FUZZQ4R-DZ_jSF6N.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-BYCOi9bf.js → chunk-5PVQY5BW-De4G1vfV.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-CBxsC4JP.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-CAACaqFr.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-BoamZdS6.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-CHk82eFy.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-DdeW9zxl.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DJqSRv2w.js → chunk-EDXVE4YY-C2f_Qvx-.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-BRi4BwM5.js → chunk-ENJZ2VHE-B-Q9EbSQ.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-OJp2CtUE.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-BUAQ6spG.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DeRggD-Q.js → chunk-ICPOFSXX-Wyym1Q8K.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-EHiXZ8Py.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-tOajPcpn.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-uwO6oHIU.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Dpa1_BCA.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-ChdDqaQL.js → chunk-OYMX7WX6-BUWVAfQA.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-5JgWosPv.js → chunk-U2HBQHQK-DKS5adQo.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-BGJqysB2.js → chunk-X2U36JSP-CT_UM4IN.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-GERE2ekx.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-BskqX1V5.js → chunk-YZCP3GAM-DCetC20J.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DG1iY-L9.js → chunk-ZZ45TVLE-Bh1QIv8N.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +1 -0
- package/payload/server/public/assets/clone-BiTMTzp_.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-C8Rl4ykh.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-C0YrJPF3.js → dagre-KV5264BT-BfkUPrEV.js} +1 -1
- package/payload/server/public/assets/{dagre-DTlv-FFy.js → dagre-unhMIyVo.js} +1 -1
- package/payload/server/public/assets/data-CJbC_cVy.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-Dfk5oepI.js → diagram-5BDNPKRD-BjjwcTD9.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-QRj55DiB.js → diagram-G4DWMVQ6-BbgWtVSS.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BAPKsohU.js → diagram-MMDJMWI5-DyAnkWGy.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-C3iZXPkC.js → diagram-TYMM5635-SAMMoLxq.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-DIbI7g_5.js → erDiagram-SMLLAGMA-Cvo-5-fD.js} +1 -1
- package/payload/server/public/assets/{flatten-DlAJeN0L.js → flatten-BXzaHgtL.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-l3eJJr8R.js → flowDiagram-DWJPFMVM-y3o2Ycld.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-aaEzHxIq.js → ganttDiagram-T4ZO3ILL-BEm_CQaK.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-Di-1eg_m.js → gitGraphDiagram-UUTBAWPF-CqmOxS7F.js} +1 -1
- package/payload/server/public/assets/{graph-Cyh6FWaV.js → graph-kaxS9a_7.js} +2 -2
- package/payload/server/public/assets/{graph-labels-CUQtXFKW.js → graph-labels-GgejRtgV.js} +1 -1
- package/payload/server/public/assets/{graphlib-D5awC2yp.js → graphlib-_HfbQcBf.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +2 -0
- package/payload/server/public/assets/{isEmpty-Dh1dF-tM.js → isEmpty-uB1dEu_Z.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CLOd_cNf.js → ishikawaDiagram-UXIWVN3A-C_d0vcZC.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-cGyfW6Tg.js → journeyDiagram-VCZTEJTY-DsjRBqlW.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CRTKWPlh.js → kanban-definition-6JOO6SKY-BwtK6LS-.js} +1 -1
- package/payload/server/public/assets/{line-B8Brm0TD.js → line-szBCFOLJ.js} +1 -1
- package/payload/server/public/assets/{linear-BHnSKXBe.js → linear-X3O7lwK3.js} +1 -1
- package/payload/server/public/assets/{maximize-2-k3Fu5hdP.js → maximize-2-CPq3nwZA.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BtNpdQct.js → mermaid-parser.core-BavvMAhU.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-BY87anQZ.js → mermaid.core-Br2F9Hdm.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-rUSv9qN1.js → mindmap-definition-QFDTVHPH-4qlwXoJ-.js} +1 -1
- package/payload/server/public/assets/operator-CldXXilj.js +1 -0
- package/payload/server/public/assets/{ordinal-Bnl5GL-x.js → ordinal-CZY9laxg.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
- package/payload/server/public/assets/{page-Cno8NNMk.js → page-BE6DkJP9.js} +1 -1
- package/payload/server/public/assets/{page-DhwPG5Zf.js → page-BIwdWXnv.js} +3 -3
- package/payload/server/public/assets/{pdf-render-CrpAi4Ba.js → pdf-render-H884L3EI.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-Cjn9IP-C.js → pieDiagram-DEJITSTG-BbXryIg0.js} +1 -1
- package/payload/server/public/assets/{public-BJ5Oo2Fw.js → public-BKQEQGPE.js} +1 -1
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BkSHkSsn.js → quadrantDiagram-34T5L4WZ-BHLjcvh3.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
- package/payload/server/public/assets/{reduce-Bp5oNw1n.js → reduce-BGQzAenn.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-hLwgyB-F.js → requirementDiagram-MS252O5E-BXaVO0lk.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-D-ov5jM4.js → rotate-ccw-DieZzl10.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D6Dl1RIa.js → sankeyDiagram-XADWPNL6-CmN1s17Q.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-j0QBqkbL.js → sequenceDiagram-FGHM5R23-DC08e5NY.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-TvEBNsuV.js → stateDiagram-FHFEXIEX-DnoQMuOW.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +1 -0
- package/payload/server/public/assets/{tasks-DHMxTZVD.js → tasks-DYyCGz8W.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-EmV5lz6o.js → time-entry-format-CC_9jFuR.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BfivOP-j.js → timeline-definition-GMOUNBTQ-eTn3uRaH.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
- package/payload/server/public/assets/{triangle-alert-CivyPfAL.js → triangle-alert-7mQfCQWP.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-hbnPS-r9.js → useCopyFeedback-os7N0yCx.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Bgfqig1V.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-QN9_GqB_.js → useVoiceRecorder-ZFwt5i6d.js} +2 -2
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bvfw9aFJ.js → vennDiagram-DHZGUBPP-DNx6o_ZQ.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C9h04G6b.js → wardleyDiagram-NUSXRM2D-BZOchqF3.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BMvi2m2q.js → xychartDiagram-5P7HB3ND-ZBEGH6ZI.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +14 -14
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +10 -11
- package/payload/server/public/index.html +16 -17
- package/payload/server/public/operator.html +15 -16
- package/payload/server/public/public.html +14 -14
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +1488 -895
- package/payload/server/{src-YJF3UJNR.js → src-UYDFMWF4.js} +1 -1
- package/payload/platform/lib/active-rules/package-lock.json +0 -12
- package/payload/server/public/assets/AdminLoginScreens-Dic_fPt6.js +0 -1
- package/payload/server/public/assets/AdminShell-JN7rZ8aG.js +0 -2
- package/payload/server/public/assets/Checkbox-CB74kINA.js +0 -1
- package/payload/server/public/assets/activity-DbfpHg3i.js +0 -1
- package/payload/server/public/assets/admin-DvQPTxqe.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CGvD4r9F.js +0 -1
- package/payload/server/public/assets/channel-Bpg-ADC0.js +0 -1
- package/payload/server/public/assets/chat-DcefIV9q.js +0 -1
- package/payload/server/public/assets/chevron-down-Blma_CKl.js +0 -1
- package/payload/server/public/assets/chevron-left-xeYDxFiC.js +0 -1
- package/payload/server/public/assets/chevron-right-BonbtjqJ.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-1poG7Nd3.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-GN1tIO8J.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DMGVq8Jv.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DfddT8UV.js +0 -1
- package/payload/server/public/assets/clone-Cc9vFIEk.js +0 -1
- package/payload/server/public/assets/data-Bu-5SUmR.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-DtjXE0UI.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-CmArhGYf.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-utWz7oZy.js +0 -2
- package/payload/server/public/assets/operator-8NVpNLeD.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-BTfK0Zw4.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-BvtPeMQY.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-D2UXvQgn.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ur4eM6rb.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-0dE7FvM2.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-BrwlXpr5.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-kwhG-aJj.css +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-CO9HdfJ-.js +0 -1
- /package/payload/server/public/assets/{_baseFor-BBkmqPEk.js → _baseFor-CMWvz0uA.js} +0 -0
- /package/payload/server/public/assets/{array-Bjtb2C_5.js → array-Cny_NT6T.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-B1dQ664k.js → cytoscape.esm-De0CCmB2.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-iwYA7ne8.js → defaultLocale-BahRht8U.js} +0 -0
- /package/payload/server/public/assets/{dist-CLmrZbY7.js → dist-C9D_xz81.js} +0 -0
- /package/payload/server/public/assets/{init-CxnBAgcf.js → init-DoIvjoJ_.js} +0 -0
- /package/payload/server/public/assets/{katex-DojHoLIr.js → katex-Bfn1OZEl.js} +0 -0
- /package/payload/server/public/assets/{path-JZiojYzQ.js → path-CTezU8L5.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-D5NinLFK.js → rough.esm-BJo4pvXu.js} +0 -0
- /package/payload/server/public/assets/{src-DQ0MSD6q.js → src-BTKk8KAG.js} +0 -0
- /package/payload/server/public/assets/{useSubAccountSwitcher-DotTrvX3.js → useSubAccountSwitcher-Q941cZYV.js} +0 -0
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Where an upload may land (Task 1910).
|
|
2
|
+
//
|
|
3
|
+
// Before this, an upload had no destination at all: the key was a flat
|
|
4
|
+
// `ownerId/filename` and the client could not say which folder they meant. This
|
|
5
|
+
// module is the one definition of a legal target on the portal side.
|
|
6
|
+
//
|
|
7
|
+
// It is NOT the security boundary. The device re-derives the account's exposed
|
|
8
|
+
// folder set itself and refuses anything outside it, exactly as the download
|
|
9
|
+
// path already does. Both checks exist for the same reason: if this end is ever
|
|
10
|
+
// wrong — a stale index, a bug here, a compromised Pages project — the device
|
|
11
|
+
// is what contains it. A parity test pins the two rules to the same answers.
|
|
12
|
+
|
|
13
|
+
import type { D1Database } from './types'
|
|
14
|
+
import { grantAllows } from './session'
|
|
15
|
+
import { ownerPrefix } from './authorize'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One path segment of a legal target: not empty, not a traversal token, and
|
|
19
|
+
* carrying no separator of either flavour.
|
|
20
|
+
*
|
|
21
|
+
* Rejected outright rather than normalised, the same rule and the same reason
|
|
22
|
+
* as authorizeKey — a normaliser is one more thing that can be wrong, and no
|
|
23
|
+
* legitimate folder name contains any of these.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately identical to the device's own safeSegment in
|
|
26
|
+
* portal-uploads-pull.ts. The two are separate implementations on purpose (no
|
|
27
|
+
* import crosses that boundary) and the parity test is what keeps them honest.
|
|
28
|
+
*/
|
|
29
|
+
export function isSafeTargetSegment(s: string): boolean {
|
|
30
|
+
if (!s || s === '.' || s === '..') return false
|
|
31
|
+
return !s.includes('/') && !s.includes('\\') && !s.includes('\0')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type TargetCheck = { ok: true } | { ok: false; reason: 'segment' | 'grant' | 'index' }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether this person may upload into `targetPath`.
|
|
38
|
+
*
|
|
39
|
+
* Three clauses, in the order that costs least: every segment is safe, the
|
|
40
|
+
* person's grant admits the top-level name, and the account's published index
|
|
41
|
+
* carries a folder row for the exact path.
|
|
42
|
+
*
|
|
43
|
+
* That last clause is the whole permission model: you can only drop where you
|
|
44
|
+
* can see. The legal target set is exactly the set of folders already rendered
|
|
45
|
+
* to this person, so no new concept is introduced and no folder can be named
|
|
46
|
+
* that the client could not already open.
|
|
47
|
+
*
|
|
48
|
+
* The empty target is always legal and costs no query. It is the default
|
|
49
|
+
* uploads area, which is deliberately never in the index.
|
|
50
|
+
*
|
|
51
|
+
* The three reasons are distinct in the log because they mean different things
|
|
52
|
+
* to an operator: `segment` is a malformed request, `grant` is a person seeing
|
|
53
|
+
* less than their account exposes, and `index` is usually a folder that exists
|
|
54
|
+
* on the device but has not been published yet.
|
|
55
|
+
*/
|
|
56
|
+
export async function checkTarget(
|
|
57
|
+
db: D1Database,
|
|
58
|
+
accountId: string,
|
|
59
|
+
grant: string[],
|
|
60
|
+
targetPath: string,
|
|
61
|
+
): Promise<TargetCheck> {
|
|
62
|
+
if (targetPath === '') return { ok: true }
|
|
63
|
+
if (!targetPath.split('/').every(isSafeTargetSegment)) return { ok: false, reason: 'segment' }
|
|
64
|
+
if (!grantAllows(grant, targetPath)) return { ok: false, reason: 'grant' }
|
|
65
|
+
const row = await db
|
|
66
|
+
.prepare(
|
|
67
|
+
`SELECT d.relPath AS relPath
|
|
68
|
+
FROM directory d
|
|
69
|
+
JOIN directory_state s
|
|
70
|
+
ON s.accountId = d.accountId AND s.currentGeneration = d.generation
|
|
71
|
+
WHERE d.accountId = ? AND d.relPath = ? AND d.isDir = 1`,
|
|
72
|
+
)
|
|
73
|
+
.bind(accountId, targetPath)
|
|
74
|
+
.first<{ relPath: string }>()
|
|
75
|
+
return row ? { ok: true } : { ok: false, reason: 'index' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* `ownerId/targetPath/filename`, collapsing to `ownerId/filename` when
|
|
80
|
+
* untargeted.
|
|
81
|
+
*
|
|
82
|
+
* The target is IN the key because `objectKey` is UNIQUE. With a flat key the
|
|
83
|
+
* same filename dropped into two folders would collide: the upsert would
|
|
84
|
+
* overwrite the first object, reset its ingested flag, and the first file would
|
|
85
|
+
* never reach the device at all.
|
|
86
|
+
*/
|
|
87
|
+
export function uploadKey(ownerId: string, targetPath: string, filename: string): string {
|
|
88
|
+
return targetPath
|
|
89
|
+
? `${ownerPrefix(ownerId)}${targetPath}/${filename}`
|
|
90
|
+
: `${ownerPrefix(ownerId)}${filename}`
|
|
91
|
+
}
|
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts
CHANGED
|
@@ -24,11 +24,34 @@ export interface R2ObjectBody {
|
|
|
24
24
|
size: number
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** One completed part, as R2 returns it and as `complete` wants it back. */
|
|
28
|
+
export interface R2UploadedPart {
|
|
29
|
+
partNumber: number
|
|
30
|
+
etag: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* An open multipart session (Task 1910).
|
|
35
|
+
*
|
|
36
|
+
* The client slices a large file and sends parts, because an upload body is
|
|
37
|
+
* read wholly into the Function's memory: one 1 GiB request would OOM the
|
|
38
|
+
* isolate, and an OOM emits no [data-portal] line at all. Parts keep the
|
|
39
|
+
* existing per-request bound and let a large file respect it repeatedly.
|
|
40
|
+
*/
|
|
41
|
+
export interface R2MultipartUpload {
|
|
42
|
+
uploadId: string
|
|
43
|
+
uploadPart: (partNumber: number, value: ArrayBuffer | Uint8Array) => Promise<R2UploadedPart>
|
|
44
|
+
complete: (parts: R2UploadedPart[]) => Promise<{ size?: number } | null>
|
|
45
|
+
abort: () => Promise<void>
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
export interface R2Bucket {
|
|
28
49
|
put: (key: string, value: ArrayBuffer | Uint8Array) => Promise<unknown>
|
|
29
50
|
get: (key: string) => Promise<R2ObjectBody | null>
|
|
30
51
|
delete: (key: string) => Promise<void>
|
|
31
52
|
head: (key: string) => Promise<{ size: number } | null>
|
|
53
|
+
createMultipartUpload: (key: string) => Promise<R2MultipartUpload>
|
|
54
|
+
resumeMultipartUpload: (key: string, uploadId: string) => R2MultipartUpload
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
export interface PortalEnv {
|
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts
CHANGED
|
@@ -22,6 +22,51 @@ export async function processDelete(
|
|
|
22
22
|
}
|
|
23
23
|
const owner = q(session.ownerId)
|
|
24
24
|
|
|
25
|
+
// What the client is asking to remove decides HOW it is removed, so the row
|
|
26
|
+
// is read before anything is written (Task 1910).
|
|
27
|
+
const row = await env.DB.prepare('SELECT devicePath FROM manifest WHERE objectKey = ?')
|
|
28
|
+
.bind(key)
|
|
29
|
+
.first<{ devicePath: string }>()
|
|
30
|
+
if (!row) {
|
|
31
|
+
log(`[data-portal] op=delete owner=${owner} key=${q(key)} result=absent`)
|
|
32
|
+
return { status: 404, payload: { ok: false, error: 'not found' } }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A moved file's bytes are on the device and its R2 object is already gone.
|
|
36
|
+
// Deleting here would report success while the client's file stayed on the
|
|
37
|
+
// device forever. Instead the row is marked, and the pull removes the device
|
|
38
|
+
// file, confirms it is absent, and only then removes the row — so a
|
|
39
|
+
// withdrawal completes on BOTH sides or on neither.
|
|
40
|
+
//
|
|
41
|
+
// `pending: true` is not decoration: the client is told the file is on its
|
|
42
|
+
// way out rather than gone, because it will still be there for up to a
|
|
43
|
+
// minute and saying otherwise would be a lie the next refresh exposes.
|
|
44
|
+
if (row.devicePath) {
|
|
45
|
+
try {
|
|
46
|
+
await env.DB.prepare('UPDATE manifest SET deleted = 1 WHERE objectKey = ?').bind(key).run()
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log(
|
|
49
|
+
`[data-portal] op=withdraw owner=${owner} key=${q(key)} ` +
|
|
50
|
+
`result=failed err="${(err as Error).message}"`,
|
|
51
|
+
)
|
|
52
|
+
return { status: 502, payload: { ok: false, error: 'withdraw failed' } }
|
|
53
|
+
}
|
|
54
|
+
// Re-read rather than assume the UPDATE landed: an unmarked row is a
|
|
55
|
+
// withdrawal the pull will never see, and nothing else would report it.
|
|
56
|
+
const marked = await env.DB.prepare('SELECT deleted FROM manifest WHERE objectKey = ?')
|
|
57
|
+
.bind(key)
|
|
58
|
+
.first<{ deleted: number }>()
|
|
59
|
+
const ok = marked?.deleted === 1
|
|
60
|
+
log(
|
|
61
|
+
`[data-portal] op=withdraw owner=${owner} key=${q(key)} devicePath=${q(row.devicePath)} ` +
|
|
62
|
+
`ms=${now() - startedMs} result=${ok ? 'marked' : 'not-marked'}`,
|
|
63
|
+
)
|
|
64
|
+
return {
|
|
65
|
+
status: ok ? 200 : 500,
|
|
66
|
+
payload: { ok, pending: true },
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
// Both writes are wrapped and each emits its own line, mirroring upload's
|
|
26
71
|
// steps 3-5. Unwrapped, a throw here escapes the handler, Pages returns a
|
|
27
72
|
// bare 500, and NOTHING is logged — and if the R2 delete succeeded before the
|
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts
CHANGED
|
@@ -2,10 +2,13 @@ import type { Handler, Logger, PortalEnv, R2ObjectBody } from './_lib/types'
|
|
|
2
2
|
import { grantAllows, readSessionCookie, resolveSession } from './_lib/session'
|
|
3
3
|
import { q } from './_lib/log'
|
|
4
4
|
import { authorizeKey } from './_lib/authorize'
|
|
5
|
-
import { buildSignedUrl, SIGNED_TTL_MS } from './_lib/portal-sign.mjs'
|
|
5
|
+
import { buildOwnUploadUrl, buildSignedUrl, SIGNED_TTL_MS } from './_lib/portal-sign.mjs'
|
|
6
6
|
|
|
7
7
|
interface DownloadResult extends Handler {
|
|
8
8
|
body?: R2ObjectBody | null
|
|
9
|
+
/** Set when the row has moved: the bytes are on the device, so the caller
|
|
10
|
+
* 302s to a signed own-upload link instead of streaming R2. */
|
|
11
|
+
url?: string
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export async function processDownload(
|
|
@@ -25,9 +28,49 @@ export async function processDownload(
|
|
|
25
28
|
log(`[data-portal] op=download owner=${q(session.ownerId)} result=denied reason=not-owner`)
|
|
26
29
|
return { status: 403, payload: { ok: false, error: 'denied' } }
|
|
27
30
|
}
|
|
31
|
+
// Where the bytes are decides how they are served (Task 1910). A moved row's
|
|
32
|
+
// R2 object has been deleted, so reading it here would 404 the client's own
|
|
33
|
+
// upload the moment the device collected it.
|
|
34
|
+
const row = await env.DB.prepare('SELECT devicePath FROM manifest WHERE objectKey = ?')
|
|
35
|
+
.bind(key)
|
|
36
|
+
.first<{ devicePath: string }>()
|
|
37
|
+
if (!row) {
|
|
38
|
+
log(`[data-portal] op=download owner=${q(session.ownerId)} result=absent`)
|
|
39
|
+
return { status: 404, payload: { ok: false, error: 'not found' } }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (row.devicePath) {
|
|
43
|
+
const secret = env.PORTAL_FETCH_SECRET
|
|
44
|
+
const origin = env.PORTAL_INSTALL_ORIGIN
|
|
45
|
+
if (!secret || !origin) {
|
|
46
|
+
// Reported as offline rather than a server error, the same judgement the
|
|
47
|
+
// indexed path makes: from the client's side the file genuinely is
|
|
48
|
+
// unreachable, and the operator's signal is this line, not a 500.
|
|
49
|
+
log(`[data-portal] op=download owner=${q(session.ownerId)} result=unconfigured`)
|
|
50
|
+
return { status: 503, payload: { ok: false, error: 'device-offline' } }
|
|
51
|
+
}
|
|
52
|
+
const expiresAt = nowMs + SIGNED_TTL_MS
|
|
53
|
+
const url = await buildOwnUploadUrl(
|
|
54
|
+
origin,
|
|
55
|
+
secret,
|
|
56
|
+
session.accountId,
|
|
57
|
+
session.ownerId,
|
|
58
|
+
row.devicePath,
|
|
59
|
+
expiresAt,
|
|
60
|
+
)
|
|
61
|
+
log(
|
|
62
|
+
`[data-portal] op=download owner=${q(session.ownerId)} key=${q(key)} ` +
|
|
63
|
+
`devicePath=${q(row.devicePath)} result=moved`,
|
|
64
|
+
)
|
|
65
|
+
return { status: 200, payload: { ok: true, url }, url }
|
|
66
|
+
}
|
|
67
|
+
|
|
28
68
|
const obj = await env.BUCKET.get(key)
|
|
29
69
|
if (!obj) {
|
|
30
|
-
|
|
70
|
+
// The row says the bytes are in R2 and they are not. That is a phantom row,
|
|
71
|
+
// which emits nothing at request time anywhere else — the audit's
|
|
72
|
+
// reconcile is what counts them, and this line is what dates one.
|
|
73
|
+
log(`[data-portal] op=download owner=${q(session.ownerId)} result=absent reason=no-object`)
|
|
31
74
|
return { status: 404, payload: { ok: false, error: 'not found' } }
|
|
32
75
|
}
|
|
33
76
|
log(`[data-portal] op=download owner=${q(session.ownerId)} result=ok bytes=${obj.size ?? 'na'}`)
|
|
@@ -84,7 +127,13 @@ export async function processIndexedDownload(
|
|
|
84
127
|
}
|
|
85
128
|
|
|
86
129
|
const row = await env.DB.prepare(
|
|
87
|
-
|
|
130
|
+
// Through the generation pointer, like every other reader: a row staged by
|
|
131
|
+
// a push in flight must not be servable before the flip blesses it.
|
|
132
|
+
`SELECT d.relPath AS relPath, d.isDir AS isDir
|
|
133
|
+
FROM directory d
|
|
134
|
+
JOIN directory_state s
|
|
135
|
+
ON s.accountId = d.accountId AND s.currentGeneration = d.generation
|
|
136
|
+
WHERE d.accountId = ? AND d.relPath = ?`,
|
|
88
137
|
)
|
|
89
138
|
.bind(session.accountId, relPath)
|
|
90
139
|
.first<{ relPath: string; isDir: number }>()
|
|
@@ -176,13 +225,19 @@ export async function onRequestGet(context: PagesContext): Promise<Response> {
|
|
|
176
225
|
}
|
|
177
226
|
|
|
178
227
|
const key = params.get('key') ?? ''
|
|
179
|
-
const { status, payload, body } = await processDownload(
|
|
228
|
+
const { status, payload, body, url } = await processDownload(
|
|
180
229
|
sessionId,
|
|
181
230
|
key,
|
|
182
231
|
context.env,
|
|
183
232
|
(line) => console.log(line),
|
|
184
233
|
Date.now(),
|
|
185
234
|
)
|
|
235
|
+
// A moved row has no bytes here to stream. Both the click path and a drag-out
|
|
236
|
+
// follow a 302 to the device, so this needs no redirect=1 discriminator the
|
|
237
|
+
// way the indexed path does: there is no JSON alternative to fall back to.
|
|
238
|
+
if (status === 200 && url) {
|
|
239
|
+
return Response.redirect(url, 302)
|
|
240
|
+
}
|
|
186
241
|
if (status === 200 && body) {
|
|
187
242
|
return new Response(body.body, {
|
|
188
243
|
status: 200,
|
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts
CHANGED
|
@@ -80,6 +80,33 @@ export function entriesUnder(rows: DirRow[], prefix: string): DirEntry[] {
|
|
|
80
80
|
return [...dirList, ...files]
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Whether an upload row belongs in the client's own uploads panel (Task 1910).
|
|
85
|
+
*
|
|
86
|
+
* One rule covers both halves of the move: a row is listed while its bytes are
|
|
87
|
+
* still in R2, and after the move ONLY when the file is not reachable in the
|
|
88
|
+
* folder tree.
|
|
89
|
+
*
|
|
90
|
+
* So a targeted file leaves the panel and appears in the folder it was dropped
|
|
91
|
+
* into, and an untargeted file — which lands in `uploads/`, never exposed —
|
|
92
|
+
* stays in the panel and downloads through the device. Nothing is listed twice
|
|
93
|
+
* and nothing becomes unreachable.
|
|
94
|
+
*
|
|
95
|
+
* A withdrawn row is hidden while the device removal is pending. That is a
|
|
96
|
+
* brief state, not a tombstone filter: the row is deleted outright once the
|
|
97
|
+
* device file is confirmed absent.
|
|
98
|
+
*/
|
|
99
|
+
export function shouldListUpload(
|
|
100
|
+
row: { devicePath: string; deleted: number },
|
|
101
|
+
visibleTops: Set<string>,
|
|
102
|
+
): boolean {
|
|
103
|
+
if (row.deleted) return false
|
|
104
|
+
if (!row.devicePath) return true
|
|
105
|
+
// The whole top segment, never a prefix — `job` must not satisfy `jobs`, the
|
|
106
|
+
// same rule grantAllows follows for the same reason.
|
|
107
|
+
return !visibleTops.has(row.devicePath.split('/')[0])
|
|
108
|
+
}
|
|
109
|
+
|
|
83
110
|
export async function processFiles(
|
|
84
111
|
sessionId: string,
|
|
85
112
|
env: PortalEnv,
|
|
@@ -94,34 +121,56 @@ export async function processFiles(
|
|
|
94
121
|
}
|
|
95
122
|
// Scoped by ownerId, not by key prefix: the owner is the authority.
|
|
96
123
|
const rows = await env.DB.prepare(
|
|
97
|
-
'SELECT fileId, filename, objectKey, size, uploadedAt, ingested
|
|
124
|
+
'SELECT fileId, filename, objectKey, size, uploadedAt, ingested, targetPath, devicePath, deleted ' +
|
|
125
|
+
'FROM manifest WHERE ownerId = ? ORDER BY uploadedAt DESC',
|
|
98
126
|
)
|
|
99
127
|
.bind(session.ownerId)
|
|
100
128
|
.all<Record<string, unknown>>()
|
|
101
|
-
const
|
|
129
|
+
const allFiles = rows.results ?? []
|
|
102
130
|
|
|
103
|
-
// The account's published folder index, scoped by accountId
|
|
104
|
-
//
|
|
105
|
-
//
|
|
131
|
+
// The account's published folder index, scoped by accountId and read through
|
|
132
|
+
// the generation pointer so a push in flight is never half-visible. No
|
|
133
|
+
// account bound means no folders: an enrolment predating that column lands
|
|
134
|
+
// here, and the empty `account=` field in the log line is what says so.
|
|
106
135
|
let entries: DirEntry[] = []
|
|
107
136
|
const grant = session.folders
|
|
137
|
+
// The tops the client can actually browse. Built from the SAME grant-filtered
|
|
138
|
+
// rows the tree is built from, so the uploads panel and the tree can never
|
|
139
|
+
// disagree about what is reachable.
|
|
140
|
+
const visibleTops = new Set<string>()
|
|
108
141
|
if (session.accountId) {
|
|
109
142
|
const dir = await env.DB.prepare(
|
|
110
|
-
|
|
143
|
+
`SELECT d.relPath AS relPath, d.sizeBytes AS sizeBytes, d.modifiedAt AS modifiedAt,
|
|
144
|
+
d.isDir AS isDir
|
|
145
|
+
FROM directory d
|
|
146
|
+
JOIN directory_state s
|
|
147
|
+
ON s.accountId = d.accountId AND s.currentGeneration = d.generation
|
|
148
|
+
WHERE d.accountId = ?`,
|
|
111
149
|
)
|
|
112
150
|
.bind(session.accountId)
|
|
113
151
|
.all<DirRow>()
|
|
114
152
|
// The grant narrows below the account's exposed set: keep only rows the
|
|
115
153
|
// grant admits. An empty grant admits everything, which is the full set and
|
|
116
154
|
// today's behaviour.
|
|
117
|
-
const
|
|
118
|
-
|
|
155
|
+
const granted = (dir.results ?? []).filter((row) => grantAllows(grant, row.relPath))
|
|
156
|
+
for (const row of granted) visibleTops.add(row.relPath.split('/')[0])
|
|
157
|
+
entries = entriesUnder(granted, prefix)
|
|
119
158
|
}
|
|
120
159
|
|
|
160
|
+
// Filtered AFTER the directory read, because the rule needs to know what the
|
|
161
|
+
// client can reach. A moved file is not gone; it is listed where it lives.
|
|
162
|
+
const files = allFiles.filter((f) =>
|
|
163
|
+
shouldListUpload(
|
|
164
|
+
{ devicePath: String(f.devicePath ?? ''), deleted: Number(f.deleted ?? 0) },
|
|
165
|
+
visibleTops,
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
|
|
121
169
|
log(
|
|
122
170
|
`[data-portal] op=list owner=${q(session.ownerId)} account=${q(session.accountId)} ` +
|
|
123
171
|
`granted=${grant.length === 0 ? 'full' : grant.length} ` +
|
|
124
|
-
`prefix=${q(prefix)} files=${files.length}
|
|
172
|
+
`prefix=${q(prefix)} files=${files.length} moved=${allFiles.length - files.length} ` +
|
|
173
|
+
`entries=${entries.length} result=ok`,
|
|
125
174
|
)
|
|
126
175
|
return { status: 200, payload: { ok: true, files, entries } }
|
|
127
176
|
}
|