From fa7f54d0e14572fae9cb285024e8a8fc915f0d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Malak?= Date: Thu, 14 Oct 2021 15:09:04 +0200 Subject: [PATCH] Search feature. Changed how controller fetches tags --- CHANGELOG.md | 5 ++ src/controllers/snippets.ts | 88 ++++++++++++++++++---------- src/db/associateModels.ts | 15 +++++ src/server.ts | 2 + src/typescript/interfaces/Snippet.ts | 2 +- 5 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 src/db/associateModels.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b368a..38ec14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### v1.4 (TBA) +- Added search functionality ([#18](https://github.com/pawelmalak/snippet-box/issues/18)) +- Fixed date parsing bug ([#22](https://github.com/pawelmalak/snippet-box/issues/22)) +- Minor UI fixes + ### v1.3 (2021-09-30) - Added dark mode ([#7](https://github.com/pawelmalak/snippet-box/issues/7)) - Added syntax highlighting ([#14](https://github.com/pawelmalak/snippet-box/issues/14)) diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts index 0b66da1..5bf5ebd 100644 --- a/src/controllers/snippets.ts +++ b/src/controllers/snippets.ts @@ -2,18 +2,10 @@ import { Request, Response, NextFunction } from 'express'; import { QueryTypes, Op } from 'sequelize'; import { sequelize } from '../db'; import { asyncWrapper } from '../middleware'; -import { SnippetModel, Snippet_TagModel } from '../models'; -import { - ErrorResponse, - getTags, - tagParser, - Logger, - createTags -} from '../utils'; +import { SnippetModel, Snippet_TagModel, TagModel } from '../models'; +import { ErrorResponse, tagParser, Logger, createTags } from '../utils'; import { Body, SearchQuery } from '../typescript/interfaces'; -const logger = new Logger('snippets-controller'); - /** * @description Create new snippet * @route /api/snippets @@ -57,24 +49,27 @@ export const createSnippet = asyncWrapper( export const getAllSnippets = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { 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; + include: { + model: TagModel, + as: 'tags', + attributes: ['name'], + through: { + attributes: [] } - } catch (err) { - logger.log('Error while fetching tags', 'ERROR'); - } finally { - resolve(); } }); + const populatedSnippets = snippets.map(snippet => { + const rawSnippet = snippet.get({ plain: true }); + + return { + ...rawSnippet, + tags: rawSnippet.tags?.map(tag => tag.name) + }; + }); + res.status(200).json({ - data: snippets + data: populatedSnippets }); } ); @@ -88,7 +83,14 @@ export const getSnippet = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { const snippet = await SnippetModel.findOne({ where: { id: req.params.id }, - raw: true + include: { + model: TagModel, + as: 'tags', + attributes: ['name'], + through: { + attributes: [] + } + } }); if (!snippet) { @@ -100,11 +102,14 @@ export const getSnippet = asyncWrapper( ); } - const tags = await getTags(+req.params.id); - snippet.tags = tags; + const rawSnippet = snippet.get({ plain: true }); + const populatedSnippet = { + ...rawSnippet, + tags: rawSnippet.tags?.map(tag => tag.name) + }; res.status(200).json({ - data: snippet + data: populatedSnippet }); } ); @@ -116,6 +121,8 @@ export const getSnippet = asyncWrapper( */ export const updateSnippet = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { + console.log(req.body); + let snippet = await SnippetModel.findOne({ where: { id: req.params.id } }); @@ -219,10 +226,20 @@ export const searchSnippets = asyncWrapper( async (req: Request, res: Response, next: NextFunction): Promise => { const { query, tags, languages } = req.body; - console.log(query, tags, languages); + // Check if query is empty + if (query === '' && !tags.length && !languages.length) { + res.status(200).json({ + data: [] + }); - const languageFilter = - languages.length > 0 ? { [Op.in]: languages } : { [Op.notIn]: languages }; + return; + } + + const languageFilter = languages.length + ? { [Op.in]: languages } + : { [Op.notIn]: languages }; + + const tagFilter = tags.length ? { [Op.in]: tags } : { [Op.notIn]: tags }; const snippets = await SnippetModel.findAll({ where: { @@ -237,6 +254,17 @@ export const searchSnippets = asyncWrapper( language: languageFilter } ] + }, + include: { + model: TagModel, + as: 'tags', + attributes: ['name'], + where: { + name: tagFilter + }, + through: { + attributes: [] + } } }); diff --git a/src/db/associateModels.ts b/src/db/associateModels.ts new file mode 100644 index 0000000..9ef3314 --- /dev/null +++ b/src/db/associateModels.ts @@ -0,0 +1,15 @@ +import { TagModel, SnippetModel, Snippet_TagModel } from '../models'; + +export const associateModels = async () => { + TagModel.belongsToMany(SnippetModel, { + through: Snippet_TagModel, + foreignKey: 'tag_id', + as: 'snippets' + }); + + SnippetModel.belongsToMany(TagModel, { + through: Snippet_TagModel, + foreignKey: 'snippet_id', + as: 'tags' + }); +}; diff --git a/src/server.ts b/src/server.ts index 4fd9ccc..dfc64ea 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,6 +7,7 @@ import { errorHandler } from './middleware'; // Routers import { snippetRouter } from './routes/snippets'; +import { associateModels } from './db/associateModels'; // Env config dotenv.config({ path: './src/config/.env' }); @@ -32,6 +33,7 @@ app.use(errorHandler); (async () => { await connectDB(); + await associateModels(); app.listen(PORT, () => { logger.log( diff --git a/src/typescript/interfaces/Snippet.ts b/src/typescript/interfaces/Snippet.ts index 2a4c637..4feabe1 100644 --- a/src/typescript/interfaces/Snippet.ts +++ b/src/typescript/interfaces/Snippet.ts @@ -8,7 +8,7 @@ export interface Snippet extends Model { code: string; docs: string; isPinned: number; - tags?: string[]; + tags?: { name: string }[]; } export interface SnippetCreationAttributes