diff --git a/client/package-lock.json b/client/package-lock.json index f0ed26eb..31f81b10 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,14 +9,20 @@ "version": "0.1.0", "dependencies": { "@libsql/client": "^0.11.0", + "@react-sigma/core": "^4.0.3", "@tanstack/react-query": "^5.56.2", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-force": "^0.2.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.2", "react-scripts": "^5.0.1", + "react-sigma": "^1.2.35", + "sigma": "^3.0.0-beta.29", "web-vitals": "^2.1.4" } }, @@ -3461,6 +3467,16 @@ } } }, + "node_modules/@react-sigma/core": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-sigma/core/-/core-4.0.3.tgz", + "integrity": "sha512-/y/U1GH18xjGYMWNYXXqHGJ+8tHf+e4z1i0gNSm9iuhch8sGFJooO3VfyPJlBox42Wl4/gCapQhOHAfVW0pRtw==", + "peerDependencies": { + "graphology": "^0.25.4", + "react": "^18.0.0", + "sigma": "^3.0.0-beta.24" + } + }, "node_modules/@remix-run/router": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", @@ -9246,6 +9262,55 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-layout": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/graphology-layout/-/graphology-layout-0.6.1.tgz", + "integrity": "sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==", + "dependencies": { + "graphology-utils": "^2.3.0", + "pandemonium": "^2.4.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-layout-force": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/graphology-layout-force/-/graphology-layout-force-0.2.4.tgz", + "integrity": "sha512-NYZz0YAnDkn5pkm30cvB0IScFoWGtbzJMrqaiH070dYlYJiag12Oc89dbVfaMaVR/w8DMIKxn/ix9Bqj+Umm9Q==", + "dependencies": { + "graphology-utils": "^2.4.2" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.7.tgz", + "integrity": "sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==", + "peer": true + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -12913,6 +12978,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13242,6 +13315,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -13370,6 +13448,14 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, + "node_modules/pandemonium": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz", + "integrity": "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==", + "dependencies": { + "mnemonist": "^0.39.2" + } + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -15378,6 +15464,18 @@ } } }, + "node_modules/react-sigma": { + "version": "1.2.35", + "resolved": "https://registry.npmjs.org/react-sigma/-/react-sigma-1.2.35.tgz", + "integrity": "sha512-amjKjaQusefDp8dD9er3wE/lfubSQT5fJSRF4SsS0LjxRue4bpSapAQ1WpmSM6nQEgawbrKel+bQBmatl7MAIw==", + "engines": { + "node": ">=8.0" + }, + "peerDependencies": { + "react": ">=15.3", + "react-dom": ">=15.3" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16222,6 +16320,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sigma": { + "version": "3.0.0-beta.29", + "resolved": "https://registry.npmjs.org/sigma/-/sigma-3.0.0-beta.29.tgz", + "integrity": "sha512-3VpLIEnuDiZbO7XPnsyyUwUoTOI9u5aDq9nIPvc/eO3FHE/z7P7gCvfmAu2JB1635gZSLwt39mvDpYTXZ8+jaQ==", + "dependencies": { + "events": "^3.3.0", + "graphology-utils": "^2.5.2" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/client/package.json b/client/package.json index 74ecfa39..4758315c 100644 --- a/client/package.json +++ b/client/package.json @@ -4,14 +4,20 @@ "private": true, "dependencies": { "@libsql/client": "^0.11.0", + "@react-sigma/core": "^4.0.3", "@tanstack/react-query": "^5.56.2", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-force": "^0.2.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.2", "react-scripts": "^5.0.1", + "react-sigma": "^1.2.35", + "sigma": "^3.0.0-beta.29", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/client/src/App.css b/client/src/App.css index 4223ef20..2962eebb 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1,50 +1,18 @@ -.App { - text-align: center; - padding-left: 60vmin; - padding-right: 60vmin; - background-color: #282c34; +body { + background: rgb(0,131,77); + background: radial-gradient(circle, rgba(0,131,77,1) 0%, rgba(17,66,0,1) 100%); + font-family: sans-serif; } -.App-logo { - height: 10vmin; - pointer-events: none; +button { + border-radius: 1rem; + padding: 1rem; + margin: 1rem; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.Page-contents { - text-align: left; - padding: 1em; - border: 1px solid black; - width: 100%; -} -.Page-contents pre { - width: 100%; +a { + color: lightslategrey; } +a:visited { + color: slategray; +} \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js index d928df36..80424b25 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,8 +1,10 @@ -import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import Landing from './Landing.jsx' -import PageView from './PageView.jsx' -import EditPage from './EditPage.jsx' +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import Landing from './landing/Landing.jsx' +import PageView from './page/PageView.jsx' +import PageEdit from './page/PageEdit.jsx' + +import './App.css' const queryClient = new QueryClient(); @@ -13,7 +15,7 @@ function App() { }/> }/> - }/> + }/> diff --git a/client/src/Landing.jsx b/client/src/Landing.jsx deleted file mode 100644 index cc3111ff..00000000 --- a/client/src/Landing.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -import { useNavigate, Link } from 'react-router-dom' -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; - -import { apiUrl, fetchPageList, postNewPage } from './apiTools.jsx'; -import { useFixLinks } from './clientStuff.jsx'; - -function Landing() { - const queryClient = useQueryClient(); - const navigate = useNavigate(); - const noLoad = useFixLinks(); - const { isPending, error, data } = useQuery({ // fetch the currrent values - queryKey: ['pages'], - queryFn: fetchPageList - }) - - const makeNewPage = useMutation({ // for changing the value when we're done with it - mutationFn: () => postNewPage() - .catch((error) => { console.log("got an error", error.json()) }), - onSettled: async (data, error, variables) => { - // Invalidate and navigate to the new page - await queryClient.invalidateQueries({ queryKey: ['pages'] }); - navigate(`/${data}/edit`); - }, - }); - - return ( -
-
- logo -

Welcome to the forest!

-

This is some random stuff I added!

- { isPending ? - "Loading..." : - (error ? -
{error}
- : - - ) - } - -
-
- ); -} - -export default Landing; \ No newline at end of file diff --git a/client/src/apiTools.jsx b/client/src/apiTools.jsx index bf4293c6..980b409e 100644 --- a/client/src/apiTools.jsx +++ b/client/src/apiTools.jsx @@ -1,3 +1,5 @@ +import Graph from 'graphology'; + // This is wrapper functtions to do requests to the api, from the frontend. export const apiUrl = "http://127.0.0.1:3001" @@ -42,4 +44,16 @@ export async function deletePage(id) { headers: {'Content-Type': 'application/json'}, ...defaults }) - } \ No newline at end of file +} + +export async function fetchGraph() { + return fetch(`${apiUrl}/graph`, { + method: 'GET', + ...defaults + }).then((res) => res.json()) + .then((serialized) => { + const graph = new Graph(); + graph.import(serialized); + return graph; + }) +} \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/client/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/client/src/index.js b/client/src/index.js index d563c0fb..5d4fdcfa 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; diff --git a/client/src/landing/GraphRender.jsx b/client/src/landing/GraphRender.jsx new file mode 100644 index 00000000..106edee6 --- /dev/null +++ b/client/src/landing/GraphRender.jsx @@ -0,0 +1,29 @@ +import { SigmaContainer } from "@react-sigma/core"; +import "@react-sigma/core/lib/react-sigma.min.css"; + +import { useQuery } from '@tanstack/react-query'; +import { fetchGraph } from '../apiTools.jsx'; + +import ForceSupervisor from 'graphology-layout-force/worker'; + + +export default function GraphRender() { + const { isPending, error, data } = useQuery({ // fetch the currrent values + queryKey: ['graph'], + queryFn: fetchGraph + }); + + let outputStuff; + if (isPending) outputStuff = "Loading..."; + else if (error) outputStuff = `Error encountered: ${error}`; + else outputStuff = ( + + ); + + if (data) (new ForceSupervisor(data, {allowInvalidContainer: true})).start(); + return outputStuff; +} \ No newline at end of file diff --git a/client/src/landing/Landing.css b/client/src/landing/Landing.css new file mode 100644 index 00000000..0b0be1c0 --- /dev/null +++ b/client/src/landing/Landing.css @@ -0,0 +1,30 @@ + +.landing-page { + padding-left:10rem; + padding-right: 10rem; + padding-top: 3rem; + color: white; +} +.landing-page header { + text-align: right; + width: 100%; +} +.landing-page header hr { + width: 60%; + margin-inline-start: auto; + margin-inline-end: 0; + color: white; +} +.landing-container { + display: grid; + width: 100%; + grid-template-columns: repeat(3, 1fr); +} +.landing-section { + margin: 1rem; + padding: 1rem; + border-radius: 1rem; + color: white; + background: rgba(10,66,30, 0.75); +} + diff --git a/client/src/landing/Landing.jsx b/client/src/landing/Landing.jsx new file mode 100644 index 00000000..9b768f11 --- /dev/null +++ b/client/src/landing/Landing.jsx @@ -0,0 +1,49 @@ +import { useNavigate, Link } from 'react-router-dom' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; + +import { fetchPageList, postNewPage } from '../apiTools.jsx'; +import { useFixLinks } from '../clientStuff.jsx'; + +import PageList from './PageList.jsx'; +import GraphRender from './GraphRender.jsx'; + +import './Landing.css' + +function Landing() { + const queryClient = useQueryClient(); + const navigate = useNavigate(); + + const makeNewPage = useMutation({ // for changing the value when we're done with it + mutationFn: () => postNewPage() + .catch((error) => { console.log("got an error", error.json()) }), + onSettled: async (data, error, variables) => { + // Invalidate and navigate to the new page + await queryClient.invalidateQueries({ queryKey: ['pages'] }); + navigate(`/${data}/edit`); + }, + }); + + return ( +
+
+

