Create snippet with tags

This commit is contained in:
unknown
2021-09-28 12:17:59 +02:00
parent 3cd2688589
commit 86ea246e90
8 changed files with 121 additions and 76 deletions

View File

@@ -64,8 +64,8 @@ export const SnippetDetails = (props: Props): JSX.Element => {
{/* TAGS */} {/* TAGS */}
<div> <div>
{tags.map(tag => ( {tags.map((tag, idx) => (
<span className='me-2'> <span className='me-2' key={idx}>
<Badge text={tag} color='dark' /> <Badge text={tag} color='dark' />
</span> </span>
))} ))}

View File

@@ -26,7 +26,7 @@ export const SnippetForm = (props: Props): JSX.Element => {
code: '', code: '',
docs: '', docs: '',
isPinned: false, isPinned: false,
tags: '' tags: []
}); });
useEffect(() => { useEffect(() => {
@@ -46,6 +46,14 @@ export const SnippetForm = (props: Props): JSX.Element => {
}); });
}; };
const stringToTags = (e: ChangeEvent<HTMLInputElement>) => {
const tags = e.target.value.split(',');
setFormData({
...formData,
tags
});
};
const formHandler = (e: FormEvent) => { const formHandler = (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -126,13 +134,13 @@ export const SnippetForm = (props: Props): JSX.Element => {
className='form-control' className='form-control'
id='tags' id='tags'
name='tags' name='tags'
value={formData.tags} // value={formData.tags}
placeholder='automation, files, loop' placeholder='automation, files, loop'
required onChange={e => stringToTags(e)}
onChange={e => inputHandler(e)}
/> />
<div className='form-text'> <div className='form-text'>
Language tag will be added automatically Tags should be separate with a comma. Language tag will be added
automatically
</div> </div>
</div> </div>
<hr /> <hr />

View File

@@ -2,8 +2,13 @@ import { Request, Response, NextFunction } from 'express';
import { QueryTypes } from 'sequelize'; import { QueryTypes } from 'sequelize';
import { sequelize } from '../db'; import { sequelize } from '../db';
import { asyncWrapper } from '../middleware'; import { asyncWrapper } from '../middleware';
import { SnippetInstance, SnippetModel } from '../models'; import {
import { ErrorResponse, getTags, tagsParser, Logger } from '../utils'; SnippetInstance,
SnippetModel,
Snippet_TagModel,
TagModel
} from '../models';
import { ErrorResponse, getTags, tagParser, Logger } from '../utils';
const logger = new Logger('snippets-controller'); const logger = new Logger('snippets-controller');
@@ -14,20 +19,65 @@ const logger = new Logger('snippets-controller');
*/ */
export const createSnippet = asyncWrapper( export const createSnippet = asyncWrapper(
async (req: Request, res: Response, next: NextFunction): Promise<void> => { async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const { language, tags } = <{ language: string; tags: string }>req.body; interface Body {
const parsedTags = tagsParser(tags); language: string;
tags: string[];
if (!parsedTags.includes(language.toLowerCase())) {
parsedTags.push(language.toLowerCase());
} }
// Get tags from request body
const { language, tags: requestTags } = <Body>req.body;
const parsedRequestTags = tagParser([
...requestTags,
language.toLowerCase()
]);
// Create snippet
const snippet = await SnippetModel.create({ const snippet = await SnippetModel.create({
...req.body, ...req.body,
tags: parsedTags.join(',') tags: [...parsedRequestTags].join(',')
}); });
// Get all tags
const rawAllTags = await sequelize.query<{ id: number; name: string }>(
`SELECT * FROM tags`,
{ type: QueryTypes.SELECT }
);
const parsedAllTags = rawAllTags.map(tag => tag.name);
// Create array of new tags
const newTags = [...parsedRequestTags].filter(
tag => !parsedAllTags.includes(tag)
);
// Create new tags
if (newTags.length > 0) {
for (const tag of newTags) {
const { id, name } = await TagModel.create({ name: tag });
rawAllTags.push({ id, name });
}
}
// Associate tags with snippet
for (const tag of parsedRequestTags) {
const tagObj = rawAllTags.find(t => t.name == tag);
if (tagObj) {
await Snippet_TagModel.create({
snippet_id: snippet.id,
tag_id: tagObj.id
});
}
}
// Get raw snippet values
const rawSnippet = snippet.get({ plain: true });
res.status(201).json({ res.status(201).json({
data: snippet data: {
...rawSnippet,
tags: [...parsedRequestTags]
}
}); });
} }
); );
@@ -50,7 +100,7 @@ export const getAllSnippets = asyncWrapper(
snippet.tags = tags; snippet.tags = tags;
} }
} catch (err) { } catch (err) {
logger.log('Error while fetching tags'); logger.log('Error while fetching tags', 'ERROR');
} finally { } finally {
resolve(); resolve();
} }
@@ -112,23 +162,7 @@ export const updateSnippet = asyncWrapper(
); );
} }
// Check if language was changed. Edit tags if so snippet = await snippet.update(req.body);
const { language: oldLanguage } = snippet;
const { language, tags } = <{ language: string; tags: string }>req.body;
let parsedTags = tagsParser(tags);
if (oldLanguage != language) {
parsedTags = parsedTags.filter(tag => tag != oldLanguage);
if (!parsedTags.includes(language)) {
parsedTags.push(language.toLowerCase());
}
}
snippet = await snippet.update({
...req.body,
tags: parsedTags.join(',')
});
res.status(200).json({ res.status(200).json({
data: snippet data: snippet
@@ -156,6 +190,7 @@ export const deleteSnippet = asyncWrapper(
); );
} }
await Snippet_TagModel.destroy({ where: { snippet_id: req.params.id } });
await snippet.destroy(); await snippet.destroy();
res.status(200).json({ res.status(200).json({

View File

@@ -20,7 +20,8 @@ export const up = async (queryInterface: QueryInterface): Promise<void> => {
}, },
name: { name: {
type: STRING, type: STRING,
allowNull: false allowNull: false,
unique: true
} }
}); });
@@ -47,6 +48,7 @@ export const up = async (queryInterface: QueryInterface): Promise<void> => {
const uniqueLanguages = [...new Set(languages)]; const uniqueLanguages = [...new Set(languages)];
const tags: TagInstance[] = []; const tags: TagInstance[] = [];
if (snippets.length > 0) {
await new Promise<void>(resolve => { await new Promise<void>(resolve => {
uniqueLanguages.forEach(async language => { uniqueLanguages.forEach(async language => {
try { try {
@@ -81,6 +83,7 @@ export const up = async (queryInterface: QueryInterface): Promise<void> => {
} }
}); });
}); });
}
}; };
export const down = async (queryInterface: QueryInterface): Promise<void> => { export const down = async (queryInterface: QueryInterface): Promise<void> => {

View File

@@ -16,7 +16,8 @@ export const TagModel = sequelize.define<TagInstance>(
}, },
name: { name: {
type: STRING, type: STRING,
allowNull: false allowNull: false,
unique: true
} }
}, },
{ {

View File

@@ -1,4 +1,4 @@
export * from './Logger'; export * from './Logger';
export * from './ErrorResponse'; export * from './ErrorResponse';
export * from './tagsParser'; export * from './tagParser';
export * from './getTags'; export * from './getTags';

6
src/utils/tagParser.ts Normal file
View File

@@ -0,0 +1,6 @@
export const tagParser = (tags: string[]): Set<string> => {
const parsedTags = tags.map(tag => tag.trim().toLowerCase()).filter(String);
const uniqueTags = new Set([...parsedTags]);
return uniqueTags;
};

View File

@@ -1,8 +0,0 @@
export const tagsParser = (tags: string): string[] => {
const parsedTags = tags
.split(',')
.map(tag => tag.trim().toLowerCase())
.filter(String);
return parsedTags;
};