diff --git a/client/src/components/Snippets/SnippetDetails.tsx b/client/src/components/Snippets/SnippetDetails.tsx index 9de585b..a3cb22e 100644 --- a/client/src/components/Snippets/SnippetDetails.tsx +++ b/client/src/components/Snippets/SnippetDetails.tsx @@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom'; import { SnippetsContext } from '../../store'; import { Snippet } from '../../typescript/interfaces'; import { dateParser } from '../../utils'; -import { Button, Card } from '../UI'; +import { Badge, Button, Card } from '../UI'; import copy from 'clipboard-copy'; import { SnippetPin } from './SnippetPin'; @@ -15,6 +15,7 @@ export const SnippetDetails = (props: Props): JSX.Element => { const { title, language, + tags, createdAt, updatedAt, description, @@ -61,6 +62,16 @@ export const SnippetDetails = (props: Props): JSX.Element => {
+ {/* TAGS */} +
+ {tags.map(tag => ( + + + + ))} +
+
+ {/* ACTIONS */}
-
Filter by language
+
Filter by tags
- {languageCount.map((el, idx) => { - const isActiveFilter = filter === el.language; + {tagCount.map((tag, idx) => { + const isActiveFilter = filter === tag.name; return (
filterHandler(el.language)} + onClick={() => filterHandler(tag.name)} > - {el.language} - {el.count} + {tag.name} + {tag.count}
); })} diff --git a/client/src/store/SnippetsContext.tsx b/client/src/store/SnippetsContext.tsx index f73d7ee..5d3b305 100644 --- a/client/src/store/SnippetsContext.tsx +++ b/client/src/store/SnippetsContext.tsx @@ -5,14 +5,14 @@ import { Context, Snippet, Response, - LanguageCount, + TagCount, NewSnippet } from '../typescript/interfaces'; export const SnippetsContext = createContext({ snippets: [], currentSnippet: null, - languageCount: [], + tagCount: [], getSnippets: () => {}, getSnippetById: (id: number) => {}, setSnippet: (id: number) => {}, @@ -20,7 +20,7 @@ export const SnippetsContext = createContext({ updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {}, deleteSnippet: (id: number) => {}, toggleSnippetPin: (id: number) => {}, - countSnippets: () => {} + countTags: () => {} }); interface Props { @@ -30,7 +30,7 @@ interface Props { export const SnippetsContextProvider = (props: Props): JSX.Element => { const [snippets, setSnippets] = useState([]); const [currentSnippet, setCurrentSnippet] = useState(null); - const [languageCount, setLanguageCount] = useState([]); + const [tagCount, setTagCount] = useState([]); const history = useHistory(); @@ -132,17 +132,17 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => { } }; - const countSnippets = (): void => { + const countTags = (): void => { axios - .get>('/api/snippets/statistics/count') - .then(res => setLanguageCount(res.data.data)) + .get>('/api/snippets/statistics/count') + .then(res => setTagCount(res.data.data)) .catch(err => redirectOnError()); }; const context = { snippets, currentSnippet, - languageCount, + tagCount, getSnippets, getSnippetById, setSnippet, @@ -150,7 +150,7 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => { updateSnippet, deleteSnippet, toggleSnippetPin, - countSnippets + countTags }; return ( diff --git a/client/src/typescript/interfaces/Context.ts b/client/src/typescript/interfaces/Context.ts index 215674e..6e33734 100644 --- a/client/src/typescript/interfaces/Context.ts +++ b/client/src/typescript/interfaces/Context.ts @@ -1,9 +1,9 @@ -import { LanguageCount, NewSnippet, Snippet } from '.'; +import { TagCount, NewSnippet, Snippet } from '.'; export interface Context { snippets: Snippet[]; currentSnippet: Snippet | null; - languageCount: LanguageCount[]; + tagCount: TagCount[]; getSnippets: () => void; getSnippetById: (id: number) => void; setSnippet: (id: number) => void; @@ -11,5 +11,5 @@ export interface Context { updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => void; deleteSnippet: (id: number) => void; toggleSnippetPin: (id: number) => void; - countSnippets: () => void; + countTags: () => void; } diff --git a/client/src/typescript/interfaces/Snippet.ts b/client/src/typescript/interfaces/Snippet.ts index 8e1ced5..1973787 100644 --- a/client/src/typescript/interfaces/Snippet.ts +++ b/client/src/typescript/interfaces/Snippet.ts @@ -7,7 +7,7 @@ export interface NewSnippet { code: string; docs?: string; isPinned: boolean; - tags: string; + tags: string[]; } export interface Snippet extends Model, NewSnippet {} diff --git a/client/src/typescript/interfaces/Statistics.ts b/client/src/typescript/interfaces/Statistics.ts index 2bf242b..80cedcd 100644 --- a/client/src/typescript/interfaces/Statistics.ts +++ b/client/src/typescript/interfaces/Statistics.ts @@ -1,4 +1,4 @@ -export interface LanguageCount { +export interface TagCount { count: number; - language: string; + name: string; } diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts index fe6a93e..65fac50 100644 --- a/src/controllers/snippets.ts +++ b/src/controllers/snippets.ts @@ -2,8 +2,10 @@ import { Request, Response, NextFunction } from 'express'; import { QueryTypes } from 'sequelize'; import { sequelize } from '../db'; import { asyncWrapper } from '../middleware'; -import { SnippetModel } from '../models'; -import { ErrorResponse, tagsParser } from '../utils'; +import { SnippetInstance, SnippetModel } from '../models'; +import { ErrorResponse, getTags, tagsParser, Logger } from '../utils'; + +const logger = new Logger('snippets-controller'); /** * @description Create new snippet @@ -37,7 +39,22 @@ export const createSnippet = asyncWrapper( */ export const getAllSnippets = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { - const snippets = await SnippetModel.findAll(); + const snippets = await SnippetModel.findAll({ + raw: true + }); + + await new Promise(async resolve => { + try { + for await (let snippet of snippets) { + const tags = await getTags(+snippet.id); + snippet.tags = tags; + } + } catch (err) { + logger.log('Error while fetching tags'); + } finally { + resolve(); + } + }); res.status(200).json({ data: snippets @@ -46,14 +63,15 @@ export const getAllSnippets = asyncWrapper( ); /** - * @description Get single sinppet by id + * @description Get single snippet by id * @route /api/snippets/:id * @request GET */ export const getSnippet = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { const snippet = await SnippetModel.findOne({ - where: { id: req.params.id } + where: { id: req.params.id }, + raw: true }); if (!snippet) { @@ -65,6 +83,9 @@ export const getSnippet = asyncWrapper( ); } + const tags = await getTags(+req.params.id); + snippet.tags = tags; + res.status(200).json({ data: snippet }); @@ -144,19 +165,20 @@ export const deleteSnippet = asyncWrapper( ); /** - * @description Count snippets by language + * @description Count tags * @route /api/snippets/statistics/count * @request GET */ -export const countSnippets = asyncWrapper( +export const countTags = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { const result = await sequelize.query( `SELECT - COUNT(language) AS count, - language - FROM snippets - GROUP BY language - ORDER BY language ASC`, + COUNT(tags.name) as count, + tags.name + FROM snippets_tags + INNER JOIN tags ON snippets_tags.tag_id = tags.id + GROUP BY tags.name + ORDER BY name ASC`, { type: QueryTypes.SELECT } diff --git a/src/db/migrations/02_tags.ts b/src/db/migrations/02_tags.ts index d15c3fd..558a144 100644 --- a/src/db/migrations/02_tags.ts +++ b/src/db/migrations/02_tags.ts @@ -76,7 +76,6 @@ export const up = async (queryInterface: QueryInterface): Promise => { } } catch (err) { logger.log('Error while assigning tags to snippets'); - console.log(err); } finally { resolve(); } diff --git a/src/routes/snippets.ts b/src/routes/snippets.ts index 4b3727c..19ca584 100644 --- a/src/routes/snippets.ts +++ b/src/routes/snippets.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import { - countSnippets, + countTags, createSnippet, deleteSnippet, getAllSnippets, @@ -22,4 +22,4 @@ snippetRouter .put(updateSnippet) .delete(deleteSnippet); -snippetRouter.route('/statistics/count').get(countSnippets); +snippetRouter.route('/statistics/count').get(countTags); diff --git a/src/typescript/interfaces/Snippet.ts b/src/typescript/interfaces/Snippet.ts index 785ee09..2a4c637 100644 --- a/src/typescript/interfaces/Snippet.ts +++ b/src/typescript/interfaces/Snippet.ts @@ -8,6 +8,7 @@ export interface Snippet extends Model { code: string; docs: string; isPinned: number; + tags?: string[]; } export interface SnippetCreationAttributes diff --git a/src/utils/getTags.ts b/src/utils/getTags.ts new file mode 100644 index 0000000..f44a1c1 --- /dev/null +++ b/src/utils/getTags.ts @@ -0,0 +1,18 @@ +import { sequelize } from '../db'; +import { QueryTypes } from 'sequelize'; + +export const getTags = async (snippetId: number): Promise => { + const tags = await sequelize.query<{ name: string }>( + `SELECT tags.name + FROM tags + INNER JOIN + snippets_tags ON tags.id = snippets_tags.tag_id + INNER JOIN + snippets ON snippets.id = snippets_tags.snippet_id + WHERE + snippets_tags.snippet_id = ${snippetId};`, + { type: QueryTypes.SELECT } + ); + + return tags.map(tag => tag.name); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index c86e0f3..f05b365 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './Logger'; export * from './ErrorResponse'; export * from './tagsParser'; +export * from './getTags';