ocean-brain 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/scripts/postinstall-prisma.mjs +3 -2
- package/server/client/dist/assets/Calendar-CSXEuWmQ.js +25 -0
- package/server/client/dist/assets/Callout-Ap1OcxWN.js +1 -0
- package/server/client/dist/assets/Graph-C0Ax9qjI.js +1 -0
- package/server/client/dist/assets/{Image-ZxF459sC.js → Image-CSbcG-5a.js} +1 -1
- package/server/client/dist/assets/{Image.es-BQfyKc2-.js → Image.es-Fbe7aDDo.js} +1 -1
- package/server/client/dist/assets/{ModalActionRow-DyOb58KJ.js → ModalActionRow-DjI5b4bl.js} +1 -1
- package/server/client/dist/assets/Note-BY6WgYLq.js +1 -0
- package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
- package/server/client/dist/assets/Reminders-vRksvSQE.js +1 -0
- package/server/client/dist/assets/Search-Cq4tuiJO.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-nZNf9lyE.js → SurfaceCard-Br6TjX5W.js} +1 -1
- package/server/client/dist/assets/Tag-ARDLA-Pb.js +1 -0
- package/server/client/dist/assets/TagNotes-CDGVrCJU.js +1 -0
- package/server/client/dist/assets/{Trash.es-BGoN5q9L.js → Trash.es-BfCEiL3-.js} +1 -1
- package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +1 -0
- package/server/client/dist/assets/Views-DQHPfdwM.js +1 -0
- package/server/client/dist/assets/{image.api-0mMkElZk.js → image.api-DGXlQ_Ca.js} +1 -1
- package/server/client/dist/assets/index-B28TO4FC.js +1 -0
- package/server/client/dist/assets/{index-DL7pUwq2.css → index-Bm9l_L20.css} +1 -1
- package/server/client/dist/assets/index-Bsx4P7EA.js +210 -0
- package/server/client/dist/assets/manage-image-Dg297A_h.js +1 -0
- package/server/client/dist/assets/manage-image-detail-hk6jWyiB.js +1 -0
- package/server/client/dist/assets/mcp-CSjmBjFR.js +1 -0
- package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
- package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-5N5mRkIT.js} +3 -3
- package/server/client/dist/assets/{placeholder-i-uNe71U.js → placeholder-Bmiowo28.js} +4 -4
- package/server/client/dist/assets/trash-CM7SbVtW.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-tNMwwRnx.js → useReminderMutate-DsIvVpCQ.js} +2 -2
- package/server/client/dist/assets/view-dashboard-CP8nhfxQ.js +90 -0
- package/server/client/dist/index.html +4 -4
- package/server/dist/app.js +8 -84
- package/server/dist/app.js.map +1 -1
- package/server/dist/features/auth/http/api.js +43 -0
- package/server/dist/features/auth/http/api.js.map +1 -0
- package/server/dist/features/auth/http/login-page.js +28 -0
- package/server/dist/features/auth/http/login-page.js.map +1 -0
- package/server/dist/features/auth/http/pages.js +50 -0
- package/server/dist/features/auth/http/pages.js.map +1 -0
- package/server/dist/features/auth/service.js +94 -0
- package/server/dist/features/auth/service.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
- package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
- package/server/dist/features/cache/graphql/index.js +21 -0
- package/server/dist/features/cache/graphql/index.js.map +1 -0
- package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
- package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
- package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.resolver.js +12 -0
- package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.type-defs.js +31 -0
- package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
- package/server/dist/features/image/graphql/index.js +16 -0
- package/server/dist/features/image/graphql/index.js.map +1 -0
- package/server/dist/features/image/http/upload.js +48 -0
- package/server/dist/features/image/http/upload.js.map +1 -0
- package/server/dist/features/image/services/delete.js +52 -0
- package/server/dist/features/image/services/delete.js.map +1 -0
- package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
- package/server/dist/features/image/services/remote-fetch.js.map +1 -0
- package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
- package/server/dist/features/image/services/upload.js.map +1 -0
- package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
- package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
- package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
- package/server/dist/features/mcp-admin/service.js.map +1 -0
- package/server/dist/features/note/graphql/index.js +8 -0
- package/server/dist/features/note/graphql/index.js.map +1 -0
- package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
- package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.query.resolver.js +382 -0
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.resolver.js +14 -0
- package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.type-defs.js +184 -0
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
- package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
- package/server/dist/features/note/http/mcp.js.map +1 -0
- package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
- package/server/dist/features/note/services/authoring.js.map +1 -0
- package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
- package/server/dist/features/note/services/cleanup.js.map +1 -0
- package/server/dist/features/note/services/search-backfill.js +84 -0
- package/server/dist/features/note/services/search-backfill.js.map +1 -0
- package/server/dist/features/note/services/search.js +197 -0
- package/server/dist/features/note/services/search.js.map +1 -0
- package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
- package/server/dist/features/note/services/snapshot.js.map +1 -0
- package/server/dist/{modules/note-tag-filter.js → features/note/services/tag-filter.js} +2 -6
- package/server/dist/features/note/services/tag-filter.js.map +1 -0
- package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +28 -23
- package/server/dist/features/note/services/trash.js.map +1 -0
- package/server/dist/features/placeholder/graphql/index.js +27 -0
- package/server/dist/features/placeholder/graphql/index.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
- package/server/dist/features/reminder/graphql/index.js +27 -0
- package/server/dist/features/reminder/graphql/index.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
- package/server/dist/features/tag/graphql/index.js +14 -0
- package/server/dist/features/tag/graphql/index.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
- package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
- package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
- package/server/dist/features/tag/http/mcp.js.map +1 -0
- package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
- package/server/dist/features/tag/services/organization.js.map +1 -0
- package/server/dist/features/view/graphql/index.js +11 -0
- package/server/dist/features/view/graphql/index.js.map +1 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
- package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.resolver.js +10 -0
- package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.type-defs.js +63 -0
- package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
- package/server/dist/features/view/services/workspace.js +432 -0
- package/server/dist/features/view/services/workspace.js.map +1 -0
- package/server/dist/main.js +14 -0
- package/server/dist/main.js.map +1 -1
- package/server/dist/modules/auth-guard.js +10 -9
- package/server/dist/modules/auth-guard.js.map +1 -1
- package/server/dist/modules/auth-mode.js +1 -3
- package/server/dist/modules/auth-mode.js.map +1 -1
- package/server/dist/modules/blocknote.js +8 -17
- package/server/dist/modules/blocknote.js.map +1 -1
- package/server/dist/modules/data-maintenance.js +75 -0
- package/server/dist/modules/data-maintenance.js.map +1 -0
- package/server/dist/modules/error-handler.js.map +1 -1
- package/server/dist/modules/logger.js +5 -23
- package/server/dist/modules/logger.js.map +1 -1
- package/server/dist/modules/mcp-auth.js +4 -5
- package/server/dist/modules/mcp-auth.js.map +1 -1
- package/server/dist/modules/mcp-token.js.map +1 -1
- package/server/dist/modules/prisma-runtime.js +2 -7
- package/server/dist/modules/prisma-runtime.js.map +1 -1
- package/server/dist/modules/recovery-retention.js +1 -1
- package/server/dist/modules/recovery-retention.js.map +1 -1
- package/server/dist/modules/server-events-handler.js +34 -0
- package/server/dist/modules/server-events-handler.js.map +1 -0
- package/server/dist/modules/server-events.js +32 -0
- package/server/dist/modules/server-events.js.map +1 -0
- package/server/dist/modules/use-async.js.map +1 -1
- package/server/dist/paths.js.map +1 -1
- package/server/dist/routes/api.js +34 -0
- package/server/dist/routes/api.js.map +1 -0
- package/server/dist/routes/auth-pages.js +11 -0
- package/server/dist/routes/auth-pages.js.map +1 -0
- package/server/dist/routes/client.js +27 -0
- package/server/dist/routes/client.js.map +1 -0
- package/server/dist/routes/graphql.js +33 -0
- package/server/dist/routes/graphql.js.map +1 -0
- package/server/dist/routes/index.js +11 -0
- package/server/dist/routes/index.js.map +1 -0
- package/server/dist/routes/mcp.js +30 -0
- package/server/dist/routes/mcp.js.map +1 -0
- package/server/dist/schema/index.js +11 -8
- package/server/dist/schema/index.js.map +1 -1
- package/server/dist/types/index.js.map +1 -1
- package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
- package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
- package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
- package/server/prisma/schema.prisma +66 -12
- package/server/client/dist/assets/Calendar-Kjy1PX6L.js +0 -25
- package/server/client/dist/assets/Graph-Bl5K2qUw.js +0 -1
- package/server/client/dist/assets/Note-BfGma2Qc.css +0 -1
- package/server/client/dist/assets/Note-DN_ko2M3.js +0 -1
- package/server/client/dist/assets/Reminders-BK-Uv9Ps.js +0 -1
- package/server/client/dist/assets/Search-ClHeWvmI.js +0 -1
- package/server/client/dist/assets/Tag-Dph4V_kl.js +0 -1
- package/server/client/dist/assets/TagNotes-CxP5qHZj.js +0 -1
- package/server/client/dist/assets/index-6lIw6jYW.js +0 -1
- package/server/client/dist/assets/index-Ba2XPRI8.js +0 -205
- package/server/client/dist/assets/manage-image-Br_kKxbb.js +0 -1
- package/server/client/dist/assets/manage-image-detail-9DzqbiqI.js +0 -1
- package/server/client/dist/assets/mcp-C_So91eX.js +0 -1
- package/server/client/dist/assets/trash-Cg3nJ1m5.js +0 -1
- package/server/dist/modules/auth.js +0 -25
- package/server/dist/modules/auth.js.map +0 -1
- package/server/dist/modules/image-upload.js.map +0 -1
- package/server/dist/modules/mcp-admin.js.map +0 -1
- package/server/dist/modules/note-authoring.js.map +0 -1
- package/server/dist/modules/note-cleanup.js.map +0 -1
- package/server/dist/modules/note-snapshot.js.map +0 -1
- package/server/dist/modules/note-tag-filter.js.map +0 -1
- package/server/dist/modules/note-trash.js.map +0 -1
- package/server/dist/modules/remote-image.js.map +0 -1
- package/server/dist/modules/tag-organization.js.map +0 -1
- package/server/dist/schema/cache/index.js +0 -58
- package/server/dist/schema/cache/index.js.map +0 -1
- package/server/dist/schema/image/index.js +0 -88
- package/server/dist/schema/image/index.js.map +0 -1
- package/server/dist/schema/note/index.js +0 -582
- package/server/dist/schema/note/index.js.map +0 -1
- package/server/dist/schema/placeholder/index.js +0 -105
- package/server/dist/schema/placeholder/index.js.map +0 -1
- package/server/dist/schema/reminder/index.js +0 -161
- package/server/dist/schema/reminder/index.js.map +0 -1
- package/server/dist/schema/tag/index.js +0 -81
- package/server/dist/schema/tag/index.js.map +0 -1
- package/server/dist/urls.js +0 -9
- package/server/dist/urls.js.map +0 -1
- package/server/dist/views/auth.js +0 -305
- package/server/dist/views/auth.js.map +0 -1
- package/server/dist/views/image.js +0 -44
- package/server/dist/views/image.js.map +0 -1
- package/server/dist/views/index.js +0 -6
- package/server/dist/views/index.js.map +0 -1
- package/server/dist/views/mcp-admin.js.map +0 -1
- package/server/dist/views/note.js.map +0 -1
- package/server/dist/views/tag.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e}from"./note-vendor-BofYbzmZ.js";import{t as v,q as x,ak as w,K as E,A as b,U as A,Q as T,P as o,E as _,L as G,al as M,T as R,B as S,l as P,i as l,j as k,ad as q}from"./index-Ba2XPRI8.js";import"./graph-vendor-CUxe67Lr.js";import{I as D}from"./Image-ZxF459sC.js";import{S as f}from"./SurfaceCard-nZNf9lyE.js";import{f as K,d as L}from"./image.api-0mMkElZk.js";import{c as Q,n as C}from"./Trash.es-BGoN5q9L.js";import"./note-core-BCgMq5QA.js";const U=r=>{const{data:d}=v({queryKey:x.images.list(r.searchParams),async queryFn(){const i=await K({offset:r.searchParams.offset,limit:r.searchParams.limit});if(i.type==="error")throw i;return i.allImages}});return r.render(d)},W=240,B=20,F=4,m=192,g=k(q),Z=()=>{const r=w(),d=E(),i=g.useNavigate(),{page:c}=g.useSearch(),{containerRef:p,limit:n}=b({minItemWidth:W,gap:B,rows:F}),j=A({mutationFn:L,onSuccess:s=>{if(s.type==="error")throw s;d.invalidateQueries({queryKey:x.images.listAll(),exact:!1})}}),y=async s=>{await r("Delete this image? It is not referenced by any notes.")&&j.mutate(s)},I=s=>s===1?"1 reference":`${s} references`;return e.jsx("div",{ref:p,children:e.jsx(T,{fallback:e.jsx(o,{title:"Images",variant:"default",heading:e.jsx(l,{width:136,height:24,className:"rounded-full"}),description:e.jsx(l,{width:232,height:16,className:"rounded-full"}),children:e.jsx("div",{className:"flex flex-col gap-4",children:e.jsx("div",{className:"grid-auto-cards grid gap-4",children:Array.from({length:3},(s,t)=>e.jsxs(f,{flush:!0,children:[e.jsx("div",{className:"flex items-center justify-center bg-muted/25 p-3",style:{height:m},children:e.jsx(l,{width:"100%",height:m-24,className:"rounded-[12px]"})}),e.jsxs("div",{className:"flex items-center justify-between gap-3 border-t border-border-subtle px-3 py-2.5",children:[e.jsx(l,{width:92,height:14,className:"rounded-full"}),e.jsx(l,{width:32,height:32,className:"rounded-[12px]"})]})]},t))})})}),errorTitle:"Failed to load images",errorDescription:"Retry loading uploaded image metadata",resetKeys:[c,n],children:e.jsx(U,{searchParams:{offset:(c-1)*n,limit:n},render:({images:s,totalCount:t})=>{const u=t>0?`Images (${t})`:void 0,h="Review and manage the images you uploaded inside notes";return s.length===0?e.jsx(o,{title:"Images",variant:"default",heading:u,description:h,children:e.jsx(_,{title:"There are no images",description:"Upload an image in any note and it will appear here"})}):e.jsx(o,{title:"Images",variant:"default",heading:u,description:h,children:e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx("div",{className:"grid-auto-cards grid gap-4",children:s.map(a=>e.jsxs(f,{flush:!0,children:[e.jsx(G,{to:M,params:{id:a.id},className:"focus-ring-soft block overflow-hidden rounded-t-[18px] outline-none",children:e.jsx("div",{className:"flex items-center justify-center bg-muted/25 p-3",style:{height:m},children:e.jsx(D,{className:"h-full w-full rounded-[12px] object-contain transition-transform duration-200 hover:scale-[1.02]",src:a.url,alt:a.id})})}),e.jsxs("div",{className:"flex items-center justify-between gap-3 border-t border-border-subtle px-3 py-2.5",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-2 text-fg-secondary",children:[e.jsx(Q,{className:"h-3.5 w-3.5 shrink-0 text-fg-tertiary"}),e.jsx(R,{as:"span",variant:"meta",weight:"medium",tone:"secondary",className:"truncate",children:I(a.referenceCount)})]}),e.jsx(S,{variant:"ghost",size:"icon-sm",disabled:a.referenceCount>0,onClick:()=>y(a.id),children:e.jsx(C,{className:"h-4 w-4"})})]})]},a.id))}),t&&n<t&&e.jsx(P,{page:c,last:Math.ceil(t/n),onChange:a=>{i({search:N=>({...N,page:a})})}})]})})}})})})};export{Z as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e}from"./note-vendor-BofYbzmZ.js";import{p as D,T as r,am as F,L as p,N as R,G as _,an as Z,ad as E,f as q,Q as B,j as G,al as L,ak as z,J as H,K,t as b,q as x,ao as O,U as v,ap as Q,aq as U,B as w,i as l}from"./index-Ba2XPRI8.js";import{a as t}from"./graph-vendor-CUxe67Lr.js";import{I as P}from"./Image-ZxF459sC.js";import{S as g}from"./SurfaceCard-nZNf9lyE.js";import{a as $,d as J}from"./image.api-0mMkElZk.js";import{c as V,n as W}from"./Trash.es-BGoN5q9L.js";import{I as X}from"./Image.es-BQfyKc2-.js";import"./note-core-BCgMq5QA.js";const Y=new Map([["bold",t.createElement(t.Fragment,null,t.createElement("path",{d:"M178,36c-20.09,0-37.92,7.93-50,21.56C115.92,43.93,98.09,36,78,36a66.08,66.08,0,0,0-66,66c0,72.34,105.81,130.14,110.31,132.57a12,12,0,0,0,11.38,0C138.19,232.14,244,174.34,244,102A66.08,66.08,0,0,0,178,36Zm-5.49,142.36A328.69,328.69,0,0,1,128,210.16a328.69,328.69,0,0,1-44.51-31.8C61.82,159.77,36,131.42,36,102A42,42,0,0,1,78,60c17.8,0,32.7,9.4,38.89,24.54a12,12,0,0,0,22.22,0C145.3,69.4,160.2,60,178,60a42,42,0,0,1,42,42C220,131.42,194.18,159.77,172.51,178.36Z"}))],["duotone",t.createElement(t.Fragment,null,t.createElement("path",{d:"M232,102c0,66-104,122-104,122S24,168,24,102A54,54,0,0,1,78,48c22.59,0,41.94,12.31,50,32,8.06-19.69,27.41-32,50-32A54,54,0,0,1,232,102Z",opacity:"0.2"}),t.createElement("path",{d:"M178,40c-20.65,0-38.73,8.88-50,23.89C116.73,48.88,98.65,40,78,40a62.07,62.07,0,0,0-62,62c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,228.66,240,172,240,102A62.07,62.07,0,0,0,178,40ZM128,214.8C109.74,204.16,32,155.69,32,102A46.06,46.06,0,0,1,78,56c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,155.61,146.24,204.15,128,214.8Z"}))],["fill",t.createElement(t.Fragment,null,t.createElement("path",{d:"M240,102c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,228.66,16,172,16,102A62.07,62.07,0,0,1,78,40c20.65,0,38.73,8.88,50,23.89C139.27,48.88,157.35,40,178,40A62.07,62.07,0,0,1,240,102Z"}))],["light",t.createElement(t.Fragment,null,t.createElement("path",{d:"M178,42c-21,0-39.26,9.47-50,25.34C117.26,51.47,99,42,78,42a60.07,60.07,0,0,0-60,60c0,29.2,18.2,59.59,54.1,90.31a334.68,334.68,0,0,0,53.06,37,6,6,0,0,0,5.68,0,334.68,334.68,0,0,0,53.06-37C219.8,161.59,238,131.2,238,102A60.07,60.07,0,0,0,178,42ZM128,217.11C111.59,207.64,30,157.72,30,102A48.05,48.05,0,0,1,78,54c20.28,0,37.31,10.83,44.45,28.27a6,6,0,0,0,11.1,0C140.69,64.83,157.72,54,178,54a48.05,48.05,0,0,1,48,48C226,157.72,144.41,207.64,128,217.11Z"}))],["regular",t.createElement(t.Fragment,null,t.createElement("path",{d:"M178,40c-20.65,0-38.73,8.88-50,23.89C116.73,48.88,98.65,40,78,40a62.07,62.07,0,0,0-62,62c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,228.66,240,172,240,102A62.07,62.07,0,0,0,178,40ZM128,214.8C109.74,204.16,32,155.69,32,102A46.06,46.06,0,0,1,78,56c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,155.61,146.24,204.15,128,214.8Z"}))],["thin",t.createElement(t.Fragment,null,t.createElement("path",{d:"M178,44c-21.44,0-39.92,10.19-50,27.07C117.92,54.19,99.44,44,78,44a58.07,58.07,0,0,0-58,58c0,28.59,18,58.47,53.4,88.79a333.81,333.81,0,0,0,52.7,36.73,4,4,0,0,0,3.8,0,333.81,333.81,0,0,0,52.7-36.73C218,160.47,236,130.59,236,102A58.07,58.07,0,0,0,178,44ZM128,219.42c-14-8-100-59.35-100-117.42A50.06,50.06,0,0,1,78,52c21.11,0,38.85,11.31,46.3,29.51a4,4,0,0,0,7.4,0C139.15,63.31,156.89,52,178,52a50.06,50.06,0,0,1,50,50C228,160,142,211.46,128,219.42Z"}))]]),A=t.forwardRef((a,n)=>t.createElement(D,{ref:n,...a,weights:Y}));A.displayName="HeartIcon";const ee=A;function ae({name:a}){return e.jsx(r,{as:"div",variant:"label",weight:"medium",tone:"secondary",className:"inline-flex items-center rounded-full border border-border-subtle bg-hover-subtle px-2.5 py-1",children:a})}function se({id:a,title:n,tags:i,createdAt:u,updatedAt:d}){const o=new Date(Number(u)),m=F(Number(d));return e.jsx("div",{className:"border-b-2 border-dashed border-border-subtle last:border-b-0",children:e.jsxs("div",{className:"py-4 flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(r,{variant:"label",weight:"medium",tone:"tertiary",children:m}),e.jsxs(r,{variant:"label",tone:"placeholder",children:["(",o.toDateString(),")"]})]}),e.jsx(r,{as:"div",variant:"body",weight:"bold",tracking:"tight",children:e.jsx(p,{className:"transition-colors hover:text-accent-primary",to:R,params:{id:a},children:n||"Untitled"})}),i&&i.length>0&&e.jsx("div",{className:"flex flex-wrap gap-1.5",children:i.map(c=>e.jsx(p,{to:_,params:{id:c.id},search:{page:1},children:e.jsx(ae,{name:c.name})},c.id))})]})})}const I=G(L),j=352,te="mb-4 inline-flex items-center gap-1 text-fg-secondary transition-colors hover:text-fg-default",T="flex items-center justify-center bg-muted/25 p-3",re="h-full w-full rounded-[12px] object-contain",M="mb-3 flex flex-wrap items-start justify-between gap-3 border-b border-border-subtle pb-3",S="space-y-1",ne=a=>a===1?"1 reference":`${a} references`,C=(a,n)=>typeof a=="object"&&a!==null&&"errors"in a&&Array.isArray(a.errors)&&typeof a.errors[0]?.message=="string"?a.errors[0].message:a instanceof Error&&a.message?a.message:n,ie=()=>e.jsxs("div",{className:"flex flex-col gap-6 lg:flex-row",children:[e.jsx("div",{className:"w-full lg:w-[400px] lg:flex-shrink-0",children:e.jsxs(g,{flush:!0,children:[e.jsx("div",{className:T,style:{height:j},children:e.jsx(l,{width:"100%",height:j-24,className:"rounded-[12px]"})}),e.jsxs("div",{className:"flex flex-col gap-4 border-t border-border-subtle p-4",children:[e.jsx(l,{width:96,height:14,className:"rounded-full"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(l,{height:32,className:"flex-1 rounded-[12px]"}),e.jsx(l,{height:32,className:"flex-1 rounded-[12px]"})]})]})]})}),e.jsx("div",{className:"flex-1 min-w-0",children:e.jsxs(g,{children:[e.jsxs("div",{className:M,children:[e.jsxs("div",{className:S,children:[e.jsx(l,{width:140,height:18}),e.jsx(l,{width:220,height:14,className:"rounded-full"})]}),e.jsx(l,{width:84,height:14,className:"rounded-full"})]}),e.jsx("div",{className:"flex flex-col gap-3",children:Array.from({length:3},(a,n)=>e.jsx(l,{height:72,className:"rounded-[16px]"},n))})]})})]}),le=({id:a})=>{const n=z(),i=H(),u=I.useNavigate(),d=K(),{data:o}=b({queryKey:x.images.detail(a),queryFn:async()=>{const s=await $(a);if(s.type==="error")throw s;return s.image}}),{data:m}=b({queryKey:x.images.notes(a),queryFn:async()=>{const s=await O(o.url);if(s.type==="error")throw s;return s.imageNotes}}),c=m.length,y=ne(c),h=c===0,f=v({mutationFn:async()=>{const s=await J(a);if(s.type==="error")throw s;if(!s.deleteImage)throw new Error("Failed to delete image");return s.deleteImage},onSuccess:async()=>{await d.invalidateQueries({queryKey:x.images.listAll(),exact:!1}),i("Image deleted"),u({to:E,search:{page:1}})},onError:s=>{i(C(s,"Failed to delete image"))}}),N=v({mutationFn:async()=>{const s=await Q("heroBanner",o.url);if(s.type==="error")throw s;return s.cache.value},onSuccess:async()=>{await d.invalidateQueries({queryKey:x.ui.heroBanner(),exact:!0}),i("Hero banner updated")},onError:s=>{i(C(s,"Failed to update hero banner"))}}),k=async()=>{!h||f.isPending||await n("Delete this image? It is not referenced by any notes.")&&f.mutate()};return e.jsxs("div",{className:"flex flex-col gap-6 lg:flex-row",children:[e.jsx("div",{className:"w-full lg:w-[400px] lg:flex-shrink-0",children:e.jsxs(g,{flush:!0,children:[e.jsx("div",{className:T,style:{height:j},children:e.jsx(P,{className:re,src:o.url,alt:`Image ${o.id}`})}),e.jsxs("div",{className:"flex flex-col gap-4 border-t border-border-subtle p-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(V,{size:14,className:"text-fg-tertiary"}),e.jsx(r,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:y})]}),e.jsx(r,{as:"p",variant:"meta",tone:"tertiary",children:h?"Delete is available because no notes reference this image":"Remove this image from notes before deleting it"})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(U,{content:h?"Delete this image":"Cannot delete while referenced by notes",side:"bottom",children:e.jsxs(w,{variant:"soft-danger",size:"sm",className:"flex-1",disabled:!h,isLoading:f.isPending,onClick:k,children:[e.jsx(W,{size:16}),"Delete"]})}),e.jsxs(w,{variant:"primary",size:"sm",className:"flex-1",isLoading:N.isPending,onClick:()=>N.mutate(),children:[e.jsx(ee,{size:16}),"Set hero banner"]})]})]})]})}),e.jsx("div",{className:"flex-1 min-w-0",children:e.jsxs(g,{children:[e.jsxs("div",{className:M,children:[e.jsxs("div",{className:S,children:[e.jsx(r,{as:"h2",variant:"subheading",weight:"medium",tracking:"tight",children:"Referenced Notes"}),e.jsx(r,{as:"p",variant:"meta",tone:"secondary",children:"Notes currently using this image"})]}),e.jsx(r,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:y})]}),c>0?e.jsx("ul",{className:"flex flex-col",children:m.map(s=>e.jsx("li",{children:e.jsx(se,{...s})},s.id))}):e.jsxs("div",{className:"flex items-start gap-3 py-1",children:[e.jsx("div",{className:"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-[10px] bg-muted/25 text-fg-disabled",children:e.jsx(X,{size:18})}),e.jsxs("div",{className:"space-y-1",children:[e.jsx(r,{as:"p",variant:"body",weight:"medium",tone:"secondary",children:"No notes reference this image"}),e.jsx(r,{as:"p",variant:"meta",tone:"secondary",children:"This image can be deleted or reused in another note"})]})]})]})})]})},pe=()=>{const{id:a}=I.useParams();return e.jsxs(e.Fragment,{children:[e.jsx(Z,{children:e.jsx("title",{children:"Image Detail | Ocean Brain"})}),e.jsxs(p,{to:E,search:{page:1},className:te,children:[e.jsx(q,{size:16}),e.jsx(r,{as:"span",variant:"meta",weight:"medium",className:"text-current",children:"Back to Images"})]}),e.jsx(r,{as:"h1",variant:"heading",weight:"bold",tracking:"tighter",className:"sr-only",children:"Image Detail"}),e.jsx(B,{fallback:e.jsx(ie,{}),errorTitle:"Failed to load image detail",errorDescription:"Retry loading the image preview and its referenced notes",resetKeys:[a],children:e.jsx(le,{id:a})})]})};export{pe as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e}from"./note-vendor-BofYbzmZ.js";import{ag as r,J as R,K as B,u as E,U as p,P as G,T as t,ah as J,B as M,_ as T,a0 as K,$ as U,a1 as O,a2 as w}from"./index-Ba2XPRI8.js";import{a as h}from"./graph-vendor-CUxe67Lr.js";import{S as x}from"./SurfaceCard-nZNf9lyE.js";import"./note-core-BCgMq5QA.js";const D=async()=>{const{data:s}=await r.get("/api/mcp-admin/status");return s},z=async s=>{const{data:n}=await r.post("/api/mcp-admin/enabled",{enabled:s});return n},V=async()=>{const{data:s}=await r.post("/api/mcp-admin/token/rotate");return s},_=async()=>{const{data:s}=await r.post("/api/mcp-admin/token/revoke");return s},o=["mcp-admin","status"],$=s=>JSON.stringify({mcpServers:{"ocean-brain":{command:"npx",args:["-y","ocean-brain","mcp","--server",s,"--token-file","/path/to/token.txt"]}}},null,2),H=s=>JSON.stringify({mcpServers:{"ocean-brain":{command:"npx",args:["-y","ocean-brain","mcp","--server",s,"--token","your-token-here"]}}},null,2),se=()=>{const s=R(),n=B(),[c,S]=h.useState(()=>window.location.origin),[v,g]=h.useState(""),[k,P]=h.useState("token-file"),{data:y,isLoading:A}=E({queryKey:o,queryFn:D}),b=p({mutationFn:z,onSuccess:a=>{n.setQueryData(o,a),s(a.enabled?"MCP access enabled.":"MCP access disabled.")}}),j=p({mutationFn:V,onSuccess:async({token:a})=>{g(a),s("A new MCP token has been issued."),await n.invalidateQueries({queryKey:o})}}),f=p({mutationFn:_,onSuccess:a=>{g(""),n.setQueryData(o,a),s("The active MCP token has been revoked.")}}),l=y?.enabled??!1,d=y?.hasActiveToken??!1,F=!A&&!b.isPending,i="space-y-1",N="space-y-4.5",I="inline-flex items-center gap-3 rounded-[14px] border border-border-subtle bg-muted px-3 py-2",L="rounded-full border border-border-subtle bg-hover-subtle px-2.5 py-1",q="space-y-3 rounded-[16px] border border-border-subtle bg-surface px-4 py-3",Q="overflow-x-auto rounded-[14px] border border-border-subtle bg-surface px-4 py-3 text-xs text-fg-secondary",u={variant:"subheading",weight:"medium",tracking:"tight"},C="font-medium text-fg-tertiary",m=k==="token-file"?{title:"Token file",description:"Recommended. Keeps the token out of config.",snippet:$(c)}:{title:"Inline token",description:"Useful for quick local testing.",snippet:H(c)};return e.jsx(G,{title:"MCP",variant:"default",description:"Manage MCP access, tokens, and connection details",children:e.jsxs("div",{className:"grid grid-cols-1 gap-4",children:[e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-4",children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"MCP Access"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:"Allow or block MCP requests at the server level."})]}),e.jsxs("div",{className:I,children:[e.jsx(t,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:l?"Enabled":"Disabled"}),e.jsx(J,{"aria-label":"Allow MCP access",checked:l,disabled:!F,onCheckedChange:()=>{b.mutate(!l)}})]})]})}),e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:N,children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"Token Management"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",className:"max-w-[64ch]",children:"Ocean Brain supports one active MCP token at a time. Rotating immediately invalidates the previous one."})]}),e.jsx(t,{as:"span",variant:"meta",weight:"medium",tone:d?"secondary":"tertiary",className:L,children:d?"1 active token":"No active token"})]}),e.jsxs("div",{className:"flex flex-wrap gap-2.5",children:[e.jsx(M,{onClick:()=>j.mutate(void 0),isLoading:j.isPending,children:"Rotate token"}),e.jsx(M,{variant:"soft-danger",onClick:()=>f.mutate(),isLoading:f.isPending,disabled:!d,children:"Revoke token"})]}),v&&e.jsxs("div",{className:"space-y-2.5",children:[e.jsx(T,{htmlFor:"issued-mcp-token",className:C,children:"Issued token"}),e.jsx(K,{id:"issued-mcp-token",rows:3,readOnly:!0,value:v})]})]})}),e.jsx(x,{tone:"elevated",children:e.jsxs("div",{className:N,children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"h2",...u,children:"Connection Guide"}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:"Connect your MCP client with either a token file or an inline token."})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{htmlFor:"mcp-server-url",className:C,children:"Server URL"}),e.jsx(U,{id:"mcp-server-url",value:c,onChange:a=>S(a.target.value)})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs(O,{type:"single",variant:"pills",size:"sm",value:k,onValueChange:a=>{(a==="token-file"||a==="inline-token")&&P(a)},children:[e.jsx(w,{value:"token-file",children:"Token file"}),e.jsx(w,{value:"inline-token",children:"Inline token"})]}),e.jsxs("div",{className:q,children:[e.jsxs("div",{className:i,children:[e.jsx(t,{as:"p",variant:"meta",weight:"semibold",children:m.title}),e.jsx(t,{as:"p",variant:"meta",tone:"secondary",children:m.description})]}),e.jsx("pre",{className:Q,children:m.snippet})]})]})]})})]})})};export{se as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e}from"./note-vendor-BofYbzmZ.js";import{Q as j,j as N,a7 as v,K as w,J as T,t as b,ai as S,q as n,U as E,aj as C,N as R,P as m,E as q,T as h,a8 as D,B as K,l as P,i as t,d as Q}from"./index-Ba2XPRI8.js";import"./graph-vendor-CUxe67Lr.js";import{S as g}from"./SurfaceCard-nZNf9lyE.js";import"./note-core-BCgMq5QA.js";const i=25,u=N(v),y=30,p=`Deleted notes stay here for ${y} days before permanent removal`,A=a=>Q(a).format("YYYY-MM-DD HH:mm"),_=()=>e.jsx(m,{title:"Trash",variant:"default",heading:e.jsx(t,{width:118,height:24,className:"rounded-full"}),description:e.jsx(t,{width:352,height:16,className:"rounded-full"}),children:e.jsx("div",{className:"grid gap-3",children:Array.from({length:3},(a,d)=>e.jsx(g,{padding:"compact",children:e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1 space-y-2",children:[e.jsx(t,{width:"40%",height:18,className:"rounded-full"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(t,{width:168,height:14,className:"rounded-full"}),e.jsx(t,{width:176,height:14,className:"rounded-full"})]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(t,{width:56,height:24,className:"rounded-full"}),e.jsx(t,{width:72,height:24,className:"rounded-full"})]})]}),e.jsx(t,{width:92,height:32,className:"rounded-[12px]"})]})},d))})}),k=()=>{const{page:a}=u.useSearch(),d=u.useNavigate(),l=w(),x=T(),{data:r}=b({queryKey:n.notes.trash({limit:i,offset:(a-1)*i}),queryFn:async()=>{const s=await S({limit:i,offset:(a-1)*i});if(s.type==="error")throw s;return s.trashedNotes}}),c=E({mutationFn:C,onSuccess:async s=>{if(s.type==="error")throw s;await Promise.all([l.invalidateQueries({queryKey:n.notes.all(),exact:!1}),l.invalidateQueries({queryKey:n.tags.all(),exact:!1}),l.invalidateQueries({queryKey:n.reminders.all(),exact:!1}),l.invalidateQueries({queryKey:n.images.all(),exact:!1}),l.invalidateQueries({queryKey:["calendar"],exact:!1})]),x("The note has been restored."),d({to:R,params:{id:s.restoreTrashedNote.id}})},onError:()=>{x("Failed to restore the note.")}}),f=r.totalCount>0?`Trash (${r.totalCount})`:void 0;return r.notes.length===0?e.jsx(m,{title:"Trash",variant:"default",heading:f,description:p,children:e.jsx(q,{title:"Trash is empty",description:`Delete a note and you can restore it here for ${y} days before permanent removal`})}):e.jsx(m,{title:"Trash",variant:"default",heading:f,description:p,children:e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx("div",{className:"grid gap-3",children:r.notes.map(s=>e.jsx(g,{padding:"compact",children:e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex min-w-0 items-start gap-1.5",children:[e.jsx(h,{as:"h2",variant:"body",weight:"medium",className:"min-w-0 flex-1 break-words leading-[1.25]",children:s.title||"Untitled note"}),s.pinned&&e.jsx("span",{title:"Pinned",className:"inline-flex h-5 w-5 shrink-0 items-center justify-center text-fg-tertiary",children:e.jsx(D,{className:"h-3.5 w-3.5"})})]}),e.jsx("div",{className:"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1",children:e.jsxs(h,{as:"div",variant:"meta",weight:"medium",tone:"secondary",children:["Deleted ",A(s.deletedAt)]})}),s.tagNames.length>0&&e.jsx("div",{className:"mt-2 flex flex-wrap gap-1.5",children:s.tagNames.map(o=>e.jsx(h,{as:"span",variant:"label",weight:"medium",tone:"tertiary",className:"rounded-full border border-border-subtle bg-hover-subtle px-2 py-0.5",children:o},`${s.id}-${o}`))})]}),e.jsx(K,{variant:"subtle",size:"sm",className:"self-start",isLoading:c.isPending&&c.variables===s.id,onClick:()=>c.mutate(s.id),children:"Restore"})]})},s.id))}),r.totalCount>i&&e.jsx(P,{page:a,last:Math.ceil(r.totalCount/i),onChange:s=>{d({search:o=>({...o,page:s})})}})]})})},F=()=>{const{page:a}=u.useSearch();return e.jsx(j,{fallback:e.jsx(_,{}),errorTitle:"Failed to load trash",errorDescription:"Retry loading deleted notes",resetKeys:[a],children:e.jsx(k,{})})};export{F as default};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto";
|
|
2
|
-
const createPasswordHash = async (password) => {
|
|
3
|
-
const salt = crypto.randomBytes(16).toString("hex");
|
|
4
|
-
const hash = crypto.pbkdf2Sync(password, salt, 1e3, 64, "sha512").toString("hex");
|
|
5
|
-
return `${salt}:${hash}`;
|
|
6
|
-
};
|
|
7
|
-
const comparePassword = async (password, storedHash) => {
|
|
8
|
-
const [salt, hash] = storedHash.split(":");
|
|
9
|
-
const newHash = crypto.pbkdf2Sync(password, salt, 1e3, 64, "sha512").toString("hex");
|
|
10
|
-
return hash === newHash;
|
|
11
|
-
};
|
|
12
|
-
const compareSharedSecret = (expected, received) => {
|
|
13
|
-
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
14
|
-
const receivedBuffer = Buffer.from(received, "utf8");
|
|
15
|
-
if (expectedBuffer.length !== receivedBuffer.length) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
|
|
19
|
-
};
|
|
20
|
-
export {
|
|
21
|
-
comparePassword,
|
|
22
|
-
compareSharedSecret,
|
|
23
|
-
createPasswordHash
|
|
24
|
-
};
|
|
25
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/auth.ts"],"sourcesContent":["import crypto from 'crypto';\n\nexport const createPasswordHash = async (password: string) => {\n const salt = crypto.randomBytes(16).toString('hex');\n const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');\n return `${salt}:${hash}`;\n};\n\nexport const comparePassword = async (password: string, storedHash: string) => {\n const [salt, hash] = storedHash.split(':');\n const newHash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');\n return hash === newHash;\n};\n\nexport const compareSharedSecret = (expected: string, received: string) => {\n const expectedBuffer = Buffer.from(expected, 'utf8');\n const receivedBuffer = Buffer.from(received, 'utf8');\n\n if (expectedBuffer.length !== receivedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);\n};\n"],"mappings":"AAAA,OAAO,YAAY;AAEZ,MAAM,qBAAqB,OAAO,aAAqB;AAC1D,QAAM,OAAO,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAClD,QAAM,OAAO,OAAO,WAAW,UAAU,MAAM,KAAM,IAAI,QAAQ,EAAE,SAAS,KAAK;AACjF,SAAO,GAAG,IAAI,IAAI,IAAI;AAC1B;AAEO,MAAM,kBAAkB,OAAO,UAAkB,eAAuB;AAC3E,QAAM,CAAC,MAAM,IAAI,IAAI,WAAW,MAAM,GAAG;AACzC,QAAM,UAAU,OAAO,WAAW,UAAU,MAAM,KAAM,IAAI,QAAQ,EAAE,SAAS,KAAK;AACpF,SAAO,SAAS;AACpB;AAEO,MAAM,sBAAsB,CAAC,UAAkB,aAAqB;AACvE,QAAM,iBAAiB,OAAO,KAAK,UAAU,MAAM;AACnD,QAAM,iBAAiB,OAAO,KAAK,UAAU,MAAM;AAEnD,MAAI,eAAe,WAAW,eAAe,QAAQ;AACjD,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,gBAAgB,gBAAgB,cAAc;AAChE;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/image-upload.ts"],"sourcesContent":["import crypto from 'crypto';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nimport models from '~/models.js';\nimport { paths } from '~/paths.js';\n\ninterface ImageRecord {\n id: number;\n url: string;\n}\n\ninterface ImageUploadDeps {\n createImage: (input: {\n hash: string;\n url: string;\n }) => Promise<ImageRecord>;\n ensureDir: (dirPath: string) => Promise<void>;\n findImageByHash: (hash: string) => Promise<ImageRecord | null>;\n removeFile: (filePath: string) => Promise<void>;\n writeFile: (filePath: string, buffer: Buffer) => Promise<void>;\n}\n\nexport interface PersistImageInput {\n buffer: Buffer;\n extension: string;\n}\n\nexport interface PersistedImageSummary {\n id: number;\n url: string;\n}\n\nexport const hashImageBuffer = (buffer: Buffer) => {\n return crypto.createHash('sha512').update(buffer).digest('hex');\n};\n\nconst buildImageTarget = (hash: string, extension: string, now = new Date()) => {\n const normalizedExtension = extension.trim().toLowerCase().replace(/^\\./, '');\n const currentPath = [\n now.getFullYear().toString(),\n (now.getMonth() + 1).toString(),\n now.getDate().toString()\n ];\n const targetDir = path.resolve(paths.imageDir, ...currentPath);\n const fileName = `${hash}.${normalizedExtension}`;\n\n return {\n targetDir,\n absolutePath: path.resolve(targetDir, fileName),\n url: `/assets/images/${currentPath.join('/')}/${fileName}`\n };\n};\n\nconst isImageHashUniqueConflict = (error: unknown) => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return false;\n }\n\n if (error.code !== 'P2002') {\n return false;\n }\n\n if (!('meta' in error) || typeof error.meta !== 'object' || error.meta === null) {\n return true;\n }\n\n const rawTarget = 'target' in error.meta ? error.meta.target : undefined;\n const targets = Array.isArray(rawTarget)\n ? rawTarget.filter((value): value is string => typeof value === 'string')\n : (typeof rawTarget === 'string' ? [rawTarget] : []);\n\n return targets.length === 0 || targets.includes('hash');\n};\n\nconst removeFileIfPresent = async (deps: Pick<ImageUploadDeps, 'removeFile'>, filePath: string) => {\n try {\n await deps.removeFile(filePath);\n } catch (error) {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT'\n ) {\n return;\n }\n\n throw error;\n }\n};\n\nexport const createImageUploadService = (deps: ImageUploadDeps) => {\n return {\n persistImage: async (input: PersistImageInput): Promise<PersistedImageSummary> => {\n const hash = hashImageBuffer(input.buffer);\n const existingImage = await deps.findImageByHash(hash);\n\n if (existingImage) {\n return existingImage;\n }\n\n const target = buildImageTarget(hash, input.extension);\n\n await deps.ensureDir(target.targetDir);\n await deps.writeFile(target.absolutePath, input.buffer);\n\n try {\n return await deps.createImage({\n hash,\n url: target.url\n });\n } catch (error) {\n await removeFileIfPresent(deps, target.absolutePath);\n\n if (isImageHashUniqueConflict(error)) {\n const conflictedImage = await deps.findImageByHash(hash);\n\n if (conflictedImage) {\n return conflictedImage;\n }\n }\n\n throw error;\n }\n }\n };\n};\n\nconst defaultImageUploadService = createImageUploadService({\n createImage: async (input) => {\n return models.image.create({\n data: input,\n select: {\n id: true,\n url: true\n }\n });\n },\n ensureDir: async (dirPath) => {\n await fs.mkdir(dirPath, { recursive: true });\n },\n findImageByHash: async (hash) => {\n return models.image.findFirst({\n where: { hash },\n select: {\n id: true,\n url: true\n }\n });\n },\n removeFile: async (filePath) => {\n await fs.unlink(filePath);\n },\n writeFile: async (filePath, buffer) => {\n await fs.writeFile(filePath, buffer);\n }\n});\n\nexport const persistUploadedImage = async (input: PersistImageInput) => {\n return defaultImageUploadService.persistImage(input);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/mcp-admin.ts"],"sourcesContent":["import models from '~/models.js';\nimport { issueMcpToken, verifyMcpToken } from './mcp-token.js';\n\nconst MCP_ENABLED_CACHE_KEY = 'MCP_ENABLED';\n\nexport interface McpTokenSummary {\n id: string;\n createdAt: string;\n lastUsedAt: string | null;\n}\n\nexport interface McpAdminStatus {\n enabled: boolean;\n hasActiveToken: boolean;\n token: McpTokenSummary | null;\n}\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminService {\n getStatus: () => Promise<McpAdminStatus>;\n setEnabled: (enabled: boolean) => Promise<void>;\n rotateToken: () => Promise<{ token: string }>;\n revokeActiveToken: () => Promise<void>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\ninterface McpAdminDb {\n cache: {\n findUnique: (args: { where: { key: string } }) => Promise<{ value: string } | null>;\n upsert: (args: {\n where: { key: string };\n update: { value: string };\n create: { key: string; value: string };\n }) => Promise<unknown>;\n };\n mcpToken: {\n findFirst: (args: {\n where: { revokedAt: Date | null };\n orderBy: { createdAt: 'desc' | 'asc' };\n }) => Promise<{\n id: number;\n tokenHash: string;\n createdAt: Date;\n lastUsedAt: Date | null;\n revokedAt: Date | null;\n } | null>;\n updateMany: (args: {\n where: { revokedAt: Date | null };\n data: { revokedAt: Date };\n }) => Promise<{ count: number }>;\n create: (args: { data: { tokenHash: string } }) => Promise<unknown>;\n update: (args: { where: { id: number }; data: { lastUsedAt: Date } }) => Promise<unknown>;\n };\n $transaction: <T>(callback: (tx: McpAdminDb) => Promise<T>) => Promise<T>;\n}\n\nconst toStatus = (\n enabled: boolean,\n activeToken: {\n id: number;\n createdAt: Date;\n lastUsedAt: Date | null;\n } | null\n): McpAdminStatus => {\n if (!activeToken) {\n return {\n enabled,\n hasActiveToken: false,\n token: null\n };\n }\n\n return {\n enabled,\n hasActiveToken: true,\n token: {\n id: String(activeToken.id),\n createdAt: activeToken.createdAt.toISOString(),\n lastUsedAt: activeToken.lastUsedAt ? activeToken.lastUsedAt.toISOString() : null\n }\n };\n};\n\nexport const createMcpAdminService = (db: McpAdminDb = models as unknown as McpAdminDb): McpAdminService => {\n return {\n async getStatus() {\n const enabledCache = await db.cache.findUnique({ where: { key: MCP_ENABLED_CACHE_KEY } });\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' }\n });\n\n return toStatus(enabledCache?.value === 'true', activeToken);\n },\n async setEnabled(enabled) {\n await db.cache.upsert({\n where: { key: MCP_ENABLED_CACHE_KEY },\n update: { value: String(enabled) },\n create: {\n key: MCP_ENABLED_CACHE_KEY,\n value: String(enabled)\n }\n });\n },\n async rotateToken() {\n const now = new Date();\n const issued = issueMcpToken();\n\n await db.$transaction(async (transaction) => {\n await transaction.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: now }\n });\n await transaction.mcpToken.create({ data: { tokenHash: issued.hash } });\n });\n\n return { token: issued.plaintext };\n },\n async revokeActiveToken() {\n await db.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: new Date() }\n });\n },\n async validatePresentedToken(token) {\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' }\n });\n\n if (!activeToken) {\n return {\n ok: false,\n reason: 'not_configured'\n };\n }\n\n if (!verifyMcpToken(activeToken.tokenHash, token)) {\n return {\n ok: false,\n reason: 'forbidden'\n };\n }\n\n await db.mcpToken.update({\n where: { id: activeToken.id },\n data: { lastUsedAt: new Date() }\n });\n\n return { ok: true };\n }\n };\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,eAAe,sBAAsB;AAE9C,MAAM,wBAAwB;AAyD9B,MAAM,WAAW,CACb,SACA,gBAKiB;AACjB,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,MACH;AAAA,MACA,gBAAgB;AAAA,MAChB,OAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA,gBAAgB;AAAA,IAChB,OAAO;AAAA,MACH,IAAI,OAAO,YAAY,EAAE;AAAA,MACzB,WAAW,YAAY,UAAU,YAAY;AAAA,MAC7C,YAAY,YAAY,aAAa,YAAY,WAAW,YAAY,IAAI;AAAA,IAChF;AAAA,EACJ;AACJ;AAEO,MAAM,wBAAwB,CAAC,KAAiB,WAAqD;AACxG,SAAO;AAAA,IACH,MAAM,YAAY;AACd,YAAM,eAAe,MAAM,GAAG,MAAM,WAAW,EAAE,OAAO,EAAE,KAAK,sBAAsB,EAAE,CAAC;AACxF,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,aAAO,SAAS,cAAc,UAAU,QAAQ,WAAW;AAAA,IAC/D;AAAA,IACA,MAAM,WAAW,SAAS;AACtB,YAAM,GAAG,MAAM,OAAO;AAAA,QAClB,OAAO,EAAE,KAAK,sBAAsB;AAAA,QACpC,QAAQ,EAAE,OAAO,OAAO,OAAO,EAAE;AAAA,QACjC,QAAQ;AAAA,UACJ,KAAK;AAAA,UACL,OAAO,OAAO,OAAO;AAAA,QACzB;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,IACA,MAAM,cAAc;AAChB,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,SAAS,cAAc;AAE7B,YAAM,GAAG,aAAa,OAAO,gBAAgB;AACzC,cAAM,YAAY,SAAS,WAAW;AAAA,UAClC,OAAO,EAAE,WAAW,KAAK;AAAA,UACzB,MAAM,EAAE,WAAW,IAAI;AAAA,QAC3B,CAAC;AACD,cAAM,YAAY,SAAS,OAAO,EAAE,MAAM,EAAE,WAAW,OAAO,KAAK,EAAE,CAAC;AAAA,MAC1E,CAAC;AAED,aAAO,EAAE,OAAO,OAAO,UAAU;AAAA,IACrC;AAAA,IACA,MAAM,oBAAoB;AACtB,YAAM,GAAG,SAAS,WAAW;AAAA,QACzB,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,MAAM,EAAE,WAAW,oBAAI,KAAK,EAAE;AAAA,MAClC,CAAC;AAAA,IACL;AAAA,IACA,MAAM,uBAAuB,OAAO;AAChC,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,UAAI,CAAC,aAAa;AACd,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,UAAI,CAAC,eAAe,YAAY,WAAW,KAAK,GAAG;AAC/C,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,YAAM,GAAG,SAAS,OAAO;AAAA,QACrB,OAAO,EAAE,IAAI,YAAY,GAAG;AAAA,QAC5B,MAAM,EAAE,YAAY,oBAAI,KAAK,EAAE;AAAA,MACnC,CAAC;AAED,aAAO,EAAE,IAAI,KAAK;AAAA,IACtB;AAAA,EACJ;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/note-authoring.ts"],"sourcesContent":["import models, { type NoteLayout } from '~/models.js';\nimport {\n extractTagIdsFromContentJson,\n markdownToBlocksJson\n} from './blocknote.js';\nimport { captureNoteBaseline } from './note-snapshot.js';\n\ninterface PlaceholderRecord {\n template: string;\n replacement: string;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface NoteAuthoringDeps {\n createNote: (input: {\n title: string;\n content: string;\n layout?: NoteLayout;\n tagIds?: string[];\n }) => Promise<NoteRecord>;\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findPlaceholders: (templates: string[]) => Promise<PlaceholderRecord[]>;\n parseMarkdownToContentJson: (markdown: string) => Promise<string>;\n extractTagIds: (contentJson: string) => string[];\n captureBaseline: (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n }) => Promise<unknown>;\n updateNote: (id: number, input: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n }) => Promise<NoteRecord>;\n}\n\nexport interface CreateNoteAuthoringInput {\n title: string;\n markdown?: string;\n layout?: NoteLayout;\n}\n\nexport interface UpdateNoteAuthoringInput {\n id: number;\n title?: string;\n markdown?: string;\n layout?: NoteLayout;\n editSessionId?: string;\n snapshotMeta?: string;\n}\n\nexport interface AuthoredNoteSummary {\n id: string;\n title: string;\n layout: NoteLayout;\n createdAt: string;\n updatedAt: string;\n}\n\nexport class InvalidNoteAuthoringInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidNoteAuthoringInputError';\n }\n}\n\nconst PLACEHOLDER_PREFIX = '{%';\nconst PLACEHOLDER_SUFFIX = '%}';\n\nconst serializeNote = (note: NoteRecord): AuthoredNoteSummary => ({\n id: String(note.id),\n title: note.title,\n layout: note.layout,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString()\n});\n\nconst extractPlaceholderTemplates = (value: string) => {\n return Array.from(\n new Set(\n Array.from(\n value.matchAll(new RegExp(`${PLACEHOLDER_PREFIX}([^}]+)${PLACEHOLDER_SUFFIX}`, 'g')),\n (match) => match[1]\n )\n )\n );\n};\n\nexport const createNoteAuthoringService = (deps: NoteAuthoringDeps) => {\n const replacePlaceholders = async (value: string) => {\n const templates = extractPlaceholderTemplates(value);\n\n if (templates.length === 0) {\n return value;\n }\n\n const placeholders = await deps.findPlaceholders(templates);\n let replacedValue = value;\n\n for (const placeholder of placeholders) {\n replacedValue = replacedValue.replace(\n new RegExp(`${PLACEHOLDER_PREFIX}${placeholder.template}${PLACEHOLDER_SUFFIX}`, 'g'),\n placeholder.replacement\n );\n }\n\n return replacedValue;\n };\n\n return {\n createNote: async (input: CreateNoteAuthoringInput): Promise<AuthoredNoteSummary> => {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n const replacedTitle = await replacePlaceholders(title);\n const replacedMarkdown = await replacePlaceholders(input.markdown ?? '');\n const content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n const tagIds = deps.extractTagIds(content);\n const note = await deps.createNote({\n title: replacedTitle,\n content,\n tagIds,\n ...(input.layout ? { layout: input.layout } : {})\n });\n\n return serializeNote(note);\n },\n\n updateNote: async (input: UpdateNoteAuthoringInput): Promise<AuthoredNoteSummary | null> => {\n if (\n input.title === undefined &&\n input.markdown === undefined &&\n input.layout === undefined\n ) {\n throw new InvalidNoteAuthoringInputError('At least one note field must be provided for update.');\n }\n\n const existingNote = await deps.findNoteById(input.id);\n\n if (!existingNote) {\n return null;\n }\n\n const nextData: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n } = {};\n\n if (input.title !== undefined) {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n nextData.title = await replacePlaceholders(title);\n }\n\n if (input.markdown !== undefined) {\n const replacedMarkdown = await replacePlaceholders(input.markdown);\n nextData.content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n nextData.tagIds = deps.extractTagIds(nextData.content);\n }\n\n if (input.layout !== undefined) {\n nextData.layout = input.layout;\n }\n\n await deps.captureBaseline({\n noteId: input.id,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.snapshotMeta ? { meta: input.snapshotMeta } : {})\n });\n\n const updatedNote = await deps.updateNote(input.id, nextData);\n return serializeNote(updatedNote);\n }\n };\n};\n\nconst defaultNoteAuthoringService = createNoteAuthoringService({\n createNote: async (input) => {\n return models.note.create({\n data: {\n title: input.title,\n content: input.content,\n ...(input.layout ? { layout: input.layout } : {}),\n ...(input.tagIds\n ? { tags: { connect: input.tagIds.map((id) => ({ id: Number(id) })) } }\n : {})\n }\n });\n },\n findNoteById: async (id) => {\n return models.note.findUnique({ where: { id } });\n },\n findPlaceholders: async (templates) => {\n if (templates.length === 0) {\n return [];\n }\n\n return models.placeholder.findMany({\n select: {\n template: true,\n replacement: true\n },\n where: { template: { in: templates } }\n });\n },\n parseMarkdownToContentJson: markdownToBlocksJson,\n extractTagIds: extractTagIdsFromContentJson,\n captureBaseline: captureNoteBaseline,\n updateNote: async (id, input) => {\n return models.note.update({\n where: { id },\n data: {\n title: input.title,\n content: input.content,\n layout: input.layout,\n ...(input.tagIds\n ? { tags: { set: input.tagIds.map((tagId) => ({ id: Number(tagId) })) } }\n : {})\n }\n });\n }\n});\n\nexport const createNoteFromMarkdown = async (input: CreateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.createNote(input);\n};\n\nexport const updateNoteFromMarkdown = async (input: UpdateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.updateNote(input);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/note-cleanup.ts"],"sourcesContent":["import models from '~/models.js';\nimport { trashNoteById as moveNoteToTrashById } from './note-trash.js';\n\nexport interface NoteCleanupBackReference {\n id: string;\n title: string;\n}\n\nexport interface NoteCleanupPreview {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferences: NoteCleanupBackReference[];\n orphanedTagNames: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidate {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferenceCount: number;\n matchedTerms: string[];\n reasons: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidatesResult {\n keywords: string[];\n limit: number;\n offset: number;\n olderThanDays: number;\n notes: NoteCleanupCandidate[];\n totalCount: number;\n}\n\nexport interface NoteCleanupService {\n deleteNoteById: (id: number) => Promise<NoteCleanupPreview | null>;\n getDeletePreview: (id: number) => Promise<NoteCleanupPreview | null>;\n listCleanupCandidates: (input?: {\n keywords?: string[];\n limit?: number;\n offset?: number;\n olderThanDays?: number;\n query?: string;\n }) => Promise<NoteCleanupCandidatesResult>;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n updatedAt: Date;\n pinned: boolean;\n tags: Array<{\n id: number;\n name: string;\n }>;\n}\n\ninterface NoteCleanupPreviewInternal extends NoteCleanupPreview {\n orphanedTagIds: number[];\n}\n\nconst DEFAULT_CLEANUP_KEYWORDS = ['temp', 'temporary', 'tmp', 'draft', 'test', 'wip'];\n\nexport const normalizeCleanupKeywords = (keywords?: string[], query?: string) => {\n const queryKeywords = typeof query === 'string'\n ? query.split(/[,\\s]+/).map((keyword) => keyword.trim())\n : [];\n const source = (keywords && keywords.length > 0)\n ? keywords\n : (queryKeywords.length > 0 ? queryKeywords : DEFAULT_CLEANUP_KEYWORDS);\n\n return Array.from(new Set(\n source\n .map((keyword) => keyword.trim().toLowerCase())\n .filter(Boolean)\n ));\n};\n\nconst buildForceReasons = (input: {\n pinned: boolean;\n reminderCount: number;\n backReferenceCount: number;\n}) => {\n const forceReasons: string[] = [];\n\n if (input.pinned) {\n forceReasons.push('note_is_pinned');\n }\n\n if (input.reminderCount > 0) {\n forceReasons.push('has_reminders');\n }\n\n if (input.backReferenceCount > 0) {\n forceReasons.push('has_back_references');\n }\n\n return forceReasons;\n};\n\nexport const createNoteCleanupService = (deps: {\n countCandidateNotes: (keywords: string[], olderThanDays: number) => Promise<number>;\n countReminders: (noteId: number) => Promise<number>;\n deleteNoteAndPruneTags: (noteId: number, orphanTagIds: number[]) => Promise<void>;\n findBackReferences: (noteId: number) => Promise<Array<{ id: number; title: string }>>;\n findCandidateNotes: (keywords: string[], skip: number, take: number, olderThanDays: number) => Promise<NoteRecord[]>;\n findNote: (id: number) => Promise<NoteRecord | null>;\n getTagNoteCounts: (tagIds: number[]) => Promise<Map<number, number>>;\n}): NoteCleanupService => {\n const buildCandidate = async (\n note: NoteRecord,\n matchedTerms?: string[]\n ): Promise<NoteCleanupCandidate> => {\n const [reminderCount, backReferences] = await Promise.all([\n deps.countReminders(note.id),\n deps.findBackReferences(note.id)\n ]);\n const normalizedMatchedTerms = matchedTerms ?? [];\n const forceReasons = buildForceReasons({\n pinned: note.pinned,\n reminderCount,\n backReferenceCount: backReferences.length\n });\n\n return {\n id: String(note.id),\n title: note.title,\n updatedAt: note.updatedAt.toISOString(),\n pinned: note.pinned,\n tagNames: note.tags.map((tag) => tag.name),\n reminderCount,\n backReferenceCount: backReferences.length,\n matchedTerms: normalizedMatchedTerms,\n reasons: [\n ...(normalizedMatchedTerms.length > 0 ? [`matched_terms:${normalizedMatchedTerms.join(',')}`] : []),\n ...(note.pinned ? [] : ['not_pinned']),\n ...(note.tags.length === 0 ? ['tagless'] : []),\n ...(reminderCount === 0 ? ['no_reminders'] : []),\n ...(backReferences.length === 0 ? ['no_back_references'] : [])\n ],\n requiresForce: forceReasons.length > 0,\n forceReasons\n };\n };\n\n const getDeletePreviewInternal = async (id: number): Promise<NoteCleanupPreviewInternal | null> => {\n const note = await deps.findNote(id);\n\n if (!note) {\n return null;\n }\n\n const [candidate, backReferences, tagNoteCounts] = await Promise.all([\n buildCandidate(note),\n deps.findBackReferences(note.id),\n deps.getTagNoteCounts(note.tags.map((tag) => tag.id))\n ]);\n\n const orphanedTags = note.tags.filter((tag) => (tagNoteCounts.get(tag.id) ?? 0) <= 1);\n\n return {\n id: candidate.id,\n title: candidate.title,\n updatedAt: candidate.updatedAt,\n pinned: candidate.pinned,\n tagNames: candidate.tagNames,\n reminderCount: candidate.reminderCount,\n backReferences: backReferences.map((backReference) => ({\n id: String(backReference.id),\n title: backReference.title\n })),\n orphanedTagNames: orphanedTags.map((tag) => tag.name),\n orphanedTagIds: orphanedTags.map((tag) => tag.id),\n requiresForce: candidate.requiresForce || orphanedTags.length > 0,\n forceReasons: [\n ...candidate.forceReasons,\n ...(orphanedTags.length > 0 ? ['orphan_tags'] : [])\n ]\n };\n };\n\n const getDeletePreview = async (id: number): Promise<NoteCleanupPreview | null> => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n };\n\n return {\n listCleanupCandidates: async (input = {}) => {\n const limit = Math.max(1, Number(input.limit ?? 20));\n const offset = Math.max(0, Number(input.offset ?? 0));\n const olderThanDays = Math.max(0, Number(input.olderThanDays ?? 0));\n const keywords = normalizeCleanupKeywords(input.keywords, input.query);\n\n const [totalCount, notes] = await Promise.all([\n deps.countCandidateNotes(keywords, olderThanDays),\n deps.findCandidateNotes(keywords, offset, limit, olderThanDays)\n ]);\n\n const candidates = await Promise.all(notes.map((note) => {\n const haystack = `${note.title}\\n${note.content}`.toLowerCase();\n const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));\n return buildCandidate(note, matchedTerms);\n }));\n\n return {\n keywords,\n limit,\n offset,\n olderThanDays,\n notes: candidates,\n totalCount\n };\n },\n getDeletePreview,\n deleteNoteById: async (id: number) => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n await deps.deleteNoteAndPruneTags(id, preview.orphanedTagIds);\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n }\n };\n};\n\nconst buildKeywordClauses = (keywords: string[]) => {\n return keywords.flatMap((keyword) => ([\n { title: { contains: keyword } },\n { content: { contains: keyword } }\n ]));\n};\n\nconst buildStaleWhere = (olderThanDays: number) => {\n if (olderThanDays <= 0) {\n return {};\n }\n\n return { updatedAt: { lte: new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000) } };\n};\n\nconst noteCleanupService = createNoteCleanupService({\n countCandidateNotes: (keywords: string[], olderThanDays: number) => models.note.count({\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords)\n }\n }),\n countReminders: (noteId: number) => models.reminder.count({ where: { noteId } }),\n deleteNoteAndPruneTags: async (noteId: number, orphanTagIds: number[]) => {\n void orphanTagIds;\n await moveNoteToTrashById(noteId);\n },\n findBackReferences: (noteId: number) => models.note.findMany({\n select: {\n id: true,\n title: true\n },\n where: {\n content: { contains: `reference\",\"props\":{\"id\":\"${noteId}\"` },\n NOT: { id: noteId }\n },\n orderBy: [\n { pinned: 'desc' },\n { updatedAt: 'desc' }\n ]\n }),\n findCandidateNotes: (keywords: string[], skip: number, take: number, olderThanDays: number) => models.note.findMany({\n include: { tags: true },\n orderBy: { updatedAt: 'asc' },\n skip,\n take,\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords)\n }\n }),\n findNote: (id: number) => models.note.findUnique({\n where: { id },\n include: { tags: true }\n }),\n getTagNoteCounts: async (tagIds: number[]) => {\n if (tagIds.length === 0) {\n return new Map<number, number>();\n }\n\n const tags = await models.tag.findMany({\n where: { id: { in: tagIds } },\n include: { _count: { select: { notes: true } } }\n });\n\n return new Map(tags.map((tag) => [tag.id, tag._count.notes]));\n }\n});\n\nexport const listNoteCleanupCandidates = noteCleanupService.listCleanupCandidates;\nexport const getNoteCleanupPreview = noteCleanupService.getDeletePreview;\nexport const deleteNoteById = noteCleanupService.deleteNoteById;\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/note-snapshot.ts"],"sourcesContent":["import models, { type NoteLayout } from '~/models.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n SNAPSHOT_MAX_PER_NOTE,\n SNAPSHOT_RETENTION_DAYS\n} from './recovery-retention.js';\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n updatedAt?: Date;\n}\n\ninterface NoteSnapshotRecord {\n id: number;\n noteId: number;\n title: string;\n payload: string;\n editSessionId: string | null;\n meta: string | null;\n createdAt: Date;\n}\n\ninterface NoteSnapshotDeps {\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findSnapshotByEditSessionId: (noteId: number, editSessionId: string) => Promise<NoteSnapshotRecord | null>;\n createSnapshot: (input: {\n noteId: number;\n title: string;\n payload: string;\n editSessionId?: string;\n meta?: string;\n }) => Promise<NoteSnapshotRecord>;\n listSnapshots: (noteId: number, limit: number) => Promise<NoteSnapshotRecord[]>;\n findSnapshotById: (id: number) => Promise<NoteSnapshotRecord | null>;\n purgeExpiredSnapshots: (before: Date, limit: number) => Promise<number>;\n trimOverflowSnapshots: (noteId: number, keep: number, limit: number) => Promise<number>;\n updateNote: (id: number, input: {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n }) => Promise<NoteRecord>;\n}\n\nexport interface NoteSnapshotMeta {\n entrypoint?: 'web' | 'mobile' | 'mcp';\n label?: string;\n}\n\ninterface NoteSnapshotPayload {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n}\n\nexport interface NoteSnapshotSummary {\n id: string;\n title: string;\n createdAt: string;\n meta: NoteSnapshotMeta;\n}\n\nconst parseMeta = (value?: string | null): NoteSnapshotMeta => {\n if (!value) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(value) as NoteSnapshotMeta;\n return {\n ...(parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {}),\n ...(parsed.label ? { label: parsed.label } : {})\n };\n } catch {\n return {};\n }\n};\n\nconst serializeSnapshot = (snapshot: NoteSnapshotRecord): NoteSnapshotSummary => ({\n id: String(snapshot.id),\n title: snapshot.title,\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta)\n});\n\nconst serializePayload = (note: NoteRecord): string => {\n const payload: NoteSnapshotPayload = {\n title: note.title,\n content: note.content,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout\n };\n\n return JSON.stringify(payload);\n};\n\nconst parsePayload = (payload: string): NoteSnapshotPayload => {\n const parsed = JSON.parse(payload) as Partial<NoteSnapshotPayload>;\n\n if (\n typeof parsed.title !== 'string' ||\n typeof parsed.content !== 'string' ||\n typeof parsed.pinned !== 'boolean' ||\n typeof parsed.order !== 'number' ||\n (parsed.layout !== 'narrow' && parsed.layout !== 'wide' && parsed.layout !== 'full')\n ) {\n throw new Error('INVALID_NOTE_SNAPSHOT_PAYLOAD');\n }\n\n return parsed as NoteSnapshotPayload;\n};\n\nexport const createSnapshotMetaFromUserAgent = (userAgent?: string | null): string | undefined => {\n const normalized = userAgent?.toLowerCase() ?? '';\n const isMobile = /iphone|ipad|android|mobile/.test(normalized);\n\n return JSON.stringify({\n entrypoint: isMobile ? 'mobile' : 'web',\n label: isMobile ? 'Mobile browser' : 'Web browser'\n } satisfies NoteSnapshotMeta);\n};\n\nexport const MCP_SNAPSHOT_META = JSON.stringify({\n entrypoint: 'mcp',\n label: 'MCP'\n} satisfies NoteSnapshotMeta);\n\nconst defaultPurgeExpiredSnapshots = async (before: Date, limit: number) => {\n const expiredSnapshots = await models.noteSnapshot.findMany({\n where: { createdAt: { lt: before } },\n orderBy: { createdAt: 'asc' },\n take: limit,\n select: { id: true }\n });\n\n if (expiredSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({ where: { id: { in: expiredSnapshots.map((snapshot) => snapshot.id) } } });\n\n return deleted.count;\n};\n\nconst defaultTrimOverflowSnapshots = async (noteId: number, keep: number, limit: number) => {\n const overflowSnapshots = await models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [\n { createdAt: 'desc' },\n { id: 'desc' }\n ],\n skip: keep,\n take: limit,\n select: { id: true }\n });\n\n if (overflowSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({ where: { id: { in: overflowSnapshots.map((snapshot) => snapshot.id) } } });\n\n return deleted.count;\n};\n\nexport const createNoteSnapshotService = (deps: NoteSnapshotDeps) => ({\n captureBaseline: async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n }) => {\n await deps.purgeExpiredSnapshots(\n createRetentionCutoff(SNAPSHOT_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const existing = input.editSessionId\n ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId)\n : null;\n\n if (existing) {\n return serializeSnapshot(existing);\n }\n\n const note = await deps.findNoteById(input.noteId);\n\n if (!note) {\n return null;\n }\n\n const snapshot = await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: serializePayload(note),\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {})\n });\n\n await deps.trimOverflowSnapshots(\n note.id,\n SNAPSHOT_MAX_PER_NOTE,\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n return serializeSnapshot(snapshot);\n },\n\n listSnapshots: async (noteId: number, limit = 5) => {\n await deps.purgeExpiredSnapshots(\n createRetentionCutoff(SNAPSHOT_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n await deps.trimOverflowSnapshots(\n noteId,\n SNAPSHOT_MAX_PER_NOTE,\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n const snapshots = await deps.listSnapshots(noteId, limit);\n return snapshots.map(serializeSnapshot);\n },\n\n restoreSnapshot: async (snapshotId: number, options?: { meta?: string }) => {\n await deps.purgeExpiredSnapshots(\n createRetentionCutoff(SNAPSHOT_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: serializePayload(note),\n ...(options?.meta ? { meta: options.meta } : {})\n });\n\n await deps.trimOverflowSnapshots(\n note.id,\n SNAPSHOT_MAX_PER_NOTE,\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const payload = parsePayload(snapshot.payload);\n\n return deps.updateNote(snapshot.noteId, payload);\n }\n});\n\nexport const defaultNoteSnapshotService = createNoteSnapshotService({\n findNoteById: async (id) => {\n return models.note.findUnique({ where: { id } });\n },\n findSnapshotByEditSessionId: async (noteId, editSessionId) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId,\n editSessionId\n }\n });\n },\n createSnapshot: async (input) => {\n return models.noteSnapshot.create({\n data: {\n noteId: input.noteId,\n title: input.title,\n payload: input.payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {})\n }\n });\n },\n purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,\n trimOverflowSnapshots: defaultTrimOverflowSnapshots,\n listSnapshots: async (noteId, limit) => {\n return models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: { createdAt: 'desc' },\n take: limit\n });\n },\n findSnapshotById: async (id) => {\n return models.noteSnapshot.findUnique({ where: { id } });\n },\n updateNote: async (id, input) => {\n return models.note.update({\n where: { id },\n data: input\n });\n }\n});\n\nexport const captureNoteBaseline = async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n}) => {\n return defaultNoteSnapshotService.captureBaseline(input);\n};\n\nexport const listNoteSnapshots = async (noteId: number, limit?: number) => {\n return defaultNoteSnapshotService.listSnapshots(noteId, limit);\n};\n\nexport const restoreNoteSnapshot = async (snapshotId: number, options?: { meta?: string }) => {\n return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);\n};\n\nexport const purgeExpiredNoteSnapshots = async () => {\n return defaultPurgeExpiredSnapshots(\n createRetentionCutoff(SNAPSHOT_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/note-tag-filter.ts"],"sourcesContent":["import type { Prisma } from '~/models.js';\n\nexport type NoteTagMatchMode = 'and' | 'or';\n\nexport const normalizeNoteTagNames = (tagNames: string[]) => {\n return Array.from(\n new Set(\n tagNames\n .map((tagName) => tagName.trim())\n .filter(Boolean)\n )\n );\n};\n\nexport const buildNoteTagNamesWhere = (\n tagNames: string[],\n mode: NoteTagMatchMode\n): Prisma.NoteWhereInput => {\n const normalizedTagNames = normalizeNoteTagNames(tagNames);\n\n if (normalizedTagNames.length === 0) {\n return { id: -1 };\n }\n\n if (mode === 'or') {\n return { tags: { some: { name: { in: normalizedTagNames } } } };\n }\n\n return { AND: normalizedTagNames.map((tagName) => ({ tags: { some: { name: tagName } } })) };\n};\n"],"mappings":"AAIO,MAAM,wBAAwB,CAAC,aAAuB;AACzD,SAAO,MAAM;AAAA,IACT,IAAI;AAAA,MACA,SACK,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC,EAC/B,OAAO,OAAO;AAAA,IACvB;AAAA,EACJ;AACJ;AAEO,MAAM,yBAAyB,CAClC,UACA,SACwB;AACxB,QAAM,qBAAqB,sBAAsB,QAAQ;AAEzD,MAAI,mBAAmB,WAAW,GAAG;AACjC,WAAO,EAAE,IAAI,GAAG;AAAA,EACpB;AAEA,MAAI,SAAS,MAAM;AACf,WAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,mBAAmB,EAAE,EAAE,EAAE;AAAA,EAClE;AAEA,SAAO,EAAE,KAAK,mBAAmB,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,EAAE,EAAE,EAAE;AAC/F;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/note-trash.ts"],"sourcesContent":["import models, { type NoteLayout, type ReminderPriority } from '~/models.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n TRASH_RETENTION_DAYS\n} from './recovery-retention.js';\n\ninterface LiveTagRecord {\n id: number;\n name: string;\n}\n\ninterface LiveReminderRecord {\n id: number;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface LiveNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: LiveTagRecord[];\n reminders: LiveReminderRecord[];\n}\n\ninterface RestoredNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n}\n\ninterface DeletedTagRecord {\n name: string;\n}\n\ninterface DeletedReminderRecord {\n originalId: number | null;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface DeletedNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n deletedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: DeletedTagRecord[];\n reminders: DeletedReminderRecord[];\n}\n\ninterface BlockNoteNode {\n type: string;\n props?: Record<string, unknown>;\n content?: BlockNoteNode[];\n children?: BlockNoteNode[];\n}\n\nexport interface TrashedNoteSummary {\n id: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n deletedAt: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagNames: string[];\n}\n\nexport interface TrashedNotesResult {\n totalCount: number;\n notes: TrashedNoteSummary[];\n}\n\nexport class NoteRestoreConflictError extends Error {\n constructor(message = 'A live note with the same id already exists.') {\n super(message);\n this.name = 'NoteRestoreConflictError';\n }\n}\n\ninterface NoteTrashServiceDeps {\n countDeletedNotes: () => Promise<number>;\n findDeletedNote: (id: number) => Promise<DeletedNoteRecord | null>;\n findLiveNote: (id: number) => Promise<LiveNoteRecord | null>;\n listDeletedNotes: (skip: number, take: number) => Promise<DeletedNoteRecord[]>;\n liveNoteExists: (id: number) => Promise<boolean>;\n moveNoteToTrash: (note: LiveNoteRecord) => Promise<DeletedNoteRecord>;\n purgeExpiredDeletedNotes: (before: Date, limit: number) => Promise<number>;\n restoreDeletedNote: (note: DeletedNoteRecord) => Promise<RestoredNoteRecord>;\n}\n\nconst serializeTrashedNote = (note: DeletedNoteRecord): TrashedNoteSummary => ({\n id: String(note.id),\n title: note.title,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString(),\n deletedAt: note.deletedAt.toISOString(),\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tagNames: note.tags.map((tag) => tag.name)\n});\n\nconst restoreTagIdsInContent = (content: string, tagIdByName: Map<string, number>) => {\n const rewriteNodes = (nodes: BlockNoteNode[]): BlockNoteNode[] => {\n return nodes.map((node) => ({\n ...node,\n content: node.content?.map((contentNode) => {\n if (contentNode.type !== 'tag') {\n return contentNode;\n }\n\n const tagName = typeof contentNode.props?.tag === 'string'\n ? contentNode.props.tag\n : null;\n const restoredTagId = tagName ? tagIdByName.get(tagName) : null;\n\n if (!restoredTagId) {\n return contentNode;\n }\n\n return {\n ...contentNode,\n props: {\n ...contentNode.props,\n id: String(restoredTagId)\n }\n };\n }),\n children: node.children ? rewriteNodes(node.children) : node.children\n }));\n };\n\n try {\n const parsed = JSON.parse(content) as BlockNoteNode[];\n return JSON.stringify(rewriteNodes(parsed));\n } catch {\n return content;\n }\n};\n\nexport const createNoteTrashService = (deps: NoteTrashServiceDeps) => ({\n listTrashedNotes: async (input?: {\n limit?: number;\n offset?: number;\n }): Promise<TrashedNotesResult> => {\n await deps.purgeExpiredDeletedNotes(\n createRetentionCutoff(TRASH_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const limit = Math.max(1, Number(input?.limit ?? 25));\n const offset = Math.max(0, Number(input?.offset ?? 0));\n const [totalCount, notes] = await Promise.all([\n deps.countDeletedNotes(),\n deps.listDeletedNotes(offset, limit)\n ]);\n\n return {\n totalCount,\n notes: notes.map(serializeTrashedNote)\n };\n },\n\n trashNoteById: async (id: number): Promise<TrashedNoteSummary | null> => {\n await deps.purgeExpiredDeletedNotes(\n createRetentionCutoff(TRASH_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const note = await deps.findLiveNote(id);\n\n if (!note) {\n return null;\n }\n\n const trashedNote = await deps.moveNoteToTrash(note);\n return serializeTrashedNote(trashedNote);\n },\n\n restoreNoteById: async (id: number) => {\n await deps.purgeExpiredDeletedNotes(\n createRetentionCutoff(TRASH_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n if (await deps.liveNoteExists(id)) {\n throw new NoteRestoreConflictError();\n }\n\n return deps.restoreDeletedNote(deletedNote);\n }\n});\n\nconst includeDeletedNote = {\n reminders: { orderBy: { reminderDate: 'asc' as const } },\n tags: { orderBy: { name: 'asc' as const } }\n};\n\nconst defaultPurgeExpiredDeletedNotes = async (before: Date, limit: number) => {\n const expiredNotes = await models.deletedNote.findMany({\n where: { deletedAt: { lt: before } },\n orderBy: { deletedAt: 'asc' },\n take: limit,\n select: { id: true }\n });\n\n if (expiredNotes.length === 0) {\n return 0;\n }\n\n const deleted = await models.deletedNote.deleteMany({ where: { id: { in: expiredNotes.map((note) => note.id) } } });\n\n return deleted.count;\n};\n\nconst noteTrashService = createNoteTrashService({\n countDeletedNotes: async () => models.deletedNote.count(),\n findDeletedNote: async (id) => {\n return models.deletedNote.findUnique({\n where: { id },\n include: includeDeletedNote\n });\n },\n findLiveNote: async (id) => {\n return models.note.findUnique({\n where: { id },\n include: {\n reminders: { orderBy: { reminderDate: 'asc' } },\n tags: { orderBy: { name: 'asc' } }\n }\n });\n },\n listDeletedNotes: async (skip, take) => {\n return models.deletedNote.findMany({\n skip,\n take,\n orderBy: { deletedAt: 'desc' },\n include: includeDeletedNote\n });\n },\n liveNoteExists: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n select: { id: true }\n });\n return Boolean(note);\n },\n purgeExpiredDeletedNotes: defaultPurgeExpiredDeletedNotes,\n moveNoteToTrash: async (note) => {\n return models.$transaction(async (tx) => {\n await tx.deletedNote.create({\n data: {\n id: note.id,\n title: note.title,\n content: note.content,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tags: { create: note.tags.map((tag) => ({ name: tag.name })) },\n reminders: {\n create: note.reminders.map((reminder) => ({\n originalId: reminder.id,\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt\n }))\n }\n }\n });\n\n await tx.note.delete({ where: { id: note.id } });\n\n if (note.tags.length > 0) {\n await tx.tag.deleteMany({\n where: {\n id: { in: note.tags.map((tag) => tag.id) },\n notes: { none: {} }\n }\n });\n }\n\n return tx.deletedNote.findUniqueOrThrow({\n where: { id: note.id },\n include: includeDeletedNote\n });\n });\n },\n restoreDeletedNote: async (deletedNote) => {\n return models.$transaction(async (tx) => {\n const tagNames = Array.from(new Set(deletedNote.tags.map((tag) => tag.name).filter(Boolean)));\n\n if (tagNames.length > 0) {\n const existingTags = await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: { name: true }\n });\n const existingTagNames = new Set(existingTags.map((tag) => tag.name));\n const missingTagNames = tagNames.filter((tagName) => !existingTagNames.has(tagName));\n\n for (const tagName of missingTagNames) {\n await tx.tag.create({ data: { name: tagName } });\n }\n }\n\n const restoreTagIds = tagNames.length > 0\n ? await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: {\n id: true,\n name: true\n }\n })\n : [];\n\n const restoredContent = restoreTagIds.length > 0\n ? restoreTagIdsInContent(\n deletedNote.content,\n new Map(restoreTagIds.map((tag) => [tag.name, tag.id]))\n )\n : deletedNote.content;\n\n const note = await tx.note.create({\n data: {\n id: deletedNote.id,\n title: deletedNote.title,\n content: restoredContent,\n createdAt: deletedNote.createdAt,\n updatedAt: deletedNote.updatedAt,\n pinned: deletedNote.pinned,\n order: deletedNote.order,\n layout: deletedNote.layout,\n ...(restoreTagIds.length > 0\n ? { tags: { connect: restoreTagIds.map((tag) => ({ id: tag.id })) } }\n : {}),\n ...(deletedNote.reminders.length > 0\n ? {\n reminders: {\n create: deletedNote.reminders.map((reminder) => ({\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt\n }))\n }\n }\n : {})\n }\n });\n\n await tx.deletedNote.delete({ where: { id: deletedNote.id } });\n\n return note;\n });\n }\n});\n\nexport const listTrashedNotes = noteTrashService.listTrashedNotes;\nexport const trashNoteById = noteTrashService.trashNoteById;\nexport const restoreTrashedNoteById = noteTrashService.restoreNoteById;\nexport const purgeExpiredTrashedNotes = async () => {\n return defaultPurgeExpiredDeletedNotes(\n createRetentionCutoff(TRASH_RETENTION_DAYS),\n RECOVERY_CLEANUP_BATCH_LIMIT\n );\n};\n"],"mappings":"AAAA,OAAO,YAAwD;AAC/D;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AA6FA,MAAM,iCAAiC,MAAM;AAAA,EAChD,YAAY,UAAU,gDAAgD;AAClE,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EAChB;AACJ;AAaA,MAAM,uBAAuB,CAAC,UAAiD;AAAA,EAC3E,IAAI,OAAO,KAAK,EAAE;AAAA,EAClB,OAAO,KAAK;AAAA,EACZ,WAAW,KAAK,UAAU,YAAY;AAAA,EACtC,WAAW,KAAK,UAAU,YAAY;AAAA,EACtC,WAAW,KAAK,UAAU,YAAY;AAAA,EACtC,QAAQ,KAAK;AAAA,EACb,OAAO,KAAK;AAAA,EACZ,QAAQ,KAAK;AAAA,EACb,UAAU,KAAK,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI;AAC7C;AAEA,MAAM,yBAAyB,CAAC,SAAiB,gBAAqC;AAClF,QAAM,eAAe,CAAC,UAA4C;AAC9D,WAAO,MAAM,IAAI,CAAC,UAAU;AAAA,MACxB,GAAG;AAAA,MACH,SAAS,KAAK,SAAS,IAAI,CAAC,gBAAgB;AACxC,YAAI,YAAY,SAAS,OAAO;AAC5B,iBAAO;AAAA,QACX;AAEA,cAAM,UAAU,OAAO,YAAY,OAAO,QAAQ,WAC5C,YAAY,MAAM,MAClB;AACN,cAAM,gBAAgB,UAAU,YAAY,IAAI,OAAO,IAAI;AAE3D,YAAI,CAAC,eAAe;AAChB,iBAAO;AAAA,QACX;AAEA,eAAO;AAAA,UACH,GAAG;AAAA,UACH,OAAO;AAAA,YACH,GAAG,YAAY;AAAA,YACf,IAAI,OAAO,aAAa;AAAA,UAC5B;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,MACD,UAAU,KAAK,WAAW,aAAa,KAAK,QAAQ,IAAI,KAAK;AAAA,IACjE,EAAE;AAAA,EACN;AAEA,MAAI;AACA,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,KAAK,UAAU,aAAa,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEO,MAAM,yBAAyB,CAAC,UAAgC;AAAA,EACnE,kBAAkB,OAAO,UAGU;AAC/B,UAAM,KAAK;AAAA,MACP,sBAAsB,oBAAoB;AAAA,MAC1C;AAAA,IACJ;AAEA,UAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,SAAS,EAAE,CAAC;AACpD,UAAM,SAAS,KAAK,IAAI,GAAG,OAAO,OAAO,UAAU,CAAC,CAAC;AACrD,UAAM,CAAC,YAAY,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC1C,KAAK,kBAAkB;AAAA,MACvB,KAAK,iBAAiB,QAAQ,KAAK;AAAA,IACvC,CAAC;AAED,WAAO;AAAA,MACH;AAAA,MACA,OAAO,MAAM,IAAI,oBAAoB;AAAA,IACzC;AAAA,EACJ;AAAA,EAEA,eAAe,OAAO,OAAmD;AACrE,UAAM,KAAK;AAAA,MACP,sBAAsB,oBAAoB;AAAA,MAC1C;AAAA,IACJ;AAEA,UAAM,OAAO,MAAM,KAAK,aAAa,EAAE;AAEvC,QAAI,CAAC,MAAM;AACP,aAAO;AAAA,IACX;AAEA,UAAM,cAAc,MAAM,KAAK,gBAAgB,IAAI;AACnD,WAAO,qBAAqB,WAAW;AAAA,EAC3C;AAAA,EAEA,iBAAiB,OAAO,OAAe;AACnC,UAAM,KAAK;AAAA,MACP,sBAAsB,oBAAoB;AAAA,MAC1C;AAAA,IACJ;AAEA,UAAM,cAAc,MAAM,KAAK,gBAAgB,EAAE;AAEjD,QAAI,CAAC,aAAa;AACd,aAAO;AAAA,IACX;AAEA,QAAI,MAAM,KAAK,eAAe,EAAE,GAAG;AAC/B,YAAM,IAAI,yBAAyB;AAAA,IACvC;AAEA,WAAO,KAAK,mBAAmB,WAAW;AAAA,EAC9C;AACJ;AAEA,MAAM,qBAAqB;AAAA,EACvB,WAAW,EAAE,SAAS,EAAE,cAAc,MAAe,EAAE;AAAA,EACvD,MAAM,EAAE,SAAS,EAAE,MAAM,MAAe,EAAE;AAC9C;AAEA,MAAM,kCAAkC,OAAO,QAAc,UAAkB;AAC3E,QAAM,eAAe,MAAM,OAAO,YAAY,SAAS;AAAA,IACnD,OAAO,EAAE,WAAW,EAAE,IAAI,OAAO,EAAE;AAAA,IACnC,SAAS,EAAE,WAAW,MAAM;AAAA,IAC5B,MAAM;AAAA,IACN,QAAQ,EAAE,IAAI,KAAK;AAAA,EACvB,CAAC;AAED,MAAI,aAAa,WAAW,GAAG;AAC3B,WAAO;AAAA,EACX;AAEA,QAAM,UAAU,MAAM,OAAO,YAAY,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,aAAa,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;AAElH,SAAO,QAAQ;AACnB;AAEA,MAAM,mBAAmB,uBAAuB;AAAA,EAC5C,mBAAmB,YAAY,OAAO,YAAY,MAAM;AAAA,EACxD,iBAAiB,OAAO,OAAO;AAC3B,WAAO,OAAO,YAAY,WAAW;AAAA,MACjC,OAAO,EAAE,GAAG;AAAA,MACZ,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AAAA,EACA,cAAc,OAAO,OAAO;AACxB,WAAO,OAAO,KAAK,WAAW;AAAA,MAC1B,OAAO,EAAE,GAAG;AAAA,MACZ,SAAS;AAAA,QACL,WAAW,EAAE,SAAS,EAAE,cAAc,MAAM,EAAE;AAAA,QAC9C,MAAM,EAAE,SAAS,EAAE,MAAM,MAAM,EAAE;AAAA,MACrC;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,kBAAkB,OAAO,MAAM,SAAS;AACpC,WAAO,OAAO,YAAY,SAAS;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,SAAS,EAAE,WAAW,OAAO;AAAA,MAC7B,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AAAA,EACA,gBAAgB,OAAO,OAAO;AAC1B,UAAM,OAAO,MAAM,OAAO,KAAK,WAAW;AAAA,MACtC,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AACD,WAAO,QAAQ,IAAI;AAAA,EACvB;AAAA,EACA,0BAA0B;AAAA,EAC1B,iBAAiB,OAAO,SAAS;AAC7B,WAAO,OAAO,aAAa,OAAO,OAAO;AACrC,YAAM,GAAG,YAAY,OAAO;AAAA,QACxB,MAAM;AAAA,UACF,IAAI,KAAK;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,SAAS,KAAK;AAAA,UACd,WAAW,KAAK;AAAA,UAChB,WAAW,KAAK;AAAA,UAChB,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,MAAM,EAAE,QAAQ,KAAK,KAAK,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,KAAK,EAAE,EAAE;AAAA,UAC7D,WAAW;AAAA,YACP,QAAQ,KAAK,UAAU,IAAI,CAAC,cAAc;AAAA,cACtC,YAAY,SAAS;AAAA,cACrB,cAAc,SAAS;AAAA,cACvB,WAAW,SAAS;AAAA,cACpB,UAAU,SAAS;AAAA,cACnB,SAAS,SAAS;AAAA,cAClB,WAAW,SAAS;AAAA,cACpB,WAAW,SAAS;AAAA,YACxB,EAAE;AAAA,UACN;AAAA,QACJ;AAAA,MACJ,CAAC;AAED,YAAM,GAAG,KAAK,OAAO,EAAE,OAAO,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;AAE/C,UAAI,KAAK,KAAK,SAAS,GAAG;AACtB,cAAM,GAAG,IAAI,WAAW;AAAA,UACpB,OAAO;AAAA,YACH,IAAI,EAAE,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE;AAAA,YACzC,OAAO,EAAE,MAAM,CAAC,EAAE;AAAA,UACtB;AAAA,QACJ,CAAC;AAAA,MACL;AAEA,aAAO,GAAG,YAAY,kBAAkB;AAAA,QACpC,OAAO,EAAE,IAAI,KAAK,GAAG;AAAA,QACrB,SAAS;AAAA,MACb,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AAAA,EACA,oBAAoB,OAAO,gBAAgB;AACvC,WAAO,OAAO,aAAa,OAAO,OAAO;AACrC,YAAM,WAAW,MAAM,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,OAAO,OAAO,CAAC,CAAC;AAE5F,UAAI,SAAS,SAAS,GAAG;AACrB,cAAM,eAAe,MAAM,GAAG,IAAI,SAAS;AAAA,UACvC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE;AAAA,UAChC,QAAQ,EAAE,MAAM,KAAK;AAAA,QACzB,CAAC;AACD,cAAM,mBAAmB,IAAI,IAAI,aAAa,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;AACpE,cAAM,kBAAkB,SAAS,OAAO,CAAC,YAAY,CAAC,iBAAiB,IAAI,OAAO,CAAC;AAEnF,mBAAW,WAAW,iBAAiB;AACnC,gBAAM,GAAG,IAAI,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC;AAAA,QACnD;AAAA,MACJ;AAEA,YAAM,gBAAgB,SAAS,SAAS,IAClC,MAAM,GAAG,IAAI,SAAS;AAAA,QACpB,OAAO,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE;AAAA,QAChC,QAAQ;AAAA,UACJ,IAAI;AAAA,UACJ,MAAM;AAAA,QACV;AAAA,MACJ,CAAC,IACC,CAAC;AAEP,YAAM,kBAAkB,cAAc,SAAS,IACzC;AAAA,QACE,YAAY;AAAA,QACZ,IAAI,IAAI,cAAc,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,IAAI,EAAE,CAAC,CAAC;AAAA,MAC1D,IACE,YAAY;AAElB,YAAM,OAAO,MAAM,GAAG,KAAK,OAAO;AAAA,QAC9B,MAAM;AAAA,UACF,IAAI,YAAY;AAAA,UAChB,OAAO,YAAY;AAAA,UACnB,SAAS;AAAA,UACT,WAAW,YAAY;AAAA,UACvB,WAAW,YAAY;AAAA,UACvB,QAAQ,YAAY;AAAA,UACpB,OAAO,YAAY;AAAA,UACnB,QAAQ,YAAY;AAAA,UACpB,GAAI,cAAc,SAAS,IACrB,EAAE,MAAM,EAAE,SAAS,cAAc,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,GAAG,EAAE,EAAE,EAAE,IAClE,CAAC;AAAA,UACP,GAAI,YAAY,UAAU,SAAS,IAC7B;AAAA,YACE,WAAW;AAAA,cACP,QAAQ,YAAY,UAAU,IAAI,CAAC,cAAc;AAAA,gBAC7C,cAAc,SAAS;AAAA,gBACvB,WAAW,SAAS;AAAA,gBACpB,UAAU,SAAS;AAAA,gBACnB,SAAS,SAAS;AAAA,gBAClB,WAAW,SAAS;AAAA,gBACpB,WAAW,SAAS;AAAA,cACxB,EAAE;AAAA,YACN;AAAA,UACJ,IACE,CAAC;AAAA,QACX;AAAA,MACJ,CAAC;AAED,YAAM,GAAG,YAAY,OAAO,EAAE,OAAO,EAAE,IAAI,YAAY,GAAG,EAAE,CAAC;AAE7D,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AACJ,CAAC;AAEM,MAAM,mBAAmB,iBAAiB;AAC1C,MAAM,gBAAgB,iBAAiB;AACvC,MAAM,yBAAyB,iBAAiB;AAChD,MAAM,2BAA2B,YAAY;AAChD,SAAO;AAAA,IACH,sBAAsB,oBAAoB;AAAA,IAC1C;AAAA,EACJ;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/remote-image.ts"],"sourcesContent":["import { lookup as dnsLookup } from 'node:dns/promises';\nimport { isIP } from 'node:net';\n\nexport interface RemoteImage {\n buffer: Buffer;\n contentType: string;\n extension: string;\n}\n\nexport interface RemoteImageFetchOptions {\n fetchImpl?: typeof fetch;\n lookupHostname?: (hostname: string) => Promise<Array<{ address: string }>>;\n maxBytes?: number;\n timeoutMs?: number;\n}\n\nexport class RemoteImageFetchError extends Error {\n public readonly code: string;\n public readonly status: number;\n\n constructor(code: string, status: number, message: string) {\n super(message);\n this.name = 'RemoteImageFetchError';\n this.code = code;\n this.status = status;\n }\n}\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst SUPPORTED_IMAGE_TYPES = new Map([\n ['image/png', 'png'],\n ['image/jpeg', 'jpg'],\n ['image/gif', 'gif'],\n ['image/webp', 'webp'],\n ['image/bmp', 'bmp'],\n ['image/avif', 'avif']\n]);\n\nconst createRemoteImageFetchError = (code: string, status: number, message: string) => {\n return new RemoteImageFetchError(code, status, message);\n};\n\nconst normalizeIp = (address: string) => {\n return address.toLowerCase().replace(/^::ffff:/, '');\n};\n\nconst isBlockedIpv4 = (address: string) => {\n const octets = address.split('.').map((segment) => Number(segment));\n\n if (octets.length !== 4 || octets.some((segment) => Number.isNaN(segment))) {\n return false;\n }\n\n if (octets[0] === 127 || octets[0] === 10) {\n return true;\n }\n\n if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {\n return true;\n }\n\n if (octets[0] === 192 && octets[1] === 168) {\n return true;\n }\n\n return octets[0] === 169 && octets[1] === 254;\n};\n\nconst isBlockedIpv6 = (address: string) => {\n if (address === '::1') {\n return true;\n }\n\n const firstSegment = address.split(':')[0];\n\n if (!firstSegment) {\n return false;\n }\n\n const firstValue = Number.parseInt(firstSegment, 16);\n\n if (Number.isNaN(firstValue)) {\n return false;\n }\n\n return (firstValue & 0xfe00) === 0xfc00 || (firstValue & 0xffc0) === 0xfe80;\n};\n\nconst isBlockedAddress = (address: string) => {\n const normalizedAddress = normalizeIp(address);\n const family = isIP(normalizedAddress);\n\n if (family === 4) {\n return isBlockedIpv4(normalizedAddress);\n }\n\n if (family === 6) {\n return isBlockedIpv6(normalizedAddress);\n }\n\n return false;\n};\n\nconst assertSafeRemoteUrl = async (\n src: string,\n lookupHostname: NonNullable<RemoteImageFetchOptions['lookupHostname']>\n) => {\n let remoteUrl: URL;\n\n try {\n remoteUrl = new URL(src);\n } catch {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (!['http:', 'https:'].includes(remoteUrl.protocol)) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL must use http or https.');\n }\n\n if (!remoteUrl.hostname || remoteUrl.username || remoteUrl.password) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (\n remoteUrl.port\n && !(\n (remoteUrl.protocol === 'http:' && remoteUrl.port === '80')\n || (remoteUrl.protocol === 'https:' && remoteUrl.port === '443')\n )\n ) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (remoteUrl.hostname.toLowerCase() === 'localhost') {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (isIP(remoteUrl.hostname)) {\n if (isBlockedAddress(remoteUrl.hostname)) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n }\n\n let addresses: Array<{ address: string }>;\n\n try {\n addresses = await lookupHostname(remoteUrl.hostname);\n } catch {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (addresses.length === 0 || addresses.some(({ address }) => isBlockedAddress(address))) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n};\n\nconst readImageBody = async (response: Response, maxBytes: number) => {\n if (!response.body) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image response body was empty.');\n }\n\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw createRemoteImageFetchError('REMOTE_IMAGE_TOO_LARGE', 413, 'Remote image exceeded the size limit.');\n }\n\n chunks.push(Buffer.from(value));\n }\n\n return Buffer.concat(chunks);\n};\n\nexport const fetchRemoteImage = async (\n src: string,\n options: RemoteImageFetchOptions = {}\n): Promise<RemoteImage> => {\n const fetchImpl = options.fetchImpl ?? fetch;\n const lookupHostname = options.lookupHostname ?? ((hostname: string) => {\n return dnsLookup(hostname, {\n all: true,\n verbatim: true\n });\n });\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const remoteUrl = await assertSafeRemoteUrl(src, lookupHostname);\n\n let response: Response;\n\n try {\n response = await fetchImpl(remoteUrl, {\n method: 'GET',\n redirect: 'error',\n signal: AbortSignal.timeout(timeoutMs)\n });\n } catch (error) {\n if (error instanceof RemoteImageFetchError) {\n throw error;\n }\n\n if (\n error instanceof Error\n && (error.name === 'TimeoutError' || error.name === 'AbortError')\n ) {\n throw createRemoteImageFetchError('REMOTE_FETCH_TIMEOUT', 504, 'Remote image fetch timed out.');\n }\n\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (!response.ok) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n const contentTypeHeader = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n const extension = SUPPORTED_IMAGE_TYPES.get(contentTypeHeader);\n\n if (!extension) {\n throw createRemoteImageFetchError(\n 'REMOTE_IMAGE_UNSUPPORTED_CONTENT_TYPE',\n 415,\n 'Remote image content type is not supported.'\n );\n }\n\n return {\n buffer: await readImageBody(response, maxBytes),\n contentType: contentTypeHeader,\n extension\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/tag-organization.ts"],"sourcesContent":["import models from '~/models.js';\n\ninterface TagRecord {\n id: number;\n name: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface TagOrganizationDeps {\n createTag: (name: string) => Promise<TagRecord>;\n findTagsByName: (name: string) => Promise<TagRecord[]>;\n}\n\nexport interface TagOrganizationResult {\n created: boolean;\n normalizedName: string;\n tag: {\n id: string;\n name: string;\n createdAt: string;\n updatedAt: string;\n };\n}\n\nexport class InvalidTagNameError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidTagNameError';\n }\n}\n\nexport const normalizeTagName = (name: string) => {\n const trimmedName = name.trim();\n\n if (!trimmedName) {\n throw new InvalidTagNameError('A tag name is required.');\n }\n\n const normalizedName = trimmedName.startsWith('@')\n ? trimmedName\n : `@${trimmedName}`;\n\n if (normalizedName === '@' || /\\s/.test(normalizedName.slice(1))) {\n throw new InvalidTagNameError('Tag names must be a single token like @project.');\n }\n\n return normalizedName;\n};\n\nconst serializeTag = (tag: TagRecord) => ({\n id: String(tag.id),\n name: tag.name,\n createdAt: tag.createdAt.toISOString(),\n updatedAt: tag.updatedAt.toISOString()\n});\n\nexport const createTagOrganizationService = (deps: TagOrganizationDeps) => {\n return {\n ensureTag: async (name: string): Promise<TagOrganizationResult> => {\n const normalizedName = normalizeTagName(name);\n const existingTags = await deps.findTagsByName(normalizedName);\n\n if (existingTags.length > 0) {\n return {\n created: false,\n normalizedName,\n tag: serializeTag(existingTags[0])\n };\n }\n\n const createdTag = await deps.createTag(normalizedName);\n return {\n created: true,\n normalizedName,\n tag: serializeTag(createdTag)\n };\n }\n };\n};\n\nconst defaultTagOrganizationService = createTagOrganizationService({\n createTag: async (name) => {\n return models.tag.create({ data: { name } });\n },\n findTagsByName: async (name) => {\n return models.tag.findMany({\n where: { name },\n orderBy: { createdAt: 'asc' }\n });\n }\n});\n\nexport const ensureTagByName = async (name: string) => {\n return defaultTagOrganizationService.ensureTag(name);\n};\n"],"mappings":"AAAA,OAAO,YAAY;AAyBZ,MAAM,4BAA4B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AACzB,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EAChB;AACJ;AAEO,MAAM,mBAAmB,CAAC,SAAiB;AAC9C,QAAM,cAAc,KAAK,KAAK;AAE9B,MAAI,CAAC,aAAa;AACd,UAAM,IAAI,oBAAoB,yBAAyB;AAAA,EAC3D;AAEA,QAAM,iBAAiB,YAAY,WAAW,GAAG,IAC3C,cACA,IAAI,WAAW;AAErB,MAAI,mBAAmB,OAAO,KAAK,KAAK,eAAe,MAAM,CAAC,CAAC,GAAG;AAC9D,UAAM,IAAI,oBAAoB,iDAAiD;AAAA,EACnF;AAEA,SAAO;AACX;AAEA,MAAM,eAAe,CAAC,SAAoB;AAAA,EACtC,IAAI,OAAO,IAAI,EAAE;AAAA,EACjB,MAAM,IAAI;AAAA,EACV,WAAW,IAAI,UAAU,YAAY;AAAA,EACrC,WAAW,IAAI,UAAU,YAAY;AACzC;AAEO,MAAM,+BAA+B,CAAC,SAA8B;AACvE,SAAO;AAAA,IACH,WAAW,OAAO,SAAiD;AAC/D,YAAM,iBAAiB,iBAAiB,IAAI;AAC5C,YAAM,eAAe,MAAM,KAAK,eAAe,cAAc;AAE7D,UAAI,aAAa,SAAS,GAAG;AACzB,eAAO;AAAA,UACH,SAAS;AAAA,UACT;AAAA,UACA,KAAK,aAAa,aAAa,CAAC,CAAC;AAAA,QACrC;AAAA,MACJ;AAEA,YAAM,aAAa,MAAM,KAAK,UAAU,cAAc;AACtD,aAAO;AAAA,QACH,SAAS;AAAA,QACT;AAAA,QACA,KAAK,aAAa,UAAU;AAAA,MAChC;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,MAAM,gCAAgC,6BAA6B;AAAA,EAC/D,WAAW,OAAO,SAAS;AACvB,WAAO,OAAO,IAAI,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,EAC/C;AAAA,EACA,gBAAgB,OAAO,SAAS;AAC5B,WAAO,OAAO,IAAI,SAAS;AAAA,MACvB,OAAO,EAAE,KAAK;AAAA,MACd,SAAS,EAAE,WAAW,MAAM;AAAA,IAChC,CAAC;AAAA,EACL;AACJ,CAAC;AAEM,MAAM,kBAAkB,OAAO,SAAiB;AACnD,SAAO,8BAA8B,UAAU,IAAI;AACvD;","names":[]}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import models from "../../models.js";
|
|
2
|
-
import { gql } from "../../modules/graphql.js";
|
|
3
|
-
const cacheType = gql`
|
|
4
|
-
type Cache {
|
|
5
|
-
id: ID!
|
|
6
|
-
key: String!
|
|
7
|
-
value: String!
|
|
8
|
-
}
|
|
9
|
-
`;
|
|
10
|
-
const cacheQuery = gql`
|
|
11
|
-
type Query {
|
|
12
|
-
allCaches: [Cache!]!
|
|
13
|
-
cache(key: String!): Cache!
|
|
14
|
-
}
|
|
15
|
-
`;
|
|
16
|
-
const cacheMutation = gql`
|
|
17
|
-
type Mutation {
|
|
18
|
-
setCache(key: String, value: String): Cache!
|
|
19
|
-
deleteCache(key: String!): Boolean!
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
const cacheTypeDefs = `
|
|
23
|
-
${cacheType}
|
|
24
|
-
${cacheQuery}
|
|
25
|
-
${cacheMutation}
|
|
26
|
-
`;
|
|
27
|
-
const cacheResolvers = {
|
|
28
|
-
Query: {
|
|
29
|
-
allCaches: models.cache.findMany,
|
|
30
|
-
cache: (_, { key }) => models.cache.findUnique({ where: { key } })
|
|
31
|
-
},
|
|
32
|
-
Mutation: {
|
|
33
|
-
setCache: async (_, { key, value }) => {
|
|
34
|
-
const cache = await models.cache.findFirst({ where: { key } });
|
|
35
|
-
if (cache) {
|
|
36
|
-
return models.cache.update({
|
|
37
|
-
where: { key },
|
|
38
|
-
data: { value: decodeURIComponent(value) }
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return models.cache.create({
|
|
42
|
-
data: {
|
|
43
|
-
key,
|
|
44
|
-
value: decodeURIComponent(value)
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
},
|
|
48
|
-
deleteCache: (_, { key }) => models.cache.delete({ where: { key } }).then(() => true)
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
export {
|
|
52
|
-
cacheMutation,
|
|
53
|
-
cacheQuery,
|
|
54
|
-
cacheResolvers,
|
|
55
|
-
cacheType,
|
|
56
|
-
cacheTypeDefs
|
|
57
|
-
};
|
|
58
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/schema/cache/index.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport models, { type Cache } from '~/models.js';\nimport { gql } from '~/modules/graphql.js';\n\nexport const cacheType = gql`\n type Cache {\n id: ID!\n key: String!\n value: String!\n }\n`;\n\nexport const cacheQuery = gql`\n type Query {\n allCaches: [Cache!]!\n cache(key: String!): Cache!\n }\n`;\n\nexport const cacheMutation = gql`\n type Mutation {\n setCache(key: String, value: String): Cache!\n deleteCache(key: String!): Boolean!\n }\n`;\n\nexport const cacheTypeDefs = `\n ${cacheType}\n ${cacheQuery}\n ${cacheMutation}\n`;\n\nexport const cacheResolvers: IResolvers = {\n Query: {\n allCaches: models.cache.findMany,\n cache: (_, { key }: Cache) => models.cache.findUnique({ where: { key } })\n },\n Mutation: {\n setCache: async (_, { key, value }: Cache) => {\n const cache = await models.cache.findFirst({ where: { key } });\n if (cache) {\n return models.cache.update({\n where: { key },\n data: { value: decodeURIComponent(value) }\n });\n }\n return models.cache.create({\n data: {\n key,\n value: decodeURIComponent(value)\n }\n });\n },\n deleteCache: (_, { key }: Cache) => models.cache.delete({ where: { key } }).then(() => true)\n }\n};\n"],"mappings":"AAEA,OAAO,YAA4B;AACnC,SAAS,WAAW;AAEb,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnB,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB,MAAM,gBAAgB;AAAA,MACvB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA;AAGZ,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,IACH,WAAW,OAAO,MAAM;AAAA,IACxB,OAAO,CAAC,GAAG,EAAE,IAAI,MAAa,OAAO,MAAM,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,EAC5E;AAAA,EACA,UAAU;AAAA,IACN,UAAU,OAAO,GAAG,EAAE,KAAK,MAAM,MAAa;AAC1C,YAAM,QAAQ,MAAM,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC7D,UAAI,OAAO;AACP,eAAO,OAAO,MAAM,OAAO;AAAA,UACvB,OAAO,EAAE,IAAI;AAAA,UACb,MAAM,EAAE,OAAO,mBAAmB,KAAK,EAAE;AAAA,QAC7C,CAAC;AAAA,MACL;AACA,aAAO,OAAO,MAAM,OAAO;AAAA,QACvB,MAAM;AAAA,UACF;AAAA,UACA,OAAO,mBAAmB,KAAK;AAAA,QACnC;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,IACA,aAAa,CAAC,GAAG,EAAE,IAAI,MAAa,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,EAC/F;AACJ;","names":[]}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import models from "../../models.js";
|
|
4
|
-
import { gql } from "../../modules/graphql.js";
|
|
5
|
-
import { paths } from "../../paths.js";
|
|
6
|
-
const imageType = gql`
|
|
7
|
-
input PaginationInput {
|
|
8
|
-
limit: Int!
|
|
9
|
-
offset: Int!
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type Image {
|
|
13
|
-
id: ID!
|
|
14
|
-
url: String!
|
|
15
|
-
referenceCount: Int!
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type Images {
|
|
19
|
-
totalCount: Int!
|
|
20
|
-
images: [Image!]!
|
|
21
|
-
}
|
|
22
|
-
`;
|
|
23
|
-
const imageQuery = gql`
|
|
24
|
-
type Query {
|
|
25
|
-
allImages(pagination: PaginationInput): Images!
|
|
26
|
-
image(id: ID!): Image!
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
29
|
-
const imageMutation = gql`
|
|
30
|
-
type Mutation {
|
|
31
|
-
deleteImage(id: ID!): Boolean!
|
|
32
|
-
}
|
|
33
|
-
`;
|
|
34
|
-
const imageTypeDefs = `
|
|
35
|
-
${imageType}
|
|
36
|
-
${imageQuery}
|
|
37
|
-
${imageMutation}
|
|
38
|
-
`;
|
|
39
|
-
const imageResolvers = {
|
|
40
|
-
Query: {
|
|
41
|
-
allImages: async (_, { pagination }) => {
|
|
42
|
-
const $images = models.image.findMany({
|
|
43
|
-
skip: pagination.offset,
|
|
44
|
-
take: pagination.limit,
|
|
45
|
-
orderBy: { createdAt: "desc" }
|
|
46
|
-
});
|
|
47
|
-
return {
|
|
48
|
-
totalCount: models.image.count(),
|
|
49
|
-
images: $images
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
image: async (_, { id }) => {
|
|
53
|
-
return models.image.findFirst({ where: { id: Number(id) } });
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
Mutation: {
|
|
57
|
-
deleteImage: async (_, { id }) => {
|
|
58
|
-
try {
|
|
59
|
-
const $image = await models.image.findFirst({ where: { id: Number(id) } });
|
|
60
|
-
if (!$image) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
const imagePath = path.resolve(paths.imageDir, $image.url.replace("/assets/images/", ""));
|
|
64
|
-
if (fs.existsSync(imagePath)) {
|
|
65
|
-
fs.unlinkSync(imagePath);
|
|
66
|
-
}
|
|
67
|
-
await models.image.delete({ where: { id: Number(id) } });
|
|
68
|
-
return true;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.log(error);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
Image: {
|
|
76
|
-
referenceCount: async (image) => {
|
|
77
|
-
return models.note.count({ where: { content: { contains: image.url } } });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
export {
|
|
82
|
-
imageMutation,
|
|
83
|
-
imageQuery,
|
|
84
|
-
imageResolvers,
|
|
85
|
-
imageType,
|
|
86
|
-
imageTypeDefs
|
|
87
|
-
};
|
|
88
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/schema/image/index.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport fs from 'fs';\nimport path from 'path';\n\nimport models, { type Image } from '~/models.js';\nimport { gql } from '~/modules/graphql.js';\nimport { paths } from '~/paths.js';\nimport type { Pagination } from '~/types/index.js';\n\nexport const imageType = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n type Image {\n id: ID!\n url: String!\n referenceCount: Int!\n }\n\n type Images {\n totalCount: Int!\n images: [Image!]!\n }\n`;\n\nexport const imageQuery = gql`\n type Query {\n allImages(pagination: PaginationInput): Images!\n image(id: ID!): Image!\n }\n`;\n\nexport const imageMutation = gql`\n type Mutation {\n deleteImage(id: ID!): Boolean!\n }\n`;\n\nexport const imageTypeDefs = `\n ${imageType}\n ${imageQuery}\n ${imageMutation}\n`;\n\nexport const imageResolvers: IResolvers = {\n Query: {\n allImages: async (_, { pagination }: {\n pagination: Pagination;\n }) => {\n const $images = models.image.findMany({\n skip: pagination.offset,\n take: pagination.limit,\n orderBy: { createdAt: 'desc' }\n });\n return {\n totalCount: models.image.count(),\n images: $images\n };\n },\n image: async (_, { id }) => {\n return models.image.findFirst({ where: { id: Number(id) } });\n }\n },\n Mutation: {\n deleteImage: async (_, { id }) => {\n try {\n const $image = await models.image.findFirst({ where: { id: Number(id) } });\n\n if (!$image) {\n return false;\n }\n\n const imagePath = path.resolve(paths.imageDir, $image.url.replace('/assets/images/', ''));\n if (fs.existsSync(imagePath)) {\n fs.unlinkSync(imagePath);\n }\n\n await models.image.delete({ where: { id: Number(id) } });\n\n return true;\n } catch (error) {\n console.log(error);\n return false;\n }\n }\n },\n Image: {\n referenceCount: async (image: Image) => {\n return models.note.count({ where: { content: { contains: image.url } } });\n }\n }\n};\n"],"mappings":"AACA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,OAAO,YAA4B;AACnC,SAAS,WAAW;AACpB,SAAS,aAAa;AAGf,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBlB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnB,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAMtB,MAAM,gBAAgB;AAAA,MACvB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA;AAGZ,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,IACH,WAAW,OAAO,GAAG,EAAE,WAAW,MAE5B;AACF,YAAM,UAAU,OAAO,MAAM,SAAS;AAAA,QAClC,MAAM,WAAW;AAAA,QACjB,MAAM,WAAW;AAAA,QACjB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AACD,aAAO;AAAA,QACH,YAAY,OAAO,MAAM,MAAM;AAAA,QAC/B,QAAQ;AAAA,MACZ;AAAA,IACJ;AAAA,IACA,OAAO,OAAO,GAAG,EAAE,GAAG,MAAM;AACxB,aAAO,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AAAA,IAC/D;AAAA,EACJ;AAAA,EACA,UAAU;AAAA,IACN,aAAa,OAAO,GAAG,EAAE,GAAG,MAAM;AAC9B,UAAI;AACA,cAAM,SAAS,MAAM,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AAEzE,YAAI,CAAC,QAAQ;AACT,iBAAO;AAAA,QACX;AAEA,cAAM,YAAY,KAAK,QAAQ,MAAM,UAAU,OAAO,IAAI,QAAQ,mBAAmB,EAAE,CAAC;AACxF,YAAI,GAAG,WAAW,SAAS,GAAG;AAC1B,aAAG,WAAW,SAAS;AAAA,QAC3B;AAEA,cAAM,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AAEvD,eAAO;AAAA,MACX,SAAS,OAAO;AACZ,gBAAQ,IAAI,KAAK;AACjB,eAAO;AAAA,MACX;AAAA,IACJ;AAAA,EACJ;AAAA,EACA,OAAO;AAAA,IACH,gBAAgB,OAAO,UAAiB;AACpC,aAAO,OAAO,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,MAAM,IAAI,EAAE,EAAE,CAAC;AAAA,IAC5E;AAAA,EACJ;AACJ;","names":[]}
|