Search feature. Changed how controller fetches tags

This commit is contained in:
Paweł Malak
2021-10-14 15:09:04 +02:00
parent b04d042255
commit fa7f54d0e1
5 changed files with 81 additions and 31 deletions

View File

@@ -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))

View File

@@ -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<void> => {
const snippets = await SnippetModel.findAll({
raw: true
});
await new Promise<void>(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<void> => {
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<void> => {
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<void> => {
const { query, tags, languages } = <SearchQuery>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: []
}
}
});

15
src/db/associateModels.ts Normal file
View File

@@ -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'
});
};

View File

@@ -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(

View File

@@ -8,7 +8,7 @@ export interface Snippet extends Model {
code: string;
docs: string;
isPinned: number;
tags?: string[];
tags?: { name: string }[];
}
export interface SnippetCreationAttributes