Welcome to the forest.

+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ ); +} + +export default Landing; \ No newline at end of file diff --git a/client/src/landing/PageList.jsx b/client/src/landing/PageList.jsx new file mode 100644 index 00000000..52320485 --- /dev/null +++ b/client/src/landing/PageList.jsx @@ -0,0 +1,26 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchPageList } from '../apiTools.jsx'; +import { useFixLinks } from '../clientStuff.jsx'; + +export default function PageList({pages, ...props}) { + const noLoad = useFixLinks(); + const { isPending, error, data } = useQuery({ // fetch the currrent values + queryKey: ['pages'], + queryFn: fetchPageList + }); + + let outputContents = ""; + if (isPending) outputContents = "Loading..."; + else if (error) outputContents = `Error encountered: ${error}`; + else outputContents = ( +
    + { data.map(({id, title}) => +
  1. + {title} +
  2. + )} +
+ ); + + return outputContents; +} diff --git a/client/src/EditPage.jsx b/client/src/page/PageEdit.jsx similarity index 60% rename from client/src/EditPage.jsx rename to client/src/page/PageEdit.jsx index 024ac25a..e7f23b58 100644 --- a/client/src/EditPage.jsx +++ b/client/src/page/PageEdit.jsx @@ -1,10 +1,8 @@ -import logo from './logo.svg'; -import { App } from './App.js'; -import './App.css'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router-dom'; -import { useState } from 'react'; -import { apiUrl, fetchPage, postPage, deletePage } from './apiTools.jsx'; +import { apiUrl, fetchPage, postPage, deletePage } from '../apiTools.jsx'; + +import './Pages.css'; function PageView() { const queryClient = useQueryClient(); @@ -57,40 +55,40 @@ function PageView() { } return ( -
-
- logo -
-

- {pagenumber}. - +
+

+ {pagenumber}.  + -

-

- { ready ? - <> -
-
-            
- - - - : -
- { fetchQuery.isPending ? "Loading..." : JSON.stringify(fetchQuery.error) } + +
+ +
+ { ready ? + <> +
+
           
- } + + + + : +
+ { fetchQuery.isPending ? "Loading..." : JSON.stringify(fetchQuery.error) } +
+ }
); diff --git a/client/src/PageView.jsx b/client/src/page/PageView.jsx similarity index 50% rename from client/src/PageView.jsx rename to client/src/page/PageView.jsx index 6392963e..2fefc43e 100644 --- a/client/src/PageView.jsx +++ b/client/src/page/PageView.jsx @@ -1,12 +1,9 @@ -import logo from './logo.svg'; -import { App } from './App.js'; -import './App.css'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router-dom'; -import { useState } from 'react'; -import { apiUrl, fetchPage, postPage } from './apiTools.jsx'; -import { useFixLinks } from './clientStuff.jsx'; +import { apiUrl, fetchPage, postPage } from '../apiTools.jsx'; +import { useFixLinks } from '../clientStuff.jsx'; +import './Pages.css'; function PageView() { const queryClient = useQueryClient(); @@ -25,26 +22,25 @@ function PageView() { if (data) [the_id, page_title, page_text, page_html] = data; return ( -
-
- logo -
-

