diff --git a/client/src/components/Snippets/SnippetDetails.tsx b/client/src/components/Snippets/SnippetDetails.tsx
index a3cb22e..514f343 100644
--- a/client/src/components/Snippets/SnippetDetails.tsx
+++ b/client/src/components/Snippets/SnippetDetails.tsx
@@ -64,8 +64,8 @@ export const SnippetDetails = (props: Props): JSX.Element => {
{/* TAGS */}
- {tags.map(tag => (
-
+ {tags.map((tag, idx) => (
+
))}
diff --git a/client/src/components/Snippets/SnippetForm.tsx b/client/src/components/Snippets/SnippetForm.tsx
index d839b25..257e9fc 100644
--- a/client/src/components/Snippets/SnippetForm.tsx
+++ b/client/src/components/Snippets/SnippetForm.tsx
@@ -26,7 +26,7 @@ export const SnippetForm = (props: Props): JSX.Element => {
code: '',
docs: '',
isPinned: false,
- tags: ''
+ tags: []
});
useEffect(() => {
@@ -46,6 +46,14 @@ export const SnippetForm = (props: Props): JSX.Element => {
});
};
+ const stringToTags = (e: ChangeEvent) => {
+ const tags = e.target.value.split(',');
+ setFormData({
+ ...formData,
+ tags
+ });
+ };
+
const formHandler = (e: FormEvent) => {
e.preventDefault();
@@ -126,13 +134,13 @@ export const SnippetForm = (props: Props): JSX.Element => {
className='form-control'
id='tags'
name='tags'
- value={formData.tags}
+ // value={formData.tags}
placeholder='automation, files, loop'
- required
- onChange={e => inputHandler(e)}
+ onChange={e => stringToTags(e)}
/>
- Language tag will be added automatically
+ Tags should be separate with a comma. Language tag will be added
+ automatically
diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts
index 65fac50..01dce23 100644
--- a/src/controllers/snippets.ts
+++ b/src/controllers/snippets.ts
@@ -2,8 +2,13 @@ import { Request, Response, NextFunction } from 'express';
import { QueryTypes } from 'sequelize';
import { sequelize } from '../db';
import { asyncWrapper } from '../middleware';
-import { SnippetInstance, SnippetModel } from '../models';
-import { ErrorResponse, getTags, tagsParser, Logger } from '../utils';
+import {
+ SnippetInstance,
+ SnippetModel,
+ Snippet_TagModel,
+ TagModel
+} from '../models';
+import { ErrorResponse, getTags, tagParser, Logger } from '../utils';
const logger = new Logger('snippets-controller');
@@ -14,20 +19,65 @@ const logger = new Logger('snippets-controller');
*/
export const createSnippet = asyncWrapper(
async (req: Request, res: Response, next: NextFunction): Promise => {
- const { language, tags } = <{ language: string; tags: string }>req.body;
- const parsedTags = tagsParser(tags);
-
- if (!parsedTags.includes(language.toLowerCase())) {
- parsedTags.push(language.toLowerCase());
+ interface Body {
+ language: string;
+ tags: string[];
}
+ // Get tags from request body
+ const { language, tags: requestTags } = req.body;
+ const parsedRequestTags = tagParser([
+ ...requestTags,
+ language.toLowerCase()
+ ]);
+
+ // Create snippet
const snippet = await SnippetModel.create({
...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({
- data: snippet
+ data: {
+ ...rawSnippet,
+ tags: [...parsedRequestTags]
+ }
});
}
);
@@ -50,7 +100,7 @@ export const getAllSnippets = asyncWrapper(
snippet.tags = tags;
}
} catch (err) {
- logger.log('Error while fetching tags');
+ logger.log('Error while fetching tags', 'ERROR');
} finally {
resolve();
}
@@ -112,23 +162,7 @@ export const updateSnippet = asyncWrapper(
);
}
- // Check if language was changed. Edit tags if so
- 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(',')
- });
+ snippet = await snippet.update(req.body);
res.status(200).json({
data: snippet
@@ -156,6 +190,7 @@ export const deleteSnippet = asyncWrapper(
);
}
+ await Snippet_TagModel.destroy({ where: { snippet_id: req.params.id } });
await snippet.destroy();
res.status(200).json({
diff --git a/src/db/migrations/02_tags.ts b/src/db/migrations/02_tags.ts
index 558a144..02d09a9 100644
--- a/src/db/migrations/02_tags.ts
+++ b/src/db/migrations/02_tags.ts
@@ -20,7 +20,8 @@ export const up = async (queryInterface: QueryInterface): Promise => {
},
name: {
type: STRING,
- allowNull: false
+ allowNull: false,
+ unique: true
}
});
@@ -47,40 +48,42 @@ export const up = async (queryInterface: QueryInterface): Promise => {
const uniqueLanguages = [...new Set(languages)];
const tags: TagInstance[] = [];
- await new Promise(resolve => {
- uniqueLanguages.forEach(async language => {
- try {
- const tag = await TagModel.create({ name: language });
- tags.push(tag);
- } catch (err) {
- logger.log('Error while creating new tags');
- } finally {
- if (uniqueLanguages.length == tags.length) {
+ if (snippets.length > 0) {
+ await new Promise(resolve => {
+ uniqueLanguages.forEach(async language => {
+ try {
+ const tag = await TagModel.create({ name: language });
+ tags.push(tag);
+ } catch (err) {
+ logger.log('Error while creating new tags');
+ } finally {
+ if (uniqueLanguages.length == tags.length) {
+ resolve();
+ }
+ }
+ });
+ });
+
+ // Assign tag to snippet
+ await new Promise(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();
}
- }
+ });
});
- });
-
- // Assign tag to snippet
- await new Promise(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 => {
diff --git a/src/models/Tag.ts b/src/models/Tag.ts
index 9c2e2de..759bd06 100644
--- a/src/models/Tag.ts
+++ b/src/models/Tag.ts
@@ -16,7 +16,8 @@ export const TagModel = sequelize.define(
},
name: {
type: STRING,
- allowNull: false
+ allowNull: false,
+ unique: true
}
},
{
diff --git a/src/utils/index.ts b/src/utils/index.ts
index f05b365..96fabc9 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,4 @@
export * from './Logger';
export * from './ErrorResponse';
-export * from './tagsParser';
+export * from './tagParser';
export * from './getTags';
diff --git a/src/utils/tagParser.ts b/src/utils/tagParser.ts
new file mode 100644
index 0000000..9e66bcc
--- /dev/null
+++ b/src/utils/tagParser.ts
@@ -0,0 +1,6 @@
+export const tagParser = (tags: string[]): Set => {
+ const parsedTags = tags.map(tag => tag.trim().toLowerCase()).filter(String);
+ const uniqueTags = new Set([...parsedTags]);
+
+ return uniqueTags;
+};
diff --git a/src/utils/tagsParser.ts b/src/utils/tagsParser.ts
deleted file mode 100644
index 14fa27e..0000000
--- a/src/utils/tagsParser.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const tagsParser = (tags: string): string[] => {
- const parsedTags = tags
- .split(',')
- .map(tag => tag.trim().toLowerCase())
- .filter(String);
-
- return parsedTags;
-};