mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 21:33:10 +01:00
Create/update snippet with tags. DB migration to support tags
This commit is contained in:
@@ -77,7 +77,7 @@ docker run -p 5000:5000 -v /path/to/data:/app/data snippet-box
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Edditor
|
- Editor
|
||||||
- Create and edit your snippets from simple and easy to use editor
|
- Create and edit your snippets from simple and easy to use editor
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const SnippetForm = (props: Props): JSX.Element => {
|
|||||||
language: '',
|
language: '',
|
||||||
code: '',
|
code: '',
|
||||||
docs: '',
|
docs: '',
|
||||||
isPinned: false
|
isPinned: false,
|
||||||
|
tags: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -109,11 +110,31 @@ export const SnippetForm = (props: Props): JSX.Element => {
|
|||||||
id='language'
|
id='language'
|
||||||
name='language'
|
name='language'
|
||||||
value={formData.language}
|
value={formData.language}
|
||||||
placeholder='bash'
|
placeholder='python'
|
||||||
required
|
required
|
||||||
onChange={e => inputHandler(e)}
|
onChange={e => inputHandler(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* TAGS */}
|
||||||
|
<div className='mb-3'>
|
||||||
|
<label htmlFor='tags' className='form-label'>
|
||||||
|
Tags
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='form-control'
|
||||||
|
id='tags'
|
||||||
|
name='tags'
|
||||||
|
value={formData.tags}
|
||||||
|
placeholder='automation, files, loop'
|
||||||
|
required
|
||||||
|
onChange={e => inputHandler(e)}
|
||||||
|
/>
|
||||||
|
<div className='form-text'>
|
||||||
|
Language tag will be added automatically
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{/* CODE SECTION */}
|
{/* CODE SECTION */}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface NewSnippet {
|
|||||||
code: string;
|
code: string;
|
||||||
docs?: string;
|
docs?: string;
|
||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
|
tags: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Snippet extends Model, NewSnippet {}
|
export interface Snippet extends Model, NewSnippet {}
|
||||||
|
|||||||
@@ -10,6 +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: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:**"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { QueryTypes } from 'sequelize';
|
|||||||
import { sequelize } from '../db';
|
import { sequelize } from '../db';
|
||||||
import { asyncWrapper } from '../middleware';
|
import { asyncWrapper } from '../middleware';
|
||||||
import { SnippetModel } from '../models';
|
import { SnippetModel } from '../models';
|
||||||
import { ErrorResponse } from '../utils';
|
import { ErrorResponse, tagsParser } from '../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Create new snippet
|
* @description Create new snippet
|
||||||
@@ -12,7 +12,17 @@ import { ErrorResponse } from '../utils';
|
|||||||
*/
|
*/
|
||||||
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 snippet = await SnippetModel.create(req.body);
|
const { language, tags } = <{ language: string; tags: string }>req.body;
|
||||||
|
const parsedTags = tagsParser(tags);
|
||||||
|
|
||||||
|
if (!parsedTags.includes(language.toLowerCase())) {
|
||||||
|
parsedTags.push(language.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
const snippet = await SnippetModel.create({
|
||||||
|
...req.body,
|
||||||
|
tags: parsedTags.join(',')
|
||||||
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
data: snippet
|
data: snippet
|
||||||
@@ -81,7 +91,23 @@ export const updateSnippet = asyncWrapper(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
snippet = await snippet.update(req.body);
|
// 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(',')
|
||||||
|
});
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
data: snippet
|
data: snippet
|
||||||
|
|||||||
19
src/db/migrations/02_tags.ts
Normal file
19
src/db/migrations/02_tags.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { DataTypes, QueryInterface, QueryTypes } from 'sequelize';
|
||||||
|
import { sequelize } from '../';
|
||||||
|
const { STRING } = DataTypes;
|
||||||
|
|
||||||
|
export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
||||||
|
await queryInterface.addColumn('snippets', 'tags', {
|
||||||
|
type: STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
await sequelize.query(`UPDATE snippets SET tags = language`, {
|
||||||
|
type: QueryTypes.UPDATE
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const down = async (queryInterface: QueryInterface): Promise<void> => {
|
||||||
|
await queryInterface.removeColumn('snippets', 'tags');
|
||||||
|
};
|
||||||
@@ -41,6 +41,11 @@ export const SnippetModel = sequelize.define<SnippetInstance>('Snippet', {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
|
tags: {
|
||||||
|
type: STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: DATE
|
type: DATE
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface Snippet extends Model {
|
|||||||
code: string;
|
code: string;
|
||||||
docs: string;
|
docs: string;
|
||||||
isPinned: number;
|
isPinned: number;
|
||||||
|
tags: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnippetCreationAttributes
|
export interface SnippetCreationAttributes
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './Logger';
|
export * from './Logger';
|
||||||
export * from './ErrorResponse';
|
export * from './ErrorResponse';
|
||||||
|
export * from './tagsParser';
|
||||||
|
|||||||
8
src/utils/tagsParser.ts
Normal file
8
src/utils/tagsParser.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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