mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Create snippet with tags
This commit is contained in:
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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,40 +48,42 @@ export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
|||||||
const uniqueLanguages = [...new Set(languages)];
|
const uniqueLanguages = [...new Set(languages)];
|
||||||
const tags: TagInstance[] = [];
|
const tags: TagInstance[] = [];
|
||||||
|
|
||||||
await new Promise<void>(resolve => {
|
if (snippets.length > 0) {
|
||||||
uniqueLanguages.forEach(async language => {
|
await new Promise<void>(resolve => {
|
||||||
try {
|
uniqueLanguages.forEach(async language => {
|
||||||
const tag = await TagModel.create({ name: language });
|
try {
|
||||||
tags.push(tag);
|
const tag = await TagModel.create({ name: language });
|
||||||
} catch (err) {
|
tags.push(tag);
|
||||||
logger.log('Error while creating new tags');
|
} catch (err) {
|
||||||
} finally {
|
logger.log('Error while creating new tags');
|
||||||
if (uniqueLanguages.length == tags.length) {
|
} finally {
|
||||||
|
if (uniqueLanguages.length == tags.length) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign tag to snippet
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
snippets.forEach(async snippet => {
|
||||||
|
try {
|
||||||
|
const tag = tags.find(tag => tag.name == snippet.language);
|
||||||
|
|
||||||
|
if (tag) {
|
||||||
|
await Snippet_TagModel.create({
|
||||||
|
snippet_id: snippet.id,
|
||||||
|
tag_id: tag.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.log('Error while assigning tags to snippets');
|
||||||
|
} finally {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// Assign tag to snippet
|
|
||||||
await new Promise<void>(resolve => {
|
|
||||||
snippets.forEach(async snippet => {
|
|
||||||
try {
|
|
||||||
const tag = tags.find(tag => tag.name == snippet.language);
|
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
await Snippet_TagModel.create({
|
|
||||||
snippet_id: snippet.id,
|
|
||||||
tag_id: tag.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.log('Error while assigning tags to snippets');
|
|
||||||
} finally {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const down = async (queryInterface: QueryInterface): Promise<void> => {
|
export const down = async (queryInterface: QueryInterface): Promise<void> => {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const TagModel = sequelize.define<TagInstance>(
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: STRING,
|
type: STRING,
|
||||||
allowNull: false
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
6
src/utils/tagParser.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export const tagsParser = (tags: string): string[] => {
|
|
||||||
const parsedTags = tags
|
|
||||||
.split(',')
|
|
||||||
.map(tag => tag.trim().toLowerCase())
|
|
||||||
.filter(String);
|
|
||||||
|
|
||||||
return parsedTags;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user