diff --git a/Makefile b/Makefile index 7c5aa4c..5b37ce2 100644 --- a/Makefile +++ b/Makefile @@ -10,3 +10,7 @@ prod: seed: node ace db:seed + +down: + -docker compose down + -docker compose -f dev.docker-compose.yml down diff --git a/app/controllers/searches_controller.ts b/app/controllers/searches_controller.ts new file mode 100644 index 0000000..4055122 --- /dev/null +++ b/app/controllers/searches_controller.ts @@ -0,0 +1,17 @@ +import type { HttpContext } from '@adonisjs/core/http'; +import db from '@adonisjs/lucid/services/db'; + +export default class SearchesController { + async search({ request, auth }: HttpContext) { + const term = request.qs()?.term; + if (!term) { + return console.warn('qs term null'); + } + + const { rows } = await db.rawQuery('SELECT * FROM search_text(?, ?)', [ + term, + auth.user!.id, + ]); + return { results: rows }; + } +} diff --git a/app/validators/search.ts b/app/validators/search.ts deleted file mode 100644 index 503dc2d..0000000 --- a/app/validators/search.ts +++ /dev/null @@ -1,9 +0,0 @@ -import vine from '@vinejs/vine'; - -export const searchValidator = vine.compile( - vine.object({ - searchTerm: vine.string().trim().minLength(1).maxLength(254), - links: vine.boolean(), - collections: vine.boolean(), - }) -); diff --git a/database/migrations/1716561885061_create_full_text_searches_table.ts b/database/migrations/1716561885061_create_full_text_searches_table.ts new file mode 100644 index 0000000..ee23cad --- /dev/null +++ b/database/migrations/1716561885061_create_full_text_searches_table.ts @@ -0,0 +1,52 @@ +import { BaseSchema } from '@adonisjs/lucid/schema'; + +export default class extends BaseSchema { + async up() { + this.schema.raw(` + CREATE EXTENSION IF NOT EXISTS unaccent; + CREATE EXTENSION IF NOT EXISTS pg_trgm; + `); + this.schema.raw(` + CREATE INDEX ON links USING gin(to_tsvector('english', name)); + CREATE INDEX ON collections USING gin(to_tsvector('english', name)); + CREATE INDEX ON links USING gin(to_tsvector('french', name)); + CREATE INDEX ON collections USING gin(to_tsvector('french', name)); + `); + this.schema.raw(` + CREATE OR REPLACE FUNCTION search_text(search_query TEXT, p_author_id INTEGER) + RETURNS TABLE ( + id INTEGER, + type TEXT, + name VARCHAR(254), + collection_id INTEGER, + matched_part TEXT, + rank DOUBLE PRECISION + ) + AS $$ + BEGIN + RETURN QUERY + SELECT links.id, 'link' AS type, links.name, collections.id AS collection_id, + ts_headline('english', unaccent(links.name), plainto_tsquery('english', unaccent(search_query))) AS matched_part, + ts_rank_cd(to_tsvector('english', unaccent(links.name)), plainto_tsquery('english', unaccent(search_query)))::DOUBLE PRECISION AS rank + FROM links + LEFT JOIN collections ON links.collection_id = collections.id + WHERE unaccent(links.name) ILIKE '%' || unaccent(search_query) || '%' + AND (p_author_id IS NULL OR links.author_id = p_author_id) + UNION ALL + SELECT collections.id, 'collection' AS type, collections.name, NULL AS collection_id, + ts_headline('english', unaccent(collections.name), plainto_tsquery('english', unaccent(search_query))) AS matched_part, + ts_rank_cd(to_tsvector('english', unaccent(collections.name)), plainto_tsquery('english', unaccent(search_query)))::DOUBLE PRECISION AS rank + FROM collections + WHERE unaccent(collections.name) ILIKE '%' || unaccent(search_query) || '%' + AND (p_author_id IS NULL OR collections.author_id = p_author_id) + ORDER BY rank DESC NULLS LAST, matched_part DESC NULLS LAST; + END; + $$ + LANGUAGE plpgsql; + `); + } + + async down() { + this.schema.raw('DROP FUNCTION IF EXISTS search_text'); + } +} diff --git a/inertia/components/common/modal/modal.tsx b/inertia/components/common/modal/modal.tsx index b163f7f..cfb372d 100644 --- a/inertia/components/common/modal/modal.tsx +++ b/inertia/components/common/modal/modal.tsx @@ -17,6 +17,7 @@ interface ModalProps { title?: string; children: ReactNode; opened: boolean; + hideCloseBtn?: boolean; close: () => void; } @@ -25,13 +26,14 @@ export default function Modal({ title, children, opened = true, + hideCloseBtn = false, close, }: ModalProps) { const modalRef = useRef(null); const { setGlobalHotkeysEnabled } = useGlobalHotkeys(); useClickOutside(modalRef, close); - useShortcut('ESCAPE_KEY', close, { ignoreGlobalHotkeysStatus: true }); + useShortcut('ESCAPE_KEY', close); useEffect(() => setGlobalHotkeysEnabled(!opened), [opened]); @@ -44,12 +46,17 @@ export default function Modal({ createPortal( - - {title && {title}} - - - - + {!hideCloseBtn || + (title && ( + + {title && {title}} + {!hideCloseBtn && ( + + + + )} + + ))} {children} , diff --git a/inertia/components/dashboard/collection/list/collection_item.tsx b/inertia/components/dashboard/collection/list/collection_item.tsx index 95eb8d1..8a544fe 100644 --- a/inertia/components/dashboard/collection/list/collection_item.tsx +++ b/inertia/components/dashboard/collection/list/collection_item.tsx @@ -1,17 +1,21 @@ import type Collection from '#models/collection'; import styled from '@emotion/styled'; +import { Link } from '@inertiajs/react'; +import { route } from '@izzyjs/route/client'; import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai'; import TextEllipsis from '~/components/common/text_ellipsis'; import { Item } from '~/components/dashboard/side_nav/nav_item'; import useActiveCollection from '~/hooks/use_active_collection'; +import { appendCollectionId } from '~/lib/navigation'; -const CollectionItemStyle = styled(Item)<{ isActive: boolean }>( - ({ theme, isActive }) => ({ - cursor: 'pointer', - color: isActive ? theme.colors.primary : theme.colors.font, - backgroundColor: theme.colors.secondary, - }) -); +const CollectionItemStyle = styled(Item, { + shouldForwardProp: (propName) => propName !== 'isActive', +})<{ isActive: boolean }>(({ theme, isActive }) => ({ + cursor: 'pointer', + color: isActive ? theme.colors.primary : theme.colors.font, + backgroundColor: theme.colors.secondary, +})); +const CollectionItemLink = CollectionItemStyle.withComponent(Link); const LinksCount = styled.div(({ theme }) => ({ minWidth: 'fit-content', @@ -25,13 +29,13 @@ export default function CollectionItem({ }: { collection: Collection; }) { - const { activeCollection, setActiveCollection } = useActiveCollection(); + const { activeCollection } = useActiveCollection(); const isActiveCollection = collection.id === activeCollection?.id; const FolderIcon = isActiveCollection ? AiFillFolderOpen : AiOutlineFolder; return ( - setActiveCollection(collection)} + @@ -39,6 +43,6 @@ export default function CollectionItem({ {collection.links.length > 0 && ( — {collection.links.length} )} - + ); } diff --git a/inertia/components/dashboard/search/search_modal.tsx b/inertia/components/dashboard/search/search_modal.tsx new file mode 100644 index 0000000..36c20b5 --- /dev/null +++ b/inertia/components/dashboard/search/search_modal.tsx @@ -0,0 +1,147 @@ +import styled from '@emotion/styled'; +import { route } from '@izzyjs/route/client'; +import { FormEvent, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AiOutlineFolder } from 'react-icons/ai'; +import { CiLink } from 'react-icons/ci'; +import { IoIosSearch } from 'react-icons/io'; +import Modal from '~/components/common/modal/modal'; +import UnstyledList from '~/components/common/unstyled/unstyled_list'; +import useCollections from '~/hooks/use_collections'; +import useToggle from '~/hooks/use_modal'; +import useShortcut from '~/hooks/use_shortcut'; +import { makeRequest } from '~/lib/request'; +import { SearchResult } from '~/types/search'; + +const SearchInput = styled.input(({ theme }) => ({ + width: '100%', + fontSize: '20px', + color: theme.colors.font, + backgroundColor: 'transparent', + paddingLeft: 0, + border: '1px solid transparent', +})); + +interface SearchModalProps { + openItem: any; +} + +function SearchModal({ openItem: OpenItem }: SearchModalProps) { + const { t } = useTranslation(); + const { collections } = useCollections(); + const [searchTerm, setSearchTerm] = useState(''); + const [results, setResults] = useState([]); + + const searchModal = useToggle(!!searchTerm); + + const handleCloseModal = useCallback(() => { + searchModal.close(); + setSearchTerm(''); + }, [searchModal]); + + const handleSearchInputChange = (value: string) => setSearchTerm(value); + const handleSubmit = (event: FormEvent) => + event.preventDefault(); + + useShortcut('OPEN_SEARCH_KEY', searchModal.open, { + enabled: !searchModal.isShowing, + }); + useShortcut('ESCAPE_KEY', handleCloseModal, { + enabled: searchModal.isShowing, + }); + + useEffect(() => { + if (searchTerm.trim() === '') { + return setResults([]); + } + const { url, method } = route('search', { qs: { term: searchTerm } }); + makeRequest({ + method, + url, + }).then(({ results: _results }) => setResults(_results)); + }, [searchTerm]); + + return ( + <> + + {t('common:search')} + + +
+
+ + handleSearchInputChange(target.value)} + value={searchTerm} + placeholder={t('common:search')} + autoFocus + /> +
+ {results.length > 0 && ( + + {results.map((result) => { + const ItemIcon = + result.type === 'link' ? CiLink : AiOutlineFolder; + const collection = + result.type === 'link' + ? collections.find((c) => c.id === result.collection_id) + : undefined; + return ( +
  • + + + {collection && <>({collection.name})} +
  • + ); + })} +
    + )} + +
    +
    + + ); +} + +export default SearchModal; diff --git a/inertia/components/dashboard/side_nav/side_navigation.tsx b/inertia/components/dashboard/side_nav/side_navigation.tsx index 8167329..92d7f46 100644 --- a/inertia/components/dashboard/side_nav/side_navigation.tsx +++ b/inertia/components/dashboard/side_nav/side_navigation.tsx @@ -2,8 +2,8 @@ import styled from '@emotion/styled'; import { route } from '@izzyjs/route/client'; import { useTranslation } from 'react-i18next'; import { AiOutlineFolderAdd } from 'react-icons/ai'; -import { IoAdd } from 'react-icons/io5'; -import { MdOutlineAdminPanelSettings } from 'react-icons/md'; +import { IoAdd, IoShieldHalfSharp } from 'react-icons/io5'; +import SearchModal from '~/components/dashboard/search/search_modal'; import FavoriteList from '~/components/dashboard/side_nav/favorite/favorite_list'; import { Item, ItemLink } from '~/components/dashboard/side_nav/nav_item'; import UserCard from '~/components/dashboard/side_nav/user_card'; @@ -40,6 +40,8 @@ const AddButton = styled(ItemLink)(({ theme }) => ({ }, })); +const SearchButton = AddButton.withComponent(Item); + export default function SideNavigation() { const { t } = useTranslation('common'); const { activeCollection } = useActiveCollection(); @@ -48,9 +50,10 @@ export default function SideNavigation() {
    - {t('admin')} + {t('admin')} + ) => { event.preventDefault(); diff --git a/inertia/components/layouts/form_layout.tsx b/inertia/components/layouts/form_layout.tsx index 91bf44c..43037f8 100644 --- a/inertia/components/layouts/form_layout.tsx +++ b/inertia/components/layouts/form_layout.tsx @@ -31,7 +31,7 @@ interface FormLayoutProps { disableHomeLink?: boolean; submitBtnDanger?: boolean; - collectionId?: string; + collectionId?: number; } export default function FormLayout({ diff --git a/inertia/constants/index.ts b/inertia/constants/index.ts index 2bcd071..4b75c94 100644 --- a/inertia/constants/index.ts +++ b/inertia/constants/index.ts @@ -1 +1,2 @@ export const LS_LANG_KEY = 'language'; +export const GOOGLE_SEARCH_URL = 'https://google.com/search?q='; diff --git a/inertia/hooks/use_search_item.tsx b/inertia/hooks/use_search_item.tsx deleted file mode 100644 index c1bc0cb..0000000 --- a/inertia/hooks/use_search_item.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { buildSearchItem, formatSearchItem } from 'lib/search'; -import { useMemo } from 'react'; -import useCollections from '~/hooks/use_collections'; -import { SearchItem, SearchResult } from '~/types/search'; - -export default function useSearchItem({ - searchTerm = '', - disableLinks = false, - disableCollections = false, -}: { - searchTerm: string; - disableLinks?: boolean; - disableCollections?: boolean; -}) { - const { collections } = useCollections(); - - const itemsSearch: SearchItem[] = useMemo(() => { - return collections.reduce((acc, collection) => { - const collectionItem = buildSearchItem(collection, 'collection'); - const items: SearchItem[] = collection.links.map((link) => - buildSearchItem(link, 'link') - ); - return [...acc, ...items, collectionItem]; - }, [] as SearchItem[]); - }, [collections]); - - const itemsCompletion: SearchResult[] = useMemo(() => { - return itemsSearch.reduce((acc, item) => { - const formattedItem = formatSearchItem(item, searchTerm); - - if ( - (!disableLinks && item.type === 'link') || - (!disableCollections && item.type === 'collection') - ) { - return formattedItem ? [...acc, formattedItem] : acc; - } - - return acc; - }, [] as SearchResult[]); - }, [itemsSearch, searchTerm, disableLinks, disableCollections]); - - return itemsCompletion; -} diff --git a/inertia/hooks/use_shortcut.tsx b/inertia/hooks/use_shortcut.tsx index faeca4d..e4252b2 100644 --- a/inertia/hooks/use_shortcut.tsx +++ b/inertia/hooks/use_shortcut.tsx @@ -2,18 +2,27 @@ import KEYS from '#constants/keys'; import { useHotkeys } from 'react-hotkeys-hook'; import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; -type ShortcutOptions = { ignoreGlobalHotkeysStatus?: boolean }; +type ShortcutOptions = { + enabled?: boolean; +}; export default function useShortcut( key: keyof typeof KEYS, cb: () => void, - options: ShortcutOptions = { - ignoreGlobalHotkeysStatus: false, + { enabled }: ShortcutOptions = { + enabled: true, } ) { const { globalHotkeysEnabled } = useGlobalHotkeys(); - return useHotkeys(KEYS[key], cb, { - enabled: !options.ignoreGlobalHotkeysStatus ? globalHotkeysEnabled : true, - enableOnFormTags: ['INPUT'], - }); + return useHotkeys( + KEYS[key], + (event) => { + event.preventDefault(); + cb(); + }, + { + enabled: key === 'ESCAPE_KEY' ? true : enabled && globalHotkeysEnabled, + enableOnFormTags: ['INPUT'], + } + ); } diff --git a/inertia/lib/search.tsx b/inertia/lib/search.tsx deleted file mode 100644 index b949473..0000000 --- a/inertia/lib/search.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import Collection from '#models/collection'; -import Link from '#models/link'; -import { route } from '@izzyjs/route/client'; -import { appendCollectionId } from '~/lib/navigation'; -import { SearchItem, SearchResult } from '~/types/search'; - -export function buildSearchItem( - item: Collection | Link, - type: SearchItem['type'] -): SearchItem { - return { - id: item.id, - name: item.name, - url: - type === 'link' - ? (item as Link).url - : appendCollectionId(route('dashboard').url, item.id), - type, - collection: type === 'link' ? (item as Link).collection : undefined, - }; -} - -export function formatSearchItem( - item: SearchItem, - searchTerm: string -): SearchResult | null { - const lowerCaseSearchTerm = searchTerm.toLowerCase().trim(); - const lowerCaseName = item.name.toLowerCase().trim(); - - let currentIndex = 0; - let formattedName = ''; - - for (const [i, element] of Object(lowerCaseName).entries()) { - if (element === lowerCaseSearchTerm[currentIndex]) { - formattedName += `${item.name[i]}`; - currentIndex++; - } else { - formattedName += item.name[i]; - } - } - - if (currentIndex !== lowerCaseSearchTerm.length) { - // Search term not fully matched - return null; - } - - return { - id: item.id, - name:
    , - url: item.url, - type: item.type, - collection: item.collection, - }; -} diff --git a/inertia/pages/dashboard.tsx b/inertia/pages/dashboard.tsx index 5e91621..a3a29ca 100644 --- a/inertia/pages/dashboard.tsx +++ b/inertia/pages/dashboard.tsx @@ -105,19 +105,28 @@ function DashboardProviders( const favoritesContextValue = useMemo(() => ({ favorites }), [favorites]); const globalHotkeysContextValue = useMemo( () => ({ - globalHotkeysEnabled: globalHotkeysEnabled, + globalHotkeysEnabled, setGlobalHotkeysEnabled, }), [globalHotkeysEnabled] ); - useShortcut('OPEN_CREATE_LINK_KEY', () => - router.visit( - appendCollectionId(route('link.create-form').url, activeCollection?.id) - ) + useShortcut( + 'OPEN_CREATE_LINK_KEY', + () => + router.visit( + appendCollectionId(route('link.create-form').url, activeCollection?.id) + ), + { + enabled: globalHotkeysEnabled, + } ); - useShortcut('OPEN_CREATE_COLLECTION_KEY', () => - router.visit(route('collection.create-form').url) + useShortcut( + 'OPEN_CREATE_COLLECTION_KEY', + () => router.visit(route('collection.create-form').url), + { + enabled: globalHotkeysEnabled, + } ); return ( diff --git a/inertia/pages/links/create.tsx b/inertia/pages/links/create.tsx index 3bbb9d0..f584890 100644 --- a/inertia/pages/links/create.tsx +++ b/inertia/pages/links/create.tsx @@ -13,7 +13,8 @@ export default function CreateLinkPage({ collections: Collection[]; }) { const { t } = useTranslation('common'); - const collectionId = useSearchParam('collectionId') ?? collections[0].id; + const collectionId = + Number(useSearchParam('collectionId')) ?? collections[0].id; const { data, setData, submit, processing } = useForm({ name: '', description: '', diff --git a/inertia/types/search.d.ts b/inertia/types/search.d.ts index deb4d66..27cf5f6 100644 --- a/inertia/types/search.d.ts +++ b/inertia/types/search.d.ts @@ -1,17 +1,18 @@ import type Link from '#models/link'; -export interface SearchItem { - id: string; - name: string; - url: string; - type: 'collection' | 'link'; - collection?: undefined | Link['collection']; -} - -export interface SearchResult { - id: string; - name: React.ReactNode; // React node to support bold text - url: string; - type: 'collection' | 'link'; - collection?: undefined | Link['collection']; -} +export type SearchResult = + | { + id: string; + type: 'collection'; + name: string; + matched_part?: string; + rank?: number; + } + | { + id: string; + type: 'link'; + name: string; + matched_part?: string; + rank?: number; + collection_id: number; + }; diff --git a/package-lock.json b/package-lock.json index 178f842..899d5ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "edge.js": "^6.0.2", "hex-rgb": "^5.0.0", "i18next": "^23.11.4", + "knex": "^3.1.0", "luxon": "^3.4.4", "node-html-parser": "^6.1.13", "pg": "^8.11.5", @@ -39,8 +40,7 @@ "react-icons": "^5.2.1", "react-swipeable": "^7.0.1", "react-toggle": "^4.1.3", - "reflect-metadata": "^0.2.2", - "uuid": "^9.0.1" + "reflect-metadata": "^0.2.2" }, "devDependencies": { "@adonisjs/assembler": "^7.5.2", @@ -57,7 +57,6 @@ "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", "@types/react-toggle": "^4.0.5", - "@types/uuid": "^9.0.8", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", "hot-hook": "^0.2.6", @@ -1797,67 +1796,6 @@ } } }, - "node_modules/@adonisjs/lucid/node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" - }, - "node_modules/@adonisjs/lucid/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@adonisjs/lucid/node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@adonisjs/lucid/node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@adonisjs/lucid/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@adonisjs/lucid/node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@adonisjs/lucid/node_modules/getopts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", - "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" - }, - "node_modules/@adonisjs/lucid/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/@adonisjs/lucid/node_modules/igniculus": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/igniculus/-/igniculus-1.5.0.tgz", @@ -1866,25 +1804,6 @@ "node": ">=4.0.0" } }, - "node_modules/@adonisjs/lucid/node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@adonisjs/lucid/node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@adonisjs/lucid/node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1893,56 +1812,6 @@ "node": ">=6" } }, - "node_modules/@adonisjs/lucid/node_modules/knex": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", - "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", - "dependencies": { - "colorette": "2.0.19", - "commander": "^10.0.0", - "debug": "4.3.4", - "escalade": "^3.1.1", - "esm": "^3.2.25", - "get-package-type": "^0.1.0", - "getopts": "2.3.0", - "interpret": "^2.2.0", - "lodash": "^4.17.21", - "pg-connection-string": "2.6.2", - "rechoir": "^0.8.0", - "resolve-from": "^5.0.0", - "tarn": "^3.0.2", - "tildify": "2.0.0" - }, - "bin": { - "knex": "bin/cli.js" - }, - "engines": { - "node": ">=16" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "mysql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, "node_modules/@adonisjs/lucid/node_modules/knex-dynamic-connection": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/knex-dynamic-connection/-/knex-dynamic-connection-3.1.1.tgz", @@ -1955,83 +1824,6 @@ "node": ">=14.0.0" } }, - "node_modules/@adonisjs/lucid/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/@adonisjs/lucid/node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/@adonisjs/lucid/node_modules/pg-connection-string": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" - }, - "node_modules/@adonisjs/lucid/node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/@adonisjs/lucid/node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@adonisjs/lucid/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@adonisjs/lucid/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@adonisjs/lucid/node_modules/tarn": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", - "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@adonisjs/lucid/node_modules/tildify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", - "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", - "engines": { - "node": ">=8" - } - }, "node_modules/@adonisjs/presets": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@adonisjs/presets/-/presets-2.4.1.tgz", @@ -2741,15 +2533,6 @@ "integrity": "sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==", "dev": true }, - "node_modules/@babel/core/node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/core/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -3674,12 +3457,6 @@ "source-map": "^0.6.1" } }, - "node_modules/@japa/runner/node_modules/getopts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", - "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", - "devOptional": true - }, "node_modules/@japa/runner/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5180,12 +4957,6 @@ "@types/react": "*" } }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true - }, "node_modules/@types/validator": { "version": "13.11.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz", @@ -6537,15 +6308,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/api-contract-validator/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/api-contract-validator/node_modules/function.prototype.name": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", @@ -6706,18 +6468,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/api-contract-validator/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/api-contract-validator/node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -7097,12 +6847,6 @@ "node": ">=0.10.0" } }, - "node_modules/api-contract-validator/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/api-contract-validator/node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -7812,14 +7556,6 @@ "node": ">=0.8.0" } }, - "node_modules/babel-plugin-macros/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-plugin-macros/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -7828,17 +7564,6 @@ "node": ">=4" } }, - "node_modules/babel-plugin-macros/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/babel-plugin-macros/node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -7859,17 +7584,6 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, - "node_modules/babel-plugin-macros/node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-plugin-macros/node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7913,11 +7627,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-plugin-macros/node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, "node_modules/babel-plugin-macros/node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7931,22 +7640,6 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, - "node_modules/babel-plugin-macros/node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-plugin-macros/node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -7966,17 +7659,6 @@ "node": ">=4" } }, - "node_modules/babel-plugin-macros/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-plugin-macros/node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -8265,6 +7947,14 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -9209,6 +8899,14 @@ "node": ">=12" } }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -9738,15 +9436,6 @@ "node": ">=8" } }, - "node_modules/eslint-plugin-unicorn/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-unicorn/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -9756,18 +9445,6 @@ "node": ">=4" } }, - "node_modules/eslint-plugin-unicorn/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/eslint-plugin-unicorn/node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -9804,18 +9481,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-unicorn/node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-unicorn/node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9858,12 +9523,6 @@ "node": ">=8" } }, - "node_modules/eslint-plugin-unicorn/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/eslint-plugin-unicorn/node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -9957,12 +9616,6 @@ "node": ">=8" } }, - "node_modules/eslint-plugin-unicorn/node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, "node_modules/eslint-plugin-unicorn/node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -10049,23 +9702,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/eslint-plugin-unicorn/node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-unicorn/node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", @@ -10143,18 +9779,6 @@ "node": ">=4" } }, - "node_modules/eslint-plugin-unicorn/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-unicorn/node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -11173,6 +10797,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -11573,6 +11205,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-port": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", @@ -11585,6 +11233,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -11711,6 +11375,25 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -11805,6 +11488,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/knex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", + "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", + "dependencies": { + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, "node_modules/lilconfig": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", @@ -12211,6 +11949,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -12347,6 +12090,11 @@ "node": ">=14.0.0" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/pg": { "version": "8.11.5", "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", @@ -12379,6 +12127,11 @@ "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", "optional": true }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, "node_modules/pg-pool": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", @@ -12761,14 +12514,6 @@ "node": ">= 0.4" } }, - "node_modules/qs/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/qs/node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -12831,17 +12576,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/qs/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/qs/node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -13133,15 +12867,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-package-up/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/read-package-up/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13151,18 +12876,6 @@ "node": ">=4" } }, - "node_modules/read-package-up/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/read-package-up/node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -13187,18 +12900,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-package-up/node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/read-package-up/node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13499,11 +13200,46 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, "node_modules/rollup": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", @@ -14018,6 +13754,33 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "engines": { + "node": ">=8" + } + }, "node_modules/ts-morph": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", @@ -14153,18 +13916,6 @@ "node": ">=14.17" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 80d28df..c6af40c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", "@types/react-toggle": "^4.0.5", - "@types/uuid": "^9.0.8", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", "hot-hook": "^0.2.6", @@ -79,6 +78,7 @@ "edge.js": "^6.0.2", "hex-rgb": "^5.0.0", "i18next": "^23.11.4", + "knex": "^3.1.0", "luxon": "^3.4.4", "node-html-parser": "^6.1.13", "pg": "^8.11.5", @@ -91,8 +91,7 @@ "react-icons": "^5.2.1", "react-swipeable": "^7.0.1", "react-toggle": "^4.1.3", - "reflect-metadata": "^0.2.2", - "uuid": "^9.0.1" + "reflect-metadata": "^0.2.2" }, "hotHook": { "boundaries": [ @@ -116,4 +115,4 @@ "lint-staged": { "*.js,*.ts,*.jsx,*.tsx": "eslint --cache --fix" } -} \ No newline at end of file +} diff --git a/start/routes.ts b/start/routes.ts index 12bc1da..5eef342 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -3,3 +3,4 @@ import './routes/auth.js'; import './routes/collection.js'; import './routes/favicon.js'; import './routes/link.js'; +import './routes/search.js'; diff --git a/start/routes/search.ts b/start/routes/search.ts new file mode 100644 index 0000000..f4b5d5c --- /dev/null +++ b/start/routes/search.ts @@ -0,0 +1,13 @@ +import { middleware } from '#start/kernel'; +import router from '@adonisjs/core/services/router'; + +const SearchesController = () => import('#controllers/searches_controller'); + +/** + * Search routes + */ +router + .group(() => { + router.get('/search', [SearchesController, 'search']).as('search'); + }) + .middleware([middleware.auth()]);