ocean-brain 0.13.3 → 0.15.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.
Files changed (177) hide show
  1. package/README.md +66 -2
  2. package/dist/mcp.js +439 -480
  3. package/package.json +3 -2
  4. package/server/client/dist/assets/ArrowClockwise.es-BwtKWcmj.js +1 -0
  5. package/server/client/dist/assets/{ArrowRight.es-BlNsZQFs.js → ArrowRight.es-E09Lfefr.js} +1 -1
  6. package/server/client/dist/assets/{AuxiliaryPanelHeader-HAttwVby.js → AuxiliaryPanelHeader-Bys98BJJ.js} +1 -1
  7. package/server/client/dist/assets/Calendar-BOhJYi8K.js +25 -0
  8. package/server/client/dist/assets/Callout-CAH1ua9B.js +1 -0
  9. package/server/client/dist/assets/{CaretUp.es-C8iY5OlB.js → CaretUp.es-DSWjIa-J.js} +1 -1
  10. package/server/client/dist/assets/{CheckCircle.es-Dqr3WImn.js → CheckCircle.es-Cou6rItm.js} +1 -1
  11. package/server/client/dist/assets/{Copy.es-fMv0ETir.js → Copy.es-ftEldGIL.js} +1 -1
  12. package/server/client/dist/assets/{File.es-CHtXXafR.js → File.es-DlKAX1uO.js} +1 -1
  13. package/server/client/dist/assets/{FileText.es-DPIwDCEe.js → FileText.es-D6JM_MqM.js} +1 -1
  14. package/server/client/dist/assets/{FlowArrow.es-Dxee98ir.js → FlowArrow.es-BRWXqf9A.js} +1 -1
  15. package/server/client/dist/assets/{Graph-hVj6vAYJ.js → Graph-BaqGi3M7.js} +1 -1
  16. package/server/client/dist/assets/{Graph-CdEu8eAn.js → Graph-L-d0cdMk.js} +1 -1
  17. package/server/client/dist/assets/{Image.es-CjXY1JVf.js → Image.es-Bh2mTNtr.js} +1 -1
  18. package/server/client/dist/assets/{Info.es-CYxg-Vku.js → Info.es-Bwz4CSvS.js} +1 -1
  19. package/server/client/dist/assets/Integration-C-rva2X9.js +1 -0
  20. package/server/client/dist/assets/{LinkSimple.es-B-P-7Iem.js → LinkSimple.es-iJvLf-MB.js} +1 -1
  21. package/server/client/dist/assets/Note-OO_uh2a7.js +19 -0
  22. package/server/client/dist/assets/{NotePropertiesPanel-6P7UfRBR.js → NotePropertiesPanel-BFWjxL_w.js} +1 -1
  23. package/server/client/dist/assets/{Plus.es-Dy6onD-Z.js → Plus.es-Dmb3oijD.js} +1 -1
  24. package/server/client/dist/assets/{Reminders-DnuALBL6.js → Reminders-CtpFtvu6.js} +1 -1
  25. package/server/client/dist/assets/{Search-AaD1Mt0d.js → Search-t48pkUg-.js} +2 -2
  26. package/server/client/dist/assets/{SurfaceCard-D5QxOte_.js → SurfaceCard-zdJ8k1o0.js} +1 -1
  27. package/server/client/dist/assets/{Tag-B2Qiq1Hv.js → Tag-BL2h3ZaX.js} +1 -1
  28. package/server/client/dist/assets/TagNotes-BpRTCEBf.js +1 -0
  29. package/server/client/dist/assets/Trash.es-DFnO8iF5.js +1 -0
  30. package/server/client/dist/assets/{ViewChip-BVkZAXyx.js → ViewChip-BdPhSEvG.js} +1 -1
  31. package/server/client/dist/assets/{Views-B5oh8GVM.js → Views-BoO6vgnF.js} +1 -1
  32. package/server/client/dist/assets/WarningCircle.es-BAvgGuQK.js +1 -0
  33. package/server/client/dist/assets/{abnfDiagram-N423BO3Z-D44O4T7q.js → abnfDiagram-N423BO3Z-CPgGmCbn.js} +1 -1
  34. package/server/client/dist/assets/app-OGsLwSqJ.js +151 -0
  35. package/server/client/dist/assets/app-yYMsgUrz.css +1 -0
  36. package/server/client/dist/assets/{arc-Cil9fXuW.js → arc-DqTgRpuF.js} +1 -1
  37. package/server/client/dist/assets/{architectureDiagram-T3A2C74G-Cjq6ayF2.js → architectureDiagram-T3A2C74G-rTYMAQ-J.js} +1 -1
  38. package/server/client/dist/assets/{blockDiagram-VBNYF7ZC-B2Xm5N0i.js → blockDiagram-VBNYF7ZC-BOluytVz.js} +1 -1
  39. package/server/client/dist/assets/{c4Diagram-5PPSVZJV-Dp0y-lCR.js → c4Diagram-5PPSVZJV-C8gbPpcb.js} +1 -1
  40. package/server/client/dist/assets/{calendar-data-wB0ifXdo.js → calendar-data-CwVF7Pz3.js} +1 -1
  41. package/server/client/dist/assets/channel--aI9fJWX.js +1 -0
  42. package/server/client/dist/assets/{chunk-2GRJ4B5K-CWaaNkFx.js → chunk-2GRJ4B5K-CRER5Ka0.js} +1 -1
  43. package/server/client/dist/assets/{chunk-2Q5K7J3B-Ces-xz2S.js → chunk-2Q5K7J3B-hxmJWGu_.js} +1 -1
  44. package/server/client/dist/assets/{chunk-5RXB4S5H-B-4Chlfc.js → chunk-5RXB4S5H-Bpr5Us8Y.js} +1 -1
  45. package/server/client/dist/assets/{chunk-5VM5RSS4-C8_HVwuz.js → chunk-5VM5RSS4-CKUz9fAb.js} +1 -1
  46. package/server/client/dist/assets/{chunk-6Q2QTUOP-xZ8ofRqk.js → chunk-6Q2QTUOP-B8XEOHPN.js} +1 -1
  47. package/server/client/dist/assets/{chunk-GF5L2VYU-Bpa43a7s.js → chunk-GF5L2VYU-BBUD-HJi.js} +1 -1
  48. package/server/client/dist/assets/{chunk-JWPE2WC7-DsyTXbuq.js → chunk-JWPE2WC7-Dq6_B8-d.js} +1 -1
  49. package/server/client/dist/assets/{chunk-KBJHAD2P-BDhjm9-8.js → chunk-KBJHAD2P--2VZEwNJ.js} +1 -1
  50. package/server/client/dist/assets/{chunk-RYQCIY6F-Cn-jdp6I.js → chunk-RYQCIY6F-CKCwnKiC.js} +1 -1
  51. package/server/client/dist/assets/{chunk-XXDRQBXY-BKsc_0wh.js → chunk-XXDRQBXY-VDufR1Oi.js} +1 -1
  52. package/server/client/dist/assets/classDiagram-JCYQIIEL-BsbwE38V.js +1 -0
  53. package/server/client/dist/assets/classDiagram-v2-OCEON4UE-BsbwE38V.js +1 -0
  54. package/server/client/dist/assets/{cose-bilkent-JH36ORCC-jmn3Q4zj.js → cose-bilkent-JH36ORCC-D4nqOx8S.js} +1 -1
  55. package/server/client/dist/assets/{cynefinDiagram-MW4NZA55-BpLstuBJ.js → cynefinDiagram-MW4NZA55-CUuLMs-6.js} +1 -1
  56. package/server/client/dist/assets/{dagre-VZM6K2ZE-B5ft5Yzm.js → dagre-VZM6K2ZE-CUSkhZgS.js} +1 -1
  57. package/server/client/dist/assets/{diagram-7IWD3JNH-B_W9IsWZ.js → diagram-7IWD3JNH-D95eVcKx.js} +4 -4
  58. package/server/client/dist/assets/{diagram-B4RE2ZJO-32chS3GX.js → diagram-B4RE2ZJO-7OH_fb0K.js} +1 -1
  59. package/server/client/dist/assets/{diagram-LBJQPF4R-Bqupdb3-.js → diagram-LBJQPF4R-D9UwEIMd.js} +1 -1
  60. package/server/client/dist/assets/{diagram-Q27KOJAE-BD8jKBsv.js → diagram-Q27KOJAE-Dx7WFDuX.js} +1 -1
  61. package/server/client/dist/assets/{diagram-UB23O5K3-C40jAU8D.js → diagram-UB23O5K3-D2BYwAKT.js} +1 -1
  62. package/server/client/dist/assets/{ebnfDiagram-BXEA7PRR-BQfW9mFA.js → ebnfDiagram-BXEA7PRR-DbeA-fk4.js} +1 -1
  63. package/server/client/dist/assets/{erDiagram-JOGREHBK-ILZnLzz2.js → erDiagram-JOGREHBK-jWW-ICGO.js} +1 -1
  64. package/server/client/dist/assets/{flowDiagram-UKHOOZJN-CDmwr2dI.js → flowDiagram-UKHOOZJN-68iZdxxw.js} +1 -1
  65. package/server/client/dist/assets/{ganttDiagram-PKOTCBZU-DBcrUc49.js → ganttDiagram-PKOTCBZU-CxwY3VXg.js} +1 -1
  66. package/server/client/dist/assets/{gitGraphDiagram-DS77QQ5N-_xO6__FK.js → gitGraphDiagram-DS77QQ5N-Ci6o5MfZ.js} +1 -1
  67. package/server/client/dist/assets/{image.api-ChvlfuZg.js → image.api-CHuhmq_G.js} +1 -1
  68. package/server/client/dist/assets/index-CuCzOHJ6.js +1 -0
  69. package/server/client/dist/assets/{infoDiagram-6WML65LV-CuIIcu-L.js → infoDiagram-6WML65LV-DY7aMdjV.js} +1 -1
  70. package/server/client/dist/assets/integrations-B5SIEnG_.js +1 -0
  71. package/server/client/dist/assets/{ishikawaDiagram-WSZJBQD7-BVtRGTOj.js → ishikawaDiagram-WSZJBQD7-Bh5qKh1d.js} +1 -1
  72. package/server/client/dist/assets/{journeyDiagram-NVQOT4AX-D5-Nzb2N.js → journeyDiagram-NVQOT4AX-B_svXTTT.js} +1 -1
  73. package/server/client/dist/assets/{kanban-definition-27J2QSJJ-DmOhgC8H.js → kanban-definition-27J2QSJJ-CaJslHGg.js} +1 -1
  74. package/server/client/dist/assets/manage-image-Kd-7IGfB.js +1 -0
  75. package/server/client/dist/assets/{manage-image-detail-Dw3tAW3h.js → manage-image-detail-HU0QLP-t.js} +1 -1
  76. package/server/client/dist/assets/mcp-BQ418pyx.js +5 -0
  77. package/server/client/dist/assets/{mcp-admin-adapter-D0R8p9az.js → mcp-admin-adapter-CUakQ_Xm.js} +1 -1
  78. package/server/client/dist/assets/{mermaid.core-C2tL9tq7.js → mermaid.core-BfUQM5vF.js} +4 -4
  79. package/server/client/dist/assets/{mindmap-definition-FAOFIHXS-DuvsPqrh.js → mindmap-definition-FAOFIHXS-DGbkJda2.js} +1 -1
  80. package/server/client/dist/assets/{note-core-YhYEmI_R.js → note-core-DRBsBf9H.js} +9 -9
  81. package/server/client/dist/assets/{note-runtime-B3ats-dC.js → note-runtime-ztZvXRIu.js} +16 -16
  82. package/server/client/dist/assets/{note-ui-BtzPk8gA.js → note-ui-CRcP5BjO.js} +1 -1
  83. package/server/client/dist/assets/{pegDiagram-VL7TDLO6-D64crEZV.js → pegDiagram-VL7TDLO6-Bip6zQct.js} +1 -1
  84. package/server/client/dist/assets/{pieDiagram-7S7Q4E2Y--kl7vopC.js → pieDiagram-7S7Q4E2Y-CjfjGY8Y.js} +1 -1
  85. package/server/client/dist/assets/{placeholder-B5uPsE7-.js → placeholder-D7JKnpuh.js} +1 -1
  86. package/server/client/dist/assets/{properties-B7Fv95qY.js → properties-CtagYHMr.js} +1 -1
  87. package/server/client/dist/assets/{quadrantDiagram-CIZ2JOQS-DuvJ4M2y.js → quadrantDiagram-CIZ2JOQS-B6F1yUT-.js} +1 -1
  88. package/server/client/dist/assets/{railroadDiagram-AXF67PYL-BMKmCOY7.js → railroadDiagram-AXF67PYL-D-0fLe0F.js} +1 -1
  89. package/server/client/dist/assets/{requirementDiagram-LRYGKXZP-DY1eJcy0.js → requirementDiagram-LRYGKXZP-tm4mnCyJ.js} +1 -1
  90. package/server/client/dist/assets/route-preload.js +2 -2
  91. package/server/client/dist/assets/{sankeyDiagram-W5VNT64P-C3Ka4GKM.js → sankeyDiagram-W5VNT64P-DSBWFurb.js} +1 -1
  92. package/server/client/dist/assets/search-B5tCkhwj.js +1 -0
  93. package/server/client/dist/assets/{search-admin-adapter-Daf7e2YQ.js → search-admin-adapter-Be9xSY88.js} +1 -1
  94. package/server/client/dist/assets/{sequenceDiagram-SI44F4Z6-Bhpwtk2s.js → sequenceDiagram-SI44F4Z6-KrwjjJIV.js} +1 -1
  95. package/server/client/dist/assets/{sizeCapture-X5ZJPWSS-BFta71TW.js → sizeCapture-X5ZJPWSS-DL2cn_Q6.js} +1 -1
  96. package/server/client/dist/assets/{stateDiagram-OKZ733FA-D6GCn1io.js → stateDiagram-OKZ733FA-CKuNDH85.js} +1 -1
  97. package/server/client/dist/assets/{stateDiagram-v2-UEYNNEHI-g4yinGDn.js → stateDiagram-v2-UEYNNEHI-BFaj2wbM.js} +1 -1
  98. package/server/client/dist/assets/{swimlanes-SLNWSIFB-DLFvOjUl.js → swimlanes-SLNWSIFB-5S17nqhK.js} +2 -2
  99. package/server/client/dist/assets/swimlanesDiagram-ULZ7WXOC-dexsHJq2.js +8 -0
  100. package/server/client/dist/assets/{tag.api-DEUROVKQ.js → tag.api-DGfi8hWL.js} +1 -1
  101. package/server/client/dist/assets/time-Kh6oav5R.js +361 -0
  102. package/server/client/dist/assets/{timeline-definition-Z64GVDOM-DTSIekSM.js → timeline-definition-Z64GVDOM-CU1hp_7d.js} +3 -3
  103. package/server/client/dist/assets/{trash-BHida7BG.js → trash-Di2BWyUO.js} +1 -1
  104. package/server/client/dist/assets/url-Bp8sXMb4.js +1 -0
  105. package/server/client/dist/assets/{useNoteMutate-BjvZUmtX.js → useNoteMutate-BQj6jHhV.js} +3 -3
  106. package/server/client/dist/assets/{useReminderMutate-C5n-5LM-.js → useReminderMutate-jubqwSD9.js} +1 -1
  107. package/server/client/dist/assets/{vennDiagram-T6HMQDX7-CMNqSI2X.js → vennDiagram-T6HMQDX7-MoiVLnil.js} +1 -1
  108. package/server/client/dist/assets/{wardleyDiagram-T6FBY63Y-DV-JOYqB.js → wardleyDiagram-T6FBY63Y-cZkfdI6C.js} +1 -1
  109. package/server/client/dist/assets/{xychartDiagram-ELKLHX3M-BUIniF4m.js → xychartDiagram-ELKLHX3M-B63hxD-r.js} +1 -1
  110. package/server/client/dist/index.html +6 -6
  111. package/server/dist/app.js +11 -1
  112. package/server/dist/features/app-gateway/access.js +73 -0
  113. package/server/dist/features/app-gateway/database.js +20 -0
  114. package/server/dist/features/app-gateway/gateway.js +208 -0
  115. package/server/dist/features/integration/access-events.js +17 -0
  116. package/server/dist/features/integration/auth.js +21 -0
  117. package/server/dist/features/integration/manifest.js +116 -0
  118. package/server/dist/features/integration/note-sync.js +156 -0
  119. package/server/dist/features/integration/schema.js +54 -0
  120. package/server/dist/features/integration/service.js +246 -0
  121. package/server/dist/features/integration/status-report.js +22 -0
  122. package/server/dist/features/mcp-admin/service.js +19 -83
  123. package/server/dist/features/note/graphql/note.field.resolver.js +1 -1
  124. package/server/dist/features/note/graphql/note.mutation.resolver.js +20 -12
  125. package/server/dist/features/note/graphql/note.query.resolver.js +10 -1
  126. package/server/dist/features/note/graphql/note.type-defs.js +13 -1
  127. package/server/dist/features/note/http/authoring.js +493 -0
  128. package/server/dist/features/note/http/mcp.js +15 -463
  129. package/server/dist/features/note/services/authoring.js +17 -1
  130. package/server/dist/features/note/services/change-events.js +26 -0
  131. package/server/dist/features/note/services/markdown-intent-write.js +1 -0
  132. package/server/dist/features/note/services/markdown-read.js +85 -0
  133. package/server/dist/features/note/services/properties.js +36 -30
  134. package/server/dist/features/note/services/search.js +17 -0
  135. package/server/dist/features/search/graphql/resolver.js +10 -1
  136. package/server/dist/features/search/graphql/type-defs.js +3 -0
  137. package/server/dist/features/search/search-manager.js +4 -7
  138. package/server/dist/features/tag/graphql/tag.query.resolver.js +10 -0
  139. package/server/dist/features/tag/graphql/tag.type-defs.js +1 -0
  140. package/server/dist/features/view/graphql/mcp.query.resolver.js +22 -0
  141. package/server/dist/features/view/graphql/view.query.resolver.js +2 -0
  142. package/server/dist/features/view/graphql/view.type-defs.js +27 -0
  143. package/server/dist/features/view/services/mcp-read.js +142 -0
  144. package/server/dist/features/view/services/workspace.js +12 -4
  145. package/server/dist/modules/auth-guard.js +24 -1
  146. package/server/dist/modules/integration-token.js +27 -0
  147. package/server/dist/modules/mcp-auth.js +37 -23
  148. package/server/dist/modules/mcp-token.js +6 -25
  149. package/server/dist/modules/server-events.js +3 -0
  150. package/server/dist/routes/api.js +2 -0
  151. package/server/dist/routes/apps.js +163 -0
  152. package/server/dist/routes/graphql.js +2 -1
  153. package/server/dist/routes/index.js +2 -0
  154. package/server/dist/routes/integrations.js +137 -0
  155. package/server/dist/routes/mcp.js +29 -9
  156. package/server/prisma/migrations/20260917120000_0020_plugin_installations/migration.sql +39 -0
  157. package/server/prisma/migrations/20260917150000_0021_integration_connections/migration.sql +29 -0
  158. package/server/prisma/migrations/20260919090000_0022_integration_proxy_url/migration.sql +2 -0
  159. package/server/prisma/schema.prisma +36 -10
  160. package/server/client/dist/assets/Calendar-DwQBllfw.js +0 -25
  161. package/server/client/dist/assets/Callout-CcYyvHQP.js +0 -1
  162. package/server/client/dist/assets/Note-CpzLtGVM.js +0 -19
  163. package/server/client/dist/assets/TagNotes-B27G1ZVg.js +0 -1
  164. package/server/client/dist/assets/Trash.es-XEcWG_aq.js +0 -1
  165. package/server/client/dist/assets/app-EwvT7bMw.css +0 -1
  166. package/server/client/dist/assets/app-NgiBDISG.js +0 -151
  167. package/server/client/dist/assets/channel-DZFalh-h.js +0 -1
  168. package/server/client/dist/assets/classDiagram-JCYQIIEL-CkEhFvVV.js +0 -1
  169. package/server/client/dist/assets/classDiagram-v2-OCEON4UE-CkEhFvVV.js +0 -1
  170. package/server/client/dist/assets/index-CIEap4bu.js +0 -1
  171. package/server/client/dist/assets/manage-image-CSJ3Flgc.js +0 -1
  172. package/server/client/dist/assets/mcp-D8footel.js +0 -5
  173. package/server/client/dist/assets/search-ddYl3qH-.js +0 -1
  174. package/server/client/dist/assets/swimlanesDiagram-ULZ7WXOC-Ci4mcvmo.js +0 -8
  175. package/server/client/dist/assets/time-PxWila4-.js +0 -361
  176. package/server/client/dist/assets/url-DKUMNVCI.js +0 -1
  177. package/server/dist/features/search/note-change.js +0 -16
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { emitNoteChange } from "../features/note/services/change-events.js";
2
3
  const SERVER_EVENT_CHANNEL = "server-event";
