ocean-brain 0.12.3 → 0.13.1
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/README.md +5 -3
- package/dist/index.js +12 -4
- package/package.json +10 -8
- package/server/client/dist/assets/{ArrowRight.es-CD1IMhU2.js → ArrowRight.es-BlNsZQFs.js} +1 -1
- package/server/client/dist/assets/AuxiliaryPanelHeader-HAttwVby.js +1 -0
- package/server/client/dist/assets/Calendar-DExFS7xq.js +25 -0
- package/server/client/dist/assets/Callout-CcYyvHQP.js +1 -0
- package/server/client/dist/assets/CaretUp.es-C8iY5OlB.js +1 -0
- package/server/client/dist/assets/{CheckCircle.es-C7fkzE3t.js → CheckCircle.es-Dqr3WImn.js} +1 -1
- package/server/client/dist/assets/{Copy.es-D6rl6b78.js → Copy.es-fMv0ETir.js} +1 -1
- package/server/client/dist/assets/{File.es-CcjtzE38.js → File.es-CHtXXafR.js} +1 -1
- package/server/client/dist/assets/FileText.es-DPIwDCEe.js +1 -0
- package/server/client/dist/assets/{FlowArrow.es-CAIThqWk.js → FlowArrow.es-Dxee98ir.js} +1 -1
- package/server/client/dist/assets/Graph-CdEu8eAn.js +46 -0
- package/server/client/dist/assets/Graph-hVj6vAYJ.js +1 -0
- package/server/client/dist/assets/{Image.es-BDpo5R66.js → Image.es-CjXY1JVf.js} +1 -1
- package/server/client/dist/assets/Info.es-CYxg-Vku.js +1 -0
- package/server/client/dist/assets/LinkSimple.es-B-P-7Iem.js +1 -0
- package/server/client/dist/assets/{Note-Dq2Szaxd.js → Note-CGI-Y1__.js} +4 -4
- package/server/client/dist/assets/{NotePropertiesPanel-Cu3RgDic.js → NotePropertiesPanel-6P7UfRBR.js} +2 -2
- package/server/client/dist/assets/{Plus.es-P9Fjwtud.js → Plus.es-Dy6onD-Z.js} +1 -1
- package/server/client/dist/assets/Reminders-DnuALBL6.js +1 -0
- package/server/client/dist/assets/{Search-DVROxY_B.js → Search-B_BRjOyG.js} +2 -2
- package/server/client/dist/assets/{SurfaceCard-D_OU_Oy6.js → SurfaceCard-D5QxOte_.js} +1 -1
- package/server/client/dist/assets/Tag-2hcR9dd4.js +1 -0
- package/server/client/dist/assets/TagNotes-BE20Ji5s.js +1 -0
- package/server/client/dist/assets/{Trash.es-BeR1sZMe.js → Trash.es-XEcWG_aq.js} +1 -1
- package/server/client/dist/assets/{ViewChip-D-Ax5PdL.js → ViewChip-BVkZAXyx.js} +1 -1
- package/server/client/dist/assets/Views-CQXgnbUQ.js +1 -0
- package/server/client/dist/assets/app-4M3jUhmd.js +151 -0
- package/server/client/dist/assets/app-DjDwGe0t.css +1 -0
- package/server/client/dist/assets/calendar-data-wB0ifXdo.js +1 -0
- package/server/client/dist/assets/{image.api-CtfyPwkV.js → image.api-ChvlfuZg.js} +1 -1
- package/server/client/dist/assets/index-CaT-yZJw.js +1 -0
- package/server/client/dist/assets/{manage-image-m-7v_Z1W.js → manage-image-CSJ3Flgc.js} +1 -1
- package/server/client/dist/assets/manage-image-detail-Bu9yymBn.js +1 -0
- package/server/client/dist/assets/mcp-D8footel.js +5 -0
- package/server/client/dist/assets/{mcp-admin-adapter-Cw6w5mfg.js → mcp-admin-adapter-D0R8p9az.js} +1 -1
- package/server/client/dist/assets/{note-ui-CYsZc3DI.js → note-ui-BaqzP-YU.js} +1 -1
- package/server/client/dist/assets/placeholder-BZ-p6ahW.js +34 -0
- package/server/client/dist/assets/{properties-B93_V2uD.js → properties-B7Fv95qY.js} +1 -1
- package/server/client/dist/assets/route-preload.js +2 -2
- package/server/client/dist/assets/{search-admin-adapter-Dj0KKKrJ.js → search-admin-adapter-Daf7e2YQ.js} +1 -1
- package/server/client/dist/assets/search-ddYl3qH-.js +1 -0
- package/server/client/dist/assets/{tag.api-BJ6eaHui.js → tag.api-DEUROVKQ.js} +1 -1
- package/server/client/dist/assets/time-PxWila4-.js +361 -0
- package/server/client/dist/assets/trash-BHida7BG.js +1 -0
- package/server/client/dist/assets/{url-BvjSC8PU.js → url-DKUMNVCI.js} +1 -1
- package/server/client/dist/assets/useNoteMutate-BjvZUmtX.js +9 -0
- package/server/client/dist/assets/{useReminderMutate-kTBRSjYc.js → useReminderMutate-C5n-5LM-.js} +2 -2
- package/server/client/dist/index.html +6 -6
- package/server/dist/app.js +91 -8
- package/server/dist/features/auth/http/api.js +9 -8
- package/server/dist/features/auth/http/pages.js +27 -28
- package/server/dist/features/auth/service.js +7 -27
- package/server/dist/features/cache/graphql/cache.type-defs.js +1 -1
- package/server/dist/features/image/http/upload.js +2 -2
- package/server/dist/features/mcp-admin/http/handlers.js +9 -9
- package/server/dist/features/note/http/mcp.js +17 -17
- package/server/dist/features/note/services/properties.js +32 -8
- package/server/dist/features/search/http/handlers.js +10 -10
- package/server/dist/features/search/search-manager.js +34 -6
- package/server/dist/features/tag/http/mcp.js +2 -2
- package/server/dist/features/view/graphql/view.query.resolver.js +20 -0
- package/server/dist/features/view/graphql/view.type-defs.js +17 -0
- package/server/dist/features/view/services/workspace.js +116 -1
- package/server/dist/modules/auth-guard.js +36 -53
- package/server/dist/modules/blocknote.js +16 -9
- package/server/dist/modules/error-handler.js +17 -20
- package/server/dist/modules/mcp-auth.js +50 -56
- package/server/dist/modules/rate-limit.js +23 -23
- package/server/dist/modules/server-events-handler.js +15 -13
- package/server/dist/modules/session-store.js +6 -8
- package/server/dist/paths.js +2 -1
- package/server/dist/routes/api.js +105 -71
- package/server/dist/routes/auth-pages.js +21 -11
- package/server/dist/routes/client.js +60 -72
- package/server/dist/routes/graphql.js +31 -26
- package/server/dist/routes/mcp.js +25 -35
- package/server/dist/server.js +14 -16
- package/server/client/dist/assets/AuxiliaryPanelHeader-zoJGsYWR.js +0 -1
- package/server/client/dist/assets/Calendar-6otw_EJn.js +0 -25
- package/server/client/dist/assets/Callout-C1QiCWCa.js +0 -1
- package/server/client/dist/assets/Graph-BHsmPDPh.js +0 -46
- package/server/client/dist/assets/Reminders-DKm0GFI2.js +0 -1
- package/server/client/dist/assets/Tag-wMHTRwlF.js +0 -1
- package/server/client/dist/assets/TagNotes-BRza6i14.js +0 -1
- package/server/client/dist/assets/Views-CL7rC05G.js +0 -1
- package/server/client/dist/assets/app-CyQtGTtn.js +0 -139
- package/server/client/dist/assets/app-DGFNtdpS.css +0 -1
- package/server/client/dist/assets/index-BEfKfbvr.js +0 -1
- package/server/client/dist/assets/manage-image-detail-BNYOCSmu.js +0 -1
- package/server/client/dist/assets/mcp-B-aGeAQD.js +0 -4
- package/server/client/dist/assets/placeholder-C5fNzKrL.js +0 -34
- package/server/client/dist/assets/search-BmXuuK26.js +0 -1
- package/server/client/dist/assets/sortable.esm-DgSW3bAA.js +0 -5
- package/server/client/dist/assets/time-DI902Sxa.js +0 -361
- package/server/client/dist/assets/trash-GulvRh9f.js +0 -1
- package/server/client/dist/assets/useNoteMutate-xEIypxi5.js +0 -9
- package/server/dist/modules/logger.js +0 -53
- package/server/dist/modules/use-async.js +0 -10
package/server/client/dist/assets/{useReminderMutate-kTBRSjYc.js → useReminderMutate-C5n-5LM-.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as u,j as r}from"./floating-ui.react-dom-BWTnA00G.js";import{g as p,M as f,L as h,I as
|
|
1
|
+
import{r as u,j as r}from"./floating-ui.react-dom-BWTnA00G.js";import{d as C,M as F}from"./useNoteMutate-BjvZUmtX.js";import{g as p,M as f,L as h,I as w,d as N,a as S,b as v,T as $,B as R,u as q,q as b,e as A}from"./time-PxWila4-.js";const I=(e={})=>({limit:e.limit??10,offset:e.offset??0}),y=`
|
|
2
2
|
totalCount
|
|
3
3
|
reminders {
|
|
4
4
|
id
|
|
@@ -118,4 +118,4 @@ import{r as u,j as r}from"./floating-ui.react-dom-BWTnA00G.js";import{g as p,M a
|
|
|
118
118
|
mutation DeleteReminder($id: ID!) {
|
|
119
119
|
deleteReminder(id: $id)
|
|
120
120
|
}
|
|
121
|
-
`,{id:e}),D={low:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"},medium:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"},high:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"}};function K({isOpen:e,onClose:t,onSave:o,reminder:a,mode:n}){const[i,l]=u.useState(new Date),[d,s]=u.useState("medium"),[m,g]=u.useState("");u.useEffect(()=>{e&&n==="edit"&&a?(l(new Date(Number(a.reminderDate))),s(a.priority||"medium"),g(a.content||"")):e&&n==="create"&&(l(new Date),s("medium"),g(""))},[e,n,a]);const j=()=>{o(i,d,m||void 0),t()},x=c=>d===c?D[c].active:D[c].inactive;return r.jsxs(f,{isOpen:e,onClose:t,variant:"form",children:[r.jsx(f.Header,{title:n==="create"?"Create Reminder":"Edit Reminder",onClose:t}),r.jsx(f.Body,{children:r.jsxs("div",{className:"flex flex-col gap-3 sm:gap-4",children:[r.jsxs("div",{className:"flex flex-col gap-2",children:[r.jsx(h,{children:"Date & Time"}),r.jsx(
|
|
121
|
+
`,{id:e}),D={low:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"},medium:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"},high:{active:"border-border-secondary bg-elevated text-fg-default",inactive:"border-transparent bg-transparent text-fg-muted hover:bg-hover-subtle"}};function K({isOpen:e,onClose:t,onSave:o,reminder:a,mode:n}){const[i,l]=u.useState(new Date),[d,s]=u.useState("medium"),[m,g]=u.useState("");u.useEffect(()=>{e&&n==="edit"&&a?(l(new Date(Number(a.reminderDate))),s(a.priority||"medium"),g(a.content||"")):e&&n==="create"&&(l(new Date),s("medium"),g(""))},[e,n,a]);const j=()=>{o(i,d,m||void 0),t()},x=c=>d===c?D[c].active:D[c].inactive;return r.jsxs(f,{isOpen:e,onClose:t,variant:"form",children:[r.jsx(f.Header,{title:n==="create"?"Create Reminder":"Edit Reminder",onClose:t}),r.jsx(f.Body,{children:r.jsxs("div",{className:"flex flex-col gap-3 sm:gap-4",children:[r.jsxs("div",{className:"flex flex-col gap-2",children:[r.jsx(h,{children:"Date & Time"}),r.jsx(w,{type:"datetime-local",size:"sm",value:C(i).format("YYYY-MM-DDTHH:mm"),onChange:c=>l(new Date(c.target.value))})]}),r.jsxs("div",{className:"flex flex-col gap-2",children:[r.jsx(h,{children:"Content"}),r.jsx(N,{size:"sm",placeholder:"Enter reminder content (optional)",value:m,onChange:c=>g(c.target.value)})]}),r.jsxs("div",{className:"flex flex-col gap-2",children:[r.jsx(h,{children:"Priority"}),r.jsxs(S,{type:"single",variant:"quiet",value:d,onValueChange:c=>c&&s(c),className:"gap-1.5 border-none rounded-[14px] bg-muted/70 p-1 sm:gap-2",children:[r.jsx(v,{value:"low",className:`flex-1 rounded-[10px] border ${x("low")}`,children:r.jsx($,{as:"span",weight:"medium",className:"text-current",children:"Low"})}),r.jsx(v,{value:"medium",className:`flex-1 rounded-[10px] border ${x("medium")}`,children:r.jsx($,{as:"span",weight:"medium",className:"text-current",children:"Medium"})}),r.jsx(v,{value:"high",className:`flex-1 rounded-[10px] border ${x("high")}`,children:r.jsx($,{as:"span",weight:"medium",className:"text-current",children:"High"})})]})]})]})}),r.jsx(f.Footer,{children:r.jsxs(F,{children:[r.jsx(R,{variant:"ghost",size:"sm",onClick:t,children:"Cancel"}),r.jsx(R,{variant:"primary",size:"sm",onClick:j,children:n==="create"?"Create":"Save"})]})})]})}function U(){const e=A(),t=q(),o=u.useCallback(async(i,l,d="medium",s,m)=>{const g=await P({noteId:i,reminderDate:l,priority:d,content:m});if(g.type==="error"){e(g.errors[0].message);return}await t.invalidateQueries({queryKey:b.reminders.all(),exact:!1}),s&&s()},[t,e]),a=u.useCallback(async(i,l,d,s)=>{const m=await M({id:i,...d});if(m.type==="error"){e(m.errors[0].message);return}await t.invalidateQueries({queryKey:b.reminders.all(),exact:!1}),s&&s()},[t,e]),n=u.useCallback(async(i,l,d)=>{const s=await T(i);if(s.type==="error"){e(s.errors[0].message);return}await t.invalidateQueries({queryKey:b.reminders.all(),exact:!1}),d&&d()},[t,e]);return{onCreate:o,onUpdate:a,onDelete:n}}export{K as R,z as a,E as b,Q as c,H as f,U as u};
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" href="/icon.png" />
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
10
|
<title>Ocean Brain</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/app-
|
|
11
|
+
<script type="module" crossorigin src="/assets/app-4M3jUhmd.js"></script>
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/floating-ui.react-dom-BWTnA00G.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/url-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/time-
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/useNoteMutate-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/url-DKUMNVCI.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/time-PxWila4-.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/useNoteMutate-BjvZUmtX.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-BXl3LOEh.js">
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/assets/app-
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/Graph-hVj6vAYJ.js">
|
|
18
|
+
<link rel="stylesheet" crossorigin href="/assets/app-DjDwGe0t.css">
|
|
19
19
|
</head>
|
|
20
20
|
|
|
21
21
|
<body>
|
package/server/dist/app.js
CHANGED
|
@@ -1,29 +1,112 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fastifyCookie, { Signer } from "@fastify/cookie";
|
|
2
|
+
import fastifyCsrfProtection from "@fastify/csrf-protection";
|
|
3
|
+
import fastifyFormbody from "@fastify/formbody";
|
|
4
|
+
import fastifyRateLimit from "@fastify/rate-limit";
|
|
5
|
+
import fastifySession from "@fastify/session";
|
|
6
|
+
import Fastify from "fastify";
|
|
2
7
|
import { createMcpAdminService } from "./features/mcp-admin/service.js";
|
|
3
8
|
import { purgeExpiredNoteSnapshots } from "./features/note/services/snapshot.js";
|
|
4
9
|
import { purgeExpiredTrashedNotes } from "./features/note/services/trash.js";
|
|
5
|
-
import {
|
|
6
|
-
import { createErrorHandler } from "./modules/error-handler.js";
|
|
7
|
-
import
|
|
10
|
+
import { closeDefaultSemanticSearchManager } from "./features/search/search-manager.js";
|
|
11
|
+
import { createAppError, createErrorHandler } from "./modules/error-handler.js";
|
|
12
|
+
import { AUTH_SESSION_IDLE_TIMEOUT_MS, createSessionStore } from "./modules/session-store.js";
|
|
8
13
|
import { createApiRouter, createAuthPagesRouter, createClientRouter, createGraphqlRouter } from "./routes/index.js";
|
|
14
|
+
const MAX_REQUEST_BODY_BYTES = 50 * 1024 * 1024;
|
|
15
|
+
const isHttpLoggerEnabled = (loggerOption) => loggerOption ?? process.env.OCEAN_BRAIN_HTTP_LOG?.toLowerCase() !== "false";
|
|
16
|
+
const createLoggerOptions = () => ({
|
|
17
|
+
level: "info",
|
|
18
|
+
serializers: {
|
|
19
|
+
req(request) {
|
|
20
|
+
return {
|
|
21
|
+
method: request.method,
|
|
22
|
+
url: request.url,
|
|
23
|
+
remoteAddress: request.ip,
|
|
24
|
+
userAgent: request.headers["user-agent"]
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
res(reply) {
|
|
28
|
+
return { statusCode: reply.statusCode };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const createFastifyApplication = (options = {}) => Fastify({
|
|
33
|
+
bodyLimit: MAX_REQUEST_BODY_BYTES,
|
|
34
|
+
routerOptions: { ignoreTrailingSlash: true },
|
|
35
|
+
logger: isHttpLoggerEnabled(options.logger) ? createLoggerOptions() : false
|
|
36
|
+
});
|
|
37
|
+
const registerRequestInfrastructure = (app, authConfig) => {
|
|
38
|
+
app.addHook("onRequest", (request, _reply, done) => {
|
|
39
|
+
const contentEncoding = request.headers["content-encoding"];
|
|
40
|
+
if (contentEncoding && contentEncoding.toLowerCase() !== "identity") {
|
|
41
|
+
done(createAppError(415, "UNSUPPORTED_CONTENT_ENCODING", "Compressed request bodies are not supported."));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
done();
|
|
45
|
+
});
|
|
46
|
+
app.register(fastifyCookie);
|
|
47
|
+
app.register(fastifyFormbody);
|
|
48
|
+
app.register(fastifyRateLimit, {
|
|
49
|
+
global: false,
|
|
50
|
+
enableDraftSpec: true
|
|
51
|
+
});
|
|
52
|
+
if (authConfig.mode !== "password") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const sessionStore = createSessionStore();
|
|
56
|
+
app.addHook("onClose", (_instance, done) => {
|
|
57
|
+
if ("stopPruning" in sessionStore && typeof sessionStore.stopPruning === "function") {
|
|
58
|
+
sessionStore.stopPruning();
|
|
59
|
+
}
|
|
60
|
+
done();
|
|
61
|
+
});
|
|
62
|
+
app.register(fastifySession, {
|
|
63
|
+
// Preserve the configured secret bytes and support legacy secrets shorter than 32 characters.
|
|
64
|
+
secret: new Signer(authConfig.sessionSecret),
|
|
65
|
+
cookieName: authConfig.cookieName,
|
|
66
|
+
cookiePrefix: "s:",
|
|
67
|
+
store: sessionStore,
|
|
68
|
+
saveUninitialized: false,
|
|
69
|
+
rolling: true,
|
|
70
|
+
cookie: {
|
|
71
|
+
maxAge: AUTH_SESSION_IDLE_TIMEOUT_MS,
|
|
72
|
+
httpOnly: true,
|
|
73
|
+
sameSite: "lax",
|
|
74
|
+
secure: process.env.NODE_ENV === "production",
|
|
75
|
+
path: "/"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
app.register(fastifyCsrfProtection, {
|
|
79
|
+
sessionPlugin: "@fastify/session",
|
|
80
|
+
logLevel: "debug"
|
|
81
|
+
});
|
|
82
|
+
};
|
|
9
83
|
const createApp = (authConfig, options = {}) => {
|
|
10
84
|
const mcpAdminService = createMcpAdminService();
|
|
11
85
|
return createAppWithMcpAuth(authConfig, mcpAdminService, options);
|
|
12
86
|
};
|
|
13
87
|
const createAppWithMcpAuth = (authConfig, mcpAdminService, options = {}) => {
|
|
14
|
-
const app = options.application ??
|
|
15
|
-
|
|
16
|
-
void Promise.all([purgeExpiredNoteSnapshots(), purgeExpiredTrashedNotes()]).catch((error) => {
|
|
88
|
+
const app = options.application ?? createFastifyApplication(options);
|
|
89
|
+
const startupCleanup = Promise.all([purgeExpiredNoteSnapshots(), purgeExpiredTrashedNotes()]).catch((error) => {
|
|
17
90
|
const message = error instanceof Error ? error.message : "Unknown recovery cleanup error";
|
|
18
91
|
process.stderr.write(`[recovery] Startup cleanup failed: ${message}
|
|
19
92
|
`);
|
|
20
93
|
});
|
|
21
|
-
app.
|
|
94
|
+
app.addHook("onClose", async () => {
|
|
95
|
+
await startupCleanup;
|
|
96
|
+
await closeDefaultSemanticSearchManager();
|
|
97
|
+
});
|
|
98
|
+
registerRequestInfrastructure(app, authConfig);
|
|
99
|
+
app.setErrorHandler(createErrorHandler(authConfig));
|
|
100
|
+
app.register(createApiRouter(authConfig, mcpAdminService), { prefix: "/api" });
|
|
101
|
+
app.register(createAuthPagesRouter(authConfig));
|
|
102
|
+
app.register(createGraphqlRouter(authConfig, mcpAdminService));
|
|
103
|
+
app.register(createClientRouter(authConfig, options.clientContentHandler));
|
|
22
104
|
return app;
|
|
23
105
|
};
|
|
24
106
|
var app_default = createApp;
|
|
25
107
|
export {
|
|
26
108
|
createApp,
|
|
27
109
|
createAppWithMcpAuth,
|
|
110
|
+
createFastifyApplication,
|
|
28
111
|
app_default as default
|
|
29
112
|
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
setSessionStatusHeaders
|
|
11
11
|
} from "../service.js";
|
|
12
12
|
const createLoginHandler = (authConfig) => {
|
|
13
|
-
return async (req,
|
|
13
|
+
return async (req, reply) => {
|
|
14
14
|
const expectedPassword = assertPasswordLoginAvailable(authConfig);
|
|
15
15
|
const password = typeof req.body?.password === "string" ? req.body.password : "";
|
|
16
16
|
if (!password || !compareSharedSecret(expectedPassword, password)) {
|
|
@@ -18,22 +18,23 @@ const createLoginHandler = (authConfig) => {
|
|
|
18
18
|
}
|
|
19
19
|
await regenerateSession(req);
|
|
20
20
|
req.session.authenticated = true;
|
|
21
|
-
refreshCsrfToken(
|
|
22
|
-
|
|
21
|
+
refreshCsrfToken(authConfig, reply);
|
|
22
|
+
return reply.status(200).send(buildSessionResponse(authConfig, req));
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
25
|
const createLogoutHandler = (authConfig) => {
|
|
26
|
-
return async (req,
|
|
26
|
+
return async (req, reply) => {
|
|
27
27
|
if (authConfig.mode === "password") {
|
|
28
28
|
await destroySession(req);
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
return reply.status(200).send(buildAuthSessionResponse(authConfig, false));
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
33
|
const createSessionStatusHandler = (authConfig) => {
|
|
34
|
-
return async (req,
|
|
35
|
-
setSessionStatusHeaders(
|
|
36
|
-
|
|
34
|
+
return async (req, reply) => {
|
|
35
|
+
setSessionStatusHeaders(reply);
|
|
36
|
+
refreshCsrfToken(authConfig, reply);
|
|
37
|
+
return reply.status(200).send(buildSessionResponse(authConfig, req));
|
|
37
38
|
};
|
|
38
39
|
};
|
|
39
40
|
export {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { issueCsrfToken } from "../../../modules/auth-guard.js";
|
|
1
2
|
import {
|
|
2
3
|
compareSharedSecret,
|
|
3
4
|
destroySession,
|
|
@@ -7,65 +8,63 @@ import {
|
|
|
7
8
|
sanitizeRedirectPath
|
|
8
9
|
} from "../service.js";
|
|
9
10
|
import { renderLoginPage } from "./login-page.js";
|
|
10
|
-
const setLoginPageHeaders = (
|
|
11
|
-
|
|
11
|
+
const setLoginPageHeaders = (reply) => {
|
|
12
|
+
reply.header("Cache-Control", "no-store");
|
|
12
13
|
};
|
|
13
14
|
const createLoginPageHandler = (authConfig) => {
|
|
14
|
-
return async (req,
|
|
15
|
+
return async (req, reply) => {
|
|
15
16
|
if (authConfig.mode !== "password" || req.session?.authenticated) {
|
|
16
|
-
setLoginPageHeaders(
|
|
17
|
-
|
|
18
|
-
return;
|
|
17
|
+
setLoginPageHeaders(reply);
|
|
18
|
+
return reply.redirect(sanitizeRedirectPath(req.query.next), 303);
|
|
19
19
|
}
|
|
20
20
|
const nextPath = sanitizeRedirectPath(req.query.next);
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const csrfToken = issueCsrfToken(authConfig, reply);
|
|
22
|
+
setLoginPageHeaders(reply);
|
|
23
|
+
return reply.status(200).type("text/html; charset=utf-8").send(
|
|
23
24
|
renderLoginPage({
|
|
24
25
|
nextPath,
|
|
25
|
-
csrfToken
|
|
26
|
+
csrfToken,
|
|
26
27
|
sessionGeneration: getSessionGeneration()
|
|
27
28
|
})
|
|
28
|
-
)
|
|
29
|
+
);
|
|
29
30
|
};
|
|
30
31
|
};
|
|
31
32
|
const createLoginPageSubmitHandler = (authConfig) => {
|
|
32
|
-
return async (req,
|
|
33
|
+
return async (req, reply) => {
|
|
33
34
|
const nextPath = sanitizeRedirectPath(req.body?.next);
|
|
34
35
|
if (authConfig.mode !== "password" || !authConfig.password) {
|
|
35
|
-
setLoginPageHeaders(
|
|
36
|
-
|
|
37
|
-
return;
|
|
36
|
+
setLoginPageHeaders(reply);
|
|
37
|
+
return reply.redirect(nextPath, 303);
|
|
38
38
|
}
|
|
39
39
|
const password = typeof req.body?.password === "string" ? req.body.password : "";
|
|
40
40
|
if (!password || !compareSharedSecret(authConfig.password, password)) {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const csrfToken = issueCsrfToken(authConfig, reply);
|
|
42
|
+
setLoginPageHeaders(reply);
|
|
43
|
+
return reply.status(401).type("text/html; charset=utf-8").send(
|
|
43
44
|
renderLoginPage({
|
|
44
45
|
nextPath,
|
|
45
46
|
errorMessage: "Invalid password",
|
|
46
|
-
csrfToken
|
|
47
|
+
csrfToken,
|
|
47
48
|
sessionGeneration: getSessionGeneration()
|
|
48
49
|
})
|
|
49
|
-
)
|
|
50
|
-
return;
|
|
50
|
+
);
|
|
51
51
|
}
|
|
52
52
|
await regenerateSession(req);
|
|
53
53
|
req.session.authenticated = true;
|
|
54
|
-
refreshCsrfToken(
|
|
55
|
-
setLoginPageHeaders(
|
|
56
|
-
|
|
54
|
+
refreshCsrfToken(authConfig, reply);
|
|
55
|
+
setLoginPageHeaders(reply);
|
|
56
|
+
return reply.redirect(nextPath, 303);
|
|
57
57
|
};
|
|
58
58
|
};
|
|
59
59
|
const createLogoutPageHandler = (authConfig) => {
|
|
60
|
-
return async (req,
|
|
60
|
+
return async (req, reply) => {
|
|
61
61
|
if (authConfig.mode === "password") {
|
|
62
62
|
await destroySession(req);
|
|
63
|
-
setLoginPageHeaders(
|
|
64
|
-
|
|
65
|
-
return;
|
|
63
|
+
setLoginPageHeaders(reply);
|
|
64
|
+
return reply.redirect("/login", 303);
|
|
66
65
|
}
|
|
67
|
-
setLoginPageHeaders(
|
|
68
|
-
|
|
66
|
+
setLoginPageHeaders(reply);
|
|
67
|
+
return reply.redirect("/", 303);
|
|
69
68
|
};
|
|
70
69
|
};
|
|
71
70
|
export {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildAuthSessionResponse } from "@baejino/auth";
|
|
2
2
|
import { compareSharedSecret as compareCommonSharedSecret } from "@baejino/auth/crypto";
|
|
3
3
|
import crypto from "crypto";
|
|
4
|
+
import { issueCsrfToken } from "../../modules/auth-guard.js";
|
|
4
5
|
import { sanitizeRedirectPath } from "../../modules/auth-redirect.js";
|
|
5
6
|
import { createAppError } from "../../modules/error-handler.js";
|
|
6
7
|
const AUTH_SESSION_GENERATION_HEADER = "X-Ocean-Brain-Session-Generation";
|
|
@@ -18,44 +19,23 @@ const comparePassword = async (password, storedHash) => {
|
|
|
18
19
|
};
|
|
19
20
|
const compareSharedSecret = compareCommonSharedSecret;
|
|
20
21
|
const buildSessionResponse = (authConfig, req) => buildAuthSessionResponse(authConfig, Boolean(req.session?.authenticated));
|
|
21
|
-
const setSessionStatusHeaders = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
const refreshCsrfToken = (req) => {
|
|
26
|
-
const requestWithCsrf = req;
|
|
27
|
-
requestWithCsrf.csrfToken?.();
|
|
22
|
+
const setSessionStatusHeaders = (reply) => {
|
|
23
|
+
reply.header(AUTH_SESSION_GENERATION_HEADER, getSessionGeneration());
|
|
24
|
+
reply.header("Cache-Control", "no-store");
|
|
28
25
|
};
|
|
26
|
+
const refreshCsrfToken = (authConfig, reply) => issueCsrfToken(authConfig, reply);
|
|
29
27
|
const assertPasswordLoginAvailable = (authConfig) => {
|
|
30
28
|
if (authConfig.mode !== "password") {
|
|
31
29
|
throw createAppError(409, "AUTH_OPEN_MODE", "Login is unavailable while auth mode is open.");
|
|
32
30
|
}
|
|
33
31
|
return authConfig.password;
|
|
34
32
|
};
|
|
35
|
-
const regenerateSession = async (req) =>
|
|
36
|
-
await new Promise((resolve, reject) => {
|
|
37
|
-
req.session.regenerate((error) => {
|
|
38
|
-
if (error) {
|
|
39
|
-
reject(error);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
resolve();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
};
|
|
33
|
+
const regenerateSession = async (req) => req.session.regenerate();
|
|
46
34
|
const destroySession = async (req) => {
|
|
47
35
|
if (!req.session) {
|
|
48
36
|
return;
|
|
49
37
|
}
|
|
50
|
-
await
|
|
51
|
-
req.session.destroy((error) => {
|
|
52
|
-
if (error) {
|
|
53
|
-
reject(error);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
resolve();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
38
|
+
await req.session.destroy();
|
|
59
39
|
};
|
|
60
40
|
export {
|
|
61
41
|
AUTH_SESSION_GENERATION_HEADER,
|
|
@@ -25,7 +25,7 @@ const createUploadValidationError = (error) => {
|
|
|
25
25
|
return createAppError(400, "INVALID_IMAGE_UPLOAD", "Uploaded image content is invalid.");
|
|
26
26
|
};
|
|
27
27
|
const createUploadImageHandler = (persistImage = persistUploadedImage) => {
|
|
28
|
-
return async (req,
|
|
28
|
+
return async (req, reply) => {
|
|
29
29
|
const { image } = req.body ?? {};
|
|
30
30
|
if (typeof image !== "string") {
|
|
31
31
|
throw createAppError(400, "INVALID_IMAGE_UPLOAD", "No image uploaded");
|
|
@@ -50,7 +50,7 @@ const createUploadImageHandler = (persistImage = persistUploadedImage) => {
|
|
|
50
50
|
buffer: validatedImage.buffer,
|
|
51
51
|
extension: validatedImage.extension
|
|
52
52
|
});
|
|
53
|
-
|
|
53
|
+
return reply.status(200).send(createUploadResponse(uploadedImage));
|
|
54
54
|
};
|
|
55
55
|
};
|
|
56
56
|
export {
|
|
@@ -9,36 +9,36 @@ const createMcpAdminStatusResponse = async (service) => {
|
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
11
|
const createMcpAdminStatusHandler = (service = createMcpAdminService()) => {
|
|
12
|
-
return async (_req,
|
|
12
|
+
return async (_req, reply) => {
|
|
13
13
|
const status = await createMcpAdminStatusResponse(service);
|
|
14
|
-
|
|
14
|
+
return reply.status(200).send(status);
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
17
|
const createMcpAdminSetEnabledHandler = (service = createMcpAdminService()) => {
|
|
18
|
-
return async (req,
|
|
18
|
+
return async (req, reply) => {
|
|
19
19
|
const enabled = req.body?.enabled;
|
|
20
20
|
if (typeof enabled !== "boolean") {
|
|
21
21
|
throw createAppError(400, "INVALID_MCP_ENABLED", "enabled must be a boolean.");
|
|
22
22
|
}
|
|
23
23
|
await service.setEnabled(enabled);
|
|
24
24
|
const status = await createMcpAdminStatusResponse(service);
|
|
25
|
-
|
|
25
|
+
return reply.status(200).send(status);
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
28
|
const createMcpAdminRotateTokenHandler = (service = createMcpAdminService()) => {
|
|
29
|
-
return async (_req,
|
|
29
|
+
return async (_req, reply) => {
|
|
30
30
|
const result = await service.rotateToken();
|
|
31
|
-
|
|
31
|
+
return reply.status(200).send({
|
|
32
32
|
token: result.token,
|
|
33
33
|
message: "Save this token now. It is shown only once."
|
|
34
|
-
})
|
|
34
|
+
});
|
|
35
35
|
};
|
|
36
36
|
};
|
|
37
37
|
const createMcpAdminRevokeTokenHandler = (service = createMcpAdminService()) => {
|
|
38
|
-
return async (_req,
|
|
38
|
+
return async (_req, reply) => {
|
|
39
39
|
await service.revokeActiveToken();
|
|
40
40
|
const status = await createMcpAdminStatusResponse(service);
|
|
41
|
-
|
|
41
|
+
return reply.status(200).send(status);
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
export {
|
|
@@ -227,7 +227,7 @@ const resolveSeparator = (value) => {
|
|
|
227
227
|
throw createAppError(400, "INVALID_SEPARATOR", 'Separator must be "\\n" or "\\n\\n".');
|
|
228
228
|
};
|
|
229
229
|
const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown, emitEvent = emitServerEvent) => {
|
|
230
|
-
return async (req,
|
|
230
|
+
return async (req, reply) => {
|
|
231
231
|
const { title, markdown, layout } = req.body ?? {};
|
|
232
232
|
const resolvedLayout = resolveNoteLayout(layout);
|
|
233
233
|
if (typeof title !== "string") {
|
|
@@ -251,10 +251,10 @@ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown, emitEve
|
|
|
251
251
|
noteId: note.id,
|
|
252
252
|
updatedAt: note.updatedAt
|
|
253
253
|
});
|
|
254
|
-
|
|
254
|
+
return reply.status(200).send({
|
|
255
255
|
created: true,
|
|
256
256
|
note
|
|
257
|
-
})
|
|
257
|
+
});
|
|
258
258
|
} catch (error) {
|
|
259
259
|
if (error instanceof InvalidNoteAuthoringInputError) {
|
|
260
260
|
throw createAppError(400, "INVALID_NOTE_INPUT", error.message);
|
|
@@ -270,19 +270,19 @@ const createMcpNoteWriteBaselineHandler = (findNoteBaseline = async (id) => mode
|
|
|
270
270
|
updatedAt: true
|
|
271
271
|
}
|
|
272
272
|
})) => {
|
|
273
|
-
return async (req,
|
|
273
|
+
return async (req, reply) => {
|
|
274
274
|
const noteId = resolvePositiveNoteId(req.body?.id);
|
|
275
275
|
const note = await findNoteBaseline(noteId);
|
|
276
276
|
if (!note) {
|
|
277
277
|
throw createAppError(404, "NOTE_NOT_FOUND", "The requested note was not found.");
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
return reply.status(200).send({
|
|
280
280
|
note: serializeNoteWriteBaseline(note)
|
|
281
|
-
})
|
|
281
|
+
});
|
|
282
282
|
};
|
|
283
283
|
};
|
|
284
284
|
const createMcpPatchNoteMarkdownHandler = (patchMarkdown = patchNoteMarkdown, emitEvent = emitServerEvent) => {
|
|
285
|
-
return async (req,
|
|
285
|
+
return async (req, reply) => {
|
|
286
286
|
const { id, expectedUpdatedAt, baseMarkdownSha256, intent, selector, operation, policy } = req.body ?? {};
|
|
287
287
|
const noteId = resolvePositiveNoteId(id);
|
|
288
288
|
const resolvedExpectedUpdatedAt = resolveOptionalString(
|
|
@@ -315,11 +315,11 @@ const createMcpPatchNoteMarkdownHandler = (patchMarkdown = patchNoteMarkdown, em
|
|
|
315
315
|
updatedAt: result.note.updatedAt
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
|
-
|
|
318
|
+
return reply.status(200).send(result);
|
|
319
319
|
};
|
|
320
320
|
};
|
|
321
321
|
const createMcpAppendNoteMarkdownHandler = (appendMarkdown = appendNoteMarkdown, emitEvent = emitServerEvent) => {
|
|
322
|
-
return async (req,
|
|
322
|
+
return async (req, reply) => {
|
|
323
323
|
const { id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy } = req.body ?? {};
|
|
324
324
|
const noteId = resolvePositiveNoteId(id);
|
|
325
325
|
const resolvedExpectedUpdatedAt = resolveOptionalString(
|
|
@@ -356,11 +356,11 @@ const createMcpAppendNoteMarkdownHandler = (appendMarkdown = appendNoteMarkdown,
|
|
|
356
356
|
updatedAt: result.note.updatedAt
|
|
357
357
|
});
|
|
358
358
|
}
|
|
359
|
-
|
|
359
|
+
return reply.status(200).send(result);
|
|
360
360
|
};
|
|
361
361
|
};
|
|
362
362
|
const createMcpReplaceNoteMarkdownHandler = (replaceMarkdown = replaceNoteMarkdown, emitEvent = emitServerEvent) => {
|
|
363
|
-
return async (req,
|
|
363
|
+
return async (req, reply) => {
|
|
364
364
|
const { id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy } = req.body ?? {};
|
|
365
365
|
const noteId = resolvePositiveNoteId(id);
|
|
366
366
|
const resolvedExpectedUpdatedAt = resolveOptionalString(
|
|
@@ -395,11 +395,11 @@ const createMcpReplaceNoteMarkdownHandler = (replaceMarkdown = replaceNoteMarkdo
|
|
|
395
395
|
updatedAt: result.note.updatedAt
|
|
396
396
|
});
|
|
397
397
|
}
|
|
398
|
-
|
|
398
|
+
return reply.status(200).send(result);
|
|
399
399
|
};
|
|
400
400
|
};
|
|
401
401
|
const createMcpUpdateNoteMetadataHandler = (updateMetadata = updateNoteMetadata, emitEvent = emitServerEvent) => {
|
|
402
|
-
return async (req,
|
|
402
|
+
return async (req, reply) => {
|
|
403
403
|
const { id, expectedUpdatedAt, title, layout, properties } = req.body ?? {};
|
|
404
404
|
const noteId = resolvePositiveNoteId(id);
|
|
405
405
|
const resolvedLayout = resolveNoteLayout(layout);
|
|
@@ -431,11 +431,11 @@ const createMcpUpdateNoteMetadataHandler = (updateMetadata = updateNoteMetadata,
|
|
|
431
431
|
updatedAt: result.note.updatedAt
|
|
432
432
|
});
|
|
433
433
|
}
|
|
434
|
-
|
|
434
|
+
return reply.status(200).send(result);
|
|
435
435
|
};
|
|
436
436
|
};
|
|
437
437
|
const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById, emitEvent = emitServerEvent) => {
|
|
438
|
-
return async (req,
|
|
438
|
+
return async (req, reply) => {
|
|
439
439
|
const id = Number(req.body?.id);
|
|
440
440
|
if (!Number.isInteger(id) || id <= 0) {
|
|
441
441
|
throw createAppError(400, "INVALID_NOTE_ID", "A valid note id is required.");
|
|
@@ -449,10 +449,10 @@ const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById, emitEvent = emi
|
|
|
449
449
|
source: "mcp",
|
|
450
450
|
noteId: deletedNote.id
|
|
451
451
|
});
|
|
452
|
-
|
|
452
|
+
return reply.status(200).send({
|
|
453
453
|
deleted: true,
|
|
454
454
|
note: deletedNote
|
|
455
|
-
})
|
|
455
|
+
});
|
|
456
456
|
};
|
|
457
457
|
};
|
|
458
458
|
export {
|
|
@@ -260,16 +260,17 @@ const parseViewQueryRecord = (value) => {
|
|
|
260
260
|
return null;
|
|
261
261
|
}
|
|
262
262
|
};
|
|
263
|
-
const
|
|
264
|
-
if (typeof
|
|
263
|
+
const getNormalizedViewPropertyKey = (value) => {
|
|
264
|
+
if (typeof value !== "string") {
|
|
265
265
|
return null;
|
|
266
266
|
}
|
|
267
267
|
try {
|
|
268
|
-
return normalizePropertyKey(
|
|
268
|
+
return normalizePropertyKey(value);
|
|
269
269
|
} catch {
|
|
270
270
|
return null;
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
|
+
const getNormalizedViewFilterKey = (filter) => getNormalizedViewPropertyKey(filter.key);
|
|
273
274
|
const renamePropertyFiltersInViewQuery = ({
|
|
274
275
|
query,
|
|
275
276
|
key,
|
|
@@ -329,12 +330,31 @@ const findReferencedRemovedPropertyOptionValue = ({
|
|
|
329
330
|
}
|
|
330
331
|
return null;
|
|
331
332
|
};
|
|
332
|
-
const viewQueryReferencesProperty = ({
|
|
333
|
+
const viewQueryReferencesProperty = ({
|
|
334
|
+
query,
|
|
335
|
+
key,
|
|
336
|
+
displayType
|
|
337
|
+
}) => {
|
|
333
338
|
const parsed = parseViewQueryRecord(query);
|
|
334
|
-
if (!parsed
|
|
339
|
+
if (!parsed) {
|
|
335
340
|
return false;
|
|
336
341
|
}
|
|
337
|
-
|
|
342
|
+
const isReferencedKey = (value) => getNormalizedViewPropertyKey(value) === key;
|
|
343
|
+
const referencedByFilter = Array.isArray(parsed.propertyFilters) && parsed.propertyFilters.some((filter) => isRecord(filter) && getNormalizedViewFilterKey(filter) === key);
|
|
344
|
+
const displayOptions = isRecord(parsed.displayOptions) ? parsed.displayOptions : null;
|
|
345
|
+
if (!displayOptions) {
|
|
346
|
+
return referencedByFilter;
|
|
347
|
+
}
|
|
348
|
+
if (referencedByFilter) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
if (displayType === "board") {
|
|
352
|
+
return isReferencedKey(displayOptions.boardGroupByPropertyKey);
|
|
353
|
+
}
|
|
354
|
+
if (displayType === "calendar") {
|
|
355
|
+
return isReferencedKey(displayOptions.calendarDatePropertyKey);
|
|
356
|
+
}
|
|
357
|
+
return displayType === "table" && Array.isArray(displayOptions.tablePropertyKeys) && displayOptions.tablePropertyKeys.some(isReferencedKey);
|
|
338
358
|
};
|
|
339
359
|
const buildTypedValueData = async (input, definitionId, tx = models) => {
|
|
340
360
|
const resetValues = {
|
|
@@ -704,10 +724,14 @@ const deleteNotePropertyDefinition = async ({
|
|
|
704
724
|
}
|
|
705
725
|
const referencingViewSections = await tx.viewSection.findMany({
|
|
706
726
|
where: { query: { contains: definition.key } },
|
|
707
|
-
select: { title: true, query: true }
|
|
727
|
+
select: { title: true, displayType: true, query: true }
|
|
708
728
|
});
|
|
709
729
|
const referencingView = referencingViewSections.find(
|
|
710
|
-
(section) => viewQueryReferencesProperty({
|
|
730
|
+
(section) => viewQueryReferencesProperty({
|
|
731
|
+
query: section.query,
|
|
732
|
+
key: definition.key,
|
|
733
|
+
displayType: section.displayType
|
|
734
|
+
})
|
|
711
735
|
);
|
|
712
736
|
if (referencingView) {
|
|
713
737
|
throw new InvalidNotePropertyInputError(
|