{pagenumber}. {ready ? (page_title || " ") : "..."}

-
+
+
+
+

{pagenumber}. {ready ? (page_title || " ") : "..."}

+
+
+
{ ready ?
: -
- { isPending ? "Loading..." : JSON.stringify(error) } -
+ (isPending ? "Loading..." : JSON.stringify(error)) } - - -
+ +
+
); } diff --git a/client/src/page/Pages.css b/client/src/page/Pages.css new file mode 100644 index 00000000..779683a4 --- /dev/null +++ b/client/src/page/Pages.css @@ -0,0 +1,31 @@ + +.page-container { + padding-left:10rem; + padding-right: 10rem; + padding-top: 3rem; + color: white; +} +.page-container header { + text-align: right; + width: 100%; +} +.page-container header hr { + width: 60%; + margin-inline-start: auto; + margin-inline-end: 0; + color: white; +} + +.page-contents { + text-align: left; + background: rgba(17,66,0); + margin: 1rem; + padding: 1rem; + border-radius: 1rem; + +} +.page-contents pre { + width: 100%; + white-space: normal; +} + diff --git a/db/the_big_db.db/dbs/default/data-wal b/db/the_big_db.db/dbs/default/data-wal index e1d393b9..3868be43 100644 Binary files a/db/the_big_db.db/dbs/default/data-wal and b/db/the_big_db.db/dbs/default/data-wal differ diff --git a/db/the_big_db.db/dbs/default/stats.json b/db/the_big_db.db/dbs/default/stats.json index 2b8aadc9..eb38cc0b 100644 --- a/db/the_big_db.db/dbs/default/stats.json +++ b/db/the_big_db.db/dbs/default/stats.json @@ -1 +1 @@ -{"id":"7de13931-444c-4dd2-a19a-5cd1e70b7f36","rows_written":112,"rows_read":2745,"storage_bytes_used":12288,"write_requests_delegated":0,"current_frame_no":111,"top_query_threshold":3,"top_queries":[{"rows_written":2,"rows_read":1,"query":"\ncreate table if not exists renders (\n id integer primary key,\n title varchar(255),\n description text\n)\n"},{"rows_written":0,"rows_read":4,"query":"select id from pages"},{"rows_written":0,"rows_read":5,"query":"select id from pages"},{"rows_written":1,"rows_read":4,"query":"\nalter table pages \nadd column html text\n"},{"rows_written":0,"rows_read":6,"query":"\nselect p.id from pages p where not exists (select r.id from renders r where p.id = r.id)\n"},{"rows_written":0,"rows_read":10,"query":"select id from pages"},{"rows_written":0,"rows_read":11,"query":"select id from pages"},{"rows_written":0,"rows_read":12,"query":"select id from pages"},{"rows_written":0,"rows_read":13,"query":"select id from pages"},{"rows_written":0,"rows_read":14,"query":"select id from pages"}],"slowest_query_threshold":0,"slowest_queries":[{"elapsed_ms":0,"query":"select title from pages","rows_written":0,"rows_read":1},{"elapsed_ms":0,"query":"select title from pages","rows_written":0,"rows_read":2},{"elapsed_ms":1,"query":"\nalter table pages \nadd column html text\n","rows_written":1,"rows_read":4},{"elapsed_ms":1,"query":"\ncreate table if not exists renders (\n id integer primary key,\n title varchar(255),\n description text\n)\n","rows_written":2,"rows_read":1},{"elapsed_ms":1,"query":"insert into pages (title, description) values (?, ?) returning id","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"replace into pages (id, title, description) values (?, ?, ?)","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"replace into pages (id, title, description, html) values (?, ?, ?, ?)","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"select * from pages where id=@val","rows_written":0,"rows_read":1},{"elapsed_ms":1,"query":"select count(*) as num_pages from pages","rows_written":0,"rows_read":1},{"elapsed_ms":7,"query":"replace into pages (id, title, description) values (?, ?, ?)","rows_written":1,"rows_read":1}],"embedded_replica_frames_replicated":0,"query_count":1006,"query_latency":98084} \ No newline at end of file +{"id":"7de13931-444c-4dd2-a19a-5cd1e70b7f36","rows_written":149,"rows_read":10979,"storage_bytes_used":20480,"write_requests_delegated":0,"current_frame_no":151,"top_query_threshold":15,"top_queries":[{"rows_written":0,"rows_read":15,"query":"select id, html from pages"},{"rows_written":0,"rows_read":15,"query":"select id, title from pages"},{"rows_written":0,"rows_read":16,"query":"select id, html from pages"},{"rows_written":0,"rows_read":16,"query":"select id, title from pages"},{"rows_written":0,"rows_read":17,"query":"select id, html from pages"},{"rows_written":0,"rows_read":17,"query":"select id, title from pages"},{"rows_written":0,"rows_read":18,"query":"select id, html from pages"},{"rows_written":0,"rows_read":18,"query":"select id, title from pages"},{"rows_written":0,"rows_read":19,"query":"select id, html from pages"},{"rows_written":0,"rows_read":19,"query":"select id, title from pages"}],"slowest_query_threshold":0,"slowest_queries":[{"elapsed_ms":0,"query":"select title from pages","rows_written":0,"rows_read":1},{"elapsed_ms":0,"query":"select title from pages","rows_written":0,"rows_read":2},{"elapsed_ms":1,"query":"\nalter table pages \nadd column html text\n","rows_written":1,"rows_read":4},{"elapsed_ms":1,"query":"\ncreate table if not exists renders (\n id integer primary key,\n title varchar(255),\n description text\n)\n","rows_written":2,"rows_read":1},{"elapsed_ms":1,"query":"insert into pages (title, description) values (?, ?) returning id","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"replace into pages (id, title, description) values (?, ?, ?)","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"replace into pages (id, title, description, html) values (?, ?, ?, ?)","rows_written":1,"rows_read":1},{"elapsed_ms":1,"query":"select * from pages where id=@val","rows_written":0,"rows_read":1},{"elapsed_ms":1,"query":"select count(*) as num_pages from pages","rows_written":0,"rows_read":1},{"elapsed_ms":7,"query":"replace into pages (id, title, description) values (?, ?, ?)","rows_written":1,"rows_read":1}],"embedded_replica_frames_replicated":0,"query_count":1820,"query_latency":153654} \ No newline at end of file diff --git a/db/the_big_db.db/dbs/default/wallog b/db/the_big_db.db/dbs/default/wallog index 7fa83dab..0b67998b 100644 Binary files a/db/the_big_db.db/dbs/default/wallog and b/db/the_big_db.db/dbs/default/wallog differ diff --git a/server/graphStuff.js b/server/graphStuff.js new file mode 100644 index 00000000..2925fa28 --- /dev/null +++ b/server/graphStuff.js @@ -0,0 +1,26 @@ +const { JSDOM } = require("jsdom"); +const graphology = require("graphology"); +const { circular } = require('graphology-layout'); + +function graphFromList(allTheStuff) { + const graph = new graphology.Graph(); + + for (const [id, html] of allTheStuff) { + graph.addNode(id); + } + + for (const [id, html] of allTheStuff) { + const { document } = (new JSDOM(html)).window; + const links = document.querySelectorAll('a'); + links.forEach((link) => { + const referent = link.href.replace("/",""); + graph.mergeEdge(id, referent); + }); + } + + circular.assign(graph); + + return graph; +} + +module.exports = { graphFromList: graphFromList }; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 817619bd..be8c3c60 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,6 +10,11 @@ "license": "ISC", "dependencies": { "express": "^4.21.0", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-force": "^0.2.4", + "graphology-layout-forceatlas2": "^0.10.1", + "jsdom": "^25.0.0", "nodemon": "^3.1.5", "showdown": "^2.1.0" } @@ -26,6 +31,38 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -43,6 +80,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -151,6 +193,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -196,6 +249,29 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -204,6 +280,11 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -220,6 +301,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -250,6 +339,17 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -282,6 +382,14 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", @@ -351,6 +459,19 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -428,6 +549,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-layout": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/graphology-layout/-/graphology-layout-0.6.1.tgz", + "integrity": "sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==", + "dependencies": { + "graphology-utils": "^2.3.0", + "pandemonium": "^2.4.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-layout-force": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/graphology-layout-force/-/graphology-layout-force-0.2.4.tgz", + "integrity": "sha512-NYZz0YAnDkn5pkm30cvB0IScFoWGtbzJMrqaiH070dYlYJiag12Oc89dbVfaMaVR/w8DMIKxn/ix9Bqj+Umm9Q==", + "dependencies": { + "graphology-utils": "^2.4.2" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-layout-forceatlas2": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/graphology-layout-forceatlas2/-/graphology-layout-forceatlas2-0.10.1.tgz", + "integrity": "sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==", + "dependencies": { + "graphology-utils": "^2.1.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.7.tgz", + "integrity": "sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==", + "peer": true + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -480,6 +661,17 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -495,6 +687,72 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -562,6 +820,50 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/jsdom": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz", + "integrity": "sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -627,6 +929,14 @@ "node": "*" } }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -696,6 +1006,11 @@ "node": ">=0.10.0" } }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==" + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -707,6 +1022,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -718,6 +1038,25 @@ "node": ">= 0.8" } }, + "node_modules/pandemonium": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz", + "integrity": "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==", + "dependencies": { + "mnemonist": "^0.39.2" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -754,11 +1093,24 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -773,6 +1125,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -806,6 +1163,16 @@ "node": ">=8.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -830,6 +1197,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -974,6 +1352,11 @@ "node": ">=4" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1001,6 +1384,31 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1018,6 +1426,14 @@ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1026,6 +1442,15 @@ "node": ">= 0.8" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1041,6 +1466,100 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" } } } diff --git a/server/package.json b/server/package.json index 71a0abd8..4053197c 100644 --- a/server/package.json +++ b/server/package.json @@ -15,6 +15,11 @@ "license": "ISC", "dependencies": { "express": "^4.21.0", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-force": "^0.2.4", + "graphology-layout-forceatlas2": "^0.10.1", + "jsdom": "^25.0.0", "nodemon": "^3.1.5", "showdown": "^2.1.0" } diff --git a/server/server.js b/server/server.js index 23502708..414128c1 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,7 @@ const express = require('express'); const showdown = require('showdown'); const { query } = require('./dbHelper.js'); +const { graphFromList } = require('./graphStuff.js'); const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000 @@ -20,26 +21,26 @@ app.listen(port, () => { }); app.get('/pages', (req, res) => { - query('select id from pages', []) + query('select id, title from pages', []) .then((r) => r.rows) - .then((row) => row.map(([id]) => id)) - .then((ids) => res.status(200).json(ids)) + .then((row) => row.map(([id, title]) => {return {"id": id, "title": title};})) + .then((pages) => res.status(200).json(pages)) .catch((error) => res.status(500).json({"error": error})); }); app.post('/page/new', (req, res) => { query('insert into pages (title, description) values (?, ?) returning id', ["new page", "this page is new!"]) - .then((r) => r.rows[0][0]) - .then((row) => res.status(200).json({id: row})) + .then((r) => r.rows[0]) + .then((row) => res.status(200).json({id: row[0]})) .catch((error) => res.status(500).json({"error": error})) }); app.get('/page/:id', (req, res) => { query('select * from pages where id=?', [req.params.id]) .then((r) => { - if (r.rows.length == 0) return res.status(404).json({"error": "page not found in db"}); - return r.rows[0]; + if (r.rows.length == 0) res.status(404).json({"error": "page not found in db"}); + else return r.rows[0]; }).then((row) => res.status(200).json(row)) .catch((error) => res.status(500).json({"error": error})); }); @@ -57,3 +58,11 @@ app.delete('/page/:id', (req, res) => { .then(() => res.status(200).json({id: req.params.id})) .catch((error) => res.status(500).json({"error": error})) }) + +app.get('/graph', (req, res) => { + query('select id, html from pages', []) + .then((r) => r.rows) + .then((stuff) => graphFromList(stuff)) + .then((graph) => res.json(graph)) + .catch((error) => res.status(500).json({"error": error})); +}); \ No newline at end of file