mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Updating snippets with tags
This commit is contained in:
@@ -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)
|
### v1.1 (2021-09-24)
|
||||||
- Added pin icon directly to snippet card ([#4](https://github.com/pawelmalak/snippet-box/issues/4))
|
- 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))
|
- Fixed issue with copying snippets ([#6](https://github.com/pawelmalak/snippet-box/issues/6))
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -10,14 +10,8 @@ COPY . .
|
|||||||
|
|
||||||
RUN mkdir -p ./public ./data
|
RUN mkdir -p ./public ./data
|
||||||
|
|
||||||
# Build server code
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build \
|
||||||
|
|
||||||
# Build client code
|
|
||||||
RUN cd ./client \
|
|
||||||
&& npm install \
|
|
||||||
&& npm run build \
|
|
||||||
&& cd .. \
|
|
||||||
&& mv ./client/build/* ./public
|
&& mv ./client/build/* ./public
|
||||||
|
|
||||||
# Clean up src files
|
# Clean up src files
|
||||||
|
|||||||
@@ -12,14 +12,8 @@ COPY . .
|
|||||||
|
|
||||||
RUN mkdir -p ./public ./data
|
RUN mkdir -p ./public ./data
|
||||||
|
|
||||||
# Build server code
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build \
|
||||||
|
|
||||||
# Build client code
|
|
||||||
RUN cd ./client \
|
|
||||||
&& npm install \
|
|
||||||
&& npm run build \
|
|
||||||
&& cd .. \
|
|
||||||
&& mv ./client/build/* ./public
|
&& mv ./client/build/* ./public
|
||||||
|
|
||||||
# Clean up src files
|
# Clean up src files
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ export const SnippetForm = (props: Props): JSX.Element => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tagsToString = (): string => {
|
||||||
|
return formData.tags.join(',');
|
||||||
|
};
|
||||||
|
|
||||||
const formHandler = (e: FormEvent) => {
|
const formHandler = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -134,13 +138,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={tagsToString()}
|
||||||
placeholder='automation, files, loop'
|
placeholder='automation, files, loop'
|
||||||
onChange={e => stringToTags(e)}
|
onChange={e => stringToTags(e)}
|
||||||
/>
|
/>
|
||||||
<div className='form-text'>
|
<div className='form-text'>
|
||||||
Tags should be separate with a comma. Language tag will be added
|
Tags should be separated with a comma. Language tag will be
|
||||||
automatically
|
added automatically
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setSnippet = (id: number): void => {
|
const setSnippet = (id: number): void => {
|
||||||
getSnippetById(id);
|
|
||||||
|
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
setCurrentSnippet(null);
|
setCurrentSnippet(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSnippetById(id);
|
||||||
|
|
||||||
const snippet = snippets.find(s => s.id === id);
|
const snippet = snippets.find(s => s.id === id);
|
||||||
|
|
||||||
if (snippet) {
|
if (snippet) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dev:client": "npm start --prefix=client",
|
"dev:client": "npm start --prefix=client",
|
||||||
"dev:server": "nodemon",
|
"dev:server": "nodemon",
|
||||||
"dev": "npm-run-all -n --parallel dev:**",
|
"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:clear": "rm -rf build",
|
||||||
"build:tsc": "tsc",
|
"build:tsc": "tsc",
|
||||||
"build": "npm-run-all -n build:**"
|
"build": "npm-run-all -n build:**"
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ 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 { SnippetModel, Snippet_TagModel } from '../models';
|
||||||
import {
|
import {
|
||||||
SnippetInstance,
|
ErrorResponse,
|
||||||
SnippetModel,
|
getTags,
|
||||||
Snippet_TagModel,
|
tagParser,
|
||||||
TagModel
|
Logger,
|
||||||
} from '../models';
|
createTags
|
||||||
import { ErrorResponse, getTags, tagParser, Logger } from '../utils';
|
} from '../utils';
|
||||||
|
import { Body } from '../typescript/interfaces';
|
||||||
|
|
||||||
const logger = new Logger('snippets-controller');
|
const logger = new Logger('snippets-controller');
|
||||||
|
|
||||||
@@ -19,11 +21,6 @@ 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> => {
|
||||||
interface Body {
|
|
||||||
language: string;
|
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tags from request body
|
// Get tags from request body
|
||||||
const { language, tags: requestTags } = <Body>req.body;
|
const { language, tags: requestTags } = <Body>req.body;
|
||||||
const parsedRequestTags = tagParser([
|
const parsedRequestTags = tagParser([
|
||||||
@@ -37,38 +34,8 @@ export const createSnippet = asyncWrapper(
|
|||||||
tags: [...parsedRequestTags].join(',')
|
tags: [...parsedRequestTags].join(',')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all tags
|
// Create tags
|
||||||
const rawAllTags = await sequelize.query<{ id: number; name: string }>(
|
await createTags(parsedRequestTags, snippet.id);
|
||||||
`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
|
// Get raw snippet values
|
||||||
const rawSnippet = snippet.get({ plain: true });
|
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 } = <Body>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({
|
res.status(200).json({
|
||||||
data: snippet
|
data: {
|
||||||
|
...rawSnippet,
|
||||||
|
tags: [...parsedRequestTags]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
9
src/typescript/interfaces/Body.ts
Normal file
9
src/typescript/interfaces/Body.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface Body {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
language: string;
|
||||||
|
code: string;
|
||||||
|
docs?: string;
|
||||||
|
isPinned: boolean;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export * from './Model';
|
|||||||
export * from './Snippet';
|
export * from './Snippet';
|
||||||
export * from './Tag';
|
export * from './Tag';
|
||||||
export * from './Snippet_Tag';
|
export * from './Snippet_Tag';
|
||||||
|
export * from './Body';
|
||||||
|
|||||||
39
src/utils/createTags.ts
Normal file
39
src/utils/createTags.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { sequelize } from '../db';
|
||||||
|
import { QueryTypes } from 'sequelize';
|
||||||
|
import { TagModel, Snippet_TagModel } from '../models';
|
||||||
|
|
||||||
|
export const createTags = async (
|
||||||
|
parsedTags: Set<string>,
|
||||||
|
snippetId: number
|
||||||
|
): Promise<void> => {
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,3 +2,4 @@ export * from './Logger';
|
|||||||
export * from './ErrorResponse';
|
export * from './ErrorResponse';
|
||||||
export * from './tagParser';
|
export * from './tagParser';
|
||||||
export * from './getTags';
|
export * from './getTags';
|
||||||
|
export * from './createTags';
|
||||||
|
|||||||
Reference in New Issue
Block a user