ocean-brain 0.4.0 → 0.4.2
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/dist/index.js +5 -0
- package/package.json +4 -1
- package/server/client/dist/assets/{Calendar-CSXEuWmQ.js → Calendar-DI0PhQTq.js} +1 -1
- package/server/client/dist/assets/{Callout-Ap1OcxWN.js → Callout-BAqbXI0L.js} +1 -1
- package/server/client/dist/assets/{Graph-C0Ax9qjI.js → Graph-Dhuog4tc.js} +1 -1
- package/server/client/dist/assets/{Image-CSbcG-5a.js → Image-DlJ4MDo3.js} +1 -1
- package/server/client/dist/assets/{Image.es-Fbe7aDDo.js → Image.es-COwqkQuh.js} +1 -1
- package/server/client/dist/assets/Note-ChUWU3Su.js +21 -0
- package/server/client/dist/assets/Plus.es-KeV96R_e.js +1 -0
- package/server/client/dist/assets/Reminders-D7MCTNxb.js +1 -0
- package/server/client/dist/assets/Search-B-fwTr5y.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-Br6TjX5W.js → SurfaceCard-CHjcCuqZ.js} +1 -1
- package/server/client/dist/assets/Tag-BbV2z-Db.js +1 -0
- package/server/client/dist/assets/TagNotes-2HKF09Jm.js +1 -0
- package/server/client/dist/assets/{Trash.es-BfCEiL3-.js → Trash.es-BqRTZPrI.js} +1 -1
- package/server/client/dist/assets/ViewNotes-TfM3Uzbp.js +1 -0
- package/server/client/dist/assets/Views-CUwtnfa8.js +1 -0
- package/server/client/dist/assets/{image.api-DGXlQ_Ca.js → image.api-CMdQbCyN.js} +1 -1
- package/server/client/dist/assets/index-B4l2UvuC.js +232 -0
- package/server/client/dist/assets/{index-Bm9l_L20.css → index-D2gRB1ig.css} +1 -1
- package/server/client/dist/assets/{index-B28TO4FC.js → index-zhxZjg7V.js} +1 -1
- package/server/client/dist/assets/manage-image-DdXb5Bkj.js +1 -0
- package/server/client/dist/assets/{manage-image-detail-hk6jWyiB.js → manage-image-detail-CrrXCQIX.js} +1 -1
- package/server/client/dist/assets/{mcp-CSjmBjFR.js → mcp-DqGLZv90.js} +1 -1
- package/server/client/dist/assets/{note-vendor-5N5mRkIT.js → note-vendor-B0t5F04o.js} +3 -3
- package/server/client/dist/assets/{placeholder-Bmiowo28.js → placeholder-BXLtvdtN.js} +4 -4
- package/server/client/dist/assets/trash-3eUAjFRh.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-DsIvVpCQ.js → useReminderMutate-v1EXqM90.js} +2 -2
- package/server/client/dist/assets/{view-dashboard-CP8nhfxQ.js → view-dashboard-T6gmohr-.js} +1 -1
- package/server/client/dist/index.html +3 -3
- package/server/dist/app.js +2 -2
- package/server/dist/app.js.map +1 -1
- package/server/dist/features/auth/http/api.js +2 -5
- package/server/dist/features/auth/http/api.js.map +1 -1
- package/server/dist/features/auth/http/login-page.js +11 -4
- package/server/dist/features/auth/http/login-page.js.map +1 -1
- package/server/dist/features/auth/http/pages.js +4 -3
- package/server/dist/features/auth/http/pages.js.map +1 -1
- package/server/dist/features/auth/service.js +11 -42
- package/server/dist/features/auth/service.js.map +1 -1
- package/server/dist/features/note/graphql/note.query.resolver.js +4 -1
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.type-defs.js +3 -0
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -1
- package/server/dist/features/note/services/trash.js +37 -12
- package/server/dist/features/note/services/trash.js.map +1 -1
- package/server/dist/modules/auth-guard.js +31 -19
- package/server/dist/modules/auth-guard.js.map +1 -1
- package/server/dist/modules/auth-mode.js +16 -39
- package/server/dist/modules/auth-mode.js.map +1 -1
- package/server/dist/modules/error-handler.js +7 -0
- package/server/dist/modules/error-handler.js.map +1 -1
- package/server/dist/modules/rate-limit.js +18 -0
- package/server/dist/modules/rate-limit.js.map +1 -0
- package/server/dist/routes/api.js +2 -1
- package/server/dist/routes/api.js.map +1 -1
- package/server/dist/routes/auth-pages.js +2 -1
- package/server/dist/routes/auth-pages.js.map +1 -1
- package/server/dist/routes/client.js +2 -2
- package/server/dist/routes/client.js.map +1 -1
- package/server/client/dist/assets/ModalActionRow-DjI5b4bl.js +0 -1
- package/server/client/dist/assets/Note-BY6WgYLq.js +0 -1
- package/server/client/dist/assets/Reminders-vRksvSQE.js +0 -1
- package/server/client/dist/assets/Search-Cq4tuiJO.js +0 -1
- package/server/client/dist/assets/Tag-ARDLA-Pb.js +0 -1
- package/server/client/dist/assets/TagNotes-CDGVrCJU.js +0 -1
- package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +0 -1
- package/server/client/dist/assets/Views-DQHPfdwM.js +0 -1
- package/server/client/dist/assets/index-Bsx4P7EA.js +0 -210
- package/server/client/dist/assets/manage-image-Dg297A_h.js +0 -1
- package/server/client/dist/assets/trash-CM7SbVtW.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as e}from"./note-vendor-
|
|
1
|
+
import{j as e}from"./note-vendor-B0t5F04o.js";import{p as Y,h as E,w as J,q as v,aM as V,u as ee,af as M,P as ae,T as s,ai as te,aN as j,aO as N,B as C,a7 as se,M as m,l as w,I as b,r as le,ab as ne,a0 as re,i as $,j as ce,ay as ie,W as oe}from"./index-B4l2UvuC.js";import{a}from"./graph-vendor-CUxe67Lr.js";import{S as D}from"./SurfaceCard-CHjcCuqZ.js";import{n as de}from"./Plus.es-KeV96R_e.js";import"./note-core-GgFcksAF.js";const me=new Map([["bold",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216.49,168.49a12,12,0,0,1-17,0L128,97,56.49,168.49a12,12,0,0,1-17-17l80-80a12,12,0,0,1,17,0l80,80A12,12,0,0,1,216.49,168.49Z"}))],["duotone",a.createElement(a.Fragment,null,a.createElement("path",{d:"M208,160H48l80-80Z",opacity:"0.2"}),a.createElement("path",{d:"M213.66,154.34l-80-80a8,8,0,0,0-11.32,0l-80,80A8,8,0,0,0,48,168H208a8,8,0,0,0,5.66-13.66ZM67.31,152,128,91.31,188.69,152Z"}))],["fill",a.createElement(a.Fragment,null,a.createElement("path",{d:"M215.39,163.06A8,8,0,0,1,208,168H48a8,8,0,0,1-5.66-13.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,215.39,163.06Z"}))],["light",a.createElement(a.Fragment,null,a.createElement("path",{d:"M212.24,164.24a6,6,0,0,1-8.48,0L128,88.49,52.24,164.24a6,6,0,0,1-8.48-8.48l80-80a6,6,0,0,1,8.48,0l80,80A6,6,0,0,1,212.24,164.24Z"}))],["regular",a.createElement(a.Fragment,null,a.createElement("path",{d:"M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z"}))],["thin",a.createElement(a.Fragment,null,a.createElement("path",{d:"M210.83,162.83a4,4,0,0,1-5.66,0L128,85.66,50.83,162.83a4,4,0,0,1-5.66-5.66l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,210.83,162.83Z"}))]]),O=a.forwardRef((l,c)=>a.createElement(Y,{ref:c,...l,weights:me}));O.displayName="CaretUpIcon";const he=O,k=["id","name","template","replacement","createdAt","updatedAt"],ue=new Set(k),pe=`query FetchPlaceholders(
|
|
2
2
|
$searchFilter: SearchFilterInput,
|
|
3
3
|
$pagination: PaginationInput
|
|
4
4
|
) {
|
|
@@ -12,7 +12,7 @@ __PLACEHOLDER_FIELDS__ }
|
|
|
12
12
|
}
|
|
13
13
|
}`,xe=l=>{const c=l&&l.length>0?l.filter(r=>ue.has(r)):k;return Array.from(new Set(c)).map(r=>" "+r).join(`
|
|
14
14
|
`)+`
|
|
15
|
-
`},ge=async({limit:l=25,offset:c=0,query:h="",fields:r}={})=>{const g=xe(r),i=pe.replace("__PLACEHOLDER_FIELDS__",g);return E(i,{searchFilter:{query:h},pagination:{limit:l,offset:c}})},
|
|
15
|
+
`},ge=async({limit:l=25,offset:c=0,query:h="",fields:r}={})=>{const g=xe(r),i=pe.replace("__PLACEHOLDER_FIELDS__",g);return E(i,{searchFilter:{query:h},pagination:{limit:l,offset:c}})},ye=l=>E(`mutation CreatePlaceholder(
|
|
16
16
|
$name: String!,
|
|
17
17
|
$template: String!,
|
|
18
18
|
$replacement: String!
|
|
@@ -29,6 +29,6 @@ __PLACEHOLDER_FIELDS__ }
|
|
|
29
29
|
createdAt
|
|
30
30
|
updatedAt
|
|
31
31
|
}
|
|
32
|
-
}`,l),
|
|
32
|
+
}`,l),fe=l=>E(`mutation DeletePlaceholder($id: ID!) {
|
|
33
33
|
deletePlaceholder(id: $id)
|
|
34
|
-
}`,{id:l}),I=ce(ie),Pe=()=>{const l=
|
|
34
|
+
}`,{id:l}),I=ce(ie),Pe=()=>{const l=oe(),c=J(),h=I.useNavigate(),{page:r}=I.useSearch(),[g,i]=a.useState(!1),[y,H]=a.useState(!0),[n,u]=a.useState({name:"",template:"",replacement:""}),o=25,T=v.placeholders.list({limit:o,offset:(r-1)*o}),[P]=a.useState(V),{data:d,isLoading:p}=ee({queryKey:T,queryFn:async()=>{const t=await ge({offset:(r-1)*o,limit:o});if(t.type==="error")throw t;return t.allPlaceholders}}),F=M({mutationFn:ye,onSuccess:async()=>{l("Placeholder added successfully"),i(!1),u({name:"",template:"",replacement:""}),await c.invalidateQueries({queryKey:v.placeholders.listAll(),exact:!1})}}),q=M({mutationFn:fe,onSuccess:async()=>{l("Placeholder deleted successfully"),await c.invalidateQueries({queryKey:v.placeholders.listAll(),exact:!1})}}),B=P.length,x=d?.totalCount??0,Z=d?.placeholders??[],f="font-medium text-fg-tertiary",L="leading-[1.2]",A="break-all leading-[1.2]",S="leading-[1.2]",_="flex flex-col gap-px",R="space-y-px",Q="focus-ring-soft inline-flex items-center gap-2 rounded-[12px] border border-transparent px-2.5 py-1.5 text-fg-tertiary outline-none transition-colors hover:bg-hover-subtle hover:text-fg-default",K="relative",U="absolute right-2 top-2",G="focus-ring-soft surface-base group flex flex-col items-center justify-center gap-0.5 px-4 py-4 text-center text-fg-secondary outline-none transition-colors hover:bg-hover-subtle hover:text-fg-default",X="inline-flex h-[34px] w-[34px] items-center justify-center rounded-full border border-border-subtle bg-surface text-current transition-colors group-hover:border-border-secondary/70",z=x>0?`Placeholders (${x})`:void 0;return e.jsxs(ae,{title:"Placeholders",variant:"default",heading:p?e.jsx($,{width:244,height:24,className:"rounded-full"}):z,description:p?e.jsx($,{width:260,height:16,className:"rounded-full"}):"Create tokens that get replaced with values when you clone a note",children:[e.jsxs("div",{className:"flex flex-col gap-5",children:[e.jsxs("section",{className:"space-y-3 border-b border-border-subtle/80 pb-5",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx(s,{as:"p",variant:"body",weight:"medium",children:"System placeholders"}),e.jsx(s,{as:"p",variant:"meta",tone:"secondary",children:"Built-in tokens for date and time that are always available when cloning notes"})]}),e.jsxs("button",{type:"button",onClick:()=>H(!y),className:Q,children:[e.jsxs(s,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:[B," items"]}),y?e.jsx(he,{className:"h-4 w-4"}):e.jsx(te,{className:"h-4 w-4"})]})]}),y&&e.jsx("div",{className:"grid-auto-cards grid gap-5",children:P.map(t=>e.jsx(D,{children:e.jsxs("div",{className:_,children:[e.jsx(s,{as:"div",variant:"body",weight:"medium",className:L,children:t.name}),e.jsxs("div",{className:R,children:[e.jsxs(s,{as:"div",variant:"meta",weight:"medium",tone:"secondary",className:A,children:[j,t.template,N]}),e.jsxs(s,{as:"div",variant:"label",weight:"medium",tone:"tertiary",className:S,children:["Current value: ",t.replacement]})]})]})},t.name))})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx(s,{as:"p",variant:"body",weight:"medium",children:"Custom placeholders"}),e.jsx(s,{as:"p",variant:"meta",tone:"secondary",children:"Custom tokens you define once and reuse whenever you clone a note"})]}),!p&&e.jsx(s,{as:"span",variant:"meta",weight:"medium",tone:"secondary",children:x===1?"1 item":`${x} items`})]}),!p&&e.jsxs("div",{className:"grid-auto-cards grid gap-5",children:[Z.map(t=>e.jsxs(D,{className:K,children:[e.jsx(C,{variant:"ghost",size:"icon-sm",className:U,onClick:()=>q.mutate(t.id.toString()),children:e.jsx(se,{className:"h-4 w-4"})}),e.jsxs("div",{className:_,children:[e.jsx(s,{as:"div",variant:"body",weight:"medium",className:`min-w-0 pr-8 ${L}`,children:t.name}),e.jsxs("div",{className:R,children:[e.jsxs(s,{as:"div",variant:"meta",weight:"medium",tone:"secondary",className:A,children:[j,t.template,N]}),e.jsxs(s,{as:"div",variant:"label",weight:"medium",tone:"tertiary",className:S,children:["Replaces with: ",t.replacement]})]})]})]},t.id)),e.jsxs("button",{type:"button",onClick:()=>i(!0),className:G,children:[e.jsx("span",{className:X,children:e.jsx(de,{className:"h-4.5 w-4.5"})}),e.jsx(s,{as:"div",variant:"meta",weight:"medium",tone:"secondary",className:"text-current",children:"New custom"})]})]})]})]}),e.jsxs(m,{isOpen:g,onClose:()=>i(!1),variant:"form",children:[e.jsx(m.Header,{title:"Add Placeholder",onClose:()=>i(!1)}),e.jsxs(m.Body,{children:[e.jsx(m.Description,{className:"mb-4",children:"Create a token that swaps in a value when you clone a note"}),e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(w,{htmlFor:"name",className:f,children:"Name"}),e.jsx(b,{id:"name",type:"text",value:n.name,onChange:t=>u({...n,name:t.target.value}),placeholder:"Description of this placeholder"}),e.jsx(s,{as:"p",variant:"meta",tone:"secondary",children:"A readable label so you can find this placeholder later"})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(w,{htmlFor:"template",className:f,children:"Placeholder"}),e.jsx(b,{id:"template",type:"text",value:n.template,onChange:t=>u({...n,template:t.target.value}),placeholder:"note_app"}),e.jsxs(s,{as:"p",variant:"meta",tone:"secondary",children:["Use it in a note as ",j,"your_placeholder",N]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(w,{htmlFor:"replacement",className:f,children:"Replacement"}),e.jsx(b,{id:"replacement",type:"text",value:n.replacement,onChange:t=>u({...n,replacement:t.target.value}),placeholder:"Ocean Brain"}),e.jsx(s,{as:"p",variant:"meta",tone:"secondary",children:"This value is inserted when the placeholder is replaced during cloning"})]})]})]}),e.jsx(m.Footer,{children:e.jsxs(le,{children:[e.jsx(C,{variant:"ghost",onClick:()=>i(!1),children:"Cancel"}),e.jsx(C,{variant:"primary",isLoading:F.isPending,onClick:()=>{if(!n.name||!n.template||!n.replacement){l("All fields are required");return}F.mutate({name:n.name,template:n.template,replacement:n.replacement})},children:"Add"})]})})]}),e.jsx(ne,{fallback:null,children:d?.totalCount&&o<d.totalCount&&e.jsx(re,{page:r,last:Math.ceil(d.totalCount/o),onChange:t=>{h({search:W=>({...W,page:t})})}})})]})};export{Pe as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as e}from"./note-vendor-B0t5F04o.js";import{$ as F,j as M,ar as Q,w as L,v as _,a5 as I,aC as K,q as i,af as E,aD as B,N as O,aE as W,P as N,F as $,T as d,as as G,B as j,a0 as H,aF as Y,i as l,W as z,d as U,ah as V,u as Z,aG as J,M as y}from"./index-B4l2UvuC.js";import{a as k}from"./graph-vendor-CUxe67Lr.js";import{S as C}from"./SurfaceCard-CHjcCuqZ.js";import"./note-core-GgFcksAF.js";const m=25,v=M(Q),S=30,R=`Deleted notes stay here for ${S} days before permanent removal`,g={title:"Permanently delete note?",description:"This note is referenced by the notes below. If you permanently delete it, those links will stay broken. Other notes will not be edited automatically.",confirmLabel:"Delete now",confirmVariant:"danger"},P=a=>U(a).format("YYYY-MM-DD HH:mm"),X=({note:a,onClose:o})=>{const s=a?.id??"",c=a?.title||"Untitled note",r=Z({queryKey:i.notes.trashDetail(s),enabled:!!a,queryFn:async()=>{const h=await J(s);if(h.type==="error")throw h;if(!h.trashedNote)throw new Error("Trashed note not found");return h.trashedNote}}),f=r.data?.contentAsMarkdown.trim()??"";return e.jsxs(y,{isOpen:!!a,onClose:o,variant:"inspect",children:[e.jsx(y.Header,{title:c,onClose:o}),e.jsx(y.Body,{children:e.jsxs("div",{className:"flex flex-col gap-3",children:[a&&e.jsxs(d,{as:"p",variant:"meta",tone:"secondary",children:["Deleted ",P(a.deletedAt)]}),r.isLoading&&e.jsx(d,{as:"p",variant:"meta",tone:"secondary",children:"Loading deleted note content..."}),r.isError&&e.jsx(d,{as:"p",variant:"meta",tone:"error",children:"Failed to load deleted note content."}),!r.isLoading&&!r.isError&&f&&e.jsx("pre",{className:"max-h-[60vh] overflow-auto whitespace-pre-wrap break-words rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-4 py-3 text-sm leading-6 text-fg-secondary",children:f}),!r.isLoading&&!r.isError&&!f&&e.jsx(d,{as:"p",variant:"meta",tone:"secondary",className:"rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-4 py-3",children:"No readable content was found for this deleted note."})]})}),e.jsx(y.Footer,{children:e.jsx(j,{variant:"ghost",size:"sm",onClick:o,children:"Close"})})]})},ee=()=>e.jsx(N,{title:"Trash",variant:"default",heading:e.jsx(l,{width:118,height:24,className:"rounded-full"}),description:e.jsx(l,{width:352,height:16,className:"rounded-full"}),children:e.jsx("div",{className:"grid gap-3",children:Array.from({length:3},(a,o)=>e.jsx(C,{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(l,{width:"40%",height:18,className:"rounded-full"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(l,{width:168,height:14,className:"rounded-full"}),e.jsx(l,{width:176,height:14,className:"rounded-full"})]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(l,{width:56,height:24,className:"rounded-full"}),e.jsx(l,{width:72,height:24,className:"rounded-full"})]})]}),e.jsx(l,{width:92,height:32,className:"rounded-[12px]"})]})},o))})}),te=()=>{const{page:a}=v.useSearch(),o=v.useNavigate(),s=L(),c=z(),r=_(),[f,h]=k.useState(null),[x,b]=k.useState(null),{data:u}=I({queryKey:i.notes.trash({limit:m,offset:(a-1)*m}),queryFn:async()=>{const t=await K({limit:m,offset:(a-1)*m});if(t.type==="error")throw t;return t.trashedNotes}}),w=E({mutationFn:B,onSuccess:async t=>{if(t.type==="error")throw t;await Promise.all([s.invalidateQueries({queryKey:i.notes.all(),exact:!1}),s.invalidateQueries({queryKey:i.tags.all(),exact:!1}),s.invalidateQueries({queryKey:i.reminders.all(),exact:!1}),s.invalidateQueries({queryKey:i.images.all(),exact:!1}),s.invalidateQueries({queryKey:i.calendar.all(),exact:!1})]),c("The note has been restored."),o({to:O,params:{id:t.restoreTrashedNote.id}})},onError:()=>{c("Failed to restore the note.")}}),p=E({mutationFn:W,onSuccess:async t=>{if(t.type==="error")throw t;await s.invalidateQueries({queryKey:i.notes.trashAll(),exact:!1}),c("The note has been permanently deleted.")},onError:()=>{c("Failed to permanently delete the note.")}}),D=()=>{if(!x)return;const{id:t}=x;b(null),p.mutate(t)},q=async t=>{const n=await V(t);if(n.type==="error"){c("Failed to check linked notes before permanently deleting this note.");return}if(s.setQueryData(i.notes.backReferences(t),n.backReferences),n.backReferences.length>0){b({id:t,backReferences:n.backReferences});return}await r("Permanently delete this note? This cannot be undone.")&&p.mutate(t)},T=u.totalCount>0?`Trash (${u.totalCount})`:void 0,A=u.notes.find(t=>t.id===f)??null;return u.notes.length===0?e.jsx(N,{title:"Trash",variant:"default",heading:T,description:R,children:e.jsx($,{title:"Trash is empty",description:`Delete a note and you can restore it here for ${S} days before permanent removal`})}):e.jsxs(N,{title:"Trash",variant:"default",heading:T,description:R,children:[e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx("div",{className:"grid gap-3",children:u.notes.map(t=>e.jsx(C,{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(d,{as:"h2",variant:"body",weight:"medium",className:"min-w-0 flex-1 break-words leading-[1.25]",children:t.title||"Untitled note"}),t.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(G,{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(d,{as:"div",variant:"meta",weight:"medium",tone:"secondary",children:["Deleted ",P(t.deletedAt)]})}),t.tagNames.length>0&&e.jsx("div",{className:"mt-2 flex flex-wrap gap-1.5",children:t.tagNames.map(n=>e.jsx(d,{as:"span",variant:"label",weight:"medium",tone:"tertiary",className:"rounded-full border border-border-subtle bg-hover-subtle px-2 py-0.5",children:n},`${t.id}-${n}`))}),t.contentPreview&&e.jsx("div",{className:"mt-3 rounded-[14px] border border-border-subtle bg-hover-subtle/50 px-3 py-2",children:e.jsx(d,{as:"p",variant:"meta",tone:"secondary",className:"line-clamp-3 whitespace-pre-wrap break-words",children:t.contentPreview})})]}),e.jsxs("div",{className:"flex shrink-0 flex-wrap gap-2 sm:justify-end",children:[e.jsx(j,{variant:"subtle",size:"sm",onClick:()=>h(t.id),children:"View content"}),e.jsx(j,{variant:"subtle",size:"sm",isLoading:w.isPending&&w.variables===t.id,onClick:()=>w.mutate(t.id),children:"Restore"}),e.jsx(j,{variant:"soft-danger",size:"sm",isLoading:p.isPending&&p.variables===t.id,onClick:()=>q(t.id),children:"Delete now"})]})]})},t.id))}),u.totalCount>m&&e.jsx(H,{page:a,last:Math.ceil(u.totalCount/m),onChange:t=>{o({search:n=>({...n,page:t})})}})]}),e.jsx(X,{note:A,onClose:()=>h(null)}),e.jsx(Y,{isOpen:!!x,title:g.title,description:g.description,references:x?.backReferences??[],confirmLabel:g.confirmLabel,confirmVariant:g.confirmVariant,onClose:()=>b(null),onConfirm:D})]})},oe=()=>{const{page:a}=v.useSearch();return e.jsx(F,{fallback:e.jsx(ee,{}),errorTitle:"Failed to load trash",errorDescription:"Retry loading deleted notes",resetKeys:[a],children:e.jsx(te,{})})};export{oe as default};
|
package/server/client/dist/assets/{useReminderMutate-DsIvVpCQ.js → useReminderMutate-v1EXqM90.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as y}from"./note-vendor-
|
|
1
|
+
import{j as y}from"./note-vendor-B0t5F04o.js";import{h as u,a5 as f,q as n,w as R,W as $}from"./index-B4l2UvuC.js";import{a as l}from"./graph-vendor-CUxe67Lr.js";const g=(e={})=>({limit:e.limit??10,offset:e.offset??0}),D=async(e,t)=>u(`
|
|
2
2
|
query FetchNoteReminders($noteId: ID!, $pagination: PaginationInput) {
|
|
3
3
|
noteReminders(noteId: $noteId, pagination: $pagination) {
|
|
4
4
|
totalCount
|
|
@@ -79,4 +79,4 @@ import{j as y}from"./note-vendor-5N5mRkIT.js";import{h as u,a4 as f,q as n,v as
|
|
|
79
79
|
mutation DeleteReminder($id: ID!) {
|
|
80
80
|
deleteReminder(id: $id)
|
|
81
81
|
}
|
|
82
|
-
`,{id:e});function K({noteId:e,searchParams:t,render:a}){const{data:o}=f({queryKey:e?n.reminders.note(e,t):n.reminders.upcoming(t),queryFn:async()=>{if(e){const r=await D(e,t);if(r.type==="error")throw r;return r.noteReminders}const i=await A(t);if(i.type==="error")throw i;return i.upcomingReminders}});return y.jsx(y.Fragment,{children:a(o)})}function P(){const e
|
|
82
|
+
`,{id:e});function K({noteId:e,searchParams:t,render:a}){const{data:o}=f({queryKey:e?n.reminders.note(e,t):n.reminders.upcoming(t),queryFn:async()=>{if(e){const r=await D(e,t);if(r.type==="error")throw r;return r.noteReminders}const i=await A(t);if(i.type==="error")throw i;return i.upcomingReminders}});return y.jsx(y.Fragment,{children:a(o)})}function P(){const e=$(),t=R(),a=l.useCallback(async(r,m,d="medium",s,c)=>{const p=await q({noteId:r,reminderDate:m,priority:d,content:c});if(p.type==="error"){e(p.errors[0].message);return}await Promise.all([t.invalidateQueries({queryKey:n.reminders.noteAllPages(r),exact:!1}),t.invalidateQueries({queryKey:n.reminders.upcomingAllPages(),exact:!1}),t.invalidateQueries({queryKey:n.reminders.inDateRangeAll(),exact:!1})]),s&&s()},[t,e]),o=l.useCallback(async(r,m,d,s)=>{const c=await I({id:r,...d});if(c.type==="error"){e(c.errors[0].message);return}await Promise.all([t.invalidateQueries({queryKey:n.reminders.noteAllPages(m),exact:!1}),t.invalidateQueries({queryKey:n.reminders.upcomingAllPages(),exact:!1}),t.invalidateQueries({queryKey:n.reminders.inDateRangeAll(),exact:!1})]),s&&s()},[t,e]),i=l.useCallback(async(r,m,d)=>{const s=await x(r);if(s.type==="error"){e(s.errors[0].message);return}await Promise.all([t.invalidateQueries({queryKey:n.reminders.noteAllPages(m),exact:!1}),t.invalidateQueries({queryKey:n.reminders.upcomingAllPages(),exact:!1}),t.invalidateQueries({queryKey:n.reminders.inDateRangeAll(),exact:!1})]),d&&d()},[t,e]);return{onCreate:a,onUpdate:o,onDelete:i}}export{K as R,P as u};
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
<link rel="icon" type="image/png" href="/icon.png" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>Ocean Brain</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-B4l2UvuC.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/graph-vendor-CUxe67Lr.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/note-core-GgFcksAF.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/note-vendor-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/note-vendor-B0t5F04o.js">
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D2gRB1ig.css">
|
|
14
14
|
</head>
|
|
15
15
|
|
|
16
16
|
<body>
|
package/server/dist/app.js
CHANGED
|
@@ -2,7 +2,7 @@ import express from "express";
|
|
|
2
2
|
import { createMcpAdminService } from "./features/mcp-admin/service.js";
|
|
3
3
|
import { purgeExpiredNoteSnapshots } from "./features/note/services/snapshot.js";
|
|
4
4
|
import { purgeExpiredTrashedNotes } from "./features/note/services/trash.js";
|
|
5
|
-
import { createSessionMiddleware } from "./modules/auth-guard.js";
|
|
5
|
+
import { createCsrfProtection, createSessionMiddleware } from "./modules/auth-guard.js";
|
|
6
6
|
import { createErrorHandler } from "./modules/error-handler.js";
|
|
7
7
|
import logger from "./modules/logger.js";
|
|
8
8
|
import { createApiRouter, createAuthPagesRouter, createClientRouter, createGraphqlRouter } from "./routes/index.js";
|
|
@@ -18,7 +18,7 @@ const createAppWithMcpAuth = (authConfig, mcpAdminService) => {
|
|
|
18
18
|
process.stderr.write(`[recovery] Startup cleanup failed: ${message}
|
|
19
19
|
`);
|
|
20
20
|
});
|
|
21
|
-
app.use(logger).use(createSessionMiddleware(authConfig)).use(express.urlencoded({ extended: false })).use(express.json({ limit: "50mb" })).use("/api", createApiRouter(authConfig, mcpAdminService)).use(
|
|
21
|
+
app.use(logger).use(createSessionMiddleware(authConfig)).use(express.urlencoded({ extended: false })).use(express.json({ limit: "50mb" })).use(createCsrfProtection(authConfig)).use("/api", createApiRouter(authConfig, mcpAdminService)).use(createAuthPagesRouter(authConfig)).use("/graphql", createGraphqlRouter(authConfig, mcpAdminService)).use(createClientRouter(authConfig)).use(createErrorHandler());
|
|
22
22
|
return app;
|
|
23
23
|
};
|
|
24
24
|
var app_default = createApp;
|
package/server/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app.ts"],"sourcesContent":["import express from 'express';\nimport { createMcpAdminService, type McpAdminService } from './features/mcp-admin/service.js';\nimport { purgeExpiredNoteSnapshots } from './features/note/services/snapshot.js';\nimport { purgeExpiredTrashedNotes } from './features/note/services/trash.js';\nimport { createSessionMiddleware } from './modules/auth-guard.js';\nimport type { AuthConfig } from './modules/auth-mode.js';\nimport { createErrorHandler } from './modules/error-handler.js';\nimport logger from './modules/logger.js';\nimport { createApiRouter, createAuthPagesRouter, createClientRouter, createGraphqlRouter } from './routes/index.js';\n\nexport const createApp = (authConfig: AuthConfig) => {\n const mcpAdminService = createMcpAdminService();\n return createAppWithMcpAuth(authConfig, mcpAdminService);\n};\n\nexport const createAppWithMcpAuth = (authConfig: AuthConfig, mcpAdminService: McpAdminService) => {\n const app = express();\n app.locals.authConfig = authConfig;\n\n void Promise.all([purgeExpiredNoteSnapshots(), purgeExpiredTrashedNotes()]).catch((error) => {\n const message = error instanceof Error ? error.message : 'Unknown recovery cleanup error';\n process.stderr.write(`[recovery] Startup cleanup failed: ${message}\\n`);\n });\n\n app.use(logger)\n .use(createSessionMiddleware(authConfig))\n .use(express.urlencoded({ extended: false }))\n .use(express.json({ limit: '50mb' }))\n .use('/api', createApiRouter(authConfig, mcpAdminService))\n .use(
|
|
1
|
+
{"version":3,"sources":["../src/app.ts"],"sourcesContent":["import express from 'express';\nimport { createMcpAdminService, type McpAdminService } from './features/mcp-admin/service.js';\nimport { purgeExpiredNoteSnapshots } from './features/note/services/snapshot.js';\nimport { purgeExpiredTrashedNotes } from './features/note/services/trash.js';\nimport { createCsrfProtection, createSessionMiddleware } from './modules/auth-guard.js';\nimport type { AuthConfig } from './modules/auth-mode.js';\nimport { createErrorHandler } from './modules/error-handler.js';\nimport logger from './modules/logger.js';\nimport { createApiRouter, createAuthPagesRouter, createClientRouter, createGraphqlRouter } from './routes/index.js';\n\nexport const createApp = (authConfig: AuthConfig) => {\n const mcpAdminService = createMcpAdminService();\n return createAppWithMcpAuth(authConfig, mcpAdminService);\n};\n\nexport const createAppWithMcpAuth = (authConfig: AuthConfig, mcpAdminService: McpAdminService) => {\n const app = express();\n app.locals.authConfig = authConfig;\n\n void Promise.all([purgeExpiredNoteSnapshots(), purgeExpiredTrashedNotes()]).catch((error) => {\n const message = error instanceof Error ? error.message : 'Unknown recovery cleanup error';\n process.stderr.write(`[recovery] Startup cleanup failed: ${message}\\n`);\n });\n\n app.use(logger)\n .use(createSessionMiddleware(authConfig))\n .use(express.urlencoded({ extended: false }))\n .use(express.json({ limit: '50mb' }))\n .use(createCsrfProtection(authConfig))\n .use('/api', createApiRouter(authConfig, mcpAdminService))\n .use(createAuthPagesRouter(authConfig))\n .use('/graphql', createGraphqlRouter(authConfig, mcpAdminService))\n .use(createClientRouter(authConfig))\n .use(createErrorHandler());\n\n return app;\n};\n\nexport default createApp;\n"],"mappings":"AAAA,OAAO,aAAa;AACpB,SAAS,6BAAmD;AAC5D,SAAS,iCAAiC;AAC1C,SAAS,gCAAgC;AACzC,SAAS,sBAAsB,+BAA+B;AAE9D,SAAS,0BAA0B;AACnC,OAAO,YAAY;AACnB,SAAS,iBAAiB,uBAAuB,oBAAoB,2BAA2B;AAEzF,MAAM,YAAY,CAAC,eAA2B;AACjD,QAAM,kBAAkB,sBAAsB;AAC9C,SAAO,qBAAqB,YAAY,eAAe;AAC3D;AAEO,MAAM,uBAAuB,CAAC,YAAwB,oBAAqC;AAC9F,QAAM,MAAM,QAAQ;AACpB,MAAI,OAAO,aAAa;AAExB,OAAK,QAAQ,IAAI,CAAC,0BAA0B,GAAG,yBAAyB,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU;AACzF,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,YAAQ,OAAO,MAAM,sCAAsC,OAAO;AAAA,CAAI;AAAA,EAC1E,CAAC;AAED,MAAI,IAAI,MAAM,EACT,IAAI,wBAAwB,UAAU,CAAC,EACvC,IAAI,QAAQ,WAAW,EAAE,UAAU,MAAM,CAAC,CAAC,EAC3C,IAAI,QAAQ,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC,EACnC,IAAI,qBAAqB,UAAU,CAAC,EACpC,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,IAAI,sBAAsB,UAAU,CAAC,EACrC,IAAI,YAAY,oBAAoB,YAAY,eAAe,CAAC,EAChE,IAAI,mBAAmB,UAAU,CAAC,EAClC,IAAI,mBAAmB,CAAC;AAE7B,SAAO;AACX;AAEA,IAAO,cAAQ;","names":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildAuthSessionResponse } from "@baejino/auth";
|
|
1
2
|
import { createAppError } from "../../../modules/error-handler.js";
|
|
2
3
|
import {
|
|
3
4
|
assertPasswordLoginAvailable,
|
|
@@ -23,11 +24,7 @@ const createLogoutHandler = (authConfig) => {
|
|
|
23
24
|
if (authConfig.mode === "password") {
|
|
24
25
|
await destroySession(req);
|
|
25
26
|
}
|
|
26
|
-
res.status(200).json(
|
|
27
|
-
mode: authConfig.mode,
|
|
28
|
-
authRequired: authConfig.mode === "password",
|
|
29
|
-
authenticated: false
|
|
30
|
-
}).end();
|
|
27
|
+
res.status(200).json(buildAuthSessionResponse(authConfig, false)).end();
|
|
31
28
|
};
|
|
32
29
|
};
|
|
33
30
|
const createSessionStatusHandler = (authConfig) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/auth/http/api.ts"],"sourcesContent":["import type { AuthConfig } from '~/modules/auth-mode.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport {\n assertPasswordLoginAvailable,\n buildSessionResponse,\n compareSharedSecret,\n destroySession,\n regenerateSession,\n} from '../service.js';\n\nexport const createLoginHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const expectedPassword = assertPasswordLoginAvailable(authConfig);\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(expectedPassword, password)) {\n throw createAppError(401, 'UNAUTHORIZED', 'Invalid password');\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n\n res.status(200).json(buildSessionResponse(authConfig, req)).end();\n };\n};\n\nexport const createLogoutHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n }\n\n res.status(200)
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/auth/http/api.ts"],"sourcesContent":["import { buildAuthSessionResponse } from '@baejino/auth';\nimport type { AuthConfig } from '~/modules/auth-mode.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport {\n assertPasswordLoginAvailable,\n buildSessionResponse,\n compareSharedSecret,\n destroySession,\n regenerateSession,\n} from '../service.js';\n\nexport const createLoginHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const expectedPassword = assertPasswordLoginAvailable(authConfig);\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(expectedPassword, password)) {\n throw createAppError(401, 'UNAUTHORIZED', 'Invalid password');\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n\n res.status(200).json(buildSessionResponse(authConfig, req)).end();\n };\n};\n\nexport const createLogoutHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n }\n\n res.status(200).json(buildAuthSessionResponse(authConfig, false)).end();\n };\n};\n\nexport const createSessionStatusHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n res.status(200).json(buildSessionResponse(authConfig, req)).end();\n };\n};\n"],"mappings":"AAAA,SAAS,gCAAgC;AAEzC,SAAS,sBAAsB;AAE/B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEA,MAAM,qBAAqB,CAAC,eAAuC;AACtE,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,mBAAmB,6BAA6B,UAAU;AAChE,UAAM,WAAW,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,KAAK,WAAW;AAE9E,QAAI,CAAC,YAAY,CAAC,oBAAoB,kBAAkB,QAAQ,GAAG;AAC/D,YAAM,eAAe,KAAK,gBAAgB,kBAAkB;AAAA,IAChE;AAEA,UAAM,kBAAkB,GAAG;AAC3B,QAAI,QAAQ,gBAAgB;AAE5B,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,YAAY,GAAG,CAAC,EAAE,IAAI;AAAA,EACpE;AACJ;AAEO,MAAM,sBAAsB,CAAC,eAAuC;AACvE,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,YAAY;AAChC,YAAM,eAAe,GAAG;AAAA,IAC5B;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,yBAAyB,YAAY,KAAK,CAAC,EAAE,IAAI;AAAA,EAC1E;AACJ;AAEO,MAAM,6BAA6B,CAAC,eAAuC;AAC9E,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,YAAY,GAAG,CAAC,EAAE,IAAI;AAAA,EACpE;AACJ;","names":[]}
|
|
@@ -3,7 +3,7 @@ const LOGIN_ERROR_TOKEN = "<!-- OCEAN_BRAIN_LOGIN_ERROR -->";
|
|
|
3
3
|
const NEXT_PATH_TOKEN = "OCEAN_BRAIN_NEXT_PATH";
|
|
4
4
|
const readLoginPageTemplate = () => {
|
|
5
5
|
if (true) {
|
|
6
|
-
return '<!doctype html>\n<html lang="en">\n<head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>Ocean Brain Sign In</title>\n <style>\n :root {\n color-scheme: light dark;\n --surface: #f7f8fa;\n --elevated: #fbfcfd;\n --fg-default: #202631;\n --fg-secondary: #626d7c;\n --fg-tertiary: #8993a2;\n --fg-placeholder: #98a2b0;\n --fg-error: #a24545;\n --fg-on-filled: #f6f8fb;\n --border-subtle: #e5e9ee;\n --border-focus: #757f8d;\n --border-error: #d47777;\n --accent-soft-danger: rgba(214, 141, 135, 0.18);\n --accent-soft-primary: rgba(93, 102, 114, 0.12);\n --cta: #23272d;\n --cta-hover: #171a1f;\n --page-bg: #f2f5f8;\n font-family: "Pretendard Variable", "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;\n background: var(--page-bg);\n color: var(--fg-default);\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --surface: #16191e;\n --elevated: #1d2229;\n --fg-default: #e6ebf2;\n --fg-secondary: #a9b3c2;\n --fg-tertiary: #7f8998;\n --fg-placeholder: #717b88;\n --fg-error: #efaaaa;\n --fg-on-filled: #12161c;\n --border-subtle: #29303a;\n --border-focus: #b4c0cf;\n --border-error: #efaaaa;\n --accent-soft-danger: rgba(215, 146, 139, 0.14);\n --accent-soft-primary: rgba(184, 192, 202, 0.12);\n --cta: #edf1f5;\n --cta-hover: #f8fafc;\n --page-bg: #101318;\n }\n }\n * {\n box-sizing: border-box;\n }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: var(--page-bg);\n padding: 24px;\n }\n .shell {\n width: min(392px, 100%);\n }\n .brand {\n margin-bottom: 14px;\n color: var(--fg-tertiary);\n font-size: 12px;\n font-weight: 700;\n line-height: 1.4;\n }\n .panel {\n padding: 24px;\n border: 1px solid var(--border-subtle);\n border-radius: 16px;\n background: var(--surface);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.1),\n 0 1px 4px -2px rgba(23, 29, 38, 0.04);\n }\n h1 {\n margin: 0;\n color: var(--fg-default);\n font-size: 1.375rem;\n line-height: 1.3;\n font-weight: 700;\n }\n .lead {\n margin: 6px 0 22px;\n color: var(--fg-tertiary);\n font-size: 0.875rem;\n line-height: 1.5;\n font-weight: 500;\n }\n label {\n display: block;\n margin-bottom: 8px;\n color: var(--fg-secondary);\n font-size: 0.75rem;\n line-height: 1.4;\n font-weight: 600;\n }\n form {\n display: grid;\n gap: 16px;\n }\n .field {\n display: grid;\n }\n input {\n width: 100%;\n box-sizing: border-box;\n padding: 14px 16px;\n border: 1px solid var(--border-subtle);\n border-radius: 16px;\n background: var(--elevated);\n font-size: 16px;\n color: var(--fg-default);\n transition: border-color 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;\n }\n input::placeholder {\n color: var(--fg-placeholder);\n }\n input:focus {\n outline: none;\n border-color: var(--border-focus);\n box-shadow: 0 0 0 4px var(--accent-soft-primary);\n }\n .hint {\n margin-top: 10px;\n color: var(--fg-tertiary);\n font-size: 0.75rem;\n line-height: 1.4;\n }\n button {\n width: 100%;\n border: 1px solid transparent;\n border-radius: 14px;\n background: var(--cta);\n color: var(--fg-on-filled);\n padding: 14px 18px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;\n }\n button:hover {\n background: var(--cta-hover);\n }\n button:active {\n transform: translateY(1px);\n }\n button:focus-visible {\n outline: none;\n box-shadow: 0 0 0 4px var(--accent-soft-primary);\n }\n .error {\n margin-bottom: 18px;\n padding: 13px 14px;\n border: 1px solid var(--border-error);\n border-radius: 14px;\n background: var(--accent-soft-danger);\n color: var(--fg-error);\n font-size: 14px;\n font-weight: 600;\n }\n @media (prefers-color-scheme: dark) {\n .panel {\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.03),\n 0 1px 4px -2px rgba(0, 0, 0, 0.18);\n }\n }\n @media (max-width: 640px) {\n body {\n place-items: stretch;\n padding: 18px;\n }\n .shell {\n width: 100%;\n margin: auto 0;\n }\n .panel {\n padding: 22px;\n }\n }\n </style>\n</head>\n<body>\n <main class="shell">\n <div class="brand">Ocean Brain</div>\n <section class="panel" aria-labelledby="login-title">\n <h1 id="login-title">Sign in</h1>\n <p class="lead">Enter the workspace password to continue.</p>\n <!-- OCEAN_BRAIN_LOGIN_ERROR -->\n <form method="post" action="/
|
|
6
|
+
return '<!doctype html>\n<html lang="en">\n<head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>Ocean Brain Sign In</title>\n <style>\n :root {\n color-scheme: light dark;\n --surface: #f7f8fa;\n --elevated: #fbfcfd;\n --fg-default: #202631;\n --fg-secondary: #626d7c;\n --fg-tertiary: #8993a2;\n --fg-placeholder: #98a2b0;\n --fg-error: #a24545;\n --fg-on-filled: #f6f8fb;\n --border-subtle: #e5e9ee;\n --border-focus: #757f8d;\n --border-error: #d47777;\n --accent-soft-danger: rgba(214, 141, 135, 0.18);\n --accent-soft-primary: rgba(93, 102, 114, 0.12);\n --cta: #23272d;\n --cta-hover: #171a1f;\n --page-bg: #f2f5f8;\n font-family: "Pretendard Variable", "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;\n background: var(--page-bg);\n color: var(--fg-default);\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --surface: #16191e;\n --elevated: #1d2229;\n --fg-default: #e6ebf2;\n --fg-secondary: #a9b3c2;\n --fg-tertiary: #7f8998;\n --fg-placeholder: #717b88;\n --fg-error: #efaaaa;\n --fg-on-filled: #12161c;\n --border-subtle: #29303a;\n --border-focus: #b4c0cf;\n --border-error: #efaaaa;\n --accent-soft-danger: rgba(215, 146, 139, 0.14);\n --accent-soft-primary: rgba(184, 192, 202, 0.12);\n --cta: #edf1f5;\n --cta-hover: #f8fafc;\n --page-bg: #101318;\n }\n }\n * {\n box-sizing: border-box;\n }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: var(--page-bg);\n padding: 24px;\n }\n .shell {\n width: min(392px, 100%);\n }\n .brand {\n margin-bottom: 14px;\n color: var(--fg-tertiary);\n font-size: 12px;\n font-weight: 700;\n line-height: 1.4;\n }\n .panel {\n padding: 24px;\n border: 1px solid var(--border-subtle);\n border-radius: 16px;\n background: var(--surface);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.1),\n 0 1px 4px -2px rgba(23, 29, 38, 0.04);\n }\n h1 {\n margin: 0;\n color: var(--fg-default);\n font-size: 1.375rem;\n line-height: 1.3;\n font-weight: 700;\n }\n .lead {\n margin: 6px 0 22px;\n color: var(--fg-tertiary);\n font-size: 0.875rem;\n line-height: 1.5;\n font-weight: 500;\n }\n label {\n display: block;\n margin-bottom: 8px;\n color: var(--fg-secondary);\n font-size: 0.75rem;\n line-height: 1.4;\n font-weight: 600;\n }\n form {\n display: grid;\n gap: 16px;\n }\n .field {\n display: grid;\n }\n input {\n width: 100%;\n box-sizing: border-box;\n padding: 14px 16px;\n border: 1px solid var(--border-subtle);\n border-radius: 16px;\n background: var(--elevated);\n font-size: 16px;\n color: var(--fg-default);\n transition: border-color 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;\n }\n input::placeholder {\n color: var(--fg-placeholder);\n }\n input:focus {\n outline: none;\n border-color: var(--border-focus);\n box-shadow: 0 0 0 4px var(--accent-soft-primary);\n }\n .hint {\n margin-top: 10px;\n color: var(--fg-tertiary);\n font-size: 0.75rem;\n line-height: 1.4;\n }\n button {\n width: 100%;\n border: 1px solid transparent;\n border-radius: 14px;\n background: var(--cta);\n color: var(--fg-on-filled);\n padding: 14px 18px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;\n }\n button:hover {\n background: var(--cta-hover);\n }\n button:active {\n transform: translateY(1px);\n }\n button:focus-visible {\n outline: none;\n box-shadow: 0 0 0 4px var(--accent-soft-primary);\n }\n .error {\n margin-bottom: 18px;\n padding: 13px 14px;\n border: 1px solid var(--border-error);\n border-radius: 14px;\n background: var(--accent-soft-danger);\n color: var(--fg-error);\n font-size: 14px;\n font-weight: 600;\n }\n @media (prefers-color-scheme: dark) {\n .panel {\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.03),\n 0 1px 4px -2px rgba(0, 0, 0, 0.18);\n }\n }\n @media (max-width: 640px) {\n body {\n place-items: stretch;\n padding: 18px;\n }\n .shell {\n width: 100%;\n margin: auto 0;\n }\n .panel {\n padding: 22px;\n }\n }\n </style>\n</head>\n<body>\n <main class="shell">\n <div class="brand">Ocean Brain</div>\n <section class="panel" aria-labelledby="login-title">\n <h1 id="login-title">Sign in</h1>\n <p class="lead">Enter the workspace password to continue.</p>\n <!-- OCEAN_BRAIN_LOGIN_ERROR -->\n <form method="post" action="/login">\n <!-- OCEAN_BRAIN_CSRF_INPUT -->\n <input type="hidden" name="next" value="OCEAN_BRAIN_NEXT_PATH" />\n <div class="field">\n <label for="password">Password</label>\n <input id="password" name="password" type="password" autocomplete="current-password" required autofocus />\n </div>\n <button type="submit">Sign in</button>\n </form>\n <div class="hint">This session must be authenticated before the workspace loads.</div>\n </section>\n </main>\n</body>\n</html>\n';
|
|
7
7
|
}
|
|
8
8
|
return readFileSync(new URL("./login-page.html", import.meta.url), "utf8");
|
|
9
9
|
};
|
|
@@ -15,12 +15,19 @@ const renderLoginError = (errorMessage) => {
|
|
|
15
15
|
}
|
|
16
16
|
return `<div class="error" role="alert">${escapeHtml(errorMessage)}</div>`;
|
|
17
17
|
};
|
|
18
|
+
const renderCsrfInput = (csrfToken) => {
|
|
19
|
+
if (!csrfToken) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
return `<input type="hidden" name="_csrf" value="${escapeHtml(csrfToken)}" />`;
|
|
23
|
+
};
|
|
18
24
|
const renderLoginTemplate = (values) => {
|
|
19
|
-
return LOGIN_PAGE_TEMPLATE.replace(LOGIN_ERROR_TOKEN, values.errorBlock).replace(NEXT_PATH_TOKEN, values.nextPath);
|
|
25
|
+
return LOGIN_PAGE_TEMPLATE.replace(LOGIN_ERROR_TOKEN, values.errorBlock).replace(NEXT_PATH_TOKEN, values.nextPath).replace("<!-- OCEAN_BRAIN_CSRF_INPUT -->", values.csrfInput);
|
|
20
26
|
};
|
|
21
|
-
const renderLoginPage = ({ nextPath, errorMessage }) => renderLoginTemplate({
|
|
27
|
+
const renderLoginPage = ({ nextPath, errorMessage, csrfToken }) => renderLoginTemplate({
|
|
22
28
|
errorBlock: renderLoginError(errorMessage),
|
|
23
|
-
nextPath: escapeHtml(nextPath)
|
|
29
|
+
nextPath: escapeHtml(nextPath),
|
|
30
|
+
csrfInput: renderCsrfInput(csrfToken)
|
|
24
31
|
});
|
|
25
32
|
export {
|
|
26
33
|
renderLoginPage
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/auth/http/login-page.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\ndeclare const __LOGIN_PAGE_TEMPLATE__: string | undefined;\n\nconst LOGIN_ERROR_TOKEN = '<!-- OCEAN_BRAIN_LOGIN_ERROR -->';\nconst NEXT_PATH_TOKEN = 'OCEAN_BRAIN_NEXT_PATH';\n\nconst readLoginPageTemplate = () => {\n if (typeof __LOGIN_PAGE_TEMPLATE__ === 'string') {\n return __LOGIN_PAGE_TEMPLATE__;\n }\n\n return readFileSync(new URL('./login-page.html', import.meta.url), 'utf8');\n};\n\nconst LOGIN_PAGE_TEMPLATE = readLoginPageTemplate();\n\nconst escapeHtml = (value: string) =>\n value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n\ninterface LoginPageParams {\n nextPath: string;\n errorMessage?: string;\n}\n\nconst renderLoginError = (errorMessage?: string) => {\n if (!errorMessage) {\n return '';\n }\n\n return `<div class=\"error\" role=\"alert\">${escapeHtml(errorMessage)}</div>`;\n};\n\nconst renderLoginTemplate = (values: { errorBlock: string; nextPath: string }) => {\n return LOGIN_PAGE_TEMPLATE.replace(LOGIN_ERROR_TOKEN, values.errorBlock).replace(NEXT_PATH_TOKEN, values.nextPath);\n};\n\nexport const renderLoginPage = ({ nextPath, errorMessage }: LoginPageParams) =>\n renderLoginTemplate({\n errorBlock: renderLoginError(errorMessage),\n nextPath: escapeHtml(nextPath),\n });\n"],"mappings":"AAAA,SAAS,oBAAoB;AAI7B,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,MAAM,wBAAwB,MAAM;AAChC,MAAI,MAA6C;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,aAAa,IAAI,IAAI,qBAAqB,YAAY,GAAG,GAAG,MAAM;AAC7E;AAEA,MAAM,sBAAsB,sBAAsB;AAElD,MAAM,aAAa,CAAC,UAChB,MACK,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/auth/http/login-page.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\ndeclare const __LOGIN_PAGE_TEMPLATE__: string | undefined;\n\nconst LOGIN_ERROR_TOKEN = '<!-- OCEAN_BRAIN_LOGIN_ERROR -->';\nconst NEXT_PATH_TOKEN = 'OCEAN_BRAIN_NEXT_PATH';\n\nconst readLoginPageTemplate = () => {\n if (typeof __LOGIN_PAGE_TEMPLATE__ === 'string') {\n return __LOGIN_PAGE_TEMPLATE__;\n }\n\n return readFileSync(new URL('./login-page.html', import.meta.url), 'utf8');\n};\n\nconst LOGIN_PAGE_TEMPLATE = readLoginPageTemplate();\n\nconst escapeHtml = (value: string) =>\n value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n\ninterface LoginPageParams {\n nextPath: string;\n errorMessage?: string;\n csrfToken?: string;\n}\n\nconst renderLoginError = (errorMessage?: string) => {\n if (!errorMessage) {\n return '';\n }\n\n return `<div class=\"error\" role=\"alert\">${escapeHtml(errorMessage)}</div>`;\n};\n\nconst renderCsrfInput = (csrfToken?: string) => {\n if (!csrfToken) {\n return '';\n }\n\n return `<input type=\"hidden\" name=\"_csrf\" value=\"${escapeHtml(csrfToken)}\" />`;\n};\n\nconst renderLoginTemplate = (values: { errorBlock: string; nextPath: string; csrfInput: string }) => {\n return LOGIN_PAGE_TEMPLATE.replace(LOGIN_ERROR_TOKEN, values.errorBlock)\n .replace(NEXT_PATH_TOKEN, values.nextPath)\n .replace('<!-- OCEAN_BRAIN_CSRF_INPUT -->', values.csrfInput);\n};\n\nexport const renderLoginPage = ({ nextPath, errorMessage, csrfToken }: LoginPageParams) =>\n renderLoginTemplate({\n errorBlock: renderLoginError(errorMessage),\n nextPath: escapeHtml(nextPath),\n csrfInput: renderCsrfInput(csrfToken),\n });\n"],"mappings":"AAAA,SAAS,oBAAoB;AAI7B,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,MAAM,wBAAwB,MAAM;AAChC,MAAI,MAA6C;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,aAAa,IAAI,IAAI,qBAAqB,YAAY,GAAG,GAAG,MAAM;AAC7E;AAEA,MAAM,sBAAsB,sBAAsB;AAElD,MAAM,aAAa,CAAC,UAChB,MACK,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,OAAO;AAQhC,MAAM,mBAAmB,CAAC,iBAA0B;AAChD,MAAI,CAAC,cAAc;AACf,WAAO;AAAA,EACX;AAEA,SAAO,mCAAmC,WAAW,YAAY,CAAC;AACtE;AAEA,MAAM,kBAAkB,CAAC,cAAuB;AAC5C,MAAI,CAAC,WAAW;AACZ,WAAO;AAAA,EACX;AAEA,SAAO,4CAA4C,WAAW,SAAS,CAAC;AAC5E;AAEA,MAAM,sBAAsB,CAAC,WAAwE;AACjG,SAAO,oBAAoB,QAAQ,mBAAmB,OAAO,UAAU,EAClE,QAAQ,iBAAiB,OAAO,QAAQ,EACxC,QAAQ,mCAAmC,OAAO,SAAS;AACpE;AAEO,MAAM,kBAAkB,CAAC,EAAE,UAAU,cAAc,UAAU,MAChE,oBAAoB;AAAA,EAChB,YAAY,iBAAiB,YAAY;AAAA,EACzC,UAAU,WAAW,QAAQ;AAAA,EAC7B,WAAW,gBAAgB,SAAS;AACxC,CAAC;","names":[]}
|
|
@@ -7,7 +7,7 @@ const createLoginPageHandler = (authConfig) => {
|
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
const nextPath = sanitizeRedirectPath(req.query.next);
|
|
10
|
-
res.status(200).type("html").send(renderLoginPage({ nextPath })).end();
|
|
10
|
+
res.status(200).type("html").send(renderLoginPage({ nextPath, csrfToken: res.locals._csrf })).end();
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
13
|
const createLoginPageSubmitHandler = (authConfig) => {
|
|
@@ -22,7 +22,8 @@ const createLoginPageSubmitHandler = (authConfig) => {
|
|
|
22
22
|
res.status(401).type("html").send(
|
|
23
23
|
renderLoginPage({
|
|
24
24
|
nextPath,
|
|
25
|
-
errorMessage: "Invalid password"
|
|
25
|
+
errorMessage: "Invalid password",
|
|
26
|
+
csrfToken: res.locals._csrf
|
|
26
27
|
})
|
|
27
28
|
).end();
|
|
28
29
|
return;
|
|
@@ -36,7 +37,7 @@ const createLogoutPageHandler = (authConfig) => {
|
|
|
36
37
|
return async (req, res) => {
|
|
37
38
|
if (authConfig.mode === "password") {
|
|
38
39
|
await destroySession(req);
|
|
39
|
-
res.redirect(303, "/
|
|
40
|
+
res.redirect(303, "/login");
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
res.redirect(303, "/");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/auth/http/pages.ts"],"sourcesContent":["import type { AuthConfig } from '~/modules/auth-mode.js';\nimport type { Controller } from '~/types/index.js';\nimport { compareSharedSecret, destroySession, regenerateSession, sanitizeRedirectPath } from '../service.js';\nimport { renderLoginPage } from './login-page.js';\n\nexport const createLoginPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode !== 'password' || req.session?.authenticated) {\n res.redirect(303, sanitizeRedirectPath(req.query.next));\n return;\n }\n\n const nextPath = sanitizeRedirectPath(req.query.next);\n res.status(200).type('html').send(renderLoginPage({ nextPath })).end();\n };\n};\n\nexport const createLoginPageSubmitHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const nextPath = sanitizeRedirectPath(req.body?.next);\n\n if (authConfig.mode !== 'password' || !authConfig.password) {\n res.redirect(303, nextPath);\n return;\n }\n\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(authConfig.password, password)) {\n res.status(401)\n .type('html')\n .send(\n renderLoginPage({\n nextPath,\n errorMessage: 'Invalid password',\n }),\n )\n .end();\n return;\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n\n res.redirect(303, nextPath);\n };\n};\n\nexport const createLogoutPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n res.redirect(303, '/
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/auth/http/pages.ts"],"sourcesContent":["import type { AuthConfig } from '~/modules/auth-mode.js';\nimport type { Controller } from '~/types/index.js';\nimport { compareSharedSecret, destroySession, regenerateSession, sanitizeRedirectPath } from '../service.js';\nimport { renderLoginPage } from './login-page.js';\n\nexport const createLoginPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode !== 'password' || req.session?.authenticated) {\n res.redirect(303, sanitizeRedirectPath(req.query.next));\n return;\n }\n\n const nextPath = sanitizeRedirectPath(req.query.next);\n res.status(200)\n .type('html')\n .send(renderLoginPage({ nextPath, csrfToken: res.locals._csrf }))\n .end();\n };\n};\n\nexport const createLoginPageSubmitHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const nextPath = sanitizeRedirectPath(req.body?.next);\n\n if (authConfig.mode !== 'password' || !authConfig.password) {\n res.redirect(303, nextPath);\n return;\n }\n\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(authConfig.password, password)) {\n res.status(401)\n .type('html')\n .send(\n renderLoginPage({\n nextPath,\n errorMessage: 'Invalid password',\n csrfToken: res.locals._csrf,\n }),\n )\n .end();\n return;\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n\n res.redirect(303, nextPath);\n };\n};\n\nexport const createLogoutPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n res.redirect(303, '/login');\n return;\n }\n\n res.redirect(303, '/');\n };\n};\n"],"mappings":"AAEA,SAAS,qBAAqB,gBAAgB,mBAAmB,4BAA4B;AAC7F,SAAS,uBAAuB;AAEzB,MAAM,yBAAyB,CAAC,eAAuC;AAC1E,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,cAAc,IAAI,SAAS,eAAe;AAC9D,UAAI,SAAS,KAAK,qBAAqB,IAAI,MAAM,IAAI,CAAC;AACtD;AAAA,IACJ;AAEA,UAAM,WAAW,qBAAqB,IAAI,MAAM,IAAI;AACpD,QAAI,OAAO,GAAG,EACT,KAAK,MAAM,EACX,KAAK,gBAAgB,EAAE,UAAU,WAAW,IAAI,OAAO,MAAM,CAAC,CAAC,EAC/D,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,+BAA+B,CAAC,eAAuC;AAChF,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,WAAW,qBAAqB,IAAI,MAAM,IAAI;AAEpD,QAAI,WAAW,SAAS,cAAc,CAAC,WAAW,UAAU;AACxD,UAAI,SAAS,KAAK,QAAQ;AAC1B;AAAA,IACJ;AAEA,UAAM,WAAW,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,KAAK,WAAW;AAE9E,QAAI,CAAC,YAAY,CAAC,oBAAoB,WAAW,UAAU,QAAQ,GAAG;AAClE,UAAI,OAAO,GAAG,EACT,KAAK,MAAM,EACX;AAAA,QACG,gBAAgB;AAAA,UACZ;AAAA,UACA,cAAc;AAAA,UACd,WAAW,IAAI,OAAO;AAAA,QAC1B,CAAC;AAAA,MACL,EACC,IAAI;AACT;AAAA,IACJ;AAEA,UAAM,kBAAkB,GAAG;AAC3B,QAAI,QAAQ,gBAAgB;AAE5B,QAAI,SAAS,KAAK,QAAQ;AAAA,EAC9B;AACJ;AAEO,MAAM,0BAA0B,CAAC,eAAuC;AAC3E,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,YAAY;AAChC,YAAM,eAAe,GAAG;AACxB,UAAI,SAAS,KAAK,QAAQ;AAC1B;AAAA,IACJ;AAEA,QAAI,SAAS,KAAK,GAAG;AAAA,EACzB;AACJ;","names":[]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildAuthSessionResponse, sanitizeRedirectPath as sanitizeCommonRedirectPath } from "@baejino/auth";
|
|
2
|
+
import { compareSharedSecret as compareCommonSharedSecret } from "@baejino/auth/crypto";
|
|
1
3
|
import crypto from "crypto";
|
|
2
4
|
import { createAppError } from "../../modules/error-handler.js";
|
|
3
5
|
const createPasswordHash = async (password) => {
|
|
@@ -10,22 +12,11 @@ const comparePassword = async (password, storedHash) => {
|
|
|
10
12
|
const newHash = crypto.pbkdf2Sync(password, salt, 1e3, 64, "sha512").toString("hex");
|
|
11
13
|
return hash === newHash;
|
|
12
14
|
};
|
|
13
|
-
const compareSharedSecret =
|
|
14
|
-
|
|
15
|
-
const receivedBuffer = Buffer.from(received, "utf8");
|
|
16
|
-
if (expectedBuffer.length !== receivedBuffer.length) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
|
|
20
|
-
};
|
|
21
|
-
const buildSessionResponse = (authConfig, req) => ({
|
|
22
|
-
mode: authConfig.mode,
|
|
23
|
-
authRequired: authConfig.mode === "password",
|
|
24
|
-
authenticated: authConfig.mode === "password" ? Boolean(req.session?.authenticated) : false
|
|
25
|
-
});
|
|
15
|
+
const compareSharedSecret = compareCommonSharedSecret;
|
|
16
|
+
const buildSessionResponse = (authConfig, req) => buildAuthSessionResponse(authConfig, Boolean(req.session?.authenticated));
|
|
26
17
|
const assertPasswordLoginAvailable = (authConfig) => {
|
|
27
|
-
if (authConfig.mode !== "password"
|
|
28
|
-
throw createAppError(409, "
|
|
18
|
+
if (authConfig.mode !== "password") {
|
|
19
|
+
throw createAppError(409, "AUTH_OPEN_MODE", "Login is unavailable while auth mode is open.");
|
|
29
20
|
}
|
|
30
21
|
return authConfig.password;
|
|
31
22
|
};
|
|
@@ -54,33 +45,11 @@ const destroySession = async (req) => {
|
|
|
54
45
|
});
|
|
55
46
|
});
|
|
56
47
|
};
|
|
57
|
-
const sanitizeRedirectPath = (value) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (value.startsWith("//") || value.startsWith("/auth/login")) {
|
|
63
|
-
return "/";
|
|
64
|
-
}
|
|
65
|
-
return value;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const redirectUrl = new URL(value);
|
|
69
|
-
const hostname = redirectUrl.hostname.toLowerCase();
|
|
70
|
-
if (!["http:", "https:"].includes(redirectUrl.protocol)) {
|
|
71
|
-
return "/";
|
|
72
|
-
}
|
|
73
|
-
if (!["localhost", "127.0.0.1", "::1", "[::1]"].includes(hostname)) {
|
|
74
|
-
return "/";
|
|
75
|
-
}
|
|
76
|
-
if (redirectUrl.pathname.startsWith("/auth/login")) {
|
|
77
|
-
return "/";
|
|
78
|
-
}
|
|
79
|
-
return `${redirectUrl.origin}${redirectUrl.pathname}${redirectUrl.search}${redirectUrl.hash}`;
|
|
80
|
-
} catch {
|
|
81
|
-
return "/";
|
|
82
|
-
}
|
|
83
|
-
};
|
|
48
|
+
const sanitizeRedirectPath = (value) => sanitizeCommonRedirectPath(value, {
|
|
49
|
+
fallbackPath: "/",
|
|
50
|
+
loginPath: "/login",
|
|
51
|
+
allowedAbsoluteHosts: ["localhost", "127.0.0.1", "::1"]
|
|
52
|
+
});
|
|
84
53
|
export {
|
|
85
54
|
assertPasswordLoginAvailable,
|
|
86
55
|
buildSessionResponse,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/auth/service.ts"],"sourcesContent":["import crypto from 'crypto';\nimport type { Request } from 'express';\nimport type { AuthConfig } from '~/modules/auth-mode.js';\nimport { createAppError } from '~/modules/error-handler.js';\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 =
|
|
1
|
+
{"version":3,"sources":["../../../src/features/auth/service.ts"],"sourcesContent":["import { buildAuthSessionResponse, sanitizeRedirectPath as sanitizeCommonRedirectPath } from '@baejino/auth';\nimport { compareSharedSecret as compareCommonSharedSecret } from '@baejino/auth/crypto';\nimport crypto from 'crypto';\nimport type { Request } from 'express';\nimport type { AuthConfig } from '~/modules/auth-mode.js';\nimport { createAppError } from '~/modules/error-handler.js';\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 = compareCommonSharedSecret;\n\nexport const buildSessionResponse = (authConfig: AuthConfig, req: Request) =>\n buildAuthSessionResponse(authConfig, Boolean(req.session?.authenticated));\n\nexport const assertPasswordLoginAvailable = (authConfig: AuthConfig) => {\n if (authConfig.mode !== 'password') {\n throw createAppError(409, 'AUTH_OPEN_MODE', 'Login is unavailable while auth mode is open.');\n }\n\n return authConfig.password;\n};\n\nexport const regenerateSession = async (req: Request) => {\n await new Promise<void>((resolve, reject) => {\n req.session.regenerate((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve();\n });\n });\n};\n\nexport const destroySession = async (req: Request) => {\n if (!req.session) {\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n req.session.destroy((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve();\n });\n });\n};\n\nexport const sanitizeRedirectPath = (value: unknown) =>\n sanitizeCommonRedirectPath(value, {\n fallbackPath: '/',\n loginPath: '/login',\n allowedAbsoluteHosts: ['localhost', '127.0.0.1', '::1'],\n });\n"],"mappings":"AAAA,SAAS,0BAA0B,wBAAwB,kCAAkC;AAC7F,SAAS,uBAAuB,iCAAiC;AACjE,OAAO,YAAY;AAGnB,SAAS,sBAAsB;AAExB,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;AAE5B,MAAM,uBAAuB,CAAC,YAAwB,QACzD,yBAAyB,YAAY,QAAQ,IAAI,SAAS,aAAa,CAAC;AAErE,MAAM,+BAA+B,CAAC,eAA2B;AACpE,MAAI,WAAW,SAAS,YAAY;AAChC,UAAM,eAAe,KAAK,kBAAkB,+CAA+C;AAAA,EAC/F;AAEA,SAAO,WAAW;AACtB;AAEO,MAAM,oBAAoB,OAAO,QAAiB;AACrD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AACzC,QAAI,QAAQ,WAAW,CAAC,UAAU;AAC9B,UAAI,OAAO;AACP,eAAO,KAAK;AACZ;AAAA,MACJ;AAEA,cAAQ;AAAA,IACZ,CAAC;AAAA,EACL,CAAC;AACL;AAEO,MAAM,iBAAiB,OAAO,QAAiB;AAClD,MAAI,CAAC,IAAI,SAAS;AACd;AAAA,EACJ;AAEA,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AACzC,QAAI,QAAQ,QAAQ,CAAC,UAAU;AAC3B,UAAI,OAAO;AACP,eAAO,KAAK;AACZ;AAAA,MACJ;AAEA,cAAQ;AAAA,IACZ,CAAC;AAAA,EACL,CAAC;AACL;AAEO,MAAM,uBAAuB,CAAC,UACjC,2BAA2B,OAAO;AAAA,EAC9B,cAAc;AAAA,EACd,WAAW;AAAA,EACX,sBAAsB,CAAC,aAAa,aAAa,KAAK;AAC1D,CAAC;","names":[]}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
buildNoteTagNamesWhere,
|
|
10
10
|
normalizeNoteTagNames
|
|
11
11
|
} from "../../../features/note/services/tag-filter.js";
|
|
12
|
-
import { listTrashedNotes } from "../../../features/note/services/trash.js";
|
|
12
|
+
import { getTrashedNoteById, listTrashedNotes } from "../../../features/note/services/trash.js";
|
|
13
13
|
import models from "../../../models.js";
|
|
14
14
|
import { runDataMaintenanceInBackground } from "../../../modules/data-maintenance.js";
|
|
15
15
|
import { extractBlocksByType } from "./note.graphql.shared.js";
|
|
@@ -314,6 +314,9 @@ const noteQueryResolvers = {
|
|
|
314
314
|
}) => {
|
|
315
315
|
return listNoteSnapshots(Number(id), Number(limit));
|
|
316
316
|
},
|
|
317
|
+
trashedNote: async (_, { id }) => {
|
|
318
|
+
return getTrashedNoteById(Number(id));
|
|
319
|
+
},
|
|
317
320
|
trashedNotes: async (_, {
|
|
318
321
|
pagination = {
|
|
319
322
|
limit: 25,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/graphql/note.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { getNoteCleanupPreview, listNoteCleanupCandidates } from '~/features/note/services/cleanup.js';\nimport {\n filterNotesBySearchQuery,\n NOTE_SEARCH_TEXT_SCHEMA_VERSION,\n parseNoteSearchQuery,\n} from '~/features/note/services/search.js';\nimport { listNoteSnapshots } from '~/features/note/services/snapshot.js';\nimport {\n buildNoteTagNamesWhere,\n type NoteTagMatchMode,\n normalizeNoteTagNames,\n} from '~/features/note/services/tag-filter.js';\nimport { listTrashedNotes } from '~/features/note/services/trash.js';\nimport type { Note, Prisma } from '~/models.js';\nimport models from '~/models.js';\nimport { runDataMaintenanceInBackground } from '~/modules/data-maintenance.js';\nimport type { Pagination, SearchFilter } from '~/types/index.js';\nimport { extractBlocksByType } from './note.graphql.shared.js';\n\ninterface AllNotesResolverDeps {\n countNotes: (args: { where?: Prisma.NoteWhereInput }) => Promise<number>;\n findNotes: (args: {\n orderBy: Prisma.NoteOrderByWithRelationInput[];\n where?: Prisma.NoteWhereInput;\n take?: number;\n skip?: number;\n }) => Promise<Note[]>;\n triggerSearchBackfill: () => void;\n}\n\nconst buildAllNotesOrderBy = (searchFilter: SearchFilter) => {\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const pinnedFirst = searchFilter.pinnedFirst || false;\n const orderBy: Prisma.NoteOrderByWithRelationInput[] = [];\n\n if (pinnedFirst) {\n orderBy.push({ pinned: 'desc' });\n }\n\n if (sortBy === 'createdAt') {\n orderBy.push({ createdAt: sortOrder as 'asc' | 'desc' });\n } else {\n orderBy.push({ updatedAt: sortOrder as 'asc' | 'desc' });\n }\n\n return orderBy;\n};\n\nconst buildAllNotesSearchWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n if (!parsedQuery.hasFilters) {\n return undefined;\n }\n\n return {\n AND: [\n { searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION },\n ...parsedQuery.included.map((keyword) => ({ searchableText: { contains: keyword } })),\n ...parsedQuery.excluded.map((keyword) => ({ NOT: { searchableText: { contains: keyword } } })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst buildAllNotesStaleCandidateWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n return {\n AND: [\n { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },\n ...parsedQuery.included.map((keyword) => ({\n OR: [{ title: { contains: keyword } }, { content: { contains: keyword } }],\n })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst compareNotesForSearch = (left: Note, right: Note, searchFilter: SearchFilter) => {\n if (searchFilter.pinnedFirst && left.pinned !== right.pinned) {\n return left.pinned ? -1 : 1;\n }\n\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const leftValue = sortBy === 'createdAt' ? left.createdAt.getTime() : left.updatedAt.getTime();\n const rightValue = sortBy === 'createdAt' ? right.createdAt.getTime() : right.updatedAt.getTime();\n\n if (leftValue === rightValue) {\n return 0;\n }\n\n return sortOrder === 'asc' ? leftValue - rightValue : rightValue - leftValue;\n};\n\nconst mergeSortedNotesForSearch = (left: Note[], right: Note[], searchFilter: SearchFilter) => {\n const merged: Note[] = [];\n let leftIndex = 0;\n let rightIndex = 0;\n\n while (leftIndex < left.length && rightIndex < right.length) {\n if (compareNotesForSearch(left[leftIndex], right[rightIndex], searchFilter) <= 0) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n } else {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n }\n\n while (leftIndex < left.length) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n }\n\n while (rightIndex < right.length) {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n\n return merged;\n};\n\nexport const createAllNotesQueryResolver = (\n deps: AllNotesResolverDeps = {\n countNotes: ({ where }) => models.note.count({ where }),\n findNotes: ({ orderBy, where, take, skip }) =>\n models.note.findMany({\n orderBy,\n where,\n take,\n skip,\n }),\n triggerSearchBackfill: () => {\n void runDataMaintenanceInBackground();\n },\n },\n) => {\n return async (\n _: unknown,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const orderBy = buildAllNotesOrderBy(searchFilter);\n const limit = Number(pagination.limit);\n const offset = Number(pagination.offset);\n const where = buildAllNotesSearchWhere(searchFilter);\n\n if (!where) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({}),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const staleCandidateNotes = await deps.findNotes({\n orderBy,\n where: buildAllNotesStaleCandidateWhere(searchFilter),\n });\n\n if (staleCandidateNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n deps.triggerSearchBackfill();\n\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n const staleNotes = filterNotesBySearchQuery(staleCandidateNotes, parsedQuery);\n\n if (staleNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const [freshNotesPrefix, freshTotalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: offset + limit,\n }),\n deps.countNotes({ where }),\n ]);\n\n const mergedNotes = mergeSortedNotesForSearch(freshNotesPrefix, staleNotes, searchFilter);\n\n return {\n totalCount: freshTotalCount + staleNotes.length,\n notes: mergedNotes.slice(offset, offset + limit),\n };\n };\n};\n\ntype NoteQueryResolvers = NonNullable<IResolvers['Query']>;\n\nexport const noteQueryResolvers: NoteQueryResolvers = {\n allNotes: createAllNotesQueryResolver(),\n notesInDateRange: async (\n _,\n {\n dateRange,\n }: {\n dateRange: {\n start: string;\n end: string;\n };\n },\n ) => {\n const where: Prisma.NoteWhereInput = {\n OR: [\n {\n updatedAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n {\n createdAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n ],\n };\n\n return models.note.findMany({\n orderBy: { createdAt: 'asc' },\n where,\n });\n },\n tagNotes: async (\n _,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const where: Prisma.NoteWhereInput = { tags: { some: { id: Number(searchFilter.query) } } };\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n notesByTagNames: async (\n _,\n {\n tagNames,\n mode,\n pagination,\n }: {\n tagNames: string[];\n mode: NoteTagMatchMode;\n pagination: Pagination;\n },\n ) => {\n const normalizedTagNames = normalizeNoteTagNames(tagNames);\n\n if (normalizedTagNames.length === 0) {\n return {\n totalCount: 0,\n notes: [],\n };\n }\n\n const where = buildNoteTagNamesWhere(normalizedTagNames, mode);\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n pinnedNotes: async () =>\n models.note.findMany({\n orderBy: [{ order: 'asc' }, { updatedAt: 'desc' }],\n where: { pinned: true },\n }),\n imageNotes: async (_, { src }: { src: string }) =>\n models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where: { content: { contains: src } },\n }),\n backReferences: async (_, { id }: { id: string }) => {\n return models.note.findMany({\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n where: { content: { contains: `reference\",\"props\":{\"id\":\"${id}\"` } },\n });\n },\n note: async (_, { id }: { id: string }) => {\n const note = await models.note.findUnique({ where: { id: Number(id) } });\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n if (!note.content) {\n return note;\n }\n\n const blocks = extractBlocksByType<{\n id: string;\n title: string;\n }>('reference', JSON.parse(note.content));\n\n if (blocks.length === 0) {\n return note;\n }\n\n const referenceIds = blocks.map((block) => Number(block.props.id));\n const references = await models.note.findMany({ where: { id: { in: referenceIds } } });\n const newContent = references.reduce<string>((content, referenceNote: Note) => {\n const reference = blocks.find((block) => Number(block.props.id) === referenceNote.id);\n\n if (reference && reference.props.title !== referenceNote.title) {\n return content.replace(\n `reference\",\"props\":{\"id\":\"${reference.props.id}\",\"title\":\"${reference.props.title}\"`,\n `reference\",\"props\":{\"id\":\"${referenceNote.id}\",\"title\":\"${referenceNote.title}\"`,\n );\n }\n\n return content;\n }, note.content);\n\n if (newContent === note.content) {\n return note;\n }\n\n try {\n JSON.parse(newContent);\n\n return await models.note.update({\n where: { id: note.id },\n data: { content: newContent },\n });\n } catch {\n // Keep the stored content unchanged if the synchronized payload becomes invalid.\n return note;\n }\n },\n noteCleanupCandidates: async (\n _,\n {\n query,\n pagination = {\n limit: 20,\n offset: 0,\n },\n }: {\n query?: string;\n pagination: Pagination;\n },\n ) => {\n const result = await listNoteCleanupCandidates({\n keywords: query\n ? query\n .split(/[,\\s]+/)\n .map((keyword) => keyword.trim())\n .filter(Boolean)\n : undefined,\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n\n return result.notes;\n },\n noteCleanupPreview: async (_, { id }: { id: string }) => {\n return getNoteCleanupPreview(Number(id));\n },\n noteSnapshots: async (\n _,\n {\n id,\n limit = 5,\n }: {\n id: string;\n limit?: number;\n },\n ) => {\n return listNoteSnapshots(Number(id), Number(limit));\n },\n trashedNotes: async (\n _,\n {\n pagination = {\n limit: 25,\n offset: 0,\n },\n }: {\n pagination: Pagination;\n },\n ) => {\n return listTrashedNotes({\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n },\n noteGraph: async () => {\n const notes = await models.note.findMany({\n select: {\n id: true,\n title: true,\n content: true,\n },\n });\n\n const nodes: Array<{ id: string; title: string; connections: number }> = [];\n const links: Array<{ source: string; target: string }> = [];\n const connectionCount: Record<string, number> = {};\n const linkSet = new Set<string>();\n\n for (const note of notes) {\n if (!note.content) {\n continue;\n }\n\n try {\n const blocks = extractBlocksByType<{ id: string }>('reference', JSON.parse(note.content));\n\n for (const block of blocks) {\n const targetId = block.props.id;\n\n if (targetId && String(note.id) !== targetId) {\n const linkKey = `${note.id}-${targetId}`;\n const reverseLinkKey = `${targetId}-${note.id}`;\n\n if (!linkSet.has(linkKey) && !linkSet.has(reverseLinkKey)) {\n linkSet.add(linkKey);\n links.push({\n source: String(note.id),\n target: targetId,\n });\n connectionCount[String(note.id)] = (connectionCount[String(note.id)] || 0) + 1;\n connectionCount[targetId] = (connectionCount[targetId] || 0) + 1;\n }\n }\n }\n } catch {\n // Skip notes with invalid JSON content\n }\n }\n\n for (const note of notes) {\n nodes.push({\n id: String(note.id),\n title: note.title || 'Untitled',\n connections: connectionCount[String(note.id)] || 0,\n });\n }\n\n return {\n nodes,\n links,\n };\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/graphql/note.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { getNoteCleanupPreview, listNoteCleanupCandidates } from '~/features/note/services/cleanup.js';\nimport {\n filterNotesBySearchQuery,\n NOTE_SEARCH_TEXT_SCHEMA_VERSION,\n parseNoteSearchQuery,\n} from '~/features/note/services/search.js';\nimport { listNoteSnapshots } from '~/features/note/services/snapshot.js';\nimport {\n buildNoteTagNamesWhere,\n type NoteTagMatchMode,\n normalizeNoteTagNames,\n} from '~/features/note/services/tag-filter.js';\nimport { getTrashedNoteById, listTrashedNotes } from '~/features/note/services/trash.js';\nimport type { Note, Prisma } from '~/models.js';\nimport models from '~/models.js';\nimport { runDataMaintenanceInBackground } from '~/modules/data-maintenance.js';\nimport type { Pagination, SearchFilter } from '~/types/index.js';\nimport { extractBlocksByType } from './note.graphql.shared.js';\n\ninterface AllNotesResolverDeps {\n countNotes: (args: { where?: Prisma.NoteWhereInput }) => Promise<number>;\n findNotes: (args: {\n orderBy: Prisma.NoteOrderByWithRelationInput[];\n where?: Prisma.NoteWhereInput;\n take?: number;\n skip?: number;\n }) => Promise<Note[]>;\n triggerSearchBackfill: () => void;\n}\n\nconst buildAllNotesOrderBy = (searchFilter: SearchFilter) => {\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const pinnedFirst = searchFilter.pinnedFirst || false;\n const orderBy: Prisma.NoteOrderByWithRelationInput[] = [];\n\n if (pinnedFirst) {\n orderBy.push({ pinned: 'desc' });\n }\n\n if (sortBy === 'createdAt') {\n orderBy.push({ createdAt: sortOrder as 'asc' | 'desc' });\n } else {\n orderBy.push({ updatedAt: sortOrder as 'asc' | 'desc' });\n }\n\n return orderBy;\n};\n\nconst buildAllNotesSearchWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n if (!parsedQuery.hasFilters) {\n return undefined;\n }\n\n return {\n AND: [\n { searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION },\n ...parsedQuery.included.map((keyword) => ({ searchableText: { contains: keyword } })),\n ...parsedQuery.excluded.map((keyword) => ({ NOT: { searchableText: { contains: keyword } } })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst buildAllNotesStaleCandidateWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n return {\n AND: [\n { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },\n ...parsedQuery.included.map((keyword) => ({\n OR: [{ title: { contains: keyword } }, { content: { contains: keyword } }],\n })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst compareNotesForSearch = (left: Note, right: Note, searchFilter: SearchFilter) => {\n if (searchFilter.pinnedFirst && left.pinned !== right.pinned) {\n return left.pinned ? -1 : 1;\n }\n\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const leftValue = sortBy === 'createdAt' ? left.createdAt.getTime() : left.updatedAt.getTime();\n const rightValue = sortBy === 'createdAt' ? right.createdAt.getTime() : right.updatedAt.getTime();\n\n if (leftValue === rightValue) {\n return 0;\n }\n\n return sortOrder === 'asc' ? leftValue - rightValue : rightValue - leftValue;\n};\n\nconst mergeSortedNotesForSearch = (left: Note[], right: Note[], searchFilter: SearchFilter) => {\n const merged: Note[] = [];\n let leftIndex = 0;\n let rightIndex = 0;\n\n while (leftIndex < left.length && rightIndex < right.length) {\n if (compareNotesForSearch(left[leftIndex], right[rightIndex], searchFilter) <= 0) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n } else {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n }\n\n while (leftIndex < left.length) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n }\n\n while (rightIndex < right.length) {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n\n return merged;\n};\n\nexport const createAllNotesQueryResolver = (\n deps: AllNotesResolverDeps = {\n countNotes: ({ where }) => models.note.count({ where }),\n findNotes: ({ orderBy, where, take, skip }) =>\n models.note.findMany({\n orderBy,\n where,\n take,\n skip,\n }),\n triggerSearchBackfill: () => {\n void runDataMaintenanceInBackground();\n },\n },\n) => {\n return async (\n _: unknown,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const orderBy = buildAllNotesOrderBy(searchFilter);\n const limit = Number(pagination.limit);\n const offset = Number(pagination.offset);\n const where = buildAllNotesSearchWhere(searchFilter);\n\n if (!where) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({}),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const staleCandidateNotes = await deps.findNotes({\n orderBy,\n where: buildAllNotesStaleCandidateWhere(searchFilter),\n });\n\n if (staleCandidateNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n deps.triggerSearchBackfill();\n\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n const staleNotes = filterNotesBySearchQuery(staleCandidateNotes, parsedQuery);\n\n if (staleNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const [freshNotesPrefix, freshTotalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: offset + limit,\n }),\n deps.countNotes({ where }),\n ]);\n\n const mergedNotes = mergeSortedNotesForSearch(freshNotesPrefix, staleNotes, searchFilter);\n\n return {\n totalCount: freshTotalCount + staleNotes.length,\n notes: mergedNotes.slice(offset, offset + limit),\n };\n };\n};\n\ntype NoteQueryResolvers = NonNullable<IResolvers['Query']>;\n\nexport const noteQueryResolvers: NoteQueryResolvers = {\n allNotes: createAllNotesQueryResolver(),\n notesInDateRange: async (\n _,\n {\n dateRange,\n }: {\n dateRange: {\n start: string;\n end: string;\n };\n },\n ) => {\n const where: Prisma.NoteWhereInput = {\n OR: [\n {\n updatedAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n {\n createdAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n ],\n };\n\n return models.note.findMany({\n orderBy: { createdAt: 'asc' },\n where,\n });\n },\n tagNotes: async (\n _,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const where: Prisma.NoteWhereInput = { tags: { some: { id: Number(searchFilter.query) } } };\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n notesByTagNames: async (\n _,\n {\n tagNames,\n mode,\n pagination,\n }: {\n tagNames: string[];\n mode: NoteTagMatchMode;\n pagination: Pagination;\n },\n ) => {\n const normalizedTagNames = normalizeNoteTagNames(tagNames);\n\n if (normalizedTagNames.length === 0) {\n return {\n totalCount: 0,\n notes: [],\n };\n }\n\n const where = buildNoteTagNamesWhere(normalizedTagNames, mode);\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n pinnedNotes: async () =>\n models.note.findMany({\n orderBy: [{ order: 'asc' }, { updatedAt: 'desc' }],\n where: { pinned: true },\n }),\n imageNotes: async (_, { src }: { src: string }) =>\n models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where: { content: { contains: src } },\n }),\n backReferences: async (_, { id }: { id: string }) => {\n return models.note.findMany({\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n where: { content: { contains: `reference\",\"props\":{\"id\":\"${id}\"` } },\n });\n },\n note: async (_, { id }: { id: string }) => {\n const note = await models.note.findUnique({ where: { id: Number(id) } });\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n if (!note.content) {\n return note;\n }\n\n const blocks = extractBlocksByType<{\n id: string;\n title: string;\n }>('reference', JSON.parse(note.content));\n\n if (blocks.length === 0) {\n return note;\n }\n\n const referenceIds = blocks.map((block) => Number(block.props.id));\n const references = await models.note.findMany({ where: { id: { in: referenceIds } } });\n const newContent = references.reduce<string>((content, referenceNote: Note) => {\n const reference = blocks.find((block) => Number(block.props.id) === referenceNote.id);\n\n if (reference && reference.props.title !== referenceNote.title) {\n return content.replace(\n `reference\",\"props\":{\"id\":\"${reference.props.id}\",\"title\":\"${reference.props.title}\"`,\n `reference\",\"props\":{\"id\":\"${referenceNote.id}\",\"title\":\"${referenceNote.title}\"`,\n );\n }\n\n return content;\n }, note.content);\n\n if (newContent === note.content) {\n return note;\n }\n\n try {\n JSON.parse(newContent);\n\n return await models.note.update({\n where: { id: note.id },\n data: { content: newContent },\n });\n } catch {\n // Keep the stored content unchanged if the synchronized payload becomes invalid.\n return note;\n }\n },\n noteCleanupCandidates: async (\n _,\n {\n query,\n pagination = {\n limit: 20,\n offset: 0,\n },\n }: {\n query?: string;\n pagination: Pagination;\n },\n ) => {\n const result = await listNoteCleanupCandidates({\n keywords: query\n ? query\n .split(/[,\\s]+/)\n .map((keyword) => keyword.trim())\n .filter(Boolean)\n : undefined,\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n\n return result.notes;\n },\n noteCleanupPreview: async (_, { id }: { id: string }) => {\n return getNoteCleanupPreview(Number(id));\n },\n noteSnapshots: async (\n _,\n {\n id,\n limit = 5,\n }: {\n id: string;\n limit?: number;\n },\n ) => {\n return listNoteSnapshots(Number(id), Number(limit));\n },\n trashedNote: async (_, { id }: { id: string }) => {\n return getTrashedNoteById(Number(id));\n },\n trashedNotes: async (\n _,\n {\n pagination = {\n limit: 25,\n offset: 0,\n },\n }: {\n pagination: Pagination;\n },\n ) => {\n return listTrashedNotes({\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n },\n noteGraph: async () => {\n const notes = await models.note.findMany({\n select: {\n id: true,\n title: true,\n content: true,\n },\n });\n\n const nodes: Array<{ id: string; title: string; connections: number }> = [];\n const links: Array<{ source: string; target: string }> = [];\n const connectionCount: Record<string, number> = {};\n const linkSet = new Set<string>();\n\n for (const note of notes) {\n if (!note.content) {\n continue;\n }\n\n try {\n const blocks = extractBlocksByType<{ id: string }>('reference', JSON.parse(note.content));\n\n for (const block of blocks) {\n const targetId = block.props.id;\n\n if (targetId && String(note.id) !== targetId) {\n const linkKey = `${note.id}-${targetId}`;\n const reverseLinkKey = `${targetId}-${note.id}`;\n\n if (!linkSet.has(linkKey) && !linkSet.has(reverseLinkKey)) {\n linkSet.add(linkKey);\n links.push({\n source: String(note.id),\n target: targetId,\n });\n connectionCount[String(note.id)] = (connectionCount[String(note.id)] || 0) + 1;\n connectionCount[targetId] = (connectionCount[targetId] || 0) + 1;\n }\n }\n }\n } catch {\n // Skip notes with invalid JSON content\n }\n }\n\n for (const note of notes) {\n nodes.push({\n id: String(note.id),\n title: note.title || 'Untitled',\n connections: connectionCount[String(note.id)] || 0,\n });\n }\n\n return {\n nodes,\n links,\n };\n },\n};\n"],"mappings":"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","names":[]}
|
|
@@ -114,6 +114,8 @@ const noteType = gql`
|
|
|
114
114
|
createdAt: String!
|
|
115
115
|
updatedAt: String!
|
|
116
116
|
deletedAt: String!
|
|
117
|
+
contentPreview: String!
|
|
118
|
+
contentAsMarkdown: String
|
|
117
119
|
pinned: Boolean!
|
|
118
120
|
order: Int!
|
|
119
121
|
layout: NoteLayout!
|
|
@@ -154,6 +156,7 @@ const noteQuery = gql`
|
|
|
154
156
|
noteCleanupCandidates(query: String, pagination: PaginationInput): [NoteCleanupCandidate!]!
|
|
155
157
|
noteCleanupPreview(id: ID!): NoteCleanupPreview
|
|
156
158
|
noteSnapshots(id: ID!, limit: Int): [NoteSnapshot!]!
|
|
159
|
+
trashedNote(id: ID!): DeletedNote
|
|
157
160
|
trashedNotes(pagination: PaginationInput): DeletedNotes!
|
|
158
161
|
noteGraph: NoteGraph!
|
|
159
162
|
}
|