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';