mirror of
https://github.com/sergeyyarkov/node-docker-example.git
synced 2026-10-11 02:09:22 +03:00
feat: article creating
This commit is contained in:
parent
b4838dc19b
commit
9c84c17308
@ -81,8 +81,15 @@ class ArticleController {
|
||||
*/
|
||||
static async create(req, res) {
|
||||
try {
|
||||
res.json({ data: `POST ${req.url}` });
|
||||
return;
|
||||
const data = req.getPostData();
|
||||
if (data && data.title && data.description && data.body) {
|
||||
const article = await ArticleModel.create({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
body: data.body,
|
||||
});
|
||||
res.redirect(`/articles/${article.id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
res.serverError(error);
|
||||
}
|
||||
|
||||
20
controllers/EditorController.js
Normal file
20
controllers/EditorController.js
Normal file
@ -0,0 +1,20 @@
|
||||
import http from 'node:http';
|
||||
import Response from '#app/Response';
|
||||
|
||||
class EditorController {
|
||||
/**
|
||||
* Render editor for creating article
|
||||
*
|
||||
* @param {http.ClientRequest} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
static create(req, res) {
|
||||
try {
|
||||
res.render('./views/editor/create.hbs', { title: 'Create new article' });
|
||||
} catch (error) {
|
||||
res.serverError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EditorController;
|
||||
2
db.js
2
db.js
@ -1,5 +1,5 @@
|
||||
import pg from 'pg';
|
||||
import env from './env.js';
|
||||
import env from '#app/env';
|
||||
|
||||
class Database {
|
||||
constructor() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import db from '../db.js';
|
||||
import db from '#app/db';
|
||||
|
||||
class DbHelper {
|
||||
/**
|
||||
|
||||
2
index.js
2
index.js
@ -1,4 +1,4 @@
|
||||
import Application from './server.js';
|
||||
import Application from '#app/server';
|
||||
|
||||
const app = new Application();
|
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import DbHelper from './helpers/db.js';
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
import DbHelper from '#helpers/db';
|
||||
|
||||
DbHelper.runQueryFromFile('sql/schema_init.sql');
|
||||
|
||||
@ -18,8 +18,8 @@ class ArticleModel {
|
||||
*/
|
||||
static async getAll() {
|
||||
try {
|
||||
const data = await db.client.query('SELECT * FROM "articles"');
|
||||
return data.rows;
|
||||
const result = await db.client.query('SELECT * FROM "articles"');
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@ -33,14 +33,14 @@ class ArticleModel {
|
||||
*/
|
||||
static async getById(id) {
|
||||
try {
|
||||
const data = await db.client.query(
|
||||
const result = await db.client.query(
|
||||
'SELECT * from "articles" WHERE "id" = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (data.rows.length === 0) return null;
|
||||
if (result.rows.length === 0) return null;
|
||||
|
||||
return data.rows[0];
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@ -63,12 +63,12 @@ class ArticleModel {
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
const article = await db.client.query(
|
||||
`INSERT INTO "articles" ("id", "title", "description", "body", "created_at", "updated_at") VALUES($1, $2, $3, $4, $5, $6)`,
|
||||
const result = await db.client.query(
|
||||
`INSERT INTO "articles" ("id", "title", "description", "body", "created_at", "updated_at") VALUES($1, $2, $3, $4, $5, $6) RETURNING *`,
|
||||
values
|
||||
);
|
||||
|
||||
return article;
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import router from '#app/router';
|
||||
* Controllers
|
||||
*/
|
||||
import ArticleController from '#controllers/ArticleController';
|
||||
import EditorController from '#app/controllers/EditorController';
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
@ -16,3 +17,4 @@ import WaitMiddleware from '#middlewares/WaitMiddleware';
|
||||
router.get('/', ArticleController.index).middleware([WaitMiddleware.handle]);
|
||||
router.get('/articles/:id', ArticleController.show);
|
||||
router.post('/articles', ArticleController.create);
|
||||
router.get('/editor/create', EditorController.create);
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
body {
|
||||
* {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: justify;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@ -9,6 +12,28 @@ body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 7px 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
font-size: 16px;
|
||||
padding: 7px 5px;
|
||||
}
|
||||
|
||||
.textarea-body {
|
||||
border: none;
|
||||
border-bottom: 1px solid;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.app .app-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -25,15 +50,11 @@ body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.app .article-heading {
|
||||
.app .article-heading,
|
||||
.app .editor-heading {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.app .app-body article {
|
||||
margin-top: 2rem;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
|
||||
.app .app-footer {
|
||||
text-align: center;
|
||||
}
|
||||
@ -69,3 +90,9 @@ body {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app .app-body .app-body__editor form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@ -27,6 +27,11 @@ class Response {
|
||||
this.send(text, 404);
|
||||
}
|
||||
|
||||
redirect(location) {
|
||||
this.res.writeHead(302, { Location: location });
|
||||
this.res.end();
|
||||
}
|
||||
|
||||
serverError(error, text = '500 | Internal server error') {
|
||||
console.error(error);
|
||||
this.render('./views/500.hbs', { title: text }, 500);
|
||||
|
||||
37
router.js
37
router.js
@ -1,7 +1,12 @@
|
||||
import http from 'node:http';
|
||||
import Response from './response.js';
|
||||
import qs from 'node:querystring';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import StringHelper from './helpers/string.js';
|
||||
import Response from '#app/response';
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
import StringHelper from '#helpers/string';
|
||||
|
||||
/**
|
||||
* Single route type
|
||||
@ -29,11 +34,13 @@ class Router {
|
||||
* @param {Response} res
|
||||
* @returns
|
||||
*/
|
||||
call(req, res) {
|
||||
async call(req, res) {
|
||||
const mask = `${req.method}:${StringHelper.delTrailingSlash(req.url)}`;
|
||||
const keys = Object.keys(this.routes);
|
||||
let i = keys.length;
|
||||
|
||||
await this.#collectData(req, res);
|
||||
|
||||
while (i--) {
|
||||
let route = this.routes[mask];
|
||||
|
||||
@ -92,6 +99,30 @@ class Router {
|
||||
}
|
||||
}
|
||||
|
||||
async #collectData(req, res) {
|
||||
/**
|
||||
* Collect POST data
|
||||
*/
|
||||
if (
|
||||
req.method === 'POST' &&
|
||||
req.headers['content-type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
const buffers = [];
|
||||
|
||||
for await (const chunk of req) {
|
||||
if (
|
||||
req.method === 'POST' &&
|
||||
req.headers['content-type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
buffers.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
const data = qs.parse(Buffer.concat(buffers).toString());
|
||||
req['getPostData'] = () => data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add more routes to router store
|
||||
*
|
||||
|
||||
5
seed.js
5
seed.js
@ -1,3 +1,6 @@
|
||||
import DbHelper from './helpers/db.js';
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
import DbHelper from '#helpers/db';
|
||||
|
||||
DbHelper.runQueryFromFile('sql/seed.sql');
|
||||
|
||||
15
server.js
15
server.js
@ -1,6 +1,5 @@
|
||||
import http from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
import qs from 'node:querystring';
|
||||
import path from 'node:path';
|
||||
import mime from 'mime-types';
|
||||
import hbs from 'handlebars';
|
||||
@ -9,7 +8,7 @@ import db from '#app/db';
|
||||
import router from '#app/router';
|
||||
import Response from '#app/response';
|
||||
import FileHelper from '#helpers/file';
|
||||
import './preloads/index.js';
|
||||
import '#app/preloads/index';
|
||||
|
||||
class Application {
|
||||
constructor() {
|
||||
@ -44,17 +43,7 @@ class Application {
|
||||
#handle() {
|
||||
this.server.on('request', async (req, res) => {
|
||||
const response = new Response(req, res);
|
||||
this.router.call(req, response);
|
||||
|
||||
// const buffer = Buffer.alloc(parseInt(req.headers['content-length'], 10));
|
||||
|
||||
// req.on('data', (chunk) => {
|
||||
// if (req.method === 'POST') {
|
||||
// console.log(chunk);
|
||||
// // console.log(buffer.toString());
|
||||
// // const buffer = Buffer.alloc(chunk.length)
|
||||
// }
|
||||
// });
|
||||
await this.router.call(req, response);
|
||||
});
|
||||
this.server.listen(env.app.port, () =>
|
||||
console.log(`[LOG]: Server is running on port "${env.app.port}"`)
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="/">< return to home</a>
|
||||
<div class='app-heading article-heading'>
|
||||
<h1>{{ article.title }}</h1>
|
||||
<span>{{ article.created_at }}</span>
|
||||
<p>{{ article.created_at }}</p>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<p>{{ article.description }}</p>
|
||||
|
||||
26
views/editor/create.hbs
Normal file
26
views/editor/create.hbs
Normal file
@ -0,0 +1,26 @@
|
||||
<html lang='en'>
|
||||
{{> head }}
|
||||
<body>
|
||||
<main class='app'>
|
||||
<a href="/">< return to home</a>
|
||||
<div class='app-heading editor-heading'>
|
||||
<h1>Create new article</h1>
|
||||
<p>Fill in the required fields to create a new article</p>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<hr />
|
||||
<div class="app-body__editor">
|
||||
<div class="editor-form">
|
||||
<form action="/articles" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<input placeholder="e.g. My first nice article" type="text" name="title" id="title" required="true">
|
||||
<textarea placeholder="Description" name="description" id="description" cols="30" rows="10" required="true"></textarea>
|
||||
<textarea placeholder="Content" class="textarea-body" name="body" id="body" cols="30" rows="20" required="true"></textarea>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{> footer }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -15,6 +15,7 @@
|
||||
|
||||
<hr />
|
||||
<div class="app-body">
|
||||
<button onclick="location.href='/editor/create'" type="button">Write new</button>
|
||||
{{#unless articles.length }}
|
||||
<p style="text-align: center;margin: 30px 0;">
|
||||
No articles found.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user