mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Database changes. Added Snippet model and controller
This commit is contained in:
100
package-lock.json
generated
100
package-lock.json
generated
@@ -58,6 +58,11 @@
|
||||
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/bluebird": {
|
||||
"version": "3.5.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz",
|
||||
"integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q=="
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
|
||||
@@ -68,6 +73,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.2.0.tgz",
|
||||
"integrity": "sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==",
|
||||
"requires": {
|
||||
"bson": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||
@@ -77,6 +90,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/continuation-local-storage": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.3.tgz",
|
||||
"integrity": "sha512-4LYeWblV+6puK9tFGM7Zr4OLZkVXmaL7hUK6/wHwbfwM+q7v+HZyBWTXkNOiC9GqOxv7ehhi5TMCbebZWeVYtw==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
|
||||
@@ -100,12 +121,26 @@
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.173",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz",
|
||||
"integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg=="
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
|
||||
"integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==",
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz",
|
||||
@@ -123,6 +158,17 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/sequelize": {
|
||||
"version": "4.28.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.28.10.tgz",
|
||||
"integrity": "sha512-GKbEbl6uyEYTPvU2JZvmqZHfpwTTjaZvNSd2gFJrhcxUL1bcyG7i+S8Od2L0/+skrk2bBINl7J1Sugo0mgIY3g==",
|
||||
"requires": {
|
||||
"@types/bluebird": "*",
|
||||
"@types/continuation-local-storage": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/validator": "*"
|
||||
}
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||
@@ -133,6 +179,20 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/umzug": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/umzug/-/umzug-2.3.2.tgz",
|
||||
"integrity": "sha512-ozfpVpUwzozBVVII2T1iUTByJ1n+xgi3xpjzUnZ50tnAhBCzQ+RiewGgjyzuK2RAb3cug0mqKTvMbvd336lkDw==",
|
||||
"requires": {
|
||||
"@types/mongodb": "^3.6.20",
|
||||
"@types/sequelize": "*"
|
||||
}
|
||||
},
|
||||
"@types/validator": {
|
||||
"version": "13.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz",
|
||||
"integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@@ -306,6 +366,11 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
@@ -330,6 +395,11 @@
|
||||
"inherits": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -470,6 +540,23 @@
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.2.tgz",
|
||||
"integrity": "sha512-8CEMJpwc7qlQtrn2rney38jQSEeMar847lz0LyitwRmVknAW8iHXrzW4fTjHfyWm0E3sukyD/zppdH+QU1QefA==",
|
||||
"requires": {
|
||||
"buffer": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
@@ -1266,6 +1353,11 @@
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"ignore-by-default": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||
@@ -2831,6 +2923,14 @@
|
||||
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
|
||||
"dev": true
|
||||
},
|
||||
"umzug": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz",
|
||||
"integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==",
|
||||
"requires": {
|
||||
"bluebird": "^3.7.2"
|
||||
}
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||
|
||||
11
package.json
11
package.json
@@ -2,14 +2,14 @@
|
||||
"name": "snippet-hub",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/server.ts",
|
||||
"main": "build/server.js",
|
||||
"scripts": {
|
||||
"init:client": "npm install --prefix=client",
|
||||
"init:server": "npm install",
|
||||
"init": "npm-run-all -n init:**",
|
||||
"dev:client": "npm start --prefix=client",
|
||||
"dev:server": "nodemon src/server.ts",
|
||||
"dev": "npm-run-all -n dev:**",
|
||||
"dev:server": "nodemon",
|
||||
"dev": "npm-run-all -n --parallel dev:**",
|
||||
"build:clear": "rm -rf build",
|
||||
"build:tsc": "tsc",
|
||||
"build": "npm-run-all -n build:**"
|
||||
@@ -20,15 +20,18 @@
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.9.2",
|
||||
"@types/validator": "^13.6.3",
|
||||
"nodemon": "^2.0.12",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/umzug": "^2.3.2",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"sequelize": "^6.6.5",
|
||||
"sqlite3": "^5.0.2"
|
||||
"sqlite3": "^5.0.2",
|
||||
"umzug": "^2.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
13
src/controllers/snippets.ts
Normal file
13
src/controllers/snippets.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { asyncWrapper } from '../middleware';
|
||||
import { SnippetModel } from '../models';
|
||||
|
||||
export const createSnippet = asyncWrapper(
|
||||
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||
const snippet = await SnippetModel.create(req.body);
|
||||
|
||||
res.status(201).json({
|
||||
data: snippet
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,9 +1,9 @@
|
||||
require('ts-node/register');
|
||||
import path from 'path';
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { Umzug, SequelizeStorage } from 'umzug';
|
||||
import Umzug from 'umzug';
|
||||
import { Logger } from '../utils';
|
||||
|
||||
const logger = new Logger();
|
||||
const logger = new Logger('db');
|
||||
|
||||
// DB config
|
||||
export const sequelize = new Sequelize({
|
||||
@@ -14,15 +14,21 @@ export const sequelize = new Sequelize({
|
||||
|
||||
// Migrations config
|
||||
const umzug = new Umzug({
|
||||
migrations: { glob: '**/migrations/*.ts' },
|
||||
context: sequelize.getQueryInterface(),
|
||||
storage: new SequelizeStorage({ sequelize }),
|
||||
logger: undefined
|
||||
migrations: {
|
||||
path: path.join(__dirname, './migrations'),
|
||||
params: [sequelize.getQueryInterface()],
|
||||
pattern: /^\d+[\w-]+\.(js|ts)$/
|
||||
},
|
||||
storage: 'sequelize',
|
||||
storageOptions: {
|
||||
sequelize
|
||||
},
|
||||
logging: false
|
||||
});
|
||||
|
||||
export type Migration = typeof umzug._types.migration;
|
||||
|
||||
export const connectDB = async () => {
|
||||
const isDev = process.env.NODE_ENV == 'development';
|
||||
|
||||
try {
|
||||
// Create & connect db
|
||||
await sequelize.authenticate();
|
||||
@@ -30,15 +36,22 @@ export const connectDB = async () => {
|
||||
|
||||
// Check migrations
|
||||
const pendingMigrations = await umzug.pending();
|
||||
|
||||
if (pendingMigrations.length > 0) {
|
||||
logger.log(`Found pending migrations. Executing...`);
|
||||
|
||||
if (isDev) {
|
||||
pendingMigrations.forEach(({ file }) =>
|
||||
logger.log(`Executing ${file} migration`, 'DEV')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await umzug.up();
|
||||
} catch (err) {
|
||||
logger.log(`Database connection error`, 'ERROR');
|
||||
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
if (isDev) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DataTypes, Model } from 'sequelize';
|
||||
import type { Migration } from '../';
|
||||
import { DataTypes, Model, QueryInterface } from 'sequelize';
|
||||
import {
|
||||
Snippet,
|
||||
SnippetCreationAttributes
|
||||
@@ -7,16 +6,15 @@ import {
|
||||
|
||||
const { INTEGER, STRING, DATE } = DataTypes;
|
||||
|
||||
export const up: Migration = async ({
|
||||
context: queryInterface
|
||||
}): Promise<void> => {
|
||||
export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
||||
await queryInterface.createTable<Model<Snippet, SnippetCreationAttributes>>(
|
||||
'snippets',
|
||||
{
|
||||
id: {
|
||||
type: INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: STRING,
|
||||
@@ -38,8 +36,6 @@ export const up: Migration = async ({
|
||||
);
|
||||
};
|
||||
|
||||
export const down: Migration = async ({
|
||||
context: queryInterface
|
||||
}): Promise<void> => {
|
||||
export const down = async (queryInterface: QueryInterface): Promise<void> => {
|
||||
await queryInterface.dropTable('snippets');
|
||||
};
|
||||
|
||||
8
src/middleware/asyncWrapper.ts
Normal file
8
src/middleware/asyncWrapper.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
type Foo = (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
|
||||
export const asyncWrapper =
|
||||
(foo: Foo) => (req: Request, res: Response, next: NextFunction) => {
|
||||
return Promise.resolve(foo(req, res, next)).catch(next);
|
||||
};
|
||||
2
src/middleware/index.ts
Normal file
2
src/middleware/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './asyncWrapper';
|
||||
export * from './requireBody';
|
||||
16
src/middleware/requireBody.ts
Normal file
16
src/middleware/requireBody.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export const requireBody =
|
||||
(...fields: string[]) =>
|
||||
(req: Request, res: Response, next: NextFunction): void => {
|
||||
const bodyKeys = Object.keys(req.body);
|
||||
const missingKeys: string[] = [];
|
||||
|
||||
fields.forEach(field => {
|
||||
if (!bodyKeys.includes(field)) {
|
||||
missingKeys.push(field);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
31
src/models/Snippet.ts
Normal file
31
src/models/Snippet.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../db';
|
||||
import { Snippet, SnippetCreationAttributes } from '../typescript/interfaces';
|
||||
|
||||
const { INTEGER, STRING, DATE } = DataTypes;
|
||||
|
||||
interface SnippetInstance
|
||||
extends Model<Snippet, SnippetCreationAttributes>,
|
||||
Snippet {}
|
||||
|
||||
export const SnippetModel = sequelize.define<SnippetInstance>('Snippet', {
|
||||
id: {
|
||||
type: INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: STRING,
|
||||
allowNull: false
|
||||
},
|
||||
language: {
|
||||
type: STRING,
|
||||
allowNull: false
|
||||
},
|
||||
createdAt: {
|
||||
type: DATE
|
||||
},
|
||||
updatedAt: {
|
||||
type: DATE
|
||||
}
|
||||
});
|
||||
1
src/models/index.ts
Normal file
1
src/models/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Snippet';
|
||||
7
src/routes/snippets.ts
Normal file
7
src/routes/snippets.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import { createSnippet } from '../controllers/snippets';
|
||||
import { requireBody } from '../middleware';
|
||||
|
||||
export const snippetRouter = Router();
|
||||
|
||||
snippetRouter.route('/').post(requireBody('title', 'language'), createSnippet);
|
||||
@@ -3,16 +3,22 @@ import express from 'express';
|
||||
import { Logger } from './utils';
|
||||
import { connectDB } from './db';
|
||||
|
||||
// Routers
|
||||
import { snippetRouter } from './routes/snippets';
|
||||
|
||||
// Env config
|
||||
dotenv.config({ path: './src/config/.env' });
|
||||
|
||||
const app = express();
|
||||
const logger = new Logger();
|
||||
const logger = new Logger('server');
|
||||
const PORT = process.env.PORT;
|
||||
|
||||
// App config
|
||||
app.use(express.json());
|
||||
|
||||
// Routes
|
||||
app.use('/api/snippets', snippetRouter);
|
||||
|
||||
(async () => {
|
||||
await connectDB();
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type ErrorLevel = 'INFO' | 'ERROR' | 'WARN';
|
||||
export type ErrorLevel = 'INFO' | 'ERROR' | 'WARN' | 'DEV';
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { ErrorLevel } from '../typescript/types/ErrorLevel';
|
||||
|
||||
export class Logger {
|
||||
private namespace: string;
|
||||
|
||||
constructor(namespace: string) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public log(message: string, level: ErrorLevel = 'INFO'): void {
|
||||
console.log(`[${this.generateTimestamp()}] [${level}] ${message}`);
|
||||
console.log(
|
||||
`[${this.generateTimestamp()}] [${level}] ${this.namespace}: ${message}`
|
||||
);
|
||||
}
|
||||
|
||||
private generateTimestamp(): string {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
@@ -68,5 +68,5 @@
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"exclude": ["client"]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user