diff --git a/CHANGELOG.md b/CHANGELOG.md
index a172248..0188a0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+### v1.2 (2021-09-28)
+- Added support for tags ([#10](https://github.com/pawelmalak/snippet-box/issues/10))
+
### v1.1 (2021-09-24)
- Added pin icon directly to snippet card ([#4](https://github.com/pawelmalak/snippet-box/issues/4))
- Fixed issue with copying snippets ([#6](https://github.com/pawelmalak/snippet-box/issues/6))
diff --git a/Dockerfile b/Dockerfile
index d16175b..53f5176 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,14 +10,8 @@ COPY . .
RUN mkdir -p ./public ./data
-# Build server code
-RUN npm run build
-
-# Build client code
-RUN cd ./client \
- && npm install \
- && npm run build \
- && cd .. \
+# Build
+RUN npm run build \
&& mv ./client/build/* ./public
# Clean up src files
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 5b3067d..244b0dd 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -12,14 +12,8 @@ COPY . .
RUN mkdir -p ./public ./data
-# Build server code
-RUN npm run build
-
-# Build client code
-RUN cd ./client \
- && npm install \
- && npm run build \
- && cd .. \
+# Build
+RUN npm run build \
&& mv ./client/build/* ./public
# Clean up src files
diff --git a/client/src/components/Snippets/SnippetForm.tsx b/client/src/components/Snippets/SnippetForm.tsx
index 257e9fc..fffb372 100644
--- a/client/src/components/Snippets/SnippetForm.tsx
+++ b/client/src/components/Snippets/SnippetForm.tsx
@@ -54,6 +54,10 @@ export const SnippetForm = (props: Props): JSX.Element => {
});
};
+ const tagsToString = (): string => {
+ return formData.tags.join(',');
+ };
+
const formHandler = (e: FormEvent) => {
e.preventDefault();
@@ -134,13 +138,13 @@ export const SnippetForm = (props: Props): JSX.Element => {
className='form-control'
id='tags'
name='tags'
- // value={formData.tags}
+ value={tagsToString()}
placeholder='automation, files, loop'
onChange={e => stringToTags(e)}
/>
- Tags should be separate with a comma. Language tag will be added
- automatically
+ Tags should be separated with a comma. Language tag will be
+ added automatically
diff --git a/client/src/store/SnippetsContext.tsx b/client/src/store/SnippetsContext.tsx
index 5d3b305..b180f71 100644
--- a/client/src/store/SnippetsContext.tsx
+++ b/client/src/store/SnippetsContext.tsx
@@ -53,13 +53,13 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
};
const setSnippet = (id: number): void => {
- getSnippetById(id);
-
if (id < 0) {
setCurrentSnippet(null);
return;
}
+ getSnippetById(id);
+
const snippet = snippets.find(s => s.id === id);
if (snippet) {
diff --git a/package.json b/package.json
index 62db005..0b2d4f3 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"dev:client": "npm start --prefix=client",
"dev:server": "nodemon",
"dev": "npm-run-all -n --parallel dev:**",
- "build:client": "npm run build prefix=client",
+ "build:client": "npm run build --prefix=client",
"build:clear": "rm -rf build",
"build:tsc": "tsc",
"build": "npm-run-all -n build:**"
diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts
index 01dce23..f8c4543 100644
--- a/src/controllers/snippets.ts
+++ b/src/controllers/snippets.ts
@@ -2,13 +2,15 @@ import { Request, Response, NextFunction } from 'express';
import { QueryTypes } from 'sequelize';
import { sequelize } from '../db';
import { asyncWrapper } from '../middleware';
+import { SnippetModel, Snippet_TagModel } from '../models';
import {
- SnippetInstance,
- SnippetModel,
- Snippet_TagModel,
- TagModel
-} from '../models';
-import { ErrorResponse, getTags, tagParser, Logger } from '../utils';
+ ErrorResponse,
+ getTags,
+ tagParser,
+ Logger,
+ createTags
+} from '../utils';
+import { Body } from '../typescript/interfaces';
const logger = new Logger('snippets-controller');
@@ -19,11 +21,6 @@ const logger = new Logger('snippets-controller');
*/
export const createSnippet = asyncWrapper(
async (req: Request, res: Response, next: NextFunction): Promise => {
- interface Body {
- language: string;
- tags: string[];
- }
-
// Get tags from request body
const { language, tags: requestTags } = req.body;
const parsedRequestTags = tagParser([
@@ -37,38 +34,8 @@ export const createSnippet = asyncWrapper(
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
- });
- }
- }
+ // Create tags
+ await createTags(parsedRequestTags, snippet.id);
// Get raw snippet values
const rawSnippet = snippet.get({ plain: true });
@@ -162,10 +129,28 @@ export const updateSnippet = asyncWrapper(
);
}
- snippet = await snippet.update(req.body);
+ // Get tags from request body
+ const { language, tags: requestTags } = req.body;
+ let parsedRequestTags = tagParser([...requestTags, language.toLowerCase()]);
+
+ // Update snippet
+ snippet = await snippet.update({
+ ...req.body,
+ tags: [...parsedRequestTags].join(',')
+ });
+
+ // Delete old tags and create new ones
+ await Snippet_TagModel.destroy({ where: { snippet_id: req.params.id } });
+ await createTags(parsedRequestTags, snippet.id);
+
+ // Get raw snippet values
+ const rawSnippet = snippet.get({ plain: true });
res.status(200).json({
- data: snippet
+ data: {
+ ...rawSnippet,
+ tags: [...parsedRequestTags]
+ }
});
}
);
diff --git a/src/typescript/interfaces/Body.ts b/src/typescript/interfaces/Body.ts
new file mode 100644
index 0000000..6dcf8f7
--- /dev/null
+++ b/src/typescript/interfaces/Body.ts
@@ -0,0 +1,9 @@
+export interface Body {
+ title: string;
+ description?: string;
+ language: string;
+ code: string;
+ docs?: string;
+ isPinned: boolean;
+ tags: string[];
+}
diff --git a/src/typescript/interfaces/index.ts b/src/typescript/interfaces/index.ts
index 876ad66..b86dc4a 100644
--- a/src/typescript/interfaces/index.ts
+++ b/src/typescript/interfaces/index.ts
@@ -2,3 +2,4 @@ export * from './Model';
export * from './Snippet';
export * from './Tag';
export * from './Snippet_Tag';
+export * from './Body';
diff --git a/src/utils/createTags.ts b/src/utils/createTags.ts
new file mode 100644
index 0000000..1f53cc4
--- /dev/null
+++ b/src/utils/createTags.ts
@@ -0,0 +1,39 @@
+import { sequelize } from '../db';
+import { QueryTypes } from 'sequelize';
+import { TagModel, Snippet_TagModel } from '../models';
+
+export const createTags = async (
+ parsedTags: Set,
+ snippetId: number
+): Promise => {
+ // 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 = [...parsedTags].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 parsedTags) {
+ const tagObj = rawAllTags.find(t => t.name == tag);
+
+ if (tagObj) {
+ await Snippet_TagModel.create({
+ snippet_id: snippetId,
+ tag_id: tagObj.id
+ });
+ }
+ }
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 96fabc9..e9efd2a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -2,3 +2,4 @@ export * from './Logger';
export * from './ErrorResponse';
export * from './tagParser';
export * from './getTags';
+export * from './createTags';