3
4
  const serverEventEmitter = new EventEmitter();
4
5
  serverEventEmitter.setMaxListeners(0);
@@ -9,6 +10,8 @@ const emitServerEvent = (event) => {
9
10
  ...event
10
11
  };
11
12
  serverEventEmitter.emit(SERVER_EVENT_CHANNEL, nextEvent);
13
+ const type = event.type.replace(/^(mcp|integration)\./, "");
14
+ emitNoteChange({ type, noteId: Number(event.noteId) });
12
15
  return nextEvent;
13
16
  };
14
17
  const subscribeServerEvents = (listener) => {
@@ -16,6 +16,7 @@ import {
16
16
  import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
17
17
  import { createAuthAttemptRateLimit, createSessionAccessRateLimit } from "../modules/rate-limit.js";
18
18
  import { createServerEventsHandler } from "../modules/server-events-handler.js";
19
+ import { createIntegrationRouter } from "./integrations.js";
19
20
  import { createMcpRouter } from "./mcp.js";
20
21
  const createApiRouter = (authConfig, mcpAdminService) => {
21
22
  return async (app) => {
@@ -23,6 +24,7 @@ const createApiRouter = (authConfig, mcpAdminService) => {
23
24
  const requireSession = requireSessionForWrite(authConfig);
24
25
  const sessionAccessRateLimit = app.rateLimit(createSessionAccessRateLimit());
25
26
  app.register(createMcpRouter(authConfig, mcpAdminService), { prefix: "/mcp" });
27
+ app.register(createIntegrationRouter(authConfig));
26
28
  app.get("/auth/session", createSessionStatusHandler(authConfig));
27
29
  app.post(
28
30
  "/auth/login",
@@ -0,0 +1,163 @@
1
+ import { buildUnauthorizedPayload } from "@baejino/auth";
2
+ import fastifyHttpProxy from "@fastify/http-proxy";
3
+ import {
4
+ APP_GATEWAY_ACCESS_COOKIE_NAME,
5
+ APP_GATEWAY_ACCESS_HEADER,
6
+ APP_GATEWAY_ACCESS_TTL_MS,
7
+ createAppGatewayAccessService,
8
+ extractAppGatewayAccessCookie,
9
+ extractAppGatewayAccessToken,
10
+ selectAppGatewayProtocol
11
+ } from "../features/app-gateway/access.js";
12
+ import {
13
+ APP_GATEWAY_PUBLIC_PREFIX,
14
+ createGatewayResponseHeaders,
15
+ createProxyRequestHeaders,
16
+ createProxyWebSocketHeaders,
17
+ getAppGatewayUpstream,
18
+ resolveAppGatewayConnection,
19
+ resolveAppGatewayOptions,
20
+ validateAppGatewayRequestPath
21
+ } from "../features/app-gateway/gateway.js";
22
+ import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
23
+ import { createAppError } from "../modules/error-handler.js";
24
+ import { createSessionAccessRateLimit } from "../modules/rate-limit.js";
25
+ const PROXY_PREFIX = `${APP_GATEWAY_PUBLIC_PREFIX}/:connectionId`;
26
+ const SUPPORTED_METHODS = "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT";
27
+ const CORS_ASSET_DESTINATIONS = /* @__PURE__ */ new Set(["audio", "font", "image", "manifest", "script", "style", "video", "worker"]);
28
+ const getConnectionId = (request) => {
29
+ const params = request.params;
30
+ return typeof params.connectionId === "string" ? params.connectionId : "";
31
+ };
32
+ const isWebSocketUpgrade = (request) => request.headers.upgrade?.toLowerCase() === "websocket";
33
+ const getHeaderValue = (value) => Array.isArray(value) ? value[0] : value;
34
+ const getPublicProtocol = (request) => {
35
+ const origin = getHeaderValue(request.headers.origin);
36
+ if (origin && origin !== "null") {
37
+ try {
38
+ const url = new URL(origin);
39
+ if (url.host === request.headers.host && (url.protocol === "http:" || url.protocol === "https:")) {
40
+ return url.protocol.slice(0, -1);
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ return request.protocol === "https" ? "https" : "http";
46
+ };
47
+ const requireAllowedOrigin = (request) => {
48
+ const origin = request.headers.origin;
49
+ if (!origin || origin === "null") return;
50
+ try {
51
+ if (new URL(origin).host === request.headers.host) return;
52
+ } catch {
53
+ }
54
+ throw createAppError(403, "APP_ORIGIN_FORBIDDEN", "App requests must originate from Ocean Brain.");
55
+ };
56
+ const isCorsPreflight = (request) => request.method === "OPTIONS" && request.headers.origin === "null" && typeof request.headers["access-control-request-method"] === "string";
57
+ const sendCorsPreflight = (request, reply) => {
58
+ const requestedHeaders = request.headers["access-control-request-headers"];
59
+ return reply.status(204).headers({
60
+ "Access-Control-Allow-Credentials": "true",
61
+ "Access-Control-Allow-Headers": requestedHeaders ?? "Content-Type",
62
+ "Access-Control-Allow-Methods": SUPPORTED_METHODS,
63
+ "Access-Control-Allow-Origin": "null",
64
+ "Access-Control-Max-Age": "600",
65
+ Vary: "Origin, Access-Control-Request-Headers"
66
+ }).send();
67
+ };
68
+ const createAppGatewayRouter = (authConfig, gatewayOptions) => {
69
+ return async (app) => {
70
+ const accessRouteOptions = {
71
+ preHandler: [
72
+ app.rateLimit(createSessionAccessRateLimit()),
73
+ requireSessionForWrite(authConfig),
74
+ createCsrfProtection(authConfig)
75
+ ]
76
+ };
77
+ const options = resolveAppGatewayOptions(gatewayOptions);
78
+ const access = createAppGatewayAccessService();
79
+ const wsClientOptions = {
80
+ headers: {},
81
+ rewriteRequestHeaders: (_headers, request) => createProxyWebSocketHeaders(request)
82
+ };
83
+ app.post(
84
+ "/api/app-gateway/connections/:connectionId/access",
85
+ accessRouteOptions,
86
+ async (request, reply) => {
87
+ requireAllowedOrigin(request);
88
+ const connection = await resolveAppGatewayConnection(options, request.params.connectionId);
89
+ const grant = access.issue(connection.id, getPublicProtocol(request));
90
+ return reply.header("Cache-Control", "no-store").setCookie(APP_GATEWAY_ACCESS_COOKIE_NAME, grant.token, {
91
+ httpOnly: true,
92
+ maxAge: APP_GATEWAY_ACCESS_TTL_MS / 1e3,
93
+ path: `${APP_GATEWAY_PUBLIC_PREFIX}/${connection.id}`,
94
+ // The sandbox has an opaque origin, so app subresources need an explicitly cross-site cookie.
95
+ sameSite: "none",
96
+ secure: true
97
+ }).send({
98
+ connectionId: connection.id,
99
+ ...grant
100
+ });
101
+ }
102
+ );
103
+ app.removeContentTypeParser("application/x-www-form-urlencoded");
104
+ app.decorateRequest("appGatewayCorsAllowed", false);
105
+ app.decorateRequest("appGatewayConnection", null);
106
+ app.decorateRequest("appGatewayPublicProtocol", null);
107
+ app.register(fastifyHttpProxy, {
108
+ upstream: "",
109
+ prefix: PROXY_PREFIX,
110
+ rewritePrefix: "/",
111
+ websocket: true,
112
+ preHandler: async (request, reply) => {
113
+ validateAppGatewayRequestPath(request.url);
114
+ requireAllowedOrigin(request);
115
+ if (isCorsPreflight(request)) return sendCorsPreflight(request, reply);
116
+ const connectionId = getConnectionId(request);
117
+ const headerToken = getHeaderValue(request.headers[APP_GATEWAY_ACCESS_HEADER]);
118
+ const accessToken = isWebSocketUpgrade(request) ? extractAppGatewayAccessToken(request.headers["sec-websocket-protocol"]) : headerToken ?? extractAppGatewayAccessCookie(request.headers.cookie);
119
+ const accessGrant = access.resolve(connectionId, accessToken);
120
+ if (!accessGrant) {
121
+ const unauthorized = buildUnauthorizedPayload();
122
+ throw createAppError(401, unauthorized.code, unauthorized.message);
123
+ }
124
+ request.appGatewayPublicProtocol = accessGrant.publicProtocol;
125
+ request.appGatewayConnection = await resolveAppGatewayConnection(options, connectionId);
126
+ const requestUrl = new URL(request.url, "http://ocean-brain.invalid");
127
+ const publicRoot = `${APP_GATEWAY_PUBLIC_PREFIX}/${connectionId}`;
128
+ if (!isWebSocketUpgrade(request) && ["GET", "HEAD"].includes(request.method) && requestUrl.pathname === publicRoot) {
129
+ return reply.redirect(`${publicRoot}/${requestUrl.search}`, 308);
130
+ }
131
+ if (request.headers.origin === "null" && !["GET", "HEAD"].includes(request.method) && !headerToken && !isWebSocketUpgrade(request)) {
132
+ throw createAppError(
133
+ 403,
134
+ "APP_ACCESS_HEADER_REQUIRED",
135
+ "Sandboxed app writes require the app access header."
136
+ );
137
+ }
138
+ const fetchDestination = request.headers["sec-fetch-dest"];
139
+ request.appGatewayCorsAllowed = request.headers.origin === "null" && (Boolean(headerToken) || typeof fetchDestination === "string" && CORS_ASSET_DESTINATIONS.has(fetchDestination));
140
+ },
141
+ replyOptions: {
142
+ timeout: options.requestTimeoutMs,
143
+ getUpstream: (request) => getAppGatewayUpstream(request),
144
+ rewriteRequestHeaders: (request, headers) => createProxyRequestHeaders(request, headers),
145
+ rewriteHeaders: (headers, request) => request ? createGatewayResponseHeaders(headers, request) : headers,
146
+ onError: (reply, { error }) => {
147
+ const timeout = error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout");
148
+ return reply.status(timeout ? 504 : 502).send({
149
+ code: timeout ? "APP_PROXY_TIMEOUT" : "APP_PROXY_UNAVAILABLE",
150
+ message: timeout ? "The proxied app did not respond in time." : "The proxied app could not be reached."
151
+ });
152
+ }
153
+ },
154
+ wsClientOptions,
155
+ wsServerOptions: {
156
+ handleProtocols: selectAppGatewayProtocol
157
+ }
158
+ });
159
+ };
160
+ };
161
+ export {
162
+ createAppGatewayRouter
163
+ };
@@ -1,4 +1,5 @@
1
1
  import mercurius from "mercurius";
2
+ import { integrationReadSchema } from "../features/integration/schema.js";
2
3
  import { createCsrfProtection, isAuthenticatedRequest, requireSessionForGraphql } from "../modules/auth-guard.js";
3
4
  import { createMcpAuthMiddleware, createReadOnlyMcpValidationRule } from "../modules/mcp-auth.js";
4
5
  import schema from "../schema/index.js";
@@ -13,7 +14,7 @@ const createGraphqlRouter = (authConfig, mcpAdminService) => {
13
14
  app.register(async (mcpEndpoint) => {
14
15
  mcpEndpoint.addHook("preHandler", createMcpAuthMiddleware(authConfig, mcpAdminService));
15
16
  mcpEndpoint.register(mercurius, {
16
- schema,
17
+ schema: integrationReadSchema,
17
18
  path: "/graphql/mcp",
18
19
  graphiql: false,
19
20
  errorFormatter: (execution, context) => ({
@@ -1,9 +1,11 @@
1
1
  import { createApiRouter } from "./api.js";
2
+ import { createAppGatewayRouter } from "./apps.js";
2
3
  import { createAuthPagesRouter } from "./auth-pages.js";
3
4
  import { createClientRouter } from "./client.js";
4
5
  import { createGraphqlRouter } from "./graphql.js";
5
6
  export {
6
7
  createApiRouter,
8
+ createAppGatewayRouter,
7
9
  createAuthPagesRouter,
8
10
  createClientRouter,
9
11
  createGraphqlRouter
@@ -0,0 +1,137 @@
1
+ import mercurius from "mercurius";
2
+ import { requireIntegrationPermission } from "../features/integration/auth.js";
3
+ import {
4
+ createIntegrationNoteCatalogHandler,
5
+ createIntegrationNoteEventsHandler
6
+ } from "../features/integration/note-sync.js";
7
+ import { integrationReadSchema } from "../features/integration/schema.js";
8
+ import { createIntegrationService } from "../features/integration/service.js";
9
+ import {
10
+ createAppendNoteMarkdownHandler,
11
+ createCreateNoteHandler,
12
+ createDeleteNoteHandler,
13
+ createNoteWriteBaselineHandler,
14
+ createPatchNoteMarkdownHandler,
15
+ createReplaceNoteMarkdownHandler,
16
+ createUpdateNoteMetadataHandler
17
+ } from "../features/note/http/authoring.js";
18
+ import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
19
+ import { createAppError } from "../modules/error-handler.js";
20
+ import { createReadOnlyMcpValidationRule } from "../modules/mcp-auth.js";
21
+ import { createSessionAccessRateLimit } from "../modules/rate-limit.js";
22
+ const createIntegrationRouter = (authConfig) => async (app) => {
23
+ const service = createIntegrationService();
24
+ app.register(
25
+ async (api) => {
26
+ api.register(async (read) => {
27
+ read.addHook("preHandler", requireIntegrationPermission("notes:read", service));
28
+ read.register(mercurius, {
29
+ schema: integrationReadSchema,
30
+ path: "/graphql",
31
+ graphiql: false,
32
+ queryDepth: 16,
33
+ validationRules: [createReadOnlyMcpValidationRule("Integration GraphQL endpoint is read-only")],
34
+ context: (req) => ({ integration: req.integration })
35
+ });
36
+ });
37
+ api.get(
38
+ "/me",
39
+ { preHandler: requireIntegrationPermission("notes:read", service) },
40
+ async (request) => ({
41
+ apiVersion: 1,
42
+ ...request.integration
43
+ })
44
+ );
45
+ api.post(
46
+ "/status",
47
+ { bodyLimit: 2048, preHandler: requireIntegrationPermission("notes:read", service) },
48
+ async (request) => service.reportStatus(request.headers.authorization?.slice(7).trim() ?? "", request.body)
49
+ );
50
+ api.get(
51
+ "/events",
52
+ { preHandler: requireIntegrationPermission("notes:read", service) },
53
+ createIntegrationNoteEventsHandler()
54
+ );
55
+ api.post(
56
+ "/notes/catalog",
57
+ { preHandler: requireIntegrationPermission("notes:read", service) },
58
+ createIntegrationNoteCatalogHandler()
59
+ );
60
+ api.post(
61
+ "/notes/create",
62
+ { preHandler: requireIntegrationPermission("notes:create", service) },
63
+ createCreateNoteHandler()
64
+ );
65
+ api.post(
66
+ "/notes/baseline",
67
+ { preHandler: requireIntegrationPermission("notes:read", service) },
68
+ createNoteWriteBaselineHandler()
69
+ );
70
+ api.post(
71
+ "/notes/patch-markdown",
72
+ { preHandler: requireIntegrationPermission("notes:update", service) },
73
+ createPatchNoteMarkdownHandler()
74
+ );
75
+ api.post(
76
+ "/notes/append-markdown",
77
+ { preHandler: requireIntegrationPermission("notes:update", service) },
78
+ createAppendNoteMarkdownHandler()
79
+ );
80
+ api.post(
81
+ "/notes/replace-markdown",
82
+ { preHandler: requireIntegrationPermission("notes:update", service) },
83
+ createReplaceNoteMarkdownHandler()
84
+ );
85
+ api.post(
86
+ "/notes/metadata",
87
+ { preHandler: requireIntegrationPermission("notes:update", service) },
88
+ createUpdateNoteMetadataHandler()
89
+ );
90
+ api.post(
91
+ "/notes/delete",
92
+ { preHandler: requireIntegrationPermission("notes:delete", service) },
93
+ createDeleteNoteHandler()
94
+ );
95
+ },
96
+ { prefix: "/integrations/v1" }
97
+ );
98
+ app.register(
99
+ async (admin) => {
100
+ admin.addHook("preHandler", app.rateLimit(createSessionAccessRateLimit()));
101
+ admin.addHook("preHandler", requireSessionForWrite(authConfig));
102
+ admin.addHook("preHandler", createCsrfProtection(authConfig));
103
+ admin.get("/connections", async () => ({ connections: await service.list() }));
104
+ admin.post("/connections", { bodyLimit: 64 * 1024 }, async (request, reply) => {
105
+ if (!request.body)
106
+ throw createAppError(400, "INVALID_INTEGRATION_MANIFEST", "A manifest is required.");
107
+ const connection = await service.connect({
108
+ manifest: request.body.manifest,
109
+ grantedPermissions: request.body.grantedPermissions,
110
+ proxyUrl: request.body.proxyUrl
111
+ });
112
+ return reply.status(201).send(connection);
113
+ });
114
+ admin.patch(
115
+ "/connections/:id",
116
+ { bodyLimit: 64 * 1024 },
117
+ async (request) => service.update(request.params.id, request.body ?? {})
118
+ );
119
+ admin.delete("/connections/:id", async (request) => {
120
+ await service.disconnect(request.params.id);
121
+ return { disconnected: true };
122
+ });
123
+ admin.post(
124
+ "/connections/:id/token/rotate",
125
+ async (request) => service.rotateToken(request.params.id)
126
+ );
127
+ admin.post("/connections/:id/token/revoke", async (request) => {
128
+ await service.revokeToken(request.params.id);
129
+ return { revoked: true };
130
+ });
131
+ },
132
+ { prefix: "/integration-admin" }
133
+ );
134
+ };
135
+ export {
136
+ createIntegrationRouter
137
+ };
@@ -11,27 +11,47 @@ import { createMcpCreateTagHandler } from "../features/tag/http/mcp.js";
11
11
  import { createMcpAuthMiddleware } from "../modules/mcp-auth.js";
12
12
  const createMcpRouter = (authConfig, mcpAdminService) => {
13
13
  return async (app) => {
14
- const requireMcpAuth = createMcpAuthMiddleware(authConfig, mcpAdminService);
15
- app.post("/notes/create", { preHandler: requireMcpAuth }, createMcpCreateNoteHandler());
16
- app.post("/notes/baseline", { preHandler: requireMcpAuth }, createMcpNoteWriteBaselineHandler());
14
+ const requireMcpAuth = (permission) => createMcpAuthMiddleware(authConfig, mcpAdminService, permission);
15
+ app.post(
16
+ "/notes/create",
17
+ { preHandler: requireMcpAuth("notes:create") },
18
+ createMcpCreateNoteHandler()
19
+ );
20
+ app.post(
21
+ "/notes/baseline",
22
+ { preHandler: requireMcpAuth("notes:read") },
23
+ createMcpNoteWriteBaselineHandler()
24
+ );
17
25
  app.post(
18
26
  "/notes/patch-markdown",
19
- { preHandler: requireMcpAuth },
27
+ { preHandler: requireMcpAuth("notes:update") },
20
28
  createMcpPatchNoteMarkdownHandler()
21
29
  );
22
30
  app.post(
23
31
  "/notes/append-markdown",
24
- { preHandler: requireMcpAuth },
32
+ { preHandler: requireMcpAuth("notes:update") },
25
33
  createMcpAppendNoteMarkdownHandler()
26
34
  );
27
- app.post("/notes/metadata", { preHandler: requireMcpAuth }, createMcpUpdateNoteMetadataHandler());
35
+ app.post(
36
+ "/notes/metadata",
37
+ { preHandler: requireMcpAuth("notes:update") },
38
+ createMcpUpdateNoteMetadataHandler()
39
+ );
28
40
  app.post(
29
41
  "/notes/replace-markdown",
30
- { preHandler: requireMcpAuth },
42
+ { preHandler: requireMcpAuth("notes:update") },
31
43
  createMcpReplaceNoteMarkdownHandler()
32
44
  );
33
- app.post("/notes/delete", { preHandler: requireMcpAuth }, createMcpDeleteNoteHandler());
34
- app.post("/tags/create", { preHandler: requireMcpAuth }, createMcpCreateTagHandler());
45
+ app.post(
46
+ "/notes/delete",
47
+ { preHandler: requireMcpAuth("notes:delete") },
48
+ createMcpDeleteNoteHandler()
49
+ );
50
+ app.post(
51
+ "/tags/create",
52
+ { preHandler: requireMcpAuth("notes:update") },
53
+ createMcpCreateTagHandler()
54
+ );
35
55
  };
36
56
  };
37
57
  export {
@@ -0,0 +1,39 @@
1
+ CREATE TABLE "PluginInstallation" (
2
+ "id" TEXT NOT NULL PRIMARY KEY,
3
+ "pluginId" TEXT NOT NULL,
4
+ "native" BOOLEAN NOT NULL DEFAULT false,
5
+ "manifest" TEXT NOT NULL,
6
+ "grantedPermissions" TEXT NOT NULL DEFAULT '[]',
7
+ "enabled" BOOLEAN NOT NULL DEFAULT false,
8
+ "pinned" BOOLEAN NOT NULL DEFAULT false,
9
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
10
+ "updatedAt" DATETIME NOT NULL
11
+ );
12
+
13
+ CREATE INDEX "PluginInstallation_pluginId_idx" ON "PluginInstallation"("pluginId");
14
+
15
+ CREATE TABLE "PluginCredential" (
16
+ "installationId" TEXT NOT NULL PRIMARY KEY,
17
+ "id" TEXT NOT NULL,
18
+ "tokenHash" TEXT NOT NULL,
19
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+ "lastUsedAt" DATETIME,
21
+ CONSTRAINT "PluginCredential_installationId_fkey" FOREIGN KEY ("installationId") REFERENCES "PluginInstallation" ("id") ON DELETE CASCADE ON UPDATE CASCADE
22
+ );
23
+
24
+ CREATE UNIQUE INDEX "PluginCredential_id_key" ON "PluginCredential"("id");
25
+ CREATE UNIQUE INDEX "PluginCredential_tokenHash_key" ON "PluginCredential"("tokenHash");
26
+
27
+ -- Keep legacy tables intact. Only the latest active token was accepted by MCP.
28
+ INSERT INTO "PluginInstallation" ("id", "pluginId", "native", "manifest", "grantedPermissions", "enabled", "updatedAt")
29
+ VALUES (
30
+ 'mcp', 'ocean-brain.mcp', true,
31
+ '{"schemaVersion":1,"apiVersion":1,"id":"ocean-brain.mcp","name":"MCP","version":"1.0.0","description":"Connect AI clients to Ocean Brain.","permissions":["notes:read","notes:create","notes:update","notes:delete"]}',
32
+ '["notes:read","notes:create","notes:update","notes:delete"]',
33
+ COALESCE((SELECT "value" = 'true' FROM "Cache" WHERE "key" = 'MCP_ENABLED'), false),
34
+ CURRENT_TIMESTAMP
35
+ );
36
+
37
+ INSERT INTO "PluginCredential" ("installationId", "id", "tokenHash", "createdAt", "lastUsedAt")
38
+ SELECT 'mcp', CAST("id" AS TEXT), "tokenHash", "createdAt", "lastUsedAt"
39
+ FROM "McpToken" WHERE "revokedAt" IS NULL ORDER BY "createdAt" DESC, "id" DESC LIMIT 1;
@@ -0,0 +1,29 @@
1
+ -- Rename the existing platform in place: preserve connection IDs, grants,
2
+ -- credential hashes and timestamps, including MCP credentials migrated by 0020.
3
+ BEGIN IMMEDIATE;
4
+
5
+ ALTER TABLE "PluginInstallation" RENAME TO "IntegrationConnection";
6
+ ALTER TABLE "IntegrationConnection" RENAME COLUMN "pluginId" TO "integrationId";
7
+
8
+ -- SQLite cannot rename a foreign-key constraint; copy the credential rows
9
+ -- unchanged so the resulting table also matches Prisma's canonical schema.
10
+ CREATE TABLE "IntegrationCredential" (
11
+ "connectionId" TEXT NOT NULL PRIMARY KEY,
12
+ "id" TEXT NOT NULL,
13
+ "tokenHash" TEXT NOT NULL,
14
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
15
+ "lastUsedAt" DATETIME,
16
+ CONSTRAINT "IntegrationCredential_connectionId_fkey" FOREIGN KEY ("connectionId") REFERENCES "IntegrationConnection" ("id") ON DELETE CASCADE ON UPDATE CASCADE
17
+ );
18
+
19
+ INSERT INTO "IntegrationCredential" ("connectionId", "id", "tokenHash", "createdAt", "lastUsedAt")
20
+ SELECT "installationId", "id", "tokenHash", "createdAt", "lastUsedAt" FROM "PluginCredential";
21
+ DROP TABLE "PluginCredential";
22
+
23
+ DROP INDEX "PluginInstallation_pluginId_idx";
24
+ CREATE INDEX "IntegrationConnection_integrationId_idx" ON "IntegrationConnection"("integrationId");
25
+
26
+ CREATE UNIQUE INDEX "IntegrationCredential_id_key" ON "IntegrationCredential"("id");
27
+ CREATE UNIQUE INDEX "IntegrationCredential_tokenHash_key" ON "IntegrationCredential"("tokenHash");
28
+
29
+ COMMIT;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "IntegrationConnection" ADD COLUMN "proxyUrl" TEXT;
2
+ ALTER TABLE "IntegrationCredential" ADD COLUMN "statusReport" TEXT;
@@ -25,19 +25,45 @@ model McpToken {
25
25
  @@index([revokedAt, createdAt])
26
26
  }
27
27
 
28
+ model IntegrationConnection {
29
+ id String @id @default(cuid())
30
+ integrationId String
31
+ native Boolean @default(false)
32
+ manifest String
33
+ proxyUrl String?
34
+ grantedPermissions String @default("[]")
35
+ enabled Boolean @default(false)
36
+ pinned Boolean @default(false)
37
+ createdAt DateTime @default(now())
38
+ updatedAt DateTime @updatedAt
39
+ credential IntegrationCredential?
40
+
41
+ @@index([integrationId])
42
+ }
43
+
44
+ model IntegrationCredential {
45
+ connectionId String @id
46
+ connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
47
+ id String @unique @default(cuid())
48
+ tokenHash String @unique
49
+ createdAt DateTime @default(now())
50
+ lastUsedAt DateTime?
51
+ statusReport String?
52
+ }
53
+
28
54
  model Note {
29
- id Int @id @default(autoincrement())
55
+ id Int @id @default(autoincrement())
30
56
  title String
31
57
  content String
32
- searchableText String @default("")
33
- searchableTextVersion Int @default(0)
34
- createdAt DateTime @default(now())
35
- updatedAt DateTime @updatedAt
36
- pinned Boolean @default(false)
37
- order Int @default(0)
38
- layout NoteLayout @default(wide)
39
- tags Tag[] @relation("NoteToTag")
40
- reminders Reminder[] @relation("NoteToReminder")
58
+ searchableText String @default("")
59
+ searchableTextVersion Int @default(0)
60
+ createdAt DateTime @default(now())
61
+ updatedAt DateTime @updatedAt
62
+ pinned Boolean @default(false)
63
+ order Int @default(0)
64
+ layout NoteLayout @default(wide)
65
+ tags Tag[] @relation("NoteToTag")
66
+ reminders Reminder[] @relation("NoteToReminder")
41
67
  snapshots NoteSnapshot[]
42
68
  properties NoteProperty[]
43
69
  outgoingReferences NoteReference[] @relation("NoteReferenceSource")
@@ -1,25 +0,0 @@
1
- import{j as e,r as j}from"./floating-ui.react-dom-BWTnA00G.js";import{T as p,x as H,y as E,M as C,q as S,g as Y,P as Q,h as V}from"./time-PxWila4-.js";import{d as b,s as F,a as $}from"./useNoteMutate-BjvZUmtX.js";import{C as U}from"./url-DKUMNVCI.js";import{C as z,a as P,b as B,c as J,g as G,s as W,d as X}from"./calendar-data-wB0ifXdo.js";import{d as K}from"./app-NgiBDISG.js";import{s as Z}from"./FileText.es-DPIwDCEe.js";import{C as ee}from"./Callout-CcYyvHQP.js";import"./preload-helper-BXl3LOEh.js";import"./Graph-hVj6vAYJ.js";import"./Info.es-CYxg-Vku.js";const D=2,te=({year:t,month:a,day:n,isCurrentMonth:r,isSunday:l,isToday:m,isSelected:d,isPast:c,notes:h,reminders:g,onSelect:s})=>{const f=h.slice(0,D).map(y=>({key:`note-${y.id}`,type:"note",title:y.title})),N=g.slice(0,D).map(y=>({key:`reminder-${y.id}`,type:"reminder",title:y.content||y.note?.title||"No title",isCompleted:y.completed})),M=(c?[...f,...N]:[...N,...f]).slice(0,D),R=()=>r?d?"bg-elevated shadow-[inset_0_0_0_1px_var(--border-secondary)] hover:bg-surface":"bg-surface hover:bg-muted":"cursor-default bg-[var(--page-bg)] text-fg-disabled",k=()=>m?"bg-cta font-semibold text-fg-on-filled":r?l?"font-semibold text-fg-weekend":"font-semibold text-fg-secondary":"font-medium text-fg-tertiary";return e.jsx(z,{day:n,dateLabel:b(new Date(t,a-1,n)).format("dddd, MMMM D, YYYY"),cellClassName:R(),dayNumberClassName:k(),isCurrentMonth:r,isToday:m,isSelected:d,noteCount:h.length,reminderCount:g.length,previewItems:M,overflowCount:Math.max(0,h.length+g.length-D),onSelect:s})},ae=te,ne=({note:t,type:a})=>e.jsx(P,{params:{id:t.id},header:e.jsx(Z,{size:12}),title:t.title,meta:b(Number(a==="create"?t.createdAt:t.updatedAt)).format("HH:mm")}),se=({reminder:t})=>e.jsx(P,{params:{id:String(t.note?.id??t.noteId)},header:e.jsx(F,{size:12}),title:t.content||t.note?.title||"No title",titleClassName:t.completed?"line-through":"",meta:b(Number(t.reminderDate)).format("HH:mm")}),re=(t,a)=>t.map(n=>n.type==="note"?e.jsx(ne,{note:n.item,type:a},`note-${n.item.id}`):e.jsx(se,{reminder:n.item},`reminder-${n.item.id}`)),I=(t,a)=>`${t} ${t===1?a:`${a}s`}`,oe="mt-3 flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overscroll-y-contain pr-2 [scrollbar-color:var(--border-secondary)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border-secondary [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1.5",L=t=>b(new Date(t.year,t.month-1,t.day)).format("dddd, MMMM D"),T=({day:t,type:a,isLoading:n,presentation:r="rail"})=>{const l=j.useMemo(()=>{if(!t)return[];const h=t.notes.map(s=>({type:"note",item:s})),g=t.reminders.map(s=>({type:"reminder",item:s}));return t.isPast?[...h,...g]:[...g,...h]},[t]);if(!t)return e.jsx("section",{"aria-label":"Day details",className:"flex h-full min-h-40 items-center justify-center text-center",children:e.jsx(p,{as:"p",variant:"meta",weight:"medium",tone:"secondary",children:"Select a day to see its activity."})});const m=L(t),d=n?"Loading activity...":[I(t.notes.length,"note"),I(t.reminders.length,"reminder")].join(" · "),c=n?null:e.jsx("div",{className:r==="rail"?oe:"flex flex-col gap-1",children:l.length>0?re(l,a):e.jsx(p,{as:"p",variant:"meta",weight:"medium",tone:"secondary",className:"py-8 text-center",children:"No activity on this day."})});return r==="modal"?e.jsxs("div",{"aria-label":`${m} activity`,children:[e.jsx(p,{as:"p",variant:"label",weight:"medium",tone:"secondary",className:"mb-3",children:d}),c]}):e.jsxs("section",{"aria-label":`${m} details`,className:"flex h-full min-h-0 flex-col",children:[e.jsxs("div",{className:"flex shrink-0 items-start gap-2 border-b border-border-subtle/80 pb-3",children:[e.jsx(K,{"aria-hidden":"true",className:"mt-0.5 shrink-0 text-fg-tertiary",size:15}),e.jsxs("div",{className:"min-w-0",children:[e.jsx(p,{as:"h2",variant:"subheading",weight:"semibold",tracking:"tight",children:m}),e.jsx(p,{as:"p",variant:"label",weight:"medium",tone:"secondary",className:"mt-0.5",children:d})]})]}),c]})},ie=["January","February","March","April","May","June","July","August","September","October","November","December"],le=({month:t,year:a,type:n,onPrevMonth:r,onNextMonth:l,onToday:m,onTypeChange:d})=>e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between",children:[e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("div",{className:"flex flex-wrap items-end gap-x-2.5 gap-y-1",children:[e.jsx(p,{as:"h1",variant:"display",weight:"bold",tracking:"tighter",className:"text-2xl leading-none sm:text-[2.15rem]",children:ie[t-1]}),e.jsx(p,{as:"span",variant:"subheading",weight:"medium",tone:"secondary",tracking:"tight",className:"pb-0.5",children:a})]}),e.jsx(p,{as:"p",variant:"meta",weight:"medium",tone:"secondary",className:"hidden sm:block",children:"Track note activity and reminders across the month"})]}),e.jsx("div",{className:"flex lg:justify-end",children:e.jsxs("div",{className:"flex w-full flex-col gap-2 sm:inline-flex sm:w-auto sm:flex-row sm:items-center sm:gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(K,{className:"h-4 w-4 shrink-0 text-fg-tertiary"}),e.jsx(p,{id:"calendar-note-date-label",as:"span",variant:"label",weight:"medium",tone:"secondary",children:"Note date"}),e.jsxs(H,{value:n,"aria-labelledby":"calendar-note-date-label",onValueChange:h=>d(h),variant:"ghost",size:"sm",children:[e.jsx(E,{value:"create",children:"Created"}),e.jsx(E,{value:"update",children:"Updated"})]})]}),e.jsx("div",{className:"hidden h-5 w-px bg-divider sm:block"}),e.jsx(B,{onPrevMonth:r,onNextMonth:l,onToday:m})]})})]}),_="(min-width: 1280px)",de=({days:t,type:a,isLoading:n,selectedDayKey:r,onSelectedDayChange:l})=>{const[m,d]=j.useState(!1),c=j.useMemo(()=>t.find(s=>s.key===r),[t,r]);j.useEffect(()=>{const s=window.matchMedia(_),f=N=>{N.matches&&d(!1)};return s.addEventListener("change",f),()=>s.removeEventListener("change",f)},[]);const h=s=>{l(s),window.matchMedia(_).matches||d(!0)},g=c?L(c):"Day details";return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"min-w-0 xl:grid xl:grid-cols-[minmax(0,1fr)_20rem] 2xl:grid-cols-[minmax(0,1fr)_22rem]",children:[e.jsx(J,{isLoading:n,className:"xl:pr-5",children:t.map(s=>e.jsx(ae,{year:s.year,month:s.month,day:s.day,isCurrentMonth:s.isCurrentMonth,isSunday:s.isSunday,isToday:s.isToday,isSelected:s.key===r,isPast:s.isPast,notes:s.notes,reminders:s.reminders,onSelect:()=>h(s.key)},s.key))}),e.jsx("aside",{className:"relative hidden min-h-0 border-l border-border-subtle xl:block","aria-label":"Selected day",children:e.jsx("div",{className:"absolute inset-y-0 right-0 left-5",children:e.jsx(T,{day:c,type:a,isLoading:n})})})]}),e.jsxs(C,{isOpen:m&&!!c,onClose:()=>d(!1),variant:"inspect",className:"xl:hidden",children:[e.jsx(C.Header,{title:g,onClose:()=>d(!1)}),e.jsxs(C.Body,{children:[e.jsxs(C.Description,{className:"sr-only",children:["View notes and reminders for ",g,"."]}),e.jsx(T,{day:c,type:a,isLoading:n,presentation:"modal"})]})]})]})},ce=`
2
- query NotesInDateRange($dateRange: DateRangeInput) {
3
- notesInDateRange(dateRange: $dateRange) {
4
- id
5
- title
6
- createdAt
7
- updatedAt
8
- }
9
- }
10
- `,me=`
11
- query RemindersInDateRange($dateRange: DateRangeInput) {
12
- remindersInDateRange(dateRange: $dateRange) {
13
- id
14
- noteId
15
- reminderDate
16
- completed
17
- priority
18
- content
19
- note {
20
- id
21
- title
22
- }
23
- }
24
- }
25
- `,he=async t=>{const a=await Y(ce,{dateRange:t});if(a.type==="error")throw a;return a.notesInDateRange},ue=async t=>{const a=await Y(me,{dateRange:t});if(a.type==="error")throw a;return a.remindersInDateRange},ge=({year:t,month:a})=>{const n=G(t,a),r=$({queryKey:S.calendar.notesInDateRange(t,a),queryFn:()=>he(n)}),l=$({queryKey:S.reminders.inDateRange(t,a),queryFn:()=>ue(n)});return{notes:r.data??[],reminders:l.data??[],isLoading:r.isLoading||l.isLoading,isError:r.isError||l.isError}},xe=[],ye=[],A=V(U);function ke(){const t=A.useNavigate(),{year:a,month:n,type:r}=A.useSearch(),l=`${a}-${n}`,[m,d]=j.useState(()=>({monthKey:l})),{notes:c,reminders:h,isLoading:g,isError:s}=ge({year:a,month:n}),f=j.useMemo(()=>{const o=new Map;for(const i of c){const x=r==="create"?b(Number(i.createdAt)):b(Number(i.updatedAt)),v=`${x.year()}-${x.month()+1}-${x.date()}`,w=o.get(v)||[];w.push(i),o.set(v,w)}for(const[i,x]of o)o.set(i,W(x,r));const u=new Map;for(const i of h){const x=b(Number(i.reminderDate)),v=`${x.year()}-${x.month()+1}-${x.date()}`,w=u.get(v)||[];w.push(i),u.set(v,w)}return X(a,n).map(i=>({...i,notes:o.get(i.key)||xe,reminders:u.get(i.key)||ye}))},[a,n,c,h,r]),N=f.find(o=>o.isCurrentMonth&&o.isToday)?.key,M=m.monthKey===l?m.dayKey??N:N,R=()=>{const o=n===1?12:n-1,u=n===1?a-1:a;t({search:i=>({...i,month:o,year:u})})},k=()=>{const o=n===12?1:n+1,u=n===12?a+1:a;t({search:i=>({...i,month:o,year:u})})},y=()=>{const o=b(),u=o.month()+1,i=o.year();d({monthKey:`${i}-${u}`,dayKey:`${i}-${u}-${o.date()}`}),t({search:x=>({...x,month:u,year:i})})},O=o=>{o&&t({search:u=>({...u,type:o})})},q=o=>{d({monthKey:l,dayKey:o})};return e.jsx(Q,{title:"Calendar",variant:"none",children:s?e.jsx(ee,{children:"Failed to load calendar data. Please try again later."}):e.jsxs("div",{className:"sm:-mr-4",children:[e.jsx("div",{className:"border-b border-border-subtle/80 pb-4",children:e.jsx(le,{month:n,year:a,type:r,onPrevMonth:R,onNextMonth:k,onToday:y,onTypeChange:O})}),e.jsx("div",{className:"pt-4",children:e.jsx(de,{days:f,type:r,isLoading:g,selectedDayKey:M,onSelectedDayChange:q})})]})})}export{ke as default};
@@ -1 +0,0 @@
1
- import{r as e,j as n}from"./floating-ui.react-dom-BWTnA00G.js";import{A as d,T as Z}from"./time-PxWila4-.js";import{c as i}from"./Info.es-CYxg-Vku.js";const p=new Map([["bold",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z"}))],["duotone",e.createElement(e.Fragment,null,e.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["fill",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z"}))],["light",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm0,192a90,90,0,1,1,90-90A90.1,90.1,0,0,1,128,218Zm-6-82V80a6,6,0,0,1,12,0v56a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,172Z"}))],["regular",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["thin",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,28A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm0,192a92,92,0,1,1,92-92A92.1,92.1,0,0,1,128,220Zm-4-84V80a4,4,0,0,1,8,0v56a4,4,0,0,1-8,0Zm12,36a8,8,0,1,1-8-8A8,8,0,0,1,136,172Z"}))]]),m=e.forwardRef((t,r)=>e.createElement(d,{ref:r,...t,weights:p}));m.displayName="WarningCircleIcon";const g=m,u=({children:t,className:r="",tone:a="info"})=>{const l=a==="danger"?g:i,o=a==="danger"?"border-border-error/70 bg-accent-soft-danger/60":"border-border-subtle bg-hover-subtle/50",s=a==="danger"?"text-fg-error":"text-fg-tertiary",c=a==="danger"?"error":"secondary";return n.jsxs("div",{className:`flex items-center gap-2.5 rounded-[12px] border px-4 py-3 ${o} ${r}`,children:[n.jsx(l,{className:`h-4 w-4 shrink-0 ${s}`}),n.jsx(Z,{as:"div",variant:"meta",weight:"medium",tone:c,children:t})]})};export{u as C,g as m};