feat: article creating

This commit is contained in:
Sergey Yarkov 2022-03-18 22:14:56 +03:00
parent b4838dc19b
commit 9c84c17308
16 changed files with 153 additions and 39 deletions

View File

@ -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);
}

View 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
View File

@ -1,5 +1,5 @@
import pg from 'pg';
import env from './env.js';
import env from '#app/env';
class Database {
constructor() {

View File

@ -1,5 +1,5 @@
import fs from 'node:fs';
import db from '../db.js';
import db from '#app/db';
class DbHelper {
/**

View File

@ -1,4 +1,4 @@
import Application from './server.js';
import Application from '#app/server';
const app = new Application();

View File

@ -1,3 +1,6 @@
import DbHelper from './helpers/db.js';
/**
* Helpers
*/
import DbHelper from '#helpers/db';
DbHelper.runQueryFromFile('sql/schema_init.sql');

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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
*

View File

@ -1,3 +1,6 @@
import DbHelper from './helpers/db.js';
/**
* Helpers
*/
import DbHelper from '#helpers/db';
DbHelper.runQueryFromFile('sql/seed.sql');

View File

@ -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}"`)

View File

@ -5,7 +5,7 @@
<a href="/">&lt; 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
View File

@ -0,0 +1,26 @@
<html lang='en'>
{{> head }}
<body>
<main class='app'>
<a href="/">&lt; 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>

View File

@ -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.