From 5d9c2ba770fca34e7806915771a3f6a1a4d07656 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Thu, 14 Sep 2023 20:00:50 +0200 Subject: [PATCH 01/15] Fixed strange scroll in edit page --- CHANGELOG.md | 4 ++++ app/components/books/BooksViewer.module.scss | 6 ----- .../books/edit/AccessEditor.module.scss | 22 ++++++++++++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a893842..4e53624e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.4.2 + +- (Fix) Strange scroll behavior in edit page + # 1.4.1 - (Fix) Fixed some alignment problems with the frontpage on mobile devices diff --git a/app/components/books/BooksViewer.module.scss b/app/components/books/BooksViewer.module.scss index 87fce390..7366efe4 100644 --- a/app/components/books/BooksViewer.module.scss +++ b/app/components/books/BooksViewer.module.scss @@ -41,12 +41,6 @@ $padding: 1rem; } } -.wrapper { - max-height: 100%; - - overflow: scroll; -} - /** Sidebar tabs * First we define the mixins used to style active and inactive tabs * also hover states diff --git a/app/components/books/edit/AccessEditor.module.scss b/app/components/books/edit/AccessEditor.module.scss index cebdcd24..9ccbaaeb 100644 --- a/app/components/books/edit/AccessEditor.module.scss +++ b/app/components/books/edit/AccessEditor.module.scss @@ -137,7 +137,7 @@ .col_description { text-align: center; vertical-align: middle; - width: 50%; + width: 30%; word-break: break-all; } .col_remove { @@ -145,3 +145,23 @@ vertical-align: middle; width: 3rem; } + +.col_link { + word-break: break-all; +} +.link { + text-decoration: none; + word-break: break-all; + word-wrap: anywhere; + color: $primary; + background-color: red; + + // Show copy mouse on hover + &:hover { + cursor: URL("/img/clipboard.svg"), auto; + + &[data-copied="true"] { + cursor: URL("/img/clipboard-check.svg"), auto; + } + } +} -- GitLab From 812a11876023cc189c47363748fa6894b84e57db Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Thu, 14 Sep 2023 20:05:32 +0200 Subject: [PATCH 02/15] Added support for read only mode via tokens --- CHANGELOG.md | 4 + app/components/books/edit/AccessTokens.tsx | 139 +- .../books/edit/BookInfo.module.scss | 8 +- app/components/books/edit/BookInfo.tsx | 8 +- app/components/books/edit/ShareLinks.tsx | 162 ++ app/components/books/edit/index.tsx | 9 +- app/components/frontpage/features.tsx | 2 +- app/components/utils/Resizer.tsx | 1 - app/components/viewer/page.tsx | 2 +- app/components/viewer/sidebar/index.tsx | 44 +- .../viewer/sidebar/unplacedSnipPreviews.tsx | 8 +- app/components/viewer/tools/doodle/drawing.ts | 6 +- app/components/viewer/tools/movePage.tool.tsx | 12 +- app/components/viewer/tools/text.tool.tsx | 13 +- app/components/viewer/worker/pageWorker.tsx | 8 +- .../viewer/worker/pageWorker/messaging.ts | 1 + .../viewer/worker/pageWorker/worker.ts | 2 + app/controller/book.ts | 6 +- app/controller/booksocket.ts | 30 +- app/controller/websocket.ts | 2 +- app/database/services/sql/config.service.ts | 4 + app/database/sql.typings.ts | 34 +- app/lib/access_control/books.ts | 121 +- app/lib/fetcher.ts | 15 +- app/lib/useUser.ts | 23 + app/middleware.ts | 12 +- app/pages/_app.tsx | 2 +- app/pages/api/_service.ts | 1 + app/pages/api/admin/rerenderPreviews.ts | 2 +- app/pages/api/admin/set_acl.ts | 1 + app/pages/api/admin/set_users_groups.ts | 1 + app/pages/api/authentication/isAdmin.ts | 5 +- app/pages/api/authentication/socket_token.ts | 45 - app/pages/api/authentication/verify.ts | 2 +- .../api/books/[id]/edit/get_acl_entity.ts | 2 +- app/pages/api/books/[id]/edit/set_acl.ts | 11 +- app/pages/api/books/[id]/edit/tokens.ts | 102 +- app/pages/api/books/[id]/edit/update.ts | 13 +- app/pages/api/books/[id]/socket_token.ts | 81 + app/pages/api/books/[id]/upload.ts | 13 +- app/pages/api/socket.ts | 45 +- app/pages/books/[id]/index.tsx | 22 +- app/pages/books/index.tsx | 2 - app/public/img/clipboard-check.svg | 5 + app/public/img/clipboard.svg | 4 + app/server.ts | 14 +- app/snips/general/image.ts | 1 - app/socket/auth.ts | 60 +- app/socket/socket.ts | 39 +- database/migration/0016_token_types.sql | 8 + package-lock.json | 1338 +++++++++-------- package.json | 84 +- 52 files changed, 1628 insertions(+), 951 deletions(-) create mode 100644 app/components/books/edit/ShareLinks.tsx delete mode 100644 app/pages/api/authentication/socket_token.ts create mode 100644 app/pages/api/books/[id]/socket_token.ts create mode 100644 app/public/img/clipboard-check.svg create mode 100644 app/public/img/clipboard.svg create mode 100644 database/migration/0016_token_types.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e53624e..d85f5dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # 1.4.2 - (Fix) Strange scroll behavior in edit page +- (Features) Read only mode + - tools are dynamically picked by access level + - see new field in /book/:id/edit/access +- (Fix) Fixed a bug where the snips to place where not updated when a user adds a new snip # 1.4.1 diff --git a/app/components/books/edit/AccessTokens.tsx b/app/components/books/edit/AccessTokens.tsx index e20d943e..82e9535b 100644 --- a/app/components/books/edit/AccessTokens.tsx +++ b/app/components/books/edit/AccessTokens.tsx @@ -22,59 +22,29 @@ export default function AccessTokens() { fetcher ); - const rows = data?.map((token) => ( - <tr key={token.id}> - <td>{token.description}</td> - <td>{new Date(token.created_at).toDateString()}</td> - <td>{token.email}</td> - <td className={styles.remove}> - <button - className="btn btn-outline-secondary" - onClick={() => { - fetch(`/api/books/${book.id}/edit/tokens`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token_id: token.id, - }), - }).then((r) => { - if (r.ok) { - mutate(); - } - }); - }} - > - {trash_svg} - </button> - </td> - </tr> - )); - - const submitAdd = (e) => { - e.preventDefault(); - fetch(`/api/books/${book.id}/edit/tokens`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - description, - }), - }) - .then((r) => { - if (r.ok) { - return r.json(); - } else { - throw new Error("Failed to create token!"); - } - }) - .then((r) => { - setAddedToken(r); - setShowModal(true); - }); - }; + const rows = data + ?.filter((token) => token.type == "api") + .map((token) => ( + <tr key={token.id}> + <td>{token.description}</td> + <td>{new Date(token.created_at).toDateString()}</td> + <td>{token.email}</td> + <td className={styles.remove}> + <button + className="btn btn-outline-secondary" + onClick={() => { + deleteToken(book.id, token.id).then((r) => { + if (r.ok) { + mutate(); + } + }); + }} + > + {trash_svg} + </button> + </td> + </tr> + )); return ( <div className={styles.container}> @@ -85,14 +55,22 @@ export default function AccessTokens() { every time. This feature is particularly useful for automated insertion of snippets, as it allows you to upload code to your book programmatically. Each token is associated with a specific - book, and can only be created with the{" "} - <span style={{ fontStyle: "italic" }}>pACL</span> permission tag - set. By using tokens, you can automate workflows and streamline - your development process. For more information on how to use - tokens effectively, see the TODO section in our documentation. + book, and allows for full read and write access to that book. By + using tokens, you can automate workflows and streamline your + development process. For more information on how to use tokens + effectively, see the TODO section in our documentation. </p> - <form onSubmit={submitAdd}> + <form + onSubmit={(e) => { + e.preventDefault(); + createNewToken(book.id, description).then((r) => { + setAddedToken(r); + setShowModal(true); + setDescription(""); + }); + }} + > <InputGroup className="p-3"> <Form.Control type="text" @@ -142,7 +120,7 @@ export default function AccessTokens() { ); } -const trash_svg = ( +export const trash_svg = ( <svg xmlns="http://www.w3.org/2000/svg" className="icon" @@ -207,3 +185,44 @@ function TokenModal({ show, onHide, addedToken }) { </Modal> ); } + +export const createNewToken = async ( + book_id, + description, + type = "api", + expireIn = undefined +) => { + const payload = { + description, + type, + }; + if (expireIn) { + payload["expireIn"] = expireIn; + } + + return fetch(`/api/books/${book_id}/edit/tokens`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }).then((r) => { + if (r.ok) { + return r.json(); + } else { + throw new Error("Failed to create token!"); + } + }); +}; + +export const deleteToken = async (book_id, token_id) => { + return fetch(`/api/books/${book_id}/edit/tokens`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token_id: token_id, + }), + }); +}; diff --git a/app/components/books/edit/BookInfo.module.scss b/app/components/books/edit/BookInfo.module.scss index 97d5a502..b851eb44 100644 --- a/app/components/books/edit/BookInfo.module.scss +++ b/app/components/books/edit/BookInfo.module.scss @@ -67,7 +67,7 @@ $inset_form: 1rem; display: grid; padding: 1rem; width: 100%; - align-items: baseline; + align-items: flex-start; grid-template-columns: 3fr 2fr; grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; grid-gap: 1.5rem; @@ -136,6 +136,10 @@ $inset_form: 1rem; align-items: center; height: 100%; width: 100%; + + @media screen and (max-width: 768px) { + justify-content: center; + } } .last_updated { @@ -168,7 +172,7 @@ $inset_form: 1rem; } } - @media only screen and (max-width: 667px) { + @media only screen and (max-width: 768px) { display: flex; flex-direction: column; } diff --git a/app/components/books/edit/BookInfo.tsx b/app/components/books/edit/BookInfo.tsx index 8d783067..f1ecd5eb 100644 --- a/app/components/books/edit/BookInfo.tsx +++ b/app/components/books/edit/BookInfo.tsx @@ -177,16 +177,16 @@ const Preview = () => { const { book } = useContext(EditBookContext); return ( - <Link href={`/books/${book.id}`}> - <div className={styles.preview}> + <div className={styles.preview}> + <Link href={`/books/${book.id}`}> <Image src={"/api/books/" + book.id + "/cover_preview"} alt="No cover page set!" width={240} height={350} /> - </div> - </Link> + </Link> + </div> ); }; diff --git a/app/components/books/edit/ShareLinks.tsx b/app/components/books/edit/ShareLinks.tsx new file mode 100644 index 00000000..38c88567 --- /dev/null +++ b/app/components/books/edit/ShareLinks.tsx @@ -0,0 +1,162 @@ +import { fetcher } from "lib/fetcher"; +import ms, { StringValue } from "ms"; +import { ApiGetToken } from "pages/api/books/[id]/edit/tokens"; +import { ChangeEvent, useContext, useMemo, useState } from "react"; +import { Button, Form, InputGroup, Table } from "react-bootstrap"; +import useSWR from "swr"; + +import { EditBookContext } from "."; +import { createNewToken, deleteToken, trash_svg } from "./AccessTokens"; + +import styles from "./AccessEditor.module.scss"; + +export default function ShareLinks() { + const { book } = useContext(EditBookContext); + const [description, setDescription] = useState(""); + const [expiration, setExpiration] = useState("1d"); + + const valid_expiration = useMemo(() => isValidMs(expiration), [expiration]); + + const { data, mutate } = useSWR<ApiGetToken[]>( + `/api/books/${book.id}/edit/tokens`, + fetcher + ); + + const rows = data + ?.filter((token) => token.type == "ui") + .map((token) => { + const url = `/books/${token.book_id}?token=${token.token}`; + const base_url = `${window.location.protocol}//${window.location.host}`; + + return ( + <tr key={token.id}> + <td>{token.description}</td> + <td + className={styles.link} + onClick={async (e) => { + //Check if clipboard is already the link + const elem = e.target as HTMLElement; + copyToClipboard(e, base_url + url); + elem.dataset.copied = "true"; + elem.style.color = "var(--bs-primary)"; + + setTimeout(() => { + elem.style.removeProperty("color"); + }, 1000); + }} + > + {base_url + url} + </td> + <td>{new Date(token.created_at).toDateString()}</td> + <td>{new Date(token.expires_at).toDateString()}</td> + <td className={styles.remove}> + <button + className="btn btn-outline-secondary" + onClick={() => { + deleteToken(book.id, token.id).then((r) => { + if (r.ok) { + mutate(); + } + }); + }} + > + {trash_svg} + </button> + </td> + </tr> + ); + }); + + return ( + <div className={styles.container}> + <h2>Share Links</h2> + <p> + Share links allow you to share your book with others without the + need for them to create an account. They can be used to share + your book with anyone who has the link. You can also set an + expiration date for the link and revoke it at any time. + </p> + <p>This can only be used to grant read access!</p> + <form + onSubmit={(e) => { + e.preventDefault(); + createNewToken(book.id, description, "ui", expiration).then( + (r) => { + setDescription(""); + } + ); + }} + > + <InputGroup className="p-3" hasValidation> + <Form.Control + type="text" + placeholder="Description" + style={{ width: "60%" }} + onInput={(e: ChangeEvent<HTMLTextAreaElement>) => { + setDescription(e.target.value); + }} + value={description} + /> + <Form.Control + type="text" + placeholder="Expires In" + style={{ width: "20%" }} + onInput={(e: ChangeEvent<HTMLTextAreaElement>) => { + // Check if valid using ms + setExpiration(e.target.value); + }} + value={expiration} + isValid={valid_expiration} + isInvalid={!valid_expiration} + required + /> + <Button variant="outline-secondary" type="submit"> + Create link + </Button> + <Form.Control.Feedback type="invalid"> + Invalid expiration time use ms format i.e. 1d, 1h, 1m 1 + week etc. + </Form.Control.Feedback> + </InputGroup> + </form> + + {rows?.length > 0 && ( + <Table striped bordered hover> + <colgroup> + <col className={styles.col_description} /> + <col className={styles.col_link} /> + <col /> + <col /> + <col className={styles.col_remove} /> + </colgroup> + <thead> + <tr> + <th>Description</th> + <th>Link</th> + <th>Created at</th> + <th>Expires at</th> + <th></th> + </tr> + </thead> + <tbody>{rows}</tbody> + </Table> + )} + </div> + ); +} + +const isValidMs = (value: string) => { + console.log(value); + try { + const r = ms(value as StringValue); + return r !== undefined; + } catch (e) { + console.log(e); + return false; + } +}; + +function copyToClipboard(e: React.MouseEvent, text: string) { + e.preventDefault(); + navigator.clipboard.writeText(text); +} diff --git a/app/components/books/edit/index.tsx b/app/components/books/edit/index.tsx index 3778eee5..7e60febf 100644 --- a/app/components/books/edit/index.tsx +++ b/app/components/books/edit/index.tsx @@ -24,6 +24,7 @@ import useSWR from "swr"; import AccessTokens from "./AccessTokens"; import { BackgroundTypeEditor } from "./BackgroundTypeEditor"; +import ShareLinks from "./ShareLinks"; import styles from "./Edit.module.scss"; @@ -128,7 +129,13 @@ export default function EditBook({ {selectedTool === "background" && ( <BackgroundTypeEditor /> )} - {selectedTool === "access" && <AccessEditor />} + + {selectedTool === "access" && ( + <> + <AccessEditor /> + <ShareLinks /> + </> + )} {selectedTool === "access" && perms.pACL && ( <AccessTokens /> )} diff --git a/app/components/frontpage/features.tsx b/app/components/frontpage/features.tsx index 57d6952b..b3633184 100644 --- a/app/components/frontpage/features.tsx +++ b/app/components/frontpage/features.tsx @@ -72,7 +72,7 @@ interface CardProps { img?: string; } -function Card({ children, title, img }: CardProps) { +function Card({ children, title }: CardProps) { return ( <div className={styles.card_wrapper}> <div className={styles.card_content}> diff --git a/app/components/utils/Resizer.tsx b/app/components/utils/Resizer.tsx index 4c689d8c..c08df26c 100644 --- a/app/components/utils/Resizer.tsx +++ b/app/components/utils/Resizer.tsx @@ -1,4 +1,3 @@ -import { hasOwnProperty } from "lib/utils"; import { useEffect, useRef, useState } from "react"; import styles from "./Resizer.module.scss"; diff --git a/app/components/viewer/page.tsx b/app/components/viewer/page.tsx index d9dad772..737b9648 100644 --- a/app/components/viewer/page.tsx +++ b/app/components/viewer/page.tsx @@ -12,7 +12,7 @@ import { import { ToolBarContext } from "./toolbar"; import { getContext } from "./tools"; import { MovePageToolContext } from "./tools/movePage.tool"; -import { _centerElement, _fitElement } from "./tools/movePage/resizable"; +import { _centerElement } from "./tools/movePage/resizable"; import { OverlayWorker, useOverlayWorker } from "./worker/overlayWorker"; import { PageWorker, usePageWorker } from "./worker/pageWorker"; diff --git a/app/components/viewer/sidebar/index.tsx b/app/components/viewer/sidebar/index.tsx index c75350a7..21feec11 100644 --- a/app/components/viewer/sidebar/index.tsx +++ b/app/components/viewer/sidebar/index.tsx @@ -102,7 +102,13 @@ export function SidebarContextProvider({ ); } -export function Sidebar({ book }: { book: Book }) { +export function Sidebar({ + book, + showSnipsToPlace, +}: { + book: Book; + showSnipsToPlace: boolean; +}) { const { searchTermPages, setSearchTermPages, @@ -184,17 +190,19 @@ export function Sidebar({ book }: { book: Book }) { Pages </button> </li> - <li className="nav-item" role="presentation"> - <button - className="nav-link" - onClick={() => { - setActiveTab("snips"); - }} - data-active={activeTab == "snips"} - > - Snips to place - </button> - </li> + {showSnipsToPlace ? ( + <li className="nav-item" role="presentation"> + <button + className="nav-link" + onClick={() => { + setActiveTab("snips"); + }} + data-active={activeTab == "snips"} + > + Snips to place + </button> + </li> + ) : null} </ul> {readOnly} <div className={styles.topNavigation}> @@ -213,11 +221,13 @@ export function Sidebar({ book }: { book: Book }) { book={book} filter={filter} /> - <TabSnips - active={activeTab === "snips"} - book={book} - filter={filter} - /> + {showSnipsToPlace ? ( + <TabSnips + active={activeTab === "snips"} + book={book} + filter={filter} + /> + ) : null} <div className={styles.extraFunctions}> <DownloadBtn book={book} /> <FullScreenButton /> diff --git a/app/components/viewer/sidebar/unplacedSnipPreviews.tsx b/app/components/viewer/sidebar/unplacedSnipPreviews.tsx index ead9d49d..20e39114 100644 --- a/app/components/viewer/sidebar/unplacedSnipPreviews.tsx +++ b/app/components/viewer/sidebar/unplacedSnipPreviews.tsx @@ -29,7 +29,7 @@ export function UnplacedSnipPreviews({ }); // new unplaced snip added (can be both from this client or another) - const insertUnplacedSnip = (snip) => { + const insertedIntoQueue = (snip) => { setSnips((snips) => { return [...snips, get_snip_from_data(snip)]; }); @@ -46,12 +46,12 @@ export function UnplacedSnipPreviews({ }); }; - book.events.addListener("insertUnplacedSnip", insertUnplacedSnip); + book.events.addListener("book:insertedIntoQueue", insertedIntoQueue); book.events.addListener("snip:placed", removeUnplacedSnip); return () => { book.events.removeListener( - "insertUnplacedSnip", - insertUnplacedSnip + "book:insertedIntoQueue", + insertedIntoQueue ); book.events.removeListener("snip:placed", removeUnplacedSnip); }; diff --git a/app/components/viewer/tools/doodle/drawing.ts b/app/components/viewer/tools/doodle/drawing.ts index 5cbfb30d..c9ca66b1 100644 --- a/app/components/viewer/tools/doodle/drawing.ts +++ b/app/components/viewer/tools/doodle/drawing.ts @@ -209,7 +209,7 @@ export function useDrawing( // Prevent default touch events (zooming, panning ) const touchStartHandler = useCallback( - (event: TouchEvent, pageIdx: number) => { + (event: TouchEvent) => { event.preventDefault(); }, [] @@ -224,8 +224,8 @@ export function useDrawing( const helpers_pointerdown = containers.map((container, idx) => { return (event: PointerEvent) => pointerDownHandler(event, idx); }); - const helpers_touchstart = containers.map((container, idx) => { - return (event: TouchEvent) => touchStartHandler(event, idx); + const helpers_touchstart = containers.map(() => { + return (event: TouchEvent) => touchStartHandler(event); }); containers.forEach((container, idx) => { diff --git a/app/components/viewer/tools/movePage.tool.tsx b/app/components/viewer/tools/movePage.tool.tsx index c811c780..886c1a83 100644 --- a/app/components/viewer/tools/movePage.tool.tsx +++ b/app/components/viewer/tools/movePage.tool.tsx @@ -1,18 +1,10 @@ -import useUser from "lib/useUser"; -import { - createContext, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from "react"; +import { createContext, useCallback, useContext, useEffect } from "react"; import { PagesContext } from "../page"; import { ToolBarContext } from "../toolbar"; import { ToolContext } from "."; import { useDraggableHandlers } from "./movePage/draggable"; -import { resize, useResizable } from "./movePage/resizable"; +import { useResizable } from "./movePage/resizable"; import { SubMenu } from "./movePage/submenu"; import styles from "../toolbar/toolbar.module.scss"; diff --git a/app/components/viewer/tools/text.tool.tsx b/app/components/viewer/tools/text.tool.tsx index 65d10196..02be4d4e 100644 --- a/app/components/viewer/tools/text.tool.tsx +++ b/app/components/viewer/tools/text.tool.tsx @@ -6,7 +6,6 @@ import { useCallback, useContext, useEffect, - useRef, useState, } from "react"; @@ -41,7 +40,7 @@ export function TextToolContextProvider({ }: { children: React.ReactNode; }) { - const { config, isLoadingConfig, mutateConfig } = useUser(); + const { config, isLoadingConfig, isErrorConfig, mutateConfig } = useUser(); const { activeTool, setActiveTool, activeSubMenu, setActiveSubMenu } = useContext(ToolBarContext); @@ -106,7 +105,13 @@ export function TextToolContextProvider({ // Set values from config this is only revalidated on focus change // see useUser useEffect(() => { - if (!isLoadingConfig && config) { + if ( + !isLoadingConfig && + !isErrorConfig && + config && + config.user_id != -1 + ) { + console.log("Setting text styles from config"); _setTextStyles({ font: config.text_font as AllowedFonts, fontSize: config.text_fontSize, @@ -116,7 +121,7 @@ export function TextToolContextProvider({ return; } // Update the config when the text styles change - }, [config, isLoadingConfig]); + }, [config, isErrorConfig, isLoadingConfig]); const ret: TextToolContext = { active, diff --git a/app/components/viewer/worker/pageWorker.tsx b/app/components/viewer/worker/pageWorker.tsx index 594ddc0a..99d8943d 100644 --- a/app/components/viewer/worker/pageWorker.tsx +++ b/app/components/viewer/worker/pageWorker.tsx @@ -109,10 +109,12 @@ export class PageWorker { new URL("./pageWorker/worker.ts", import.meta.url) ); // Send init message + const url_params = new URLSearchParams(window.location.search); const offscreen = this.canvas.transferControlToOffscreen(); - this.worker.postMessage({ type: "init", canvas: offscreen }, [ - offscreen, - ]); + this.worker.postMessage( + { type: "init", canvas: offscreen, url_params }, + [offscreen] + ); return; } diff --git a/app/components/viewer/worker/pageWorker/messaging.ts b/app/components/viewer/worker/pageWorker/messaging.ts index cda66038..837234ce 100644 --- a/app/components/viewer/worker/pageWorker/messaging.ts +++ b/app/components/viewer/worker/pageWorker/messaging.ts @@ -7,6 +7,7 @@ export interface InitMsg { */ type: "init"; canvas: OffscreenCanvas; + url_params: URLSearchParams; } export interface ShutdownMsg { diff --git a/app/components/viewer/worker/pageWorker/worker.ts b/app/components/viewer/worker/pageWorker/worker.ts index 652d9a80..05d82c85 100644 --- a/app/components/viewer/worker/pageWorker/worker.ts +++ b/app/components/viewer/worker/pageWorker/worker.ts @@ -186,6 +186,8 @@ self.onmessage = async (event: MessageEvent<Message>) => { switch (event.data.type) { case "init": globalThis.worker = new PageWorker(event.data.canvas); + + globalThis.url_params = event.data.url_params; break; case "shutdown": globalThis.worker.shutdown(); diff --git a/app/controller/book.ts b/app/controller/book.ts index eeb2aa6b..ed4c809d 100644 --- a/app/controller/book.ts +++ b/app/controller/book.ts @@ -236,9 +236,9 @@ export class Book implements Required<BookData> { ); // Update on insert of not yet placed snip - this.socket?.on("book:insertUnplacedSnip", (data) => { - console.log("book:insertUnplacedSnip", data); - this.events.emit("insertUnplacedSnip", data); + this.socket?.on("book:insertedIntoQueue", (data) => { + console.log("book:insertedIntoQueue", data); + this.events.emit("book:insertedIntoQueue", data); }); this.socket.on("snip:placed", (page_id, snip_id) => { diff --git a/app/controller/booksocket.ts b/app/controller/booksocket.ts index 3c49679b..7a269fd1 100644 --- a/app/controller/booksocket.ts +++ b/app/controller/booksocket.ts @@ -60,8 +60,15 @@ export class BookSocket { token: token, }, }); - socket.on("error", (error) => { - console.log("Socket error", error); + socket.on("error", (err) => { + console.log(err); + }); + socket.on("connect_error", (err) => { + console.log(err); + }); + socket.on("connect", () => { + console.log("Connected to book socket"); + console.log(socket); }); this.socket = socket; } @@ -81,10 +88,27 @@ export async function getSocket( } async function fetchToken(book_id: string): Promise<string> { - const { token } = await fetch("/api/authentication/socket_token", { + + const header_adds = {} + let token_url = null; + // In workers the url is parsed via globalThis + if (isWorker()) { + token_url = globalThis.url_params.get("token"); + } else { + const this_url = new URL(window.location.href); + token_url = this_url.searchParams.get("token"); + } + + //Check if url has token + if (token_url) { + header_adds["Authorization"] = "Bearer " + token_url; + } + + const { token } = await fetch(`/api/books/${book_id}/socket_token`, { method: "POST", headers: { "Content-Type": "application/json", + ...header_adds }, body: JSON.stringify({ book_id }), }).then((res) => { diff --git a/app/controller/websocket.ts b/app/controller/websocket.ts index 88ae69c3..e2acf25f 100644 --- a/app/controller/websocket.ts +++ b/app/controller/websocket.ts @@ -11,7 +11,7 @@ if (!globalThis.ClientSockets) { * @param book_id * @returns {Socket} */ -export const createClientSocket = async function ( +const createClientSocket = async function ( book_id: number | string ): Promise<Socket> { if (!isBrowser() && !isWorker()) { diff --git a/app/database/services/sql/config.service.ts b/app/database/services/sql/config.service.ts index 49ce9243..b152335f 100644 --- a/app/database/services/sql/config.service.ts +++ b/app/database/services/sql/config.service.ts @@ -23,6 +23,10 @@ const config_default: Required<UserConfigData> = { async function getByUserId(user_id: number): Promise<UserConfigData> { + + if (!user_id || user_id === -1) { + return config_default; + } const sql = "SELECT * FROM user_config_resolved WHERE user_id = ?"; const configData: UserConfigData = await pool.query<UserConfigData[]>(sql, [user_id]) .then((res) => { diff --git a/app/database/sql.typings.ts b/app/database/sql.typings.ts index 1939362d..74d55067 100644 --- a/app/database/sql.typings.ts +++ b/app/database/sql.typings.ts @@ -42,8 +42,17 @@ export interface tokensData { 'created_at'?: Date; 'created_by': number; 'description'?: string | null; + + /** The time the token expires (only for ui tokens) */ + 'expires_at'?: Date | null; 'hashed_token': string; 'id'?: number; + + /** The token (only for ui tokens) */ + 'token'?: string | null; + + /** The type of token (api or ui) */ + 'type'?: string; } /** The user configs for the app */ @@ -145,7 +154,7 @@ export interface booksData { 'comment'?: string | null; 'cover_page_id'?: number | null; 'created'?: Date | null; - 'default_background_type_id'?: number; + 'default_background_type_id'?: number | null; 'finished'?: Date | null; 'id'?: number; 'last_updated'?: Date; @@ -161,7 +170,7 @@ export interface books_resolvedData { 'comment'?: string | null; 'created'?: Date | null; 'default_background_type_description'?: string | null; - 'default_background_type_id'?: number; + 'default_background_type_id'?: number | null; 'default_background_type_name': string | null; 'finished'?: Date | null; 'id'?: number; @@ -190,19 +199,6 @@ export interface browserFolders_booksData { 'browserFolder_id': number; 'user_id': number; } -export interface browserViewData { - 'comment'?: string | null; - 'created'?: Date | null; - 'finished'?: Date | null; - 'id'?: string | null; - 'isDir'?: number; - 'last_updated'?: Date | null; - 'name'?: string; - 'numPages'?: number | null; - 'owner'?: string | null; - 'parentId'?: string | null; - 'user_id'?: number | null; -} export interface instrumentsData { 'created'?: Date; 'description': string; @@ -226,12 +222,6 @@ export interface pagesViewData { 'last_updated'?: Date; 'page_number'?: number | null; } -export interface settingsData { - 'book_folders'?: string | null; - 'entity_id': number; - 'entity_type_id'?: number; - 'id'?: number; -} export interface snipsData { 'blob_id'?: number | null; 'book_id': number; @@ -247,7 +237,7 @@ export interface snipsData { export enum background_types { 'blank' = 1, 'checkered 4mm x 4mm' = 2, - 'lined 7mm' = 3, + 'lines 7mm' = 3, } export interface changelogData { 'applied_at'?: Date; diff --git a/app/lib/access_control/books.ts b/app/lib/access_control/books.ts index 7b0586a8..092bb5cc 100644 --- a/app/lib/access_control/books.ts +++ b/app/lib/access_control/books.ts @@ -34,18 +34,28 @@ export function withAuthBookIdSsr< perms: PermissionACLData ) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>> ) { - return async (ctx) => { + return async (ctx: GetServerSidePropsContext<Q, D>) => { const { req, query } = ctx; const book_id = query.id as string; - - // Get the users permission for this book - if (book_id) { + const user_id = req.session.user_id; + // First check for user + if (book_id && user_id) { const perms = await service.permission.getACLByUserBookReduced( - req.session.user_id, + user_id, parseInt(book_id) ); return await handler(ctx, perms); } + + // Check if token is present + const url = new URL(req.url, "http://localhost"); + const token = url.searchParams.get("token"); + if (book_id && token) { + console.log("Ui token found", token); + const perms = await verify_with_token(token, book_id); + return await handler(ctx, perms); + } + return { redirect: { permanent: false, @@ -76,16 +86,7 @@ export function withAuthBookId(handler: NextApiHandlerWithPerms) { // Get the users permission for this book if (book_id) { const token = req.headers.authorization?.split(" ")[1]; - - let perms; - if (token) { - perms = await verify_with_token(token, book_id); - } else { - perms = await service.permission.getACLByUserBookReduced( - req.session.user_id, - parseInt(book_id) - ); - } + const perms = await bookAuth(req.session.user_id, book_id, token); return await handler(req, res, perms); } else { res.status(400).json({ @@ -97,48 +98,84 @@ export function withAuthBookId(handler: NextApiHandlerWithPerms) { }; } + +/** This function can be used to secure any api route + * api/? route. + */ +export async function bookAuth(user_id: number, book_id: string | number, token?: string) { + let perms; + if (token) { + console.log("Api token found", token); + perms = await verify_with_token(token, book_id); + } else { + perms = await service.permission.getACLByUserBookReduced( + user_id, + parseInt(book_id as string) + ); + } + return perms; +} + + + async function verify_with_token( token: string, - book_id: string + book_id: string | number, ): Promise<PermissionACLData> { const hashed_token = hashToken(token); - const found = await pool_permissions + const { found, type } = await pool_permissions .query("SELECT * FROM tokens WHERE book_id = ? AND hashed_token = ?", [ book_id, hashed_token, ]) .then((res) => { + console.log(res); if (res.length > 0) { - return true; + return { + found: true, + type: res[0].type, + } } else { - return false; + return { + found: false, + type: null, + } } }) .catch((err) => { console.error(err); - return false; + return { + found: false, + type: null, + } }); - if (found) { - return { - entity_id: -1, - entity_type: ENTITY_TOKEN, - pACL: false, - pDelete: true, - pRead: true, - pWrite: true, - resource_id: parseInt(book_id), - resource_type: RESOURCE_BOOK, - }; - } else { - return { - entity_id: -1, - entity_type: ENTITY_TOKEN, - pACL: false, - pDelete: false, - pRead: false, - pWrite: false, - resource_id: parseInt(book_id), - resource_type: RESOURCE_BOOK, - }; + + const perms: PermissionACLData = { + entity_id: -1, + entity_type: ENTITY_TOKEN, + resource_id: parseInt(book_id as string), + resource_type: RESOURCE_BOOK, + pACL: false, + pDelete: false, + pRead: false, + pWrite: false, + } + + if (!found) { + return perms; } + + + switch (type) { + case "api": + perms.pRead = true; + perms.pWrite = true; + perms.pDelete = true; + break; + case "ui": + perms.pRead = true; + break; + } + + return perms; } diff --git a/app/lib/fetcher.ts b/app/lib/fetcher.ts index f236c325..e36549ed 100644 --- a/app/lib/fetcher.ts +++ b/app/lib/fetcher.ts @@ -8,7 +8,20 @@ interface ApiError_client extends Error { } export async function fetcher(url) { - const res = await fetch(url).then(res_parse_json); + + + //Check if token in winow + + const headers = {}; + const token = new URL(window.location.href).searchParams.get("token"); + if (token) { + headers["Authorization"] = `Bearer ${token}`; + } + const res = await fetch(url, + { + headers: headers, + } + ).then(res_parse_json); return res; } diff --git a/app/lib/useUser.ts b/app/lib/useUser.ts index 3d765a6a..4ce17826 100644 --- a/app/lib/useUser.ts +++ b/app/lib/useUser.ts @@ -28,6 +28,13 @@ export default function useUser() { revalidateIfStale: false, revalidateOnFocus: false, revalidateOnReconnect: false, + fallbackData: { + created: new Date(), + email: "", + emailValidated: false, + id: -1, + last_updated: new Date(), + }, }); const { @@ -37,6 +44,22 @@ export default function useUser() { revalidateIfStale: false, revalidateOnFocus: true, revalidateOnReconnect: true, + fallbackData: { + id: -1, + last_updated: new Date(), + pen_color: "#000000", + pen_size: 4, + pen_smoothing: 0.0, + text_font_id: 1, + text_font: "Arial", + text_fontColor: "#000000", + text_fontSize: 12, + text_lineHeight: 1.25, + text_lineWrap: 400, + user_id: -1, + zoom1: 1, + zoom2: 1, + } }); diff --git a/app/middleware.ts b/app/middleware.ts index 436c8148..b070334e 100644 --- a/app/middleware.ts +++ b/app/middleware.ts @@ -72,9 +72,6 @@ async function middleware_ui(request: NextRequest): Promise<NextResponse> { const session = await getIronSession(request, response, sessionOptions); const { user_id, email_verified } = session; - - - //Rewrite for index page if ((request.nextUrl.pathname === "/" && !user_id)) { return NextResponse.redirect(new URL(`/frontpage`, request.url)); @@ -82,6 +79,15 @@ async function middleware_ui(request: NextRequest): Promise<NextResponse> { return NextResponse.redirect(new URL(`/books`, request.url)); } + // Special check for the books route if token is in header + if (request.nextUrl.pathname.startsWith("/books/")) { + const token = request.nextUrl.searchParams.get("token"); + if (token) { + // Token is checked in the api route + return response; + } + } + // No user_id means the user is not logged in if (!user_id) { const redirect = request.nextUrl.pathname; diff --git a/app/pages/_app.tsx b/app/pages/_app.tsx index 24053b85..3079bb4d 100644 --- a/app/pages/_app.tsx +++ b/app/pages/_app.tsx @@ -27,7 +27,7 @@ type AppPropsWithLayout = AppProps & { export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { useEffect(() => { - typeof document !== undefined + typeof document !== "undefined" ? require("bootstrap/dist/js/bootstrap") : null; }, []); diff --git a/app/pages/api/_service.ts b/app/pages/api/_service.ts index d256bdc6..7d6ac641 100644 --- a/app/pages/api/_service.ts +++ b/app/pages/api/_service.ts @@ -2,6 +2,7 @@ import { SQLService } from "database/services"; export interface ApiError { error: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any details?: any; } diff --git a/app/pages/api/admin/rerenderPreviews.ts b/app/pages/api/admin/rerenderPreviews.ts index ce7832ed..d2b10e2e 100644 --- a/app/pages/api/admin/rerenderPreviews.ts +++ b/app/pages/api/admin/rerenderPreviews.ts @@ -38,7 +38,7 @@ export default async function rerenderPreviews( promises.push(page_preview(page.id, Number(book_id), true)); } - await Promise.all(promises).catch((e) => { + await Promise.all(promises).catch(() => { res.status(500).json({ error: "Internal Server Error" }); return; }); diff --git a/app/pages/api/admin/set_acl.ts b/app/pages/api/admin/set_acl.ts index a2f47108..8c9fd69a 100644 --- a/app/pages/api/admin/set_acl.ts +++ b/app/pages/api/admin/set_acl.ts @@ -50,6 +50,7 @@ export const parseBoolean = (value: number | string | boolean): boolean => { throw new Error("value must be a boolean, string or number!"); }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function parseACL(acl: Array<any>): PermissionACLData[] { const acl_parsed = acl.map((acl) => { let { id } = acl; diff --git a/app/pages/api/admin/set_users_groups.ts b/app/pages/api/admin/set_users_groups.ts index bd527e7f..c933ad1c 100644 --- a/app/pages/api/admin/set_users_groups.ts +++ b/app/pages/api/admin/set_users_groups.ts @@ -7,6 +7,7 @@ import { pool_permissions } from "database/sql.connection"; import { UsersGroups } from "components/admin_panel/utils"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const parseUserGroups = (user_groups: Array<any>): UsersGroups[] => { const user_groups_parsed = user_groups.map((user_group) => { const { group_id, user_id } = user_group; diff --git a/app/pages/api/authentication/isAdmin.ts b/app/pages/api/authentication/isAdmin.ts index 077e502b..2b84684d 100644 --- a/app/pages/api/authentication/isAdmin.ts +++ b/app/pages/api/authentication/isAdmin.ts @@ -1,6 +1,7 @@ import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; +import getSocketClient from "../socket"; export default withSessionRoute(isAdmin); /** THIS API SHOULD NOT BE USED FOR CRITICAL APPLICATION PARTS @@ -10,9 +11,11 @@ export default withSessionRoute(isAdmin); * */ async function isAdmin(req: NextApiRequest, res: NextApiResponse<boolean>) { - if (req.session.is_admin) { + + if (req.session?.is_admin) { res.json(true); } else { res.json(false); } + } diff --git a/app/pages/api/authentication/socket_token.ts b/app/pages/api/authentication/socket_token.ts deleted file mode 100644 index cdb74658..00000000 --- a/app/pages/api/authentication/socket_token.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { config } from "config"; -import { sign } from "jsonwebtoken"; -import { withSessionRoute } from "lib/withSession"; -import { NextApiRequest, NextApiResponse } from "next/types"; - -import { ApiError } from "../_service"; - -export default withSessionRoute(tokenRoute); - -async function tokenRoute( - req: NextApiRequest, - res: NextApiResponse<{ token: string } | ApiError> -) { - if (req.method !== "POST") { - res.status(405).json({ error: "Method not allowed" }); - return; - } - - const { book_id } = await req.body; - - // Check if bookid is unsigned int parseable - if (!book_id || !Number.isInteger(Number(book_id))) { - res.status(400).json({ - error: "Bad Request", - details: "Book ID is required", - }); - return; - } - - // Check if user is logged in - if (!req.session.user_id) { - res.status(401).json({ - error: "Unauthorized", - }); - return; - } - - - // Create token with book_id and send back - const token = sign({ date: new Date(), book_id, user_id: req.session.user_id }, config.SOCKET_SECRET, { - expiresIn: "24h", - }); - - res.json({ token }); -} diff --git a/app/pages/api/authentication/verify.ts b/app/pages/api/authentication/verify.ts index 866cfacd..6a238c47 100644 --- a/app/pages/api/authentication/verify.ts +++ b/app/pages/api/authentication/verify.ts @@ -64,7 +64,7 @@ export async function verify_token(token: string): Promise<VerifyPayload> { }); // check if email is in db - const payload = new Promise<VerifyPayload>((resolve, reject) => { + const payload = new Promise<VerifyPayload>((resolve,) => { email .then(async (email) => { return { diff --git a/app/pages/api/books/[id]/edit/get_acl_entity.ts b/app/pages/api/books/[id]/edit/get_acl_entity.ts index 0796e13d..da989da0 100644 --- a/app/pages/api/books/[id]/edit/get_acl_entity.ts +++ b/app/pages/api/books/[id]/edit/get_acl_entity.ts @@ -158,7 +158,7 @@ async function get_entity(entity_name): Promise<Entity> { } return res[0]; }) - .catch((err) => { + .catch(() => { return { id: -1, entity_name: undefined, entity_type: undefined }; }); return entity; diff --git a/app/pages/api/books/[id]/edit/set_acl.ts b/app/pages/api/books/[id]/edit/set_acl.ts index f192de9b..6b155410 100644 --- a/app/pages/api/books/[id]/edit/set_acl.ts +++ b/app/pages/api/books/[id]/edit/set_acl.ts @@ -3,6 +3,7 @@ import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; import { ApiError, service } from "pages/api/_service"; import { parseACL } from "pages/api/admin/set_acl"; +import getSocketClient from "pages/api/socket"; import { update_perms } from "socket/auth"; import { @@ -50,9 +51,7 @@ async function set_acl( const successes = acl_parsed.map(async (acl) => { const { entity_id, - resource_id, entity_type, - resource_type, pRead, pWrite, pDelete, @@ -86,11 +85,11 @@ async function set_acl( if (successes.every((success) => success)) { // Reset all socket permissions - req["io"].of(`/book-${book_id}`).sockets.forEach((socket) => { - update_perms(socket); - }); - + const client = getSocketClient(); + client.connect(); + client.emit("resetSocketPerms", book_id); res.status(200).json(true); + client.close(); } else { res.status(500).json({ error: "Internal Server Error", diff --git a/app/pages/api/books/[id]/edit/tokens.ts b/app/pages/api/books/[id]/edit/tokens.ts index efbd62be..92bf09e0 100644 --- a/app/pages/api/books/[id]/edit/tokens.ts +++ b/app/pages/api/books/[id]/edit/tokens.ts @@ -1,6 +1,7 @@ import { withAuthBookId } from "lib/access_control/books"; import { hashToken } from "lib/access_control/hash_token"; import { withSessionRoute } from "lib/withSession"; +import ms from "ms"; import { NextApiRequest, NextApiResponse } from "next/types"; import { randomBytes } from "node:crypto"; import { ApiError } from "pages/api/_service"; @@ -18,7 +19,11 @@ export type ApiGetToken = { created_at: Date; created_by: string; description: string; + book_id: string; email: string; + type: "api" | "ui"; + expires_at: Date | null; + token: string | null; }; /** Route to get all tokens, create a token or delete a token depending on the @@ -86,7 +91,7 @@ async function tokens( // Add request method else if (req.method === "POST") { - const { description } = req.body; + const { description, type, expireIn } = req.body; if (perms.entity_type !== ENTITY_USER) { res.status(401).json({ @@ -95,11 +100,28 @@ async function tokens( }); return; } + if (type != "api" && type != "ui") { + res.status(400).json({ + error: "Bad Request", + details: "Type must be either 'api' or 'ui'!", + }); + return; + } + //Check if expireIn is given + if (type === "ui" && !expireIn) { + res.status(400).json({ + error: "Bad Request", + details: "expireIn is required for ui tokens!", + }); + return; + } const token = await createToken( book_id, perms.entity_id, - description + description, + type, + expireIn ).catch((err) => { console.error(err); return; @@ -117,10 +139,26 @@ async function tokens( } } + async function createToken( book_id, user_id, - description + description, + type, + expireIn, +): Promise<ApiGetToken & { token: string }> { + if (type === "api") { + return await _createTokenApi(book_id, user_id, description); + } else if (type === "ui") { + return await _createTokenUi(book_id, user_id, description, expireIn); + } + throw new Error("Invalid type!"); +} + +async function _createTokenApi( + book_id, + user_id, + description, ): Promise<ApiGetToken & { token: string }> { const token = randomBytes(24).toString("hex"); @@ -128,11 +166,11 @@ async function createToken( const hashed_token = hashToken(token); const sql = ` - INSERT INTO tokens (hashed_token, book_id, created_by, description) - VALUES (?, ?, ?, ?); + INSERT INTO tokens (hashed_token, book_id, created_by, description, type) + VALUES (?, ?, ?, ?, ?); `; return await pool_permissions - .query(sql, [hashed_token, book_id, user_id, description]) + .query(sql, [hashed_token, book_id, user_id, description, "api",]) .then((res) => { if (res.warningStatus > 0) { throw new Error("Insert failed!"); @@ -145,6 +183,50 @@ async function createToken( }); } + +/** Creates a token used for the share links + * @param book_id + * @param user_id + * @param description + * @returns + */ +async function _createTokenUi( + book_id, + user_id, + description, + expireIn +): Promise<ApiGetToken & { token: string }> { + + // Validate expireIn + const eIn = ms(expireIn); + if (!eIn) { + throw new Error("Invalid expireIn!"); + } + const expires_at = new Date(Date.now() + eIn); + const token = randomBytes(24).toString("hex"); + const hashed_token = hashToken(token); + + const sql = ` + INSERT INTO tokens (token, hashed_token, book_id, created_by, description, type, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?); + `; + return await pool_permissions + .query(sql, [token, hashed_token, book_id, user_id, description, "ui", expires_at]) + .then((res) => { + if (res.warningStatus > 0) { + throw new Error("Insert failed!"); + } + return res.insertId; + }) + .then(async (token_id) => { + const tokeninfo = await getTokenById(token_id); + return { ...tokeninfo, token }; + }); +} + + + + /** Returns all tokens for the book */ async function getTokens(book_id): Promise<ApiGetToken[]> { @@ -154,6 +236,10 @@ async function getTokens(book_id): Promise<ApiGetToken[]> { tokens.created_at, tokens.created_by, tokens.description, + tokens.type, + tokens.expires_at, + tokens.token, + tokens.book_id, users.email FROM tokens LEFT JOIN users ON tokens.created_by = users.id @@ -169,6 +255,10 @@ async function getTokenById(token_id): Promise<ApiGetToken> { tokens.created_at, tokens.created_by, tokens.description, + tokens.type, + tokens.expires_at, + tokens.token, + tokens.book_id, users.email FROM tokens LEFT JOIN users ON tokens.created_by = users.id diff --git a/app/pages/api/books/[id]/edit/update.ts b/app/pages/api/books/[id]/edit/update.ts index 65a008e8..8f62e5e1 100644 --- a/app/pages/api/books/[id]/edit/update.ts +++ b/app/pages/api/books/[id]/edit/update.ts @@ -2,6 +2,7 @@ import { withAuthBookId } from "lib/access_control/books"; import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; import { ApiError } from "pages/api/_service"; +import getSocketClient from "pages/api/socket"; import { update_perms } from "socket/auth"; import { PermissionACLData } from "database/services/service.typings"; @@ -144,13 +145,11 @@ async function updateBook( if (success) { // Reset all socket permissions - req["io"].of(`/book-${book_id}`).sockets.forEach((socket) => { - update_perms(socket); - }); - - res.status(200).json( - true - ); + const client = getSocketClient(); + client.connect(); + client.emit("resetSocketPerms", book_id); + res.status(200).json(true); + client.close(); } return; } diff --git a/app/pages/api/books/[id]/socket_token.ts b/app/pages/api/books/[id]/socket_token.ts new file mode 100644 index 00000000..f6e2d7cd --- /dev/null +++ b/app/pages/api/books/[id]/socket_token.ts @@ -0,0 +1,81 @@ +import { config } from "config"; +import { JwtPayload, sign, verify } from "jsonwebtoken"; +import { withAuthBookId } from "lib/access_control/books"; +import { withSessionRoute } from "lib/withSession"; +import { NextApiRequest, NextApiResponse } from "next/types"; + +import { ApiError } from "../../_service"; + +import { PermissionACLData } from "database/services/service.typings"; + +export default withSessionRoute(withAuthBookId(tokenRoute)); + +async function tokenRoute( + req: NextApiRequest, + res: NextApiResponse<{ token: string } | ApiError>, + perms: PermissionACLData +) { + if (!perms.pRead) { + res.status(401).json({ + error: "Unauthorized", + details: + "You do not have read access to this book! ", + }); + return; + } + + if (req.method !== "POST") { + res.status(405).json({ + error: "Method not allowed", + details: "Only POST method is allowed", + }); + return; + } + const { query } = req; + const book_id = query.id as string; + + const user_id = req.session.user_id; + const token = req.headers.authorization?.split(" ")[1]; + + // Create token with book_id and send back + // see socket/auth.ts + const jwt_token = sign({ + book_id, + user_id, + token, + }, config.SOCKET_SECRET, { + expiresIn: "24h", + }); + + res.json({ token: jwt_token }); +} + + +export function verifyToken(token: string) { + + const payload = verify(token, config.SOCKET_SECRET); + + //Check if payload is valid + if (!payload || typeof payload !== "object") { + throw new Error("Invalid token" + JSON.stringify(payload)); + } + + // Check if book_id is present + if (!payload.book_id) { + throw new Error("Invalid token no book_id!"); + } + // Either user_id or token must be present + if (!payload.user_id && !payload.token) { + throw new Error("Invalid token no user_id or token!"); + } + // Add undefined to user_id and token + if (!payload.user_id) { + payload.user_id = undefined; + } + if (!payload.token) { + payload.token = undefined; + } + + + return payload as JwtPayload & { book_id: string, user_id: string | undefined, token: string | undefined }; +} \ No newline at end of file diff --git a/app/pages/api/books/[id]/upload.ts b/app/pages/api/books/[id]/upload.ts index 0d6965fc..a08ba847 100644 --- a/app/pages/api/books/[id]/upload.ts +++ b/app/pages/api/books/[id]/upload.ts @@ -5,6 +5,7 @@ import { withAuthBookId } from "lib/access_control/books"; import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; import { ApiError, service } from "pages/api/_service"; +import getSocketClient from "pages/api/socket"; import { default as sharp } from "sharp"; import { PermissionACLData } from "database/services/service.typings"; @@ -122,20 +123,20 @@ async function uploadFile( } // Save snips to db and emit event to update clients + + + const client = getSocketClient(); + client.connect(); const ids = []; for (const snip of snips) { let snip_data = snip.to_data(); snip_data = await service.snip.insert(snip_data); - req["io"] - .of("book-" + book_id) - .emit("book:insertUnplacedSnip", snip_data); + client.emit("api:insertedIntoQueue", snip_data); ids.push(snip_data.id); } - - res.status(200).json({ ids: ids }); + client.close(); } - /* Helper function to parse multiple files to snips */ diff --git a/app/pages/api/socket.ts b/app/pages/api/socket.ts index c7cab197..d14d775c 100644 --- a/app/pages/api/socket.ts +++ b/app/pages/api/socket.ts @@ -1,14 +1,35 @@ -import { Server } from "socket.io"; +import { config } from "config"; +import { sign } from "jsonwebtoken"; +import io from "socket.io-client"; -/** Initialize websocket or load if already - * running. See below for functionality. +/** Connects to socket server in admin + * namespace and returns the socket. + * + * This should only be used server side. */ -export default function SocketHandler(req, res) { - if (res.socket.server.io) { - return; - } else { - const io = new Server(res.socket.server); - res.socket.server.io = io; - console.log("[Socket] WebSocket Server initialized"); - } -} +export default function getSocketClient() { + const token = sign( + { + api: true, + date: Date.now(), + } + , config.SOCKET_SECRET + ); + + const client = io(`ws://${config.HOSTNAME}:4000/api`, { + auth: { + token, + }, + autoConnect: false, + }); + + client.on("error", (error) => { + console.log("Socket error", error); + }); + client.on("connect_error", (error) => { + console.log("Socket connect error", error); + client.close(); + }); + + return client; +} \ No newline at end of file diff --git a/app/pages/books/[id]/index.tsx b/app/pages/books/[id]/index.tsx index c80065e5..155c6379 100644 --- a/app/pages/books/[id]/index.tsx +++ b/app/pages/books/[id]/index.tsx @@ -15,12 +15,18 @@ import { type Tool } from "components/viewer/tools"; * Also handles the active pages and the layout * */ -export default function BookById({ bookData }) { +export default function BookById({ bookData, perms }) { // Create a book const [book, setBook] = useState<Book>(); // Register desired tools (snipEditor is always last) - const desiredTools: Tool[] = ["movePage", "doodle", "text", "snipEditor"]; + const desiredTools: Tool[] = ["movePage"]; + if (perms && perms.pWrite) { + desiredTools.push("doodle"); + desiredTools.push("text"); + desiredTools.push("snipEditor"); + } + const { Viewer, ViewerContext, Toolbar } = viewer_factory(desiredTools); // Load book and pageData for all pages @@ -35,11 +41,11 @@ export default function BookById({ bookData }) { }, [bookData]); // Return loading spinner - if (!book) return <Loading />; + if (!book || !perms) return <Loading />; return ( <ViewerContext> - <Sidebar book={book} /> + <Sidebar book={book} showSnipsToPlace={perms.pWrite} /> <Resizer /> <div style={{ @@ -64,7 +70,13 @@ export const getServerSideProps = withSessionSsr( if (perms.pRead) { const book_id = context.query.id as string; const bookData = await SQLService.book.getById(Number(book_id)); - return { props: { bookData, title: bookData.title } }; + const p = { + pRead: perms.pRead, + pWrite: perms.pWrite, + pDelete: perms.pDelete, + pACL: perms.pACL, + }; + return { props: { bookData, title: bookData.title, perms: p } }; } return { diff --git a/app/pages/books/index.tsx b/app/pages/books/index.tsx index 3fc9f2a5..3a82e70a 100644 --- a/app/pages/books/index.tsx +++ b/app/pages/books/index.tsx @@ -1,6 +1,5 @@ import { fetcher } from "lib/fetcher"; import { withSessionSsr } from "lib/withSession"; -import { getBooksByUser } from "pages/api/books"; import useSWR from "swr"; import { FullBooksBrowser } from "components/books/browser/BooksBrowser"; @@ -8,7 +7,6 @@ import { DefaultLayoutWithSidebar } from "components/common/DefaultLayout"; export default function Index() { //return <BooksViewer books={books} perms={perms} />; - const { data: fileMap, isLoading, diff --git a/app/public/img/clipboard-check.svg b/app/public/img/clipboard-check.svg new file mode 100644 index 00000000..f7591aec --- /dev/null +++ b/app/public/img/clipboard-check.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard-check" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/> + <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> + <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> +</svg> \ No newline at end of file diff --git a/app/public/img/clipboard.svg b/app/public/img/clipboard.svg new file mode 100644 index 00000000..360e0894 --- /dev/null +++ b/app/public/img/clipboard.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16"> + <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> + <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> +</svg> \ No newline at end of file diff --git a/app/server.ts b/app/server.ts index 3922c2de..b4af5b01 100644 --- a/app/server.ts +++ b/app/server.ts @@ -6,6 +6,7 @@ import { createServer as createServerHttp } from "http"; import { createServer as createServerHttps } from "https"; import next from "next"; import { getSocketIOServer } from "socket"; +import { type Server as IOServer } from "socket.io"; import { parse } from "url"; // Own function to setup server @@ -20,6 +21,8 @@ const dir = "./app"; const app = next({ dev, hostname, port, dir }); const handle = app.getRequestHandler(); + + // Define the callback function to add as http or https async function callback_server( req: IncomingMessage, @@ -28,20 +31,13 @@ async function callback_server( // We attach the socket to the request object // to use it in the route handler // e.g. emitting event on post requests - req["io"] = snip_socket; + try { // Be sure to pass `true` as the second argument to `url.parse`. // This tells it to parse the query portion of the URL. const parsedUrl = parse(req.url, true); - const { pathname, query } = parsedUrl; + await handle(req, res, parsedUrl); - if (pathname === "/a") { - await app.render(req, res, "/a", query); - } else if (pathname === "/b") { - await app.render(req, res, "/b", query); - } else { - await handle(req, res, parsedUrl); - } } catch (err) { console.error("Error occurred handling", req.url, err); res.statusCode = 500; diff --git a/app/snips/general/image.ts b/app/snips/general/image.ts index 491744ea..8cd92a76 100644 --- a/app/snips/general/image.ts +++ b/app/snips/general/image.ts @@ -1,6 +1,5 @@ import EventEmitter from "events"; import { sync as probe_sync } from "probe-image-size"; -import sharp from "sharp"; import { BaseData, BaseSnip, BaseSnipArgs, BaseView, SnipData } from "./base"; diff --git a/app/socket/auth.ts b/app/socket/auth.ts index 25278eab..502be798 100644 --- a/app/socket/auth.ts +++ b/app/socket/auth.ts @@ -1,23 +1,10 @@ import { config } from "config"; import { JwtPayload, verify } from "jsonwebtoken"; -import { service } from "pages/api/_service"; +import { bookAuth } from "lib/access_control/books"; +import { verifyToken } from "pages/api/books/[id]/socket_token"; import type { Socket } from "socket.io"; -function _verify(token: string, book_id: string) { - const decoded = verify(token, config.SOCKET_SECRET); - - if ( - (decoded as JwtPayload).book_id == book_id && - (decoded as JwtPayload).user_id) { - return decoded as JwtPayload; - } else { - throw new Error("Invalid token"); - } -} - export async function authenticate(socket: Socket, next) { - // Print middleware message - console.log("[Socket][Middleware]", socket.nsp.name); // Verify token const token = socket.handshake.auth.token; @@ -25,32 +12,51 @@ export async function authenticate(socket: Socket, next) { try { // Verify and decode payload - const decoded = _verify(token, book_id); + const decoded = verifyToken(token); + if (decoded.book_id != book_id) { + throw new Error("Invalid token, book_id does not match"); + } + // Set socket data socket.book_id = parseInt(decoded.book_id); - socket.user_id = decoded.user_id; + socket.user_id = parseInt(decoded.user_id) || undefined; + socket.auth_token = decoded.token; + } catch (error) { - return next(new Error("Invalid token")); + return next(error); } return next(); } export async function add_perms(socket: Socket, next) { - const perms = await service.permission.getACLByUserBookReduced( - socket.user_id, - socket.book_id - ); - + const perms = await bookAuth(socket.user_id, socket.book_id, socket.auth_token); + console.log(perms); socket.perms = perms; return next(); } export async function update_perms(socket: Socket) { - const perms = await service.permission.getACLByUserBookReduced( - socket.user_id, - socket.book_id - ); + const perms = await bookAuth(socket.user_id, socket.book_id, socket.auth_token); + console.log(perms); socket.perms = perms; return +} + + +/** For admin namespace only */ +export function authenticateApi(socket: Socket, next) { + // Verify token + const token = socket.handshake.auth.token; + + try { + const decoded = verify(token, config.SOCKET_SECRET) as JwtPayload & { api: boolean, date: Date }; + + if (!decoded.api) { + throw new Error("Invalid token, not an api token"); + } + } catch (error) { + return next(error); + } + return next(); } \ No newline at end of file diff --git a/app/socket/socket.ts b/app/socket/socket.ts index 43780789..1742c02e 100644 --- a/app/socket/socket.ts +++ b/app/socket/socket.ts @@ -1,21 +1,22 @@ /* eslint-disable no-unused-vars */ import { Namespace, Server, Socket } from "socket.io"; -import { add_perms, authenticate } from "./auth"; +import { add_perms, authenticate, authenticateApi, update_perms } from "./auth"; import { setupBookPrefix } from "./book/socket"; import { setupPagesPrefix } from "./page/socket"; import { setupSnipPrefix } from "./snip/socket"; import { SQLService } from "database/services"; import { PermissionACLData } from "database/services/service.typings"; -import { BaseData, BaseView,SnipData } from "snips/general/base"; +import { BaseData, BaseView, SnipData } from "snips/general/base"; // Extend socket to include book_id, user_id and perms // This is set in the the book_authenticate middleware declare module "socket.io" { interface Socket { book_id: number; - user_id: number; + user_id: number | undefined; + auth_token: string | undefined; perms: PermissionACLData; } } @@ -103,4 +104,36 @@ export function setupNamespaces(server: Server) { socket.leave("page:" + page_id); }); }); + + setupApiSpace(server); } + + +function setupApiSpace(server: Server) { + const api_nsp = server.of("/api"); + + api_nsp.use(authenticateApi); + + api_nsp.on("connection", async (socket: Socket) => { + log("[Api] Client (" + socket.id + ") connected"); + + + socket.on("api:insertedIntoQueue", async (snip: SnipData<BaseData, BaseView>) => { + log("[Api] insertedIntoQueue"); + server.of("/book-" + snip.book_id).emit("book:insertedIntoQueue", snip); + }); + + socket.on("resetSocketPerms", async (book_id: number) => { + log("[Api] resetSocketPerms"); + server.of("/book-" + book_id).sockets.forEach((socket) => { + update_perms(socket); + }); + }); + + + socket.on("disconnect", () => { + log("[Api] Client (" + socket.id + ") disconnected"); + }); + }); +} + diff --git a/database/migration/0016_token_types.sql b/database/migration/0016_token_types.sql new file mode 100644 index 00000000..e9c6cb24 --- /dev/null +++ b/database/migration/0016_token_types.sql @@ -0,0 +1,8 @@ +USE snip_perms; + +-- Add the user configs table (api or ui) +ALTER TABLE tokens ADD COLUMN type VARCHAR(10) NOT NULL DEFAULT 'api' COMMENT 'The type of token (api or ui)'; +-- Add token column to allow for plain text tokens (only ui) +ALTER TABLE tokens ADD COLUMN token TEXT NULL DEFAULT NULL COMMENT 'The token (only for ui tokens)'; +-- Add expires_at column to allow for token expiration (only ui( for now)) +ALTER TABLE tokens ADD COLUMN expires_at TIMESTAMP NULL DEFAULT NULL COMMENT 'The time the token expires (only for ui tokens)'; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index db077057..f97f260f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,75 +1,71 @@ { "name": "snip", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "snip", - "version": "1.4.0", + "version": "1.4.1", "license": "GPL-3.0", "dependencies": { - "@next/bundle-analyzer": "13.4.7", - "@popperjs/core": "^2.11.5", - "bcrypt": "^5.0.1", - "bootstrap": "^5.2.0", - "bootstrap-icons": "^1.9.1", + "@next/bundle-analyzer": "^13.4.19", + "@popperjs/core": "^2.11.8", + "bcrypt": "^5.1.1", + "bootstrap": "^5.3.2", + "bootstrap-icons": "^1.11.0", "canvas": "^2.11.2", - "cli-progress": "^3.11.2", - "dotenv": "^16.0.3", - "formidable": "^2.0.1", + "cli-progress": "^3.12.0", + "dotenv": "^16.3.1", + "formidable": "^3.5.1", "gray-matter": "^4.0.3", - "highlight.js": "^11.7.0", - "iron-session": "^6.2.1", - "jsonwebtoken": "^9.0.0", - "mariadb": "^3.0.2", + "highlight.js": "^11.8.0", + "iron-session": "^6.3.1", + "jsonwebtoken": "^9.0.2", + "mariadb": "^3.2.1", "markdown-it": "^13.0.1", "markdown-it-highlightjs": "^4.0.1", - "mysql": "^2.18.1", - "next": "13.4.7", - "nodemailer": "^6.8.0", + "next": "^13.4.19", + "nodemailer": "^6.9.5", "pdf-lib": "^1.17.1", "probe-image-size": "^7.2.3", "react": "^18.2.0", - "react-bootstrap": "^2.4.0", + "react-bootstrap": "^2.8.0", "react-dom": "^18.2.0", - "react-dropzone": "^14.2.2", + "react-dropzone": "^14.2.3", "react-popper": "^2.3.0", - "sass": "^1.54.0", - "sharp": "^0.32.1", - "socket.io": "^4.5.1", - "socket.io-client": "^4.5.1", - "sprintf-js": "^1.1.2", - "swr": "^2.1.5", - "tslib": "^2.4.0" + "sass": "^1.67.0", + "sharp": "^0.32.5", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "sprintf-js": "^1.1.3", + "swr": "^2.2.2", + "tslib": "^2.6.2" }, "devDependencies": { - "@rmp135/sql-ts": "^1.15.1", + "@rmp135/sql-ts": "^1.18.0", "@types/bcrypt": "^5.0.0", - "@types/bootstrap": "^5.2.0", - "@types/cookie-parser": "^1.4.3", - "@types/ejs": "^3.1.1", - "@types/express": "^4.17.13", - "@types/express-mysql-session": "^2.1.3", - "@types/express-session": "^1.17.5", - "@types/formidable": "^2.0.5", + "@types/bootstrap": "^5.2.6", + "@types/cookie-parser": "^1.4.4", + "@types/ejs": "^3.1.2", + "@types/formidable": "^3.4.3", "@types/jsonwebtoken": "^9.0.2", "@types/mysql": "^2.15.21", - "@types/node": "^18.6", - "@types/nodemailer": "^6.4.6", - "@types/react": "^18.0.15", + "@types/node": "^20.6.0", + "@types/nodemailer": "^6.4.10", + "@types/react": "^18.2.21", "@types/sprintf-js": "^1.1.2", - "@types/webpack-env": "^1.18.0", - "@typescript-eslint/eslint-plugin": "^5.42.0", - "@typescript-eslint/parser": "^5.42.0", - "eslint": "^8.42.0", - "eslint-config-next": "13.4.7", - "eslint-config-prettier": "^8.5.0", + "@types/webpack-env": "^1.18.1", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "eslint": "^8.49.0", + "eslint-config-next": "^13.4.19", + "eslint-config-prettier": "^9.0.0", "eslint-plugin-simple-import-sort": "^10.0.0", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", - "tsconfig-paths": "^4.0.0", - "typescript": "^5.0.4" + "tsconfig-paths": "^4.2.0", + "typescript": "^5.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -129,9 +125,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -152,18 +148,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", - "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -238,31 +234,91 @@ } }, "node_modules/@next/bundle-analyzer": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.4.7.tgz", - "integrity": "sha512-cIcU07u6WdDUF1nrFYOSPvx/pie8euxSnWB1Y/XMYj2g5ls+iyaDr/ChyZugD5hNW754x0NFy1CwzpObA0bffQ==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.4.19.tgz", + "integrity": "sha512-nXKHz63dM0Kn3XPFOKrv2wK+hP9rdBg2iR1PsxuXLHVBoZhMyS1/ldRcX80YFsm2VUws9zM4a0E/1HlLI+P92g==", "dependencies": { "webpack-bundle-analyzer": "4.7.0" } }, "node_modules/@next/env": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.7.tgz", - "integrity": "sha512-ZlbiFulnwiFsW9UV1ku1OvX/oyIPLtMk9p/nnvDSwI0s7vSoZdRtxXNsaO+ZXrLv/pMbXVGq4lL8TbY9iuGmVw==" + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", + "integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.7.tgz", - "integrity": "sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", + "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", "dev": true, "dependencies": { "glob": "7.1.7" } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", + "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", + "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", + "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", + "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.7.tgz", - "integrity": "sha512-zaEC+iEiAHNdhl6fuwl0H0shnTzQoAoJiDYBUze8QTntE/GNPfTYpYboxF5LRYIjBwETUatvE0T64W6SKDipvg==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", + "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", "cpu": [ "x64" ], @@ -275,9 +331,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.7.tgz", - "integrity": "sha512-X6r12F8d8SKAtYJqLZBBMIwEqcTRvUdVm+xIq+l6pJqlgT2tNsLLf2i5Cl88xSsIytBICGsCNNHd+siD2fbWBA==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", + "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", "cpu": [ "x64" ], @@ -289,6 +345,51 @@ "node": ">= 10" } }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", + "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", + "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", + "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -600,9 +701,9 @@ "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==" }, "node_modules/@types/cookie-parser": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", - "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.4.tgz", + "integrity": "sha512-Var+aj5I6ZgIqsQ05N2V8q5OBrFfZXtIGWWDSrEYLIbMw758obagSwdGcLCjwh1Ga7M7+wj0SDIAaAC/WT7aaA==", "dev": true, "dependencies": { "@types/express": "*" @@ -644,15 +745,6 @@ "@types/serve-static": "*" } }, - "node_modules/@types/express-mysql-session": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/express-mysql-session/-/express-mysql-session-2.1.3.tgz", - "integrity": "sha512-Df6DqYTwvdH+L91Mn03Gv1qqFUUJueKb5fiFY+GbimwOKQuT7m9a5Ao8O7UZ86Cbri7S7yWFmw+fK7o4OP+voQ==", - "dev": true, - "dependencies": { - "@types/express-session": "*" - } - }, "node_modules/@types/express-serve-static-core": { "version": "4.17.35", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", @@ -664,19 +756,10 @@ "@types/send": "*" } }, - "node_modules/@types/express-session": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz", - "integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/formidable": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", - "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.3.tgz", + "integrity": "sha512-FRB5KNIYgCLDvw/ho7daBVqdQRG9Ptk1tMlZ2B5ibAjxDfXFkO6fJaP+ojmS8ljqmnC5If9u24WdWcVPaN3thg==", "dev": true, "dependencies": { "@types/node": "*" @@ -761,14 +844,14 @@ } }, "node_modules/@types/node": { - "version": "18.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", - "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==" + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==" }, "node_modules/@types/nodemailer": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", - "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.10.tgz", + "integrity": "sha512-oPW/IdhkU3FyZc1dzeqmS+MBjrjZNiiINnrEOrWALzccJlP5xTlbkNr2YnTnnyj9Eqm5ofjRoASEbrCYpA7BrA==", "dev": true, "dependencies": { "@types/node": "*" @@ -790,9 +873,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.19.tgz", - "integrity": "sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==", + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", + "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -813,9 +896,9 @@ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, "node_modules/@types/send": { @@ -867,32 +950,33 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", + "integrity": "sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/type-utils": "6.7.0", + "@typescript-eslint/utils": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -901,25 +985,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.0.tgz", + "integrity": "sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -928,16 +1013,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", + "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -945,25 +1030,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.0.tgz", + "integrity": "sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/utils": "6.7.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -972,12 +1057,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", + "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -985,21 +1070,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", + "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1012,42 +1097,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", + "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.7.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1439,12 +1523,12 @@ } }, "node_modules/bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.10", + "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" }, "engines": { @@ -1464,6 +1548,9 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -1523,9 +1610,9 @@ } }, "node_modules/bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", "funding": [ { "type": "github", @@ -1541,9 +1628,9 @@ } }, "node_modules/bootstrap-icons": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz", - "integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.0.tgz", + "integrity": "sha512-bLTbtACfUqwZf6f/xUYUb7bTRZC68QaQwwy9h1b96NPKfnwqzSatHqDypW6R2CBW7zUE7lP+O93GdZuPY3RIHA==", "funding": [ { "type": "github", @@ -1651,6 +1738,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -1929,7 +2017,10 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/cors": { "version": "2.8.5", @@ -2406,16 +2497,16 @@ } }, "node_modules/eslint": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", - "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.1", - "@eslint/js": "^8.46.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", @@ -2425,7 +2516,7 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.2", + "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", @@ -2460,20 +2551,20 @@ } }, "node_modules/eslint-config-next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.7.tgz", - "integrity": "sha512-+IRAyD0+J1MZaTi9RQMPUfr6Q+GCZ1wOkK6XM52Vokh7VI4R6YFGOFzdkEFHl4ZyIX4FKa5vcwUP2WscSFNjNQ==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", + "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "13.4.7", + "@next/eslint-plugin-next": "13.4.19", "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.42.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0", @@ -2486,9 +2577,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -2813,32 +2904,10 @@ "eslint": ">=5.0.0" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3124,14 +3193,13 @@ } }, "node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", + "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" + "once": "^1.4.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" @@ -3172,7 +3240,8 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -3233,6 +3302,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3340,9 +3410,9 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3490,6 +3560,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3530,6 +3601,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3541,6 +3613,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -4119,7 +4192,10 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/isexe": { "version": "2.0.0", @@ -4169,14 +4245,20 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dependencies": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">=12", @@ -4341,11 +4423,46 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4367,11 +4484,11 @@ } }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/make-dir": { @@ -4403,15 +4520,15 @@ "dev": true }, "node_modules/mariadb": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.2.0.tgz", - "integrity": "sha512-IH2nidQat1IBMxP5gjuNxG6dADtz1PESEC6rKrcATen5v3ngFyZITjehyYiwNfz3zUNQupfYmVntz93M+Pz8pQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.2.1.tgz", + "integrity": "sha512-EWCsvu8IwK5WRYPJ9iRekFiBfHfKB/wBcUwAzbhQKhSc6ivbvLcO/cktkYTCz3HVoGNYVWhBN38485Cq3qMSbQ==", "dependencies": { "@types/geojson": "^7946.0.10", "@types/node": "^17.0.45", "denque": "^2.1.0", "iconv-lite": "^0.6.3", - "lru-cache": "^7.14.0" + "lru-cache": "^10.0.1" }, "engines": { "node": ">= 12" @@ -4603,6 +4720,9 @@ "version": "2.18.1", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "bignumber.js": "9.0.0", "readable-stream": "2.3.7", @@ -4616,7 +4736,10 @@ "node_modules/mysql/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/nan": { "version": "2.17.0", @@ -4651,12 +4774,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/needle": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", @@ -4707,11 +4824,11 @@ "dev": true }, "node_modules/next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.7.tgz", - "integrity": "sha512-M8z3k9VmG51SRT6v5uDKdJXcAqLzP3C+vaKfLIAM0Mhx1um1G7MDnO63+m52qPdZfrTFzMZNzfsgvm3ghuVHIQ==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", + "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", "dependencies": { - "@next/env": "13.4.7", + "@next/env": "13.4.19", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -4727,19 +4844,18 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.7", - "@next/swc-darwin-x64": "13.4.7", - "@next/swc-linux-arm64-gnu": "13.4.7", - "@next/swc-linux-arm64-musl": "13.4.7", - "@next/swc-linux-x64-gnu": "13.4.7", - "@next/swc-linux-x64-musl": "13.4.7", - "@next/swc-win32-arm64-msvc": "13.4.7", - "@next/swc-win32-ia32-msvc": "13.4.7", - "@next/swc-win32-x64-msvc": "13.4.7" + "@next/swc-darwin-arm64": "13.4.19", + "@next/swc-darwin-x64": "13.4.19", + "@next/swc-linux-arm64-gnu": "13.4.19", + "@next/swc-linux-arm64-musl": "13.4.19", + "@next/swc-linux-x64-gnu": "13.4.19", + "@next/swc-linux-x64-musl": "13.4.19", + "@next/swc-win32-arm64-msvc": "13.4.19", + "@next/swc-win32-ia32-msvc": "13.4.19", + "@next/swc-win32-x64-msvc": "13.4.19" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "fibers": ">= 3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -4748,9 +4864,6 @@ "@opentelemetry/api": { "optional": true }, - "fibers": { - "optional": true - }, "sass": { "optional": true } @@ -4802,9 +4915,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", - "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==", + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.5.tgz", + "integrity": "sha512-/dmdWo62XjumuLc5+AYQZeiRj+PRR8y8qKtFCOyuOl1k/hckZd8durUUHs/ucKx6/8kN+wFxqKJlQ/LK/qR5FA==", "engines": { "node": ">=6.0.0" } @@ -4881,6 +4994,7 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5370,7 +5484,10 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/prop-types": { "version": "15.8.1", @@ -5428,20 +5545,6 @@ "node": ">=6.0.0" } }, - "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5605,6 +5708,9 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5618,7 +5724,10 @@ "node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -5923,9 +6032,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.64.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.2.tgz", - "integrity": "sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==", + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", + "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -6005,9 +6114,9 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/sharp": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.4.tgz", - "integrity": "sha512-exUnZewqVZC6UXqXuQ8fyJJv0M968feBi04jb9GcUHrWtkRoAKnbJt8IfwT4NJs7FskArbJ14JAFGVuooszoGg==", + "version": "0.32.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz", + "integrity": "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", @@ -6105,6 +6214,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -6267,14 +6377,17 @@ } }, "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -6510,10 +6623,11 @@ } }, "node_modules/swr": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", - "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", "dependencies": { + "client-only": "^0.0.1", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { @@ -6650,6 +6764,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -6775,30 +6901,9 @@ } }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -6901,9 +7006,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -7353,9 +7458,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -7370,15 +7475,15 @@ } }, "@eslint/js": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", - "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -7437,37 +7542,79 @@ } }, "@next/bundle-analyzer": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.4.7.tgz", - "integrity": "sha512-cIcU07u6WdDUF1nrFYOSPvx/pie8euxSnWB1Y/XMYj2g5ls+iyaDr/ChyZugD5hNW754x0NFy1CwzpObA0bffQ==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.4.19.tgz", + "integrity": "sha512-nXKHz63dM0Kn3XPFOKrv2wK+hP9rdBg2iR1PsxuXLHVBoZhMyS1/ldRcX80YFsm2VUws9zM4a0E/1HlLI+P92g==", "requires": { "webpack-bundle-analyzer": "4.7.0" } }, "@next/env": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.7.tgz", - "integrity": "sha512-ZlbiFulnwiFsW9UV1ku1OvX/oyIPLtMk9p/nnvDSwI0s7vSoZdRtxXNsaO+ZXrLv/pMbXVGq4lL8TbY9iuGmVw==" + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", + "integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==" }, "@next/eslint-plugin-next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.7.tgz", - "integrity": "sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", + "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", "dev": true, "requires": { "glob": "7.1.7" } }, + "@next/swc-darwin-arm64": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", + "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "optional": true + }, + "@next/swc-darwin-x64": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", + "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", + "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", + "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "optional": true + }, "@next/swc-linux-x64-gnu": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.7.tgz", - "integrity": "sha512-zaEC+iEiAHNdhl6fuwl0H0shnTzQoAoJiDYBUze8QTntE/GNPfTYpYboxF5LRYIjBwETUatvE0T64W6SKDipvg==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", + "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.7.tgz", - "integrity": "sha512-X6r12F8d8SKAtYJqLZBBMIwEqcTRvUdVm+xIq+l6pJqlgT2tNsLLf2i5Cl88xSsIytBICGsCNNHd+siD2fbWBA==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", + "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", + "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", + "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", + "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", "optional": true }, "@nodelib/fs.scandir": { @@ -7715,9 +7862,9 @@ "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==" }, "@types/cookie-parser": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", - "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.4.tgz", + "integrity": "sha512-Var+aj5I6ZgIqsQ05N2V8q5OBrFfZXtIGWWDSrEYLIbMw758obagSwdGcLCjwh1Ga7M7+wj0SDIAaAC/WT7aaA==", "dev": true, "requires": { "@types/express": "*" @@ -7759,15 +7906,6 @@ "@types/serve-static": "*" } }, - "@types/express-mysql-session": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/express-mysql-session/-/express-mysql-session-2.1.3.tgz", - "integrity": "sha512-Df6DqYTwvdH+L91Mn03Gv1qqFUUJueKb5fiFY+GbimwOKQuT7m9a5Ao8O7UZ86Cbri7S7yWFmw+fK7o4OP+voQ==", - "dev": true, - "requires": { - "@types/express-session": "*" - } - }, "@types/express-serve-static-core": { "version": "4.17.35", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", @@ -7779,19 +7917,10 @@ "@types/send": "*" } }, - "@types/express-session": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz", - "integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, "@types/formidable": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", - "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.3.tgz", + "integrity": "sha512-FRB5KNIYgCLDvw/ho7daBVqdQRG9Ptk1tMlZ2B5ibAjxDfXFkO6fJaP+ojmS8ljqmnC5If9u24WdWcVPaN3thg==", "dev": true, "requires": { "@types/node": "*" @@ -7876,14 +8005,14 @@ } }, "@types/node": { - "version": "18.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", - "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==" + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==" }, "@types/nodemailer": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", - "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.10.tgz", + "integrity": "sha512-oPW/IdhkU3FyZc1dzeqmS+MBjrjZNiiINnrEOrWALzccJlP5xTlbkNr2YnTnnyj9Eqm5ofjRoASEbrCYpA7BrA==", "dev": true, "requires": { "@types/node": "*" @@ -7905,9 +8034,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/react": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.19.tgz", - "integrity": "sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==", + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", + "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -7928,9 +8057,9 @@ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, "@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, "@types/send": { @@ -7982,102 +8111,103 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", + "integrity": "sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/type-utils": "6.7.0", + "@typescript-eslint/utils": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.0.tgz", + "integrity": "sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", + "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0" } }, "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.0.tgz", + "integrity": "sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/utils": "6.7.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", + "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", + "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", + "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.7.0", + "eslint-visitor-keys": "^3.4.1" } }, "abbrev": { @@ -8360,11 +8490,11 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "requires": { - "@mapbox/node-pre-gyp": "^1.0.10", + "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" } }, @@ -8377,7 +8507,10 @@ "bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "dev": true, + "optional": true, + "peer": true }, "binary-extensions": { "version": "2.2.0", @@ -8416,15 +8549,15 @@ } }, "bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", "requires": {} }, "bootstrap-icons": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz", - "integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.0.tgz", + "integrity": "sha512-bLTbtACfUqwZf6f/xUYUb7bTRZC68QaQwwy9h1b96NPKfnwqzSatHqDypW6R2CBW7zUE7lP+O93GdZuPY3RIHA==" }, "bplist-parser": { "version": "0.2.0", @@ -8493,6 +8626,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -8705,7 +8839,10 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "optional": true, + "peer": true }, "cors": { "version": "2.8.5", @@ -9074,16 +9211,16 @@ "dev": true }, "eslint": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", - "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.1", - "@eslint/js": "^8.46.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", @@ -9093,7 +9230,7 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.2", + "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", @@ -9131,26 +9268,26 @@ } }, "eslint-config-next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.7.tgz", - "integrity": "sha512-+IRAyD0+J1MZaTi9RQMPUfr6Q+GCZ1wOkK6XM52Vokh7VI4R6YFGOFzdkEFHl4ZyIX4FKa5vcwUP2WscSFNjNQ==", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", + "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", "dev": true, "requires": { - "@next/eslint-plugin-next": "13.4.7", + "@next/eslint-plugin-next": "13.4.19", "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.42.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" } }, "eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", "dev": true, "requires": {} }, @@ -9403,28 +9540,10 @@ "dev": true, "requires": {} }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "esm": { @@ -9626,14 +9745,13 @@ } }, "formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", + "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", "requires": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" + "once": "^1.4.0" } }, "fs-constants": { @@ -9667,7 +9785,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "function.prototype.name": { "version": "1.1.5", @@ -9713,6 +9832,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -9790,9 +9910,9 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -9901,6 +10021,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -9928,12 +10049,14 @@ "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true }, "has-tostringtag": { "version": "1.0.0", @@ -10309,7 +10432,10 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "optional": true, + "peer": true }, "isexe": { "version": "2.0.0", @@ -10350,14 +10476,20 @@ "dev": true }, "jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "requires": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" } }, "jsx-ast-utils": { @@ -10473,11 +10605,46 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -10496,9 +10663,9 @@ } }, "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==" }, "make-dir": { "version": "3.1.0", @@ -10522,15 +10689,15 @@ "dev": true }, "mariadb": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.2.0.tgz", - "integrity": "sha512-IH2nidQat1IBMxP5gjuNxG6dADtz1PESEC6rKrcATen5v3ngFyZITjehyYiwNfz3zUNQupfYmVntz93M+Pz8pQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.2.1.tgz", + "integrity": "sha512-EWCsvu8IwK5WRYPJ9iRekFiBfHfKB/wBcUwAzbhQKhSc6ivbvLcO/cktkYTCz3HVoGNYVWhBN38485Cq3qMSbQ==", "requires": { "@types/geojson": "^7946.0.10", "@types/node": "^17.0.45", "denque": "^2.1.0", "iconv-lite": "^0.6.3", - "lru-cache": "^7.14.0" + "lru-cache": "^10.0.1" }, "dependencies": { "@types/node": { @@ -10672,6 +10839,9 @@ "version": "2.18.1", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dev": true, + "optional": true, + "peer": true, "requires": { "bignumber.js": "9.0.0", "readable-stream": "2.3.7", @@ -10682,7 +10852,10 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true, + "peer": true } } }, @@ -10707,12 +10880,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "needle": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", @@ -10753,20 +10920,20 @@ "dev": true }, "next": { - "version": "13.4.7", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.7.tgz", - "integrity": "sha512-M8z3k9VmG51SRT6v5uDKdJXcAqLzP3C+vaKfLIAM0Mhx1um1G7MDnO63+m52qPdZfrTFzMZNzfsgvm3ghuVHIQ==", - "requires": { - "@next/env": "13.4.7", - "@next/swc-darwin-arm64": "13.4.7", - "@next/swc-darwin-x64": "13.4.7", - "@next/swc-linux-arm64-gnu": "13.4.7", - "@next/swc-linux-arm64-musl": "13.4.7", - "@next/swc-linux-x64-gnu": "13.4.7", - "@next/swc-linux-x64-musl": "13.4.7", - "@next/swc-win32-arm64-msvc": "13.4.7", - "@next/swc-win32-ia32-msvc": "13.4.7", - "@next/swc-win32-x64-msvc": "13.4.7", + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", + "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", + "requires": { + "@next/env": "13.4.19", + "@next/swc-darwin-arm64": "13.4.19", + "@next/swc-darwin-x64": "13.4.19", + "@next/swc-linux-arm64-gnu": "13.4.19", + "@next/swc-linux-arm64-musl": "13.4.19", + "@next/swc-linux-x64-gnu": "13.4.19", + "@next/swc-linux-x64-musl": "13.4.19", + "@next/swc-win32-arm64-msvc": "13.4.19", + "@next/swc-win32-ia32-msvc": "13.4.19", + "@next/swc-win32-x64-msvc": "13.4.19", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -10808,9 +10975,9 @@ } }, "nodemailer": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", - "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==" + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.5.tgz", + "integrity": "sha512-/dmdWo62XjumuLc5+AYQZeiRj+PRR8y8qKtFCOyuOl1k/hckZd8durUUHs/ucKx6/8kN+wFxqKJlQ/LK/qR5FA==" }, "nopt": { "version": "5.0.0", @@ -10861,7 +11028,8 @@ "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true }, "object-keys": { "version": "1.1.1", @@ -11220,7 +11388,10 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "optional": true, + "peer": true }, "prop-types": { "version": "15.8.1", @@ -11269,14 +11440,6 @@ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==" }, - "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "requires": { - "side-channel": "^1.0.4" - } - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11391,6 +11554,9 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "peer": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11404,7 +11570,10 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true, + "peer": true } } }, @@ -11605,9 +11774,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.64.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.2.tgz", - "integrity": "sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==", + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", + "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -11671,9 +11840,9 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "sharp": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.4.tgz", - "integrity": "sha512-exUnZewqVZC6UXqXuQ8fyJJv0M968feBi04jb9GcUHrWtkRoAKnbJt8IfwT4NJs7FskArbJ14JAFGVuooszoGg==", + "version": "0.32.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz", + "integrity": "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ==", "requires": { "color": "^4.2.3", "detect-libc": "^2.0.2", @@ -11734,6 +11903,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -11858,14 +12028,17 @@ } }, "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "dev": true, + "optional": true, + "peer": true }, "stream-parser": { "version": "0.3.1", @@ -12039,10 +12212,11 @@ "dev": true }, "swr": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", - "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", "requires": { + "client-only": "^0.0.1", "use-sync-external-store": "^1.2.0" } }, @@ -12143,6 +12317,13 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "requires": {} + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -12225,26 +12406,9 @@ } }, "tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tunnel-agent": { "version": "0.6.0", @@ -12317,9 +12481,9 @@ } }, "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index f32af84f..4d79db94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "snip", - "version": "1.4.0", + "version": "1.4.2", "description": "The digital labbook", "scripts": { "dev": "npm run prebuild && ts-node --project ./app/tsconfig.json ./app/server.ts", @@ -25,66 +25,62 @@ "author": "Sebastian B. Mohr, Markus Osterhoff", "license": "GPL-3.0", "devDependencies": { - "@rmp135/sql-ts": "^1.15.1", + "@rmp135/sql-ts": "^1.18.0", "@types/bcrypt": "^5.0.0", - "@types/bootstrap": "^5.2.0", - "@types/cookie-parser": "^1.4.3", - "@types/ejs": "^3.1.1", - "@types/express": "^4.17.13", - "@types/express-mysql-session": "^2.1.3", - "@types/express-session": "^1.17.5", - "@types/formidable": "^2.0.5", + "@types/bootstrap": "^5.2.6", + "@types/cookie-parser": "^1.4.4", + "@types/ejs": "^3.1.2", + "@types/formidable": "^3.4.3", "@types/jsonwebtoken": "^9.0.2", "@types/mysql": "^2.15.21", - "@types/node": "^18.6", - "@types/nodemailer": "^6.4.6", - "@types/react": "^18.0.15", + "@types/node": "^20.6.0", + "@types/nodemailer": "^6.4.10", + "@types/react": "^18.2.21", "@types/sprintf-js": "^1.1.2", - "@types/webpack-env": "^1.18.0", - "@typescript-eslint/eslint-plugin": "^5.42.0", - "@typescript-eslint/parser": "^5.42.0", - "eslint": "^8.42.0", - "eslint-config-next": "13.4.7", - "eslint-config-prettier": "^8.5.0", + "@types/webpack-env": "^1.18.1", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "eslint": "^8.49.0", + "eslint-config-next": "^13.4.19", + "eslint-config-prettier": "^9.0.0", "eslint-plugin-simple-import-sort": "^10.0.0", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", - "tsconfig-paths": "^4.0.0", - "typescript": "^5.0.4" + "tsconfig-paths": "^4.2.0", + "typescript": "^5.2.2" }, "dependencies": { - "@next/bundle-analyzer": "13.4.7", - "@popperjs/core": "^2.11.5", - "bcrypt": "^5.0.1", - "bootstrap": "^5.2.0", - "bootstrap-icons": "^1.9.1", + "@next/bundle-analyzer": "^13.4.19", + "@popperjs/core": "^2.11.8", + "bcrypt": "^5.1.1", + "bootstrap": "^5.3.2", + "bootstrap-icons": "^1.11.0", "canvas": "^2.11.2", - "cli-progress": "^3.11.2", - "dotenv": "^16.0.3", - "formidable": "^2.0.1", + "cli-progress": "^3.12.0", + "dotenv": "^16.3.1", + "formidable": "^3.5.1", "gray-matter": "^4.0.3", - "highlight.js": "^11.7.0", - "iron-session": "^6.2.1", - "jsonwebtoken": "^9.0.0", - "mariadb": "^3.0.2", + "highlight.js": "^11.8.0", + "iron-session": "^6.3.1", + "jsonwebtoken": "^9.0.2", + "mariadb": "^3.2.1", "markdown-it": "^13.0.1", "markdown-it-highlightjs": "^4.0.1", - "mysql": "^2.18.1", - "next": "13.4.7", - "nodemailer": "^6.8.0", + "next": "^13.4.19", + "nodemailer": "^6.9.5", "pdf-lib": "^1.17.1", "probe-image-size": "^7.2.3", "react": "^18.2.0", - "react-bootstrap": "^2.4.0", + "react-bootstrap": "^2.8.0", "react-dom": "^18.2.0", - "react-dropzone": "^14.2.2", + "react-dropzone": "^14.2.3", "react-popper": "^2.3.0", - "sass": "^1.54.0", - "sharp": "^0.32.1", - "socket.io": "^4.5.1", - "socket.io-client": "^4.5.1", - "sprintf-js": "^1.1.2", - "swr": "^2.1.5", - "tslib": "^2.4.0" + "sass": "^1.67.0", + "sharp": "^0.32.5", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "sprintf-js": "^1.1.3", + "swr": "^2.2.2", + "tslib": "^2.6.2" } } -- GitLab From 4abf96473a9ceee56dc65deebe3406f4c7cf9c61 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Thu, 14 Sep 2023 20:23:12 +0200 Subject: [PATCH 03/15] Same order token>user for all routes and added alert for token. --- app/components/viewer/sidebar/index.tsx | 20 +++++++++++++++----- app/lib/access_control/books.ts | 21 ++++++++++----------- app/pages/books/[id]/index.tsx | 16 +++++++++++++++- app/socket/auth.ts | 2 -- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/app/components/viewer/sidebar/index.tsx b/app/components/viewer/sidebar/index.tsx index 21feec11..5b6233a1 100644 --- a/app/components/viewer/sidebar/index.tsx +++ b/app/components/viewer/sidebar/index.tsx @@ -1,4 +1,5 @@ import { fetcher } from "lib/fetcher"; +import useUser from "lib/useUser"; import { useDebounce } from "lib/utils"; import { createContext, @@ -105,9 +106,11 @@ export function SidebarContextProvider({ export function Sidebar({ book, showSnipsToPlace, + tokenAlert = false, }: { book: Book; showSnipsToPlace: boolean; + tokenAlert?: boolean; }) { const { searchTermPages, @@ -135,16 +138,23 @@ export function Sidebar({ }, fetcher); // Check if book is read only i.e. finished - let readOnly; + const alerts = []; if (book?.finished) { - readOnly = ( + alerts.push( <div className={styles.readOnly + " alert alert-danger"}> This book is finished! It can no longer be edited. All changes are only local and will not be saved. </div> ); - } else { - readOnly = null; + } + + if (tokenAlert) { + alerts.push( + <div className={styles.readOnly + " alert alert-danger"}> + You are using a temporary access token you can only view the + book. + </div> + ); } // Page previews for the pages tab @@ -204,7 +214,7 @@ export function Sidebar({ </li> ) : null} </ul> - {readOnly} + {alerts} <div className={styles.topNavigation}> <SearchBar isValidating={isValidating} diff --git a/app/lib/access_control/books.ts b/app/lib/access_control/books.ts index 092bb5cc..57b8f08f 100644 --- a/app/lib/access_control/books.ts +++ b/app/lib/access_control/books.ts @@ -38,24 +38,25 @@ export function withAuthBookIdSsr< const { req, query } = ctx; const book_id = query.id as string; const user_id = req.session.user_id; - // First check for user - if (book_id && user_id) { - const perms = await service.permission.getACLByUserBookReduced( - user_id, - parseInt(book_id) - ); - return await handler(ctx, perms); - } + // Check if token is present const url = new URL(req.url, "http://localhost"); const token = url.searchParams.get("token"); if (book_id && token) { - console.log("Ui token found", token); const perms = await verify_with_token(token, book_id); return await handler(ctx, perms); } + // First check for user + if (book_id && user_id) { + const perms = await service.permission.getACLByUserBookReduced( + user_id, + parseInt(book_id) + ); + return await handler(ctx, perms); + } + return { redirect: { permanent: false, @@ -105,7 +106,6 @@ export function withAuthBookId(handler: NextApiHandlerWithPerms) { export async function bookAuth(user_id: number, book_id: string | number, token?: string) { let perms; if (token) { - console.log("Api token found", token); perms = await verify_with_token(token, book_id); } else { perms = await service.permission.getACLByUserBookReduced( @@ -129,7 +129,6 @@ async function verify_with_token( hashed_token, ]) .then((res) => { - console.log(res); if (res.length > 0) { return { found: true, diff --git a/app/pages/books/[id]/index.tsx b/app/pages/books/[id]/index.tsx index 155c6379..9cc29b02 100644 --- a/app/pages/books/[id]/index.tsx +++ b/app/pages/books/[id]/index.tsx @@ -18,6 +18,7 @@ import { type Tool } from "components/viewer/tools"; export default function BookById({ bookData, perms }) { // Create a book const [book, setBook] = useState<Book>(); + const [tokenUsed, setTokenUsed] = useState(false); // Register desired tools (snipEditor is always last) const desiredTools: Tool[] = ["movePage"]; @@ -40,12 +41,25 @@ export default function BookById({ bookData, perms }) { } }, [bookData]); + useEffect(() => { + // Check params for token + const urlParams = new URLSearchParams(window.location.search); + const token = urlParams.get("token"); + if (token) { + setTokenUsed(true); + } + }, []); + // Return loading spinner if (!book || !perms) return <Loading />; return ( <ViewerContext> - <Sidebar book={book} showSnipsToPlace={perms.pWrite} /> + <Sidebar + book={book} + showSnipsToPlace={perms.pWrite} + tokenAlert={tokenUsed} + /> <Resizer /> <div style={{ diff --git a/app/socket/auth.ts b/app/socket/auth.ts index 502be798..aae984ed 100644 --- a/app/socket/auth.ts +++ b/app/socket/auth.ts @@ -31,14 +31,12 @@ export async function authenticate(socket: Socket, next) { export async function add_perms(socket: Socket, next) { const perms = await bookAuth(socket.user_id, socket.book_id, socket.auth_token); - console.log(perms); socket.perms = perms; return next(); } export async function update_perms(socket: Socket) { const perms = await bookAuth(socket.user_id, socket.book_id, socket.auth_token); - console.log(perms); socket.perms = perms; return } -- GitLab From 5741328cbbf7bd7a464363101e63d9a37a8e8ce0 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Thu, 14 Sep 2023 20:38:37 +0200 Subject: [PATCH 04/15] Removed some undefined variables --- app/components/books/edit/ShareLinks.tsx | 2 +- app/components/viewer/sidebar/index.tsx | 3 +- app/controller/websocket.ts | 111 ----------------------- app/pages/api/authentication/isAdmin.ts | 1 - app/pages/api/books/[id]/edit/set_acl.ts | 1 - app/pages/api/books/[id]/edit/update.ts | 1 - app/server.ts | 1 - app/snips/general/image.ts | 3 +- app/snips/general/text.ts | 2 +- app/version.ts | 2 +- 10 files changed, 5 insertions(+), 122 deletions(-) delete mode 100644 app/controller/websocket.ts diff --git a/app/components/books/edit/ShareLinks.tsx b/app/components/books/edit/ShareLinks.tsx index 38c88567..ee11d75c 100644 --- a/app/components/books/edit/ShareLinks.tsx +++ b/app/components/books/edit/ShareLinks.tsx @@ -81,7 +81,7 @@ export default function ShareLinks() { onSubmit={(e) => { e.preventDefault(); createNewToken(book.id, description, "ui", expiration).then( - (r) => { + () => { setDescription(""); } ); diff --git a/app/components/viewer/sidebar/index.tsx b/app/components/viewer/sidebar/index.tsx index 5b6233a1..c1efa342 100644 --- a/app/components/viewer/sidebar/index.tsx +++ b/app/components/viewer/sidebar/index.tsx @@ -1,5 +1,4 @@ import { fetcher } from "lib/fetcher"; -import useUser from "lib/useUser"; import { useDebounce } from "lib/utils"; import { createContext, @@ -151,7 +150,7 @@ export function Sidebar({ if (tokenAlert) { alerts.push( <div className={styles.readOnly + " alert alert-danger"}> - You are using a temporary access token you can only view the + You are using a temporary access token you shall only view this book. </div> ); diff --git a/app/controller/websocket.ts b/app/controller/websocket.ts deleted file mode 100644 index e2acf25f..00000000 --- a/app/controller/websocket.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { io, Socket } from "socket.io-client"; - -//Set global clientSockets -if (!globalThis.ClientSockets) { - globalThis.ClientSockets = new Map<string, Socket>(); -} - -/** Creates Client side socket connection to the server. - * At the moment only one socket connection is allowed per client. - * - * @param book_id - * @returns {Socket} - */ -const createClientSocket = async function ( - book_id: number | string -): Promise<Socket> { - if (!isBrowser() && !isWorker()) { - throw new Error("Not in browser or worker"); - } - // Check if socket already exists - if (globalThis.ClientSockets.has(String(book_id))) { - return globalThis.ClientSockets.get(String(book_id)); - } - - // Fetch token for the user - const { token } = await fetch("/api/authentication/socket_token", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ book_id }), - }).then((res) => { - if (res.status === 200) { - return res.json(); - } else { - alert("Could not establish secure connection"); - throw new Error("Could not fetch token"); - } - }); - - const book = String(book_id); - - const socket = io("/book-" + book, { - auth: { - token: token, - }, - }); - - socket.on("error", (error) => { - console.log("Socket error", error); - }); - - await globalThis.ClientSockets.set(book, socket); - return socket; -}; - -/** Create a client socket - * @param book_id - */ -export async function getClientSocket(book_id = undefined): Promise<Socket> { - if (!isBrowser() && !isWorker()) { - throw new Error("Not in browser or worker"); - } - if (!routeSupported() && book_id == undefined) { - throw new Error("Route not supported"); - } - - let book = book_id || window.location.pathname.split("/")[2]; - book = String(book); - - let socket: Socket; - if (!globalThis.ClientSockets.has(book)) { - //Waiting for 0.5 second and then try again - await new Promise((resolve) => setTimeout(resolve, 500)); - socket = await getClientSocket(book_id); - } else { - socket = globalThis.ClientSockets.get(book); - } - - // Connect if not connected - if (!socket.connected) { - await socket.connect(); - } - return socket; -} - -export function isBrowser(): boolean { - return typeof window !== "undefined"; -} - -export function isWorker(): boolean { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return ( - typeof WorkerGlobalScope !== "undefined" && - self instanceof WorkerGlobalScope - ); -} - -export function routeSupported(): boolean { - if (isBrowser()) { - if ( - window.location.pathname.split("/")[1] != "books" || - window.location.pathname == "/books" - ) { - return false; - } - return true; - } - return false; -} diff --git a/app/pages/api/authentication/isAdmin.ts b/app/pages/api/authentication/isAdmin.ts index 2b84684d..c5f8ee1d 100644 --- a/app/pages/api/authentication/isAdmin.ts +++ b/app/pages/api/authentication/isAdmin.ts @@ -1,7 +1,6 @@ import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; -import getSocketClient from "../socket"; export default withSessionRoute(isAdmin); /** THIS API SHOULD NOT BE USED FOR CRITICAL APPLICATION PARTS diff --git a/app/pages/api/books/[id]/edit/set_acl.ts b/app/pages/api/books/[id]/edit/set_acl.ts index 6b155410..49b0a1cc 100644 --- a/app/pages/api/books/[id]/edit/set_acl.ts +++ b/app/pages/api/books/[id]/edit/set_acl.ts @@ -4,7 +4,6 @@ import { NextApiRequest, NextApiResponse } from "next/types"; import { ApiError, service } from "pages/api/_service"; import { parseACL } from "pages/api/admin/set_acl"; import getSocketClient from "pages/api/socket"; -import { update_perms } from "socket/auth"; import { ENTITY_USER, diff --git a/app/pages/api/books/[id]/edit/update.ts b/app/pages/api/books/[id]/edit/update.ts index 8f62e5e1..2b09d711 100644 --- a/app/pages/api/books/[id]/edit/update.ts +++ b/app/pages/api/books/[id]/edit/update.ts @@ -3,7 +3,6 @@ import { withSessionRoute } from "lib/withSession"; import { NextApiRequest, NextApiResponse } from "next/types"; import { ApiError } from "pages/api/_service"; import getSocketClient from "pages/api/socket"; -import { update_perms } from "socket/auth"; import { PermissionACLData } from "database/services/service.typings"; import { pool_data } from "database/sql.connection"; diff --git a/app/server.ts b/app/server.ts index b4af5b01..8b95f42a 100644 --- a/app/server.ts +++ b/app/server.ts @@ -6,7 +6,6 @@ import { createServer as createServerHttp } from "http"; import { createServer as createServerHttps } from "https"; import next from "next"; import { getSocketIOServer } from "socket"; -import { type Server as IOServer } from "socket.io"; import { parse } from "url"; // Own function to setup server diff --git a/app/snips/general/image.ts b/app/snips/general/image.ts index 8cd92a76..46c08fd3 100644 --- a/app/snips/general/image.ts +++ b/app/snips/general/image.ts @@ -3,8 +3,7 @@ import { sync as probe_sync } from "probe-image-size"; import { BaseData, BaseSnip, BaseSnipArgs, BaseView, SnipData } from "./base"; -import { isWorker } from "controller/booksocket"; -import { isBrowser } from "controller/websocket"; +import { isBrowser, isWorker } from "controller/booksocket"; /** As we use the canvas package on the server side * we need to check if we are in the browser or not diff --git a/app/snips/general/text.ts b/app/snips/general/text.ts index 946463b7..91e75541 100644 --- a/app/snips/general/text.ts +++ b/app/snips/general/text.ts @@ -1,6 +1,6 @@ import { BaseData, BaseSnip, BaseSnipArgs, BaseView, SnipData } from "./base"; -import { isBrowser } from "controller/websocket"; +import { isBrowser } from "controller/booksocket"; export const allowedFontFamilies = [ "PlexMono", diff --git a/app/version.ts b/app/version.ts index b37e1107..9fbbd7c3 100644 --- a/app/version.ts +++ b/app/version.ts @@ -1,4 +1,4 @@ /* This file is generated by scripts/prebuild.ts */ /* eslint-disable */ -export const LIB_VERSION = "1.4.0"; \ No newline at end of file +export const LIB_VERSION = "1.4.2"; \ No newline at end of file -- GitLab From 24a09b09bc345e7f770957f3d9b0cf18638ddfb9 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Fri, 15 Sep 2023 11:43:22 +0200 Subject: [PATCH 05/15] Removed some inline svgs. --- app/components/books/edit/BookInfo.tsx | 5 ++- app/components/viewer/sidebar/downloadBtn.tsx | 21 +++-------- .../viewer/sidebar/fullScreenBtn.tsx | 37 +++++-------------- .../viewer/sidebar/layoutSwitchBtn.tsx | 36 +++++------------- .../viewer/sidebar/pageSwitchBtns.tsx | 3 -- .../viewer/sidebar/sidebar.module.scss | 7 ++++ app/next.config.js | 1 + app/public/img/viewer/download.svg | 11 ++++++ app/public/img/viewer/fullscreen_enter.svg | 9 +++++ app/public/img/viewer/fullscreen_exit.svg | 9 +++++ app/public/img/viewer/layout_single.svg | 8 ++++ app/public/img/viewer/layout_split.svg | 9 +++++ 12 files changed, 83 insertions(+), 73 deletions(-) create mode 100644 app/public/img/viewer/download.svg create mode 100644 app/public/img/viewer/fullscreen_enter.svg create mode 100644 app/public/img/viewer/fullscreen_exit.svg create mode 100644 app/public/img/viewer/layout_single.svg create mode 100644 app/public/img/viewer/layout_split.svg diff --git a/app/components/books/edit/BookInfo.tsx b/app/components/books/edit/BookInfo.tsx index f1ecd5eb..e2554d0e 100644 --- a/app/components/books/edit/BookInfo.tsx +++ b/app/components/books/edit/BookInfo.tsx @@ -1,4 +1,5 @@ -import Image from "next/image"; +/* eslint-disable @next/next/no-img-element */ +//import Image from "next/image"; import Link from "next/link"; import React, { useContext, useState } from "react"; import { Alert, Button, Form, InputGroup } from "react-bootstrap"; @@ -179,7 +180,7 @@ const Preview = () => { return ( <div className={styles.preview}> <Link href={`/books/${book.id}`}> - <Image + <img src={"/api/books/" + book.id + "/cover_preview"} alt="No cover page set!" width={240} diff --git a/app/components/viewer/sidebar/downloadBtn.tsx b/app/components/viewer/sidebar/downloadBtn.tsx index 391a8c1f..f0b80fa5 100644 --- a/app/components/viewer/sidebar/downloadBtn.tsx +++ b/app/components/viewer/sidebar/downloadBtn.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import { useState } from "react"; import Modal from "components/common/Modal"; @@ -15,7 +16,11 @@ export default function DownloadBtn({ book }) { data-active={false} onClick={() => setShow(true)} > - {icon} + <Image + src="/img/viewer/download.svg" + alt="Download" + layout="fill" + /> </button> <Modal show={show} onHide={() => setShow(false)} centered> @@ -28,17 +33,3 @@ export default function DownloadBtn({ book }) { </> ); } - -const icon = ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - fill="currentColor" - className="bi bi-file-earmark-arrow-down" - viewBox="0 0 16 16" - > - <path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293V6.5z" /> - <path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z" /> - </svg> -); diff --git a/app/components/viewer/sidebar/fullScreenBtn.tsx b/app/components/viewer/sidebar/fullScreenBtn.tsx index 9925e9d6..2bc62395 100644 --- a/app/components/viewer/sidebar/fullScreenBtn.tsx +++ b/app/components/viewer/sidebar/fullScreenBtn.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import { useState } from "react"; import styles from "./sidebar.module.scss"; @@ -18,34 +19,16 @@ export default function FullScreenButton() { return ( <div className={styles.fullscreenBtn} data-active={false}> <button className="btn" onClick={toggleFullScreen}> - {isFullScreen ? fullscreen_exit : fullscreen_enter} + <Image + src={ + isFullScreen + ? "/img/viewer/fullscreen_exit.svg" + : "/img/viewer/fullscreen_enter.svg" + } + alt="Fullscreen" + layout="fill" + /> </button> </div> ); } - -const fullscreen_enter = ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - fill="currentColor" - className="bi bi-fullscreen" - viewBox="0 0 16 16" - > - <path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1h-4zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zM.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5z" /> - </svg> -); - -const fullscreen_exit = ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - fill="currentColor" - className="bi bi-fullscreen-exit" - viewBox="0 0 16 16" - > - <path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5zm5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5zM0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zm10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4z" /> - </svg> -); diff --git a/app/components/viewer/sidebar/layoutSwitchBtn.tsx b/app/components/viewer/sidebar/layoutSwitchBtn.tsx index 50235e1b..97519c48 100644 --- a/app/components/viewer/sidebar/layoutSwitchBtn.tsx +++ b/app/components/viewer/sidebar/layoutSwitchBtn.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import { useContext } from "react"; import { SidebarContext } from "."; @@ -36,33 +37,16 @@ export default function LayoutSwitchBtn({ book }: { book: Book }) { return ( <div className={styles.layoutSwitchBtn} data-active={isMultiPage}> <button className="btn" onClick={onClick}> - {isMultiPage ? svg_layout : svg_layout_split} + <Image + src={ + isMultiPage + ? "/img/viewer/layout_single.svg" + : "/img/viewer/layout_split.svg" + } + alt="Fullscreen" + layout="fill" + /> </button> </div> ); } - -const svg_layout_split = ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - fill="currentColor" - className="bi bi-layout-split" - viewBox="0 0 16 16" - > - <path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm8.5-1v12H14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H8.5zm-1 0H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.5V2z" /> - </svg> -); - -const svg_layout = ( - <svg - className="bi bi-layout" - fill="currentColor" - version="1.1" - viewBox="0 0 16 16" - xmlns="http://www.w3.org/2000/svg" - > - <path d="m0 3c0-1.1046 0.89543-2 2-2h12c1.1046 0 2 0.89543 2 2v10c0 1.1046-0.89543 2-2 2h-12c-1.1046 0-2-0.89543-2-2zm2-1c-0.55228 0-1 0.44772-1 1v10c0 0.55228 0.44772 1 1 1h12c0.55228 0 1-0.44772 1-1v-10c0-0.55228-0.44772-1-1-1z" /> - </svg> -); diff --git a/app/components/viewer/sidebar/pageSwitchBtns.tsx b/app/components/viewer/sidebar/pageSwitchBtns.tsx index acca8d59..eeed9b0a 100644 --- a/app/components/viewer/sidebar/pageSwitchBtns.tsx +++ b/app/components/viewer/sidebar/pageSwitchBtns.tsx @@ -131,7 +131,6 @@ const File_left = (props) => { width="16" height="16" fill="currentColor" - className="bi bi-file-left" viewBox="0 0 16 16" {...props} > @@ -153,7 +152,6 @@ const File_right = (props) => { width="16" height="16" fill="currentColor" - className="bi bi-file-right" viewBox="0 0 16 16" {...props} > @@ -172,7 +170,6 @@ const File_plus = (props) => { width="16" height="16" fill="currentColor" - className="bi bi-file-plus" viewBox="0 0 16 16" {...props} > diff --git a/app/components/viewer/sidebar/sidebar.module.scss b/app/components/viewer/sidebar/sidebar.module.scss index 02a4aa23..f1e13419 100644 --- a/app/components/viewer/sidebar/sidebar.module.scss +++ b/app/components/viewer/sidebar/sidebar.module.scss @@ -81,6 +81,13 @@ display: flex; height: 100%; flex: 1 0; + position: relative; + img { + display: flex; + justify-content: space-evenly; + align-items: center; + padding: 5px; + } a, button { diff --git a/app/next.config.js b/app/next.config.js index 7dfca2e8..8a29e2ec 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -19,6 +19,7 @@ module.exports = withBundleAnalyzer({ // Do not show the X-Powered-By header in the responses poweredByHeader: false, compress: true, + swcMinify: true, eslint: { dirs: [ "lib", diff --git a/app/public/img/viewer/download.svg b/app/public/img/viewer/download.svg new file mode 100644 index 00000000..792feef4 --- /dev/null +++ b/app/public/img/viewer/download.svg @@ -0,0 +1,11 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="currentColor" + className="bi bi-file-earmark-arrow-down" + viewBox="0 0 16 16" +> + <path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293V6.5z" /> + <path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z" /> +</svg> \ No newline at end of file diff --git a/app/public/img/viewer/fullscreen_enter.svg b/app/public/img/viewer/fullscreen_enter.svg new file mode 100644 index 00000000..3af0d136 --- /dev/null +++ b/app/public/img/viewer/fullscreen_enter.svg @@ -0,0 +1,9 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="currentColor" + viewBox="0 0 16 16" +> + <path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1h-4zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zM.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5z" /> +</svg> \ No newline at end of file diff --git a/app/public/img/viewer/fullscreen_exit.svg b/app/public/img/viewer/fullscreen_exit.svg new file mode 100644 index 00000000..b68088a4 --- /dev/null +++ b/app/public/img/viewer/fullscreen_exit.svg @@ -0,0 +1,9 @@ + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="currentColor" + viewBox="0 0 16 16" + > + <path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5zm5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5zM0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zm10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4z" /> + </svg> \ No newline at end of file diff --git a/app/public/img/viewer/layout_single.svg b/app/public/img/viewer/layout_single.svg new file mode 100644 index 00000000..74b2b639 --- /dev/null +++ b/app/public/img/viewer/layout_single.svg @@ -0,0 +1,8 @@ +<svg + fill="currentColor" + version="1.1" + viewBox="0 0 16 16" + xmlns="http://www.w3.org/2000/svg" +> + <path d="m0 3c0-1.1046 0.89543-2 2-2h12c1.1046 0 2 0.89543 2 2v10c0 1.1046-0.89543 2-2 2h-12c-1.1046 0-2-0.89543-2-2zm2-1c-0.55228 0-1 0.44772-1 1v10c0 0.55228 0.44772 1 1 1h12c0.55228 0 1-0.44772 1-1v-10c0-0.55228-0.44772-1-1-1z" /> +</svg> \ No newline at end of file diff --git a/app/public/img/viewer/layout_split.svg b/app/public/img/viewer/layout_split.svg new file mode 100644 index 00000000..99645890 --- /dev/null +++ b/app/public/img/viewer/layout_split.svg @@ -0,0 +1,9 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="currentColor" + viewBox="0 0 16 16" +> + <path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm8.5-1v12H14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H8.5zm-1 0H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.5V2z" /> +</svg> \ No newline at end of file -- GitLab From 619544b9a57d7703dc432dfd631a0bf0a2bb80e0 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 15:50:47 +0200 Subject: [PATCH 06/15] Fixed overlay sometimes persisted changes and reenabled live updates. Also fixed bug that background lines are not rendered. --- CHANGELOG.md | 2 + app/.eslintrc.json | 3 +- app/components/viewer/page.tsx | 6 +- app/components/viewer/sidebar/downloadBtn.tsx | 6 +- .../viewer/sidebar/fullScreenBtn.tsx | 2 +- .../viewer/sidebar/layoutSwitchBtn.tsx | 2 +- app/components/viewer/tools/doodle/drawing.ts | 2 +- .../viewer/worker/overlayWorker.tsx | 71 +++++++++++++++++++ .../viewer/worker/pageWorker/messaging.ts | 4 +- .../backgrounds/render_background_by_id.ts | 2 +- app/controller/booksocket.ts | 5 +- app/next.config.js | 5 ++ 12 files changed, 96 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d85f5dc8..9e51d03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - tools are dynamically picked by access level - see new field in /book/:id/edit/access - (Fix) Fixed a bug where the snips to place where not updated when a user adds a new snip +- (Fix) Overlay sometimes persisted on page change +- (Feature) Reenabled old live updates for drawing (might be disabled again depending on performance) # 1.4.1 diff --git a/app/.eslintrc.json b/app/.eslintrc.json index ae8e0820..6e180808 100644 --- a/app/.eslintrc.json +++ b/app/.eslintrc.json @@ -37,6 +37,7 @@ ] } ], - "simple-import-sort/exports": "error" + "simple-import-sort/exports": "error", + "@typescript-eslint/no-unused-vars": "warn" } } diff --git a/app/components/viewer/page.tsx b/app/components/viewer/page.tsx index 737b9648..c0a0638e 100644 --- a/app/components/viewer/page.tsx +++ b/app/components/viewer/page.tsx @@ -96,12 +96,15 @@ export default function SinglePageViewer({ /** Initial setup only run once * */ + console.log("Initial setup all"); const worker = workers[idx]; const overlayWorker = overlayWorkers[idx]; const draggable = draggableRef.current.get(idx); const pageData = pageDataRef.current.get(idx); worker.resize(draggable.clientWidth, draggable.clientHeight); + overlayWorker.resize(draggable.clientWidth, draggable.clientHeight); worker.setupPage(pageData); + overlayWorker.setupPage(pageData); centerPage(idx); /** Setup Rerender on resize @@ -340,12 +343,13 @@ export function PagesContextProvider({ for (let i = 0; i < s; i++) { const pD = pageDataRef.current.get(i); if (pD) { + overlayWorkers[i].setupPage(pD); workers[i].setupPage(pD); } } setVisible(true); }, - [updateHash, updateNumPages, workers] + [overlayWorkers, updateHash, updateNumPages, workers] ); useEffect(() => { diff --git a/app/components/viewer/sidebar/downloadBtn.tsx b/app/components/viewer/sidebar/downloadBtn.tsx index f0b80fa5..ba060a08 100644 --- a/app/components/viewer/sidebar/downloadBtn.tsx +++ b/app/components/viewer/sidebar/downloadBtn.tsx @@ -16,11 +16,7 @@ export default function DownloadBtn({ book }) { data-active={false} onClick={() => setShow(true)} > - <Image - src="/img/viewer/download.svg" - alt="Download" - layout="fill" - /> + <Image src="/img/viewer/download.svg" alt="Download" fill /> </button> <Modal show={show} onHide={() => setShow(false)} centered> diff --git a/app/components/viewer/sidebar/fullScreenBtn.tsx b/app/components/viewer/sidebar/fullScreenBtn.tsx index 2bc62395..cc8449dc 100644 --- a/app/components/viewer/sidebar/fullScreenBtn.tsx +++ b/app/components/viewer/sidebar/fullScreenBtn.tsx @@ -26,7 +26,7 @@ export default function FullScreenButton() { : "/img/viewer/fullscreen_enter.svg" } alt="Fullscreen" - layout="fill" + fill /> </button> </div> diff --git a/app/components/viewer/sidebar/layoutSwitchBtn.tsx b/app/components/viewer/sidebar/layoutSwitchBtn.tsx index 97519c48..869c6f5d 100644 --- a/app/components/viewer/sidebar/layoutSwitchBtn.tsx +++ b/app/components/viewer/sidebar/layoutSwitchBtn.tsx @@ -44,7 +44,7 @@ export default function LayoutSwitchBtn({ book }: { book: Book }) { : "/img/viewer/layout_split.svg" } alt="Fullscreen" - layout="fill" + fill /> </button> </div> diff --git a/app/components/viewer/tools/doodle/drawing.ts b/app/components/viewer/tools/doodle/drawing.ts index c9ca66b1..372f5067 100644 --- a/app/components/viewer/tools/doodle/drawing.ts +++ b/app/components/viewer/tools/doodle/drawing.ts @@ -114,7 +114,7 @@ export function useDrawing( overlayWorkers[idx].render(); // Send preview event to other clients - //active_snip.send_preview(); + overlayWorkers[idx].sendPreviewData(active_snip.to_data()); } } // Pointer move into canvas diff --git a/app/components/viewer/worker/overlayWorker.tsx b/app/components/viewer/worker/overlayWorker.tsx index f6f49e89..bdd1c659 100644 --- a/app/components/viewer/worker/overlayWorker.tsx +++ b/app/components/viewer/worker/overlayWorker.tsx @@ -14,6 +14,11 @@ import { MutableRefObject, useEffect, useState } from "react"; import { type EleMap } from "../page"; +import { BookSocket, getSocket } from "controller/booksocket"; +import { PageData } from "controller/page"; +import { BaseData, BaseView, SnipData } from "snips/general/base"; +import { get_snip_from_data } from "snips/get_snip_from_data"; + const debug = (...args: unknown[]) => { console.debug("[OverlayWorker]", ...args); }; @@ -24,6 +29,9 @@ export class OverlayWorker { private stack = new Array<(ctx: CanvasRenderingContext2D) => void>(); + private pageData: PageData | null = null; + private socket: BookSocket | null = null; + constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext("2d", { @@ -39,6 +47,69 @@ export class OverlayWorker { }); } + public setupPage(_page: PageData) { + // Check if page is already setup + if (this.pageData && this.pageData.id === _page.id) { + return; + } + debug("Setup page:"); + this.pageData = _page; + + // Clear stack from previews + for (const renderfn of this.preview2render.values()) { + this.remove(renderfn); + } + this.preview2render.clear(); + this.ctx.clearRect(0, 0, 1400, 2000); + this.render(); + + // Set socket in async function + (async () => { + this.socket = await getSocket(this.pageData.book_id); + //join room + await new Promise<void>((resolve, reject) => { + this.socket.emit( + "page:join", + this.pageData.id, + (success: boolean) => { + if (success) { + resolve(); + } else { + reject(); + } + } + ); + }); + this.socket.on("snip:preview", this.receivePreviewData.bind(this)); + })(); + } + + public sendPreviewData(snipData: SnipData<BaseData, BaseView>) { + if (!this.socket) return; + this.socket.volatile.emit("snip:preview", snipData); + } + + private preview2render = new Map< + number, + (ctx: CanvasRenderingContext2D) => void + >(); + public receivePreviewData(snipData: SnipData<BaseData, BaseView>) { + if (!this.pageData) return; + + // Check if snip is already rendered and remove it + if (this.preview2render.has(snipData.id)) { + const renderfn = this.preview2render.get(snipData.id); + this.remove(renderfn); + } + + // Add to stack + const snip = get_snip_from_data(snipData); + this.preview2render.set(snipData.id, (ctx) => { + snip.render(ctx); + }); + this.add(this.preview2render.get(snipData.id)); + } + public resize(width: number, height: number) { // Normally the canvas should always be 1400x2000 this.canvas.width = width; diff --git a/app/components/viewer/worker/pageWorker/messaging.ts b/app/components/viewer/worker/pageWorker/messaging.ts index 837234ce..baf2c8ab 100644 --- a/app/components/viewer/worker/pageWorker/messaging.ts +++ b/app/components/viewer/worker/pageWorker/messaging.ts @@ -49,6 +49,8 @@ export interface ResizeMsg { height: number; } + + export type Message = | InitMsg | SetupPageMsg @@ -56,4 +58,4 @@ export type Message = | RenderMsg | ResizeMsg | InsertSnipMsg - | ShutdownMsg; + | ShutdownMsg; \ No newline at end of file diff --git a/app/controller/backgrounds/render_background_by_id.ts b/app/controller/backgrounds/render_background_by_id.ts index 8053c05f..2029efa3 100644 --- a/app/controller/backgrounds/render_background_by_id.ts +++ b/app/controller/backgrounds/render_background_by_id.ts @@ -12,7 +12,7 @@ export function render_background_by_id( case background_types["checkered 4mm x 4mm"]: render_checkered(ctx, 4, 4); break; - case background_types["lined 7mm"]: + case background_types["lines 7mm"]: render_lined(ctx, 7, scaling); break; case background_types["blank"]: diff --git a/app/controller/booksocket.ts b/app/controller/booksocket.ts index 7a269fd1..8dff481d 100644 --- a/app/controller/booksocket.ts +++ b/app/controller/booksocket.ts @@ -67,8 +67,7 @@ export class BookSocket { console.log(err); }); socket.on("connect", () => { - console.log("Connected to book socket"); - console.log(socket); + console.log("Connected to server via websocket"); }); this.socket = socket; } @@ -129,3 +128,5 @@ export function isBrowser(): boolean { export function isWorker(): boolean { return typeof importScripts === "function"; } + + diff --git a/app/next.config.js b/app/next.config.js index 8a29e2ec..9035e30e 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -64,4 +64,9 @@ module.exports = withBundleAnalyzer({ ]; } }, + compiler: { + removeConsole: { + exclude: ['error'], + }, + }, }); -- GitLab From 11e387a5ac8a3bf9880cab902f5a4a21154abf9a Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 16:24:29 +0200 Subject: [PATCH 07/15] Fixed chrome bug with urlsearchparams not serializable... --- app/components/viewer/sidebar/index.tsx | 10 ++++++++-- app/components/viewer/worker/pageWorker.tsx | 8 ++++++-- app/components/viewer/worker/pageWorker/messaging.ts | 2 +- app/components/viewer/worker/pageWorker/worker.ts | 2 +- app/middleware.ts | 3 ++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/components/viewer/sidebar/index.tsx b/app/components/viewer/sidebar/index.tsx index c1efa342..024284f7 100644 --- a/app/components/viewer/sidebar/index.tsx +++ b/app/components/viewer/sidebar/index.tsx @@ -140,7 +140,10 @@ export function Sidebar({ const alerts = []; if (book?.finished) { alerts.push( - <div className={styles.readOnly + " alert alert-danger"}> + <div + className={styles.readOnly + " alert alert-danger"} + key={"finished"} + > This book is finished! It can no longer be edited. All changes are only local and will not be saved. </div> @@ -149,7 +152,10 @@ export function Sidebar({ if (tokenAlert) { alerts.push( - <div className={styles.readOnly + " alert alert-danger"}> + <div + className={styles.readOnly + " alert alert-danger"} + key={"token"} + > You are using a temporary access token you shall only view this book. </div> diff --git a/app/components/viewer/worker/pageWorker.tsx b/app/components/viewer/worker/pageWorker.tsx index 99d8943d..407347e6 100644 --- a/app/components/viewer/worker/pageWorker.tsx +++ b/app/components/viewer/worker/pageWorker.tsx @@ -11,7 +11,7 @@ * the main thread. And also via typical socket communication. * */ -import { MutableRefObject, useEffect, useState } from "react"; +import { MutableRefObject, useEffect, useRef, useState } from "react"; import type { SetupPageMsg } from "./pageWorker/messaging"; @@ -112,7 +112,11 @@ export class PageWorker { const url_params = new URLSearchParams(window.location.search); const offscreen = this.canvas.transferControlToOffscreen(); this.worker.postMessage( - { type: "init", canvas: offscreen, url_params }, + { + type: "init", + canvas: offscreen, + url_params: url_params.toString(), + }, [offscreen] ); return; diff --git a/app/components/viewer/worker/pageWorker/messaging.ts b/app/components/viewer/worker/pageWorker/messaging.ts index baf2c8ab..1049430a 100644 --- a/app/components/viewer/worker/pageWorker/messaging.ts +++ b/app/components/viewer/worker/pageWorker/messaging.ts @@ -7,7 +7,7 @@ export interface InitMsg { */ type: "init"; canvas: OffscreenCanvas; - url_params: URLSearchParams; + url_params: string; } export interface ShutdownMsg { diff --git a/app/components/viewer/worker/pageWorker/worker.ts b/app/components/viewer/worker/pageWorker/worker.ts index 05d82c85..627d7975 100644 --- a/app/components/viewer/worker/pageWorker/worker.ts +++ b/app/components/viewer/worker/pageWorker/worker.ts @@ -187,7 +187,7 @@ self.onmessage = async (event: MessageEvent<Message>) => { case "init": globalThis.worker = new PageWorker(event.data.canvas); - globalThis.url_params = event.data.url_params; + globalThis.url_params = new URLSearchParams(event.data.url_params); break; case "shutdown": globalThis.worker.shutdown(); diff --git a/app/middleware.ts b/app/middleware.ts index b070334e..f4a312fd 100644 --- a/app/middleware.ts +++ b/app/middleware.ts @@ -153,10 +153,11 @@ async function middleware_admin(request: NextRequest): Promise<NextResponse> { * "_next/image", * "favicon.ico", * "frontpage", + * "/img" */ export const config = { matcher: [ - "/((?!frontpage|account/login|account/signup|account/resetPassword|account/verify|account/forgotPassword|api/authentication/verify|api/authentication/resendVerify|api/account/login|api/account/signup|api/account/resetPassword|api/unauthorized|unauthorized|docs|privacy|imprint|_next/static|_next/image|favicon.ico).*)", + "/((?!frontpage|account/login|account/signup|account/resetPassword|account/verify|account/forgotPassword|api/authentication/verify|api/authentication/resendVerify|api/account/login|api/account/signup|api/account/resetPassword|api/unauthorized|unauthorized|docs|img|privacy|imprint|_next/static|_next/image|favicon.ico).*)", ], }; -- GitLab From ecee7b069e1bc84bef79833158828cfe5681b651 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 16:56:47 +0200 Subject: [PATCH 08/15] Added a token expired page --- app/components/forms/LoginForm.tsx | 2 +- app/lib/access_control/books.ts | 26 +++++++++++---- app/middleware.ts | 3 +- app/pages/books/[id]/index.tsx | 11 +++++++ app/pages/token_expired.tsx | 51 ++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 app/pages/token_expired.tsx diff --git a/app/components/forms/LoginForm.tsx b/app/components/forms/LoginForm.tsx index b8260aa2..4832b09b 100644 --- a/app/components/forms/LoginForm.tsx +++ b/app/components/forms/LoginForm.tsx @@ -44,7 +44,7 @@ export function LoginForm(props) { const [error, setError] = useState(null); useEffect(() => { - if (user) { + if (user.id > 0) { if (props.prev_url) { router.push(props.prev_url); } diff --git a/app/lib/access_control/books.ts b/app/lib/access_control/books.ts index 57b8f08f..b553bca2 100644 --- a/app/lib/access_control/books.ts +++ b/app/lib/access_control/books.ts @@ -31,7 +31,7 @@ export function withAuthBookIdSsr< >( handler: ( context: GetServerSidePropsContext<Q, D>, - perms: PermissionACLData + perms: PermissionACLData & { token_used: boolean, expires_at?: Date } ) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>> ) { return async (ctx: GetServerSidePropsContext<Q, D>) => { @@ -53,7 +53,8 @@ export function withAuthBookIdSsr< const perms = await service.permission.getACLByUserBookReduced( user_id, parseInt(book_id) - ); + ) as PermissionACLData & { token_used: boolean, expires_at?: Date }; + perms.token_used = false; return await handler(ctx, perms); } @@ -117,13 +118,12 @@ export async function bookAuth(user_id: number, book_id: string | number, token? } - async function verify_with_token( token: string, book_id: string | number, -): Promise<PermissionACLData> { +): Promise<PermissionACLData & { token_used: boolean, expires_at?: Date }> { const hashed_token = hashToken(token); - const { found, type } = await pool_permissions + const { found, type, expires_at } = await pool_permissions .query("SELECT * FROM tokens WHERE book_id = ? AND hashed_token = ?", [ book_id, hashed_token, @@ -133,11 +133,13 @@ async function verify_with_token( return { found: true, type: res[0].type, + expires_at: res[0].expires_at as Date } } else { return { found: false, type: null, + expires_at: null } } }) @@ -146,10 +148,12 @@ async function verify_with_token( return { found: false, type: null, + expires_at: null } }); - const perms: PermissionACLData = { + const perms: PermissionACLData & { token_used: boolean, expires_at?: Date } + = { entity_id: -1, entity_type: ENTITY_TOKEN, resource_id: parseInt(book_id as string), @@ -158,12 +162,20 @@ async function verify_with_token( pDelete: false, pRead: false, pWrite: false, + token_used: true, + expires_at: null } - if (!found) { return perms; } + if (expires_at) { + // Should work as it is valid in the database + perms.expires_at = new Date(expires_at) + if (perms.expires_at < new Date()) { + return perms; + } + } switch (type) { case "api": diff --git a/app/middleware.ts b/app/middleware.ts index f4a312fd..017ec91e 100644 --- a/app/middleware.ts +++ b/app/middleware.ts @@ -154,10 +154,11 @@ async function middleware_admin(request: NextRequest): Promise<NextResponse> { * "favicon.ico", * "frontpage", * "/img" + * "token_expired", */ export const config = { matcher: [ - "/((?!frontpage|account/login|account/signup|account/resetPassword|account/verify|account/forgotPassword|api/authentication/verify|api/authentication/resendVerify|api/account/login|api/account/signup|api/account/resetPassword|api/unauthorized|unauthorized|docs|img|privacy|imprint|_next/static|_next/image|favicon.ico).*)", + "/((?!frontpage|account/login|account/signup|account/resetPassword|account/verify|account/forgotPassword|api/authentication/verify|api/authentication/resendVerify|api/account/login|api/account/signup|api/account/resetPassword|api/unauthorized|unauthorized|docs|img|token_expired|privacy|imprint|_next/static|_next/image|favicon.ico).*)", ], }; diff --git a/app/pages/books/[id]/index.tsx b/app/pages/books/[id]/index.tsx index 9cc29b02..55118a49 100644 --- a/app/pages/books/[id]/index.tsx +++ b/app/pages/books/[id]/index.tsx @@ -93,6 +93,17 @@ export const getServerSideProps = withSessionSsr( return { props: { bookData, title: bookData.title, perms: p } }; } + if (perms.token_used && !perms.pRead) { + const expired_date = new Date(perms.expires_at); + return { + redirect: { + permanent: false, + destination: + "/token_expired/?expired=" + expired_date.toISOString(), + }, + }; + } + return { redirect: { permanent: false, diff --git a/app/pages/token_expired.tsx b/app/pages/token_expired.tsx new file mode 100644 index 00000000..6760167c --- /dev/null +++ b/app/pages/token_expired.tsx @@ -0,0 +1,51 @@ +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +import { getLayout } from "./account/login"; + +/** This route is show if an user does an action for which + * he is not authorized. + */ +export default function TokenExpired({ expired }) { + const router = useRouter(); + const [date, setDate] = useState(); + + useEffect(() => { + setDate(new Date(expired).toLocaleString()); + }, [expired]); + + return ( + <> + <div + className="d-flex alert alert-warning flex-column" + role="alert" + style={{ + fontWeight: "bold", + }} + > + <p>The token your are trying to use has expired on {date}.</p> + <p>Please request a new token or login to access your book.</p> + <button + className="btn btn-primary mt-2" + onClick={() => { + router.push("/"); + }} + > + Home + </button> + </div> + </> + ); +} + +TokenExpired.getLayout = getLayout; + +export const getServerSideProps = async (context) => { + const { expired } = context.query; + + return { + props: { + expired, + }, + }; +}; -- GitLab From a87564e6894ddba8e9dd71517059d2331fae8239 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 16:59:49 +0200 Subject: [PATCH 09/15] Reload on new link creation --- app/components/books/edit/ShareLinks.tsx | 1 + app/pages/token_expired.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/books/edit/ShareLinks.tsx b/app/components/books/edit/ShareLinks.tsx index ee11d75c..c9a9e811 100644 --- a/app/components/books/edit/ShareLinks.tsx +++ b/app/components/books/edit/ShareLinks.tsx @@ -83,6 +83,7 @@ export default function ShareLinks() { createNewToken(book.id, description, "ui", expiration).then( () => { setDescription(""); + mutate(); } ); }} diff --git a/app/pages/token_expired.tsx b/app/pages/token_expired.tsx index 6760167c..9c865575 100644 --- a/app/pages/token_expired.tsx +++ b/app/pages/token_expired.tsx @@ -8,7 +8,7 @@ import { getLayout } from "./account/login"; */ export default function TokenExpired({ expired }) { const router = useRouter(); - const [date, setDate] = useState(); + const [date, setDate] = useState<string>(); useEffect(() => { setDate(new Date(expired).toLocaleString()); -- GitLab From 5bee15c69b5aa43174cc1855b199fb10ffd0a350 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 17:12:01 +0200 Subject: [PATCH 10/15] Mutation of config now done optimistic. --- app/components/viewer/tools/movePage/resizable.ts | 2 +- app/lib/useUser.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/components/viewer/tools/movePage/resizable.ts b/app/components/viewer/tools/movePage/resizable.ts index 1292770d..7111e22f 100644 --- a/app/components/viewer/tools/movePage/resizable.ts +++ b/app/components/viewer/tools/movePage/resizable.ts @@ -98,9 +98,9 @@ export function useResizable( if (!isLoadingConfig && config && firstRender.current) { firstRender.current = false; _setZoom(config.zoom1, 0); - if (numPages > 1) _setZoom(config.zoom2, 1); return; } + if (numPages > 1) _setZoom(config.zoom2, 1); // Update the config when the text styles change }, [config, containerRef, isLoadingConfig, numPages, _setZoom]); diff --git a/app/lib/useUser.ts b/app/lib/useUser.ts index 4ce17826..52ab7d4a 100644 --- a/app/lib/useUser.ts +++ b/app/lib/useUser.ts @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; import useSWR, { KeyedMutator } from "swr"; import { fetcher } from "./fetcher"; @@ -40,6 +40,7 @@ export default function useUser() { const { data: config, error: errorConfig, + mutate: mutateConfig, } = useSWR<UserConfigData>("/api/account/config", fetcher, { revalidateIfStale: false, revalidateOnFocus: true, @@ -65,7 +66,14 @@ export default function useUser() { /** Debounce server update to prevent api spam */ - const [upd, setUpd] = useState<Partial<UserConfigData> | undefined>(undefined); + const [upd, _setUpd] = useState<Partial<UserConfigData> | undefined>(undefined); + const setUpd = useCallback((newUpd: Partial<UserConfigData>) => { + _setUpd(newUpd); + //Update local config + const newConfig = { ...config, ...newUpd }; + mutateConfig(newConfig, false); + }, [config, mutateConfig]); + const debounceUpdate = useDebounce(upd, 2000); // wait for 2 seconds before update database useEffect(() => { if (debounceUpdate) { @@ -73,7 +81,7 @@ export default function useUser() { setUpd(undefined); }); } - }, [debounceUpdate, config]) + }, [debounceUpdate, config, mutateConfig, setUpd]) const ret: UserHook = { -- GitLab From 0eeac7663ae47e0e314647240365ff579414285e Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 17:52:25 +0200 Subject: [PATCH 11/15] Hydration error in account json --- app/components/utils/PrettyPrintJson.tsx | 10 +++++++++- app/components/viewer/tools/movePage.tool.tsx | 1 + app/lib/useUser.ts | 10 +++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/components/utils/PrettyPrintJson.tsx b/app/components/utils/PrettyPrintJson.tsx index 07d8a877..13a5593d 100644 --- a/app/components/utils/PrettyPrintJson.tsx +++ b/app/components/utils/PrettyPrintJson.tsx @@ -1,7 +1,15 @@ +import { useEffect, useState } from "react"; + export default function PrettyPrintJson(props) { + const [data, setData] = useState<string>(); + + useEffect(() => { + setData(JSON.stringify(props, null, 2)); + }, [props]); + return ( <div> - <pre>{JSON.stringify(props, null, 2)}</pre> + <pre>{data}</pre> </div> ); } diff --git a/app/components/viewer/tools/movePage.tool.tsx b/app/components/viewer/tools/movePage.tool.tsx index 886c1a83..6cc59d70 100644 --- a/app/components/viewer/tools/movePage.tool.tsx +++ b/app/components/viewer/tools/movePage.tool.tsx @@ -181,6 +181,7 @@ export function MovePageToolContextProvider({ pointerUpHandlerDrag, touchMoveHandlerResize, touchEndHandlerResize, + numPages, ]); const ret: MovePageToolContext = { diff --git a/app/lib/useUser.ts b/app/lib/useUser.ts index 52ab7d4a..34fe2800 100644 --- a/app/lib/useUser.ts +++ b/app/lib/useUser.ts @@ -25,7 +25,7 @@ export default function useUser() { mutate: mutateUser, error: errorUser, } = useSWR<UserData>("/api/account/user", fetcher, { - revalidateIfStale: false, + revalidateIfStale: true, revalidateOnFocus: false, revalidateOnReconnect: false, fallbackData: { @@ -42,7 +42,7 @@ export default function useUser() { error: errorConfig, mutate: mutateConfig, } = useSWR<UserConfigData>("/api/account/config", fetcher, { - revalidateIfStale: false, + revalidateIfStale: true, revalidateOnFocus: true, revalidateOnReconnect: true, fallbackData: { @@ -71,17 +71,17 @@ export default function useUser() { _setUpd(newUpd); //Update local config const newConfig = { ...config, ...newUpd }; - mutateConfig(newConfig, false); + mutateConfig(newConfig, { revalidate: false }); }, [config, mutateConfig]); const debounceUpdate = useDebounce(upd, 2000); // wait for 2 seconds before update database useEffect(() => { - if (debounceUpdate) { + if (debounceUpdate && user.id > 0) { updateServer(debounceUpdate, config).then(() => { setUpd(undefined); }); } - }, [debounceUpdate, config, mutateConfig, setUpd]) + }, [debounceUpdate, config, mutateConfig, setUpd, user.id]) const ret: UserHook = { -- GitLab From 206611c2afaa64278a921f02ac371ecd4d34dfe8 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 18:00:30 +0200 Subject: [PATCH 12/15] Duplicate draw on overlay with two pages fixed --- app/components/viewer/worker/overlayWorker.tsx | 7 ++++++- app/socket/snip/socket.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/components/viewer/worker/overlayWorker.tsx b/app/components/viewer/worker/overlayWorker.tsx index bdd1c659..ce6fea8b 100644 --- a/app/components/viewer/worker/overlayWorker.tsx +++ b/app/components/viewer/worker/overlayWorker.tsx @@ -53,6 +53,11 @@ export class OverlayWorker { return; } debug("Setup page:"); + if (this.pageData && this.socket) { + // Leave room + this.socket.emit("page:leave", this.pageData.id); + } + this.pageData = _page; // Clear stack from previews @@ -94,7 +99,7 @@ export class OverlayWorker { (ctx: CanvasRenderingContext2D) => void >(); public receivePreviewData(snipData: SnipData<BaseData, BaseView>) { - if (!this.pageData) return; + if (!this.pageData || snipData.page_id !== this.pageData.id) return; // Check if snip is already rendered and remove it if (this.preview2render.has(snipData.id)) { diff --git a/app/socket/snip/socket.ts b/app/socket/snip/socket.ts index a75a563c..fe4c16b9 100644 --- a/app/socket/snip/socket.ts +++ b/app/socket/snip/socket.ts @@ -76,8 +76,9 @@ export function setupSnipPrefix({ socket, book_nsp }: SocketData) { ); } - socket.volatile + socket .to("page-" + String(SnipData.page_id)) + .volatile .emit("snip:preview", SnipData); } ); -- GitLab From 75b667a6a0a9c5319ae049d6e3c71c2f6d184d79 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 18:05:58 +0200 Subject: [PATCH 13/15] Added gray border to multipage view to distinguish between pages easier --- app/components/viewer/page.module.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/components/viewer/page.module.scss b/app/components/viewer/page.module.scss index 70af3d24..a86b7810 100644 --- a/app/components/viewer/page.module.scss +++ b/app/components/viewer/page.module.scss @@ -11,6 +11,11 @@ & * { touch-action: none !important; } + // Not last borders + &:not(:last-child) { + border-right: 1px solid $gray-200; + //drop-shadow(0px 0px 2px $gray-300); + } } $width_canvas: 1400px; -- GitLab From 4f70970b4cc939255fed52ee67a55dc079b24bde Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 18:16:09 +0200 Subject: [PATCH 14/15] fixed small problem with user always available because of fallback --- app/components/common/Navbar.tsx | 2 +- app/components/forms/SignupForm.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/common/Navbar.tsx b/app/components/common/Navbar.tsx index 2387aa82..b156db10 100644 --- a/app/components/common/Navbar.tsx +++ b/app/components/common/Navbar.tsx @@ -75,7 +75,7 @@ function NavbarDropdown(props: NavbarDropdownProps) { // Render profile and logout button if user is logged in // else login form let elements = null; - if (user) { + if (user.id > 0) { elements = ( <> <li> diff --git a/app/components/forms/SignupForm.tsx b/app/components/forms/SignupForm.tsx index 955e8710..2136ab3c 100644 --- a/app/components/forms/SignupForm.tsx +++ b/app/components/forms/SignupForm.tsx @@ -43,7 +43,7 @@ export function SignupForm(props) { const router = useRouter(); const { user, mutateUser } = useUser(); useEffect(() => { - if (user) { + if (user.id > 0) { const redirect = props.prev_url || ""; if (redirect[0] === "/") { router.push(redirect); -- GitLab From 6c964cfc657a6f9486002cf250b3103ea542425e Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 18 Sep 2023 18:26:07 +0200 Subject: [PATCH 15/15] Increase version number --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f97f260f..0998d51f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "snip", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "snip", - "version": "1.4.1", + "version": "1.4.2", "license": "GPL-3.0", "dependencies": { "@next/bundle-analyzer": "^13.4.19", -- GitLab