diff --git a/client/src/components/SearchBar.tsx b/client/src/components/SearchBar.tsx index c03405e..0d189fd 100644 --- a/client/src/components/SearchBar.tsx +++ b/client/src/components/SearchBar.tsx @@ -1,7 +1,9 @@ -import { useRef, useEffect, KeyboardEvent } from 'react'; +import { useRef, useEffect, KeyboardEvent, useContext } from 'react'; +import { SnippetsContext } from '../store'; import { searchParser } from '../utils'; export const SearchBar = (): JSX.Element => { + const { searchSnippets } = useContext(SnippetsContext); const inputRef = useRef(document.createElement('input')); useEffect(() => { @@ -9,7 +11,11 @@ export const SearchBar = (): JSX.Element => { }, [inputRef]); const inputHandler = (e: KeyboardEvent) => { - const rawQuery = searchParser(inputRef.current.value); + const query = searchParser(inputRef.current.value); + + if (e.key === 'Enter') { + searchSnippets(query); + } }; return ( diff --git a/client/src/containers/Home.tsx b/client/src/containers/Home.tsx index 054c35d..5f772c4 100644 --- a/client/src/containers/Home.tsx +++ b/client/src/containers/Home.tsx @@ -5,7 +5,7 @@ import { SnippetGrid } from '../components/Snippets/SnippetGrid'; import { SearchBar } from '../components/SearchBar'; export const Home = (): JSX.Element => { - const { snippets, getSnippets } = useContext(SnippetsContext); + const { snippets, getSnippets, searchResults } = useContext(SnippetsContext); useEffect(() => { getSnippets(); @@ -19,9 +19,9 @@ export const Home = (): JSX.Element => { - {/*
- s.isPinned)} /> -
*/} +
+ +
{snippets.some(s => s.isPinned) && ( diff --git a/client/src/store/SnippetsContext.tsx b/client/src/store/SnippetsContext.tsx index b180f71..474ea8b 100644 --- a/client/src/store/SnippetsContext.tsx +++ b/client/src/store/SnippetsContext.tsx @@ -6,11 +6,13 @@ import { Snippet, Response, TagCount, - NewSnippet + NewSnippet, + SearchQuery } from '../typescript/interfaces'; export const SnippetsContext = createContext({ snippets: [], + searchResults: [], currentSnippet: null, tagCount: [], getSnippets: () => {}, @@ -20,7 +22,8 @@ export const SnippetsContext = createContext({ updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {}, deleteSnippet: (id: number) => {}, toggleSnippetPin: (id: number) => {}, - countTags: () => {} + countTags: () => {}, + searchSnippets: (query: SearchQuery) => {} }); interface Props { @@ -29,6 +32,7 @@ interface Props { export const SnippetsContextProvider = (props: Props): JSX.Element => { const [snippets, setSnippets] = useState([]); + const [searchResults, setSearchResults] = useState([]); const [currentSnippet, setCurrentSnippet] = useState(null); const [tagCount, setTagCount] = useState([]); @@ -139,8 +143,19 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => { .catch(err => redirectOnError()); }; + const searchSnippets = (query: SearchQuery): void => { + axios + .post>('/api/snippets/search', query) + .then(res => { + setSearchResults(res.data.data); + console.log(res.data.data); + }) + .catch(err => console.log(err)); + }; + const context = { snippets, + searchResults, currentSnippet, tagCount, getSnippets, @@ -150,7 +165,8 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => { updateSnippet, deleteSnippet, toggleSnippetPin, - countTags + countTags, + searchSnippets }; return ( diff --git a/client/src/typescript/interfaces/Context.ts b/client/src/typescript/interfaces/Context.ts index 6e33734..7e154e0 100644 --- a/client/src/typescript/interfaces/Context.ts +++ b/client/src/typescript/interfaces/Context.ts @@ -1,7 +1,8 @@ -import { TagCount, NewSnippet, Snippet } from '.'; +import { TagCount, NewSnippet, Snippet, SearchQuery } from '.'; export interface Context { snippets: Snippet[]; + searchResults: Snippet[]; currentSnippet: Snippet | null; tagCount: TagCount[]; getSnippets: () => void; @@ -12,4 +13,5 @@ export interface Context { deleteSnippet: (id: number) => void; toggleSnippetPin: (id: number) => void; countTags: () => void; + searchSnippets: (query: SearchQuery) => void; } diff --git a/client/src/typescript/interfaces/SearchQuery.ts b/client/src/typescript/interfaces/SearchQuery.ts new file mode 100644 index 0000000..35a0315 --- /dev/null +++ b/client/src/typescript/interfaces/SearchQuery.ts @@ -0,0 +1,5 @@ +export interface SearchQuery { + query: string; + tags: string[]; + languages: string[]; +} diff --git a/client/src/typescript/interfaces/index.ts b/client/src/typescript/interfaces/index.ts index 8fdd81d..991b300 100644 --- a/client/src/typescript/interfaces/index.ts +++ b/client/src/typescript/interfaces/index.ts @@ -4,3 +4,4 @@ export * from './Route'; export * from './Response'; export * from './Context'; export * from './Statistics'; +export * from './SearchQuery'; diff --git a/client/src/utils/searchParser.ts b/client/src/utils/searchParser.ts index a43c483..041e596 100644 --- a/client/src/utils/searchParser.ts +++ b/client/src/utils/searchParser.ts @@ -1,14 +1,16 @@ -export const searchParser = (rawQuery: string): void => { - // const rawQuery = 'my search tags:ui,react lang:typescript'; +import { SearchQuery } from '../typescript/interfaces'; +export const searchParser = (rawQuery: string): SearchQuery => { // Extract filters from query const tags = extractFilters(rawQuery, 'tags'); const languages = extractFilters(rawQuery, 'lang'); const query = rawQuery.replaceAll(/(tags|lang):[a-zA-Z]+(,[a-zA-Z]+)*/g, ''); - console.log(tags); - console.log(languages); - console.log(query); + return { + query: query.trim(), + tags, + languages + }; }; const extractFilters = (query: string, filter: string): string[] => { diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts index f8c4543..0b66da1 100644 --- a/src/controllers/snippets.ts +++ b/src/controllers/snippets.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from 'express'; -import { QueryTypes } from 'sequelize'; +import { QueryTypes, Op } from 'sequelize'; import { sequelize } from '../db'; import { asyncWrapper } from '../middleware'; import { SnippetModel, Snippet_TagModel } from '../models'; @@ -10,7 +10,7 @@ import { Logger, createTags } from '../utils'; -import { Body } from '../typescript/interfaces'; +import { Body, SearchQuery } from '../typescript/interfaces'; const logger = new Logger('snippets-controller'); @@ -209,3 +209,39 @@ export const countTags = asyncWrapper( }); } ); + +/** + * @description Search snippets + * @route /api/snippets/search + * @request POST + */ +export const searchSnippets = asyncWrapper( + async (req: Request, res: Response, next: NextFunction): Promise => { + const { query, tags, languages } = req.body; + + console.log(query, tags, languages); + + const languageFilter = + languages.length > 0 ? { [Op.in]: languages } : { [Op.notIn]: languages }; + + const snippets = await SnippetModel.findAll({ + where: { + [Op.and]: [ + { + [Op.or]: [ + { title: { [Op.substring]: `${query}` } }, + { description: { [Op.substring]: `${query}` } } + ] + }, + { + language: languageFilter + } + ] + } + }); + + res.status(200).json({ + data: snippets + }); + } +); diff --git a/src/routes/snippets.ts b/src/routes/snippets.ts index 19ca584..c58e0d6 100644 --- a/src/routes/snippets.ts +++ b/src/routes/snippets.ts @@ -5,6 +5,7 @@ import { deleteSnippet, getAllSnippets, getSnippet, + searchSnippets, updateSnippet } from '../controllers/snippets'; import { requireBody } from '../middleware'; @@ -23,3 +24,4 @@ snippetRouter .delete(deleteSnippet); snippetRouter.route('/statistics/count').get(countTags); +snippetRouter.route('/search').post(searchSnippets); diff --git a/src/typescript/interfaces/SearchQuery.ts b/src/typescript/interfaces/SearchQuery.ts new file mode 100644 index 0000000..35a0315 --- /dev/null +++ b/src/typescript/interfaces/SearchQuery.ts @@ -0,0 +1,5 @@ +export interface SearchQuery { + query: string; + tags: string[]; + languages: string[]; +} diff --git a/src/typescript/interfaces/index.ts b/src/typescript/interfaces/index.ts index b86dc4a..c219f6a 100644 --- a/src/typescript/interfaces/index.ts +++ b/src/typescript/interfaces/index.ts @@ -3,3 +3,4 @@ export * from './Snippet'; export * from './Tag'; export * from './Snippet_Tag'; export * from './Body'; +export * from './SearchQuery';