mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-07-24 07:39:42 +02:00
Frontent build system is ready (#3)
* Frontent build system is ready * Set up linter for frontend part * move code to /src * update db * upd test * remove db * ignore .db
This commit is contained in:
parent
3762ea3791
commit
248558a11f
27 changed files with 3003 additions and 219 deletions
38
src/app.js
Normal file
38
src/app.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
const createError = require('http-errors');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const logger = require('morgan');
|
||||
|
||||
const routes = require('./routes');
|
||||
|
||||
const app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'twig');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({extended: true}));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
|
||||
app.use('/', routes);
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
106
src/controllers/pages.js
Normal file
106
src/controllers/pages.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
const Model = require('../models/page');
|
||||
|
||||
/**
|
||||
* @class Pages
|
||||
* @classdesc Pages controller
|
||||
*/
|
||||
class Pages {
|
||||
/**
|
||||
* @static
|
||||
* Fields required for page model creation
|
||||
*
|
||||
* @returns {['title', 'body']}
|
||||
*/
|
||||
static get REQUIRED_FIELDS() {
|
||||
return ['title', 'body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* Find and return page model with passed id
|
||||
*
|
||||
* @param {string} id - page id
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async get(id) {
|
||||
const page = await Model.get(id);
|
||||
|
||||
if (!page._id) {
|
||||
throw new Error('Page with given id does not exist');
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all pages
|
||||
*
|
||||
* @returns {Promise<Page[]>}
|
||||
*/
|
||||
static async getAll() {
|
||||
return Model.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new page model and save it in the database
|
||||
*
|
||||
* @param {PageData} data
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async insert(data) {
|
||||
if (!Pages.validate(data)) {
|
||||
throw new Error('Invalid request format');
|
||||
}
|
||||
|
||||
const page = new Model(data);
|
||||
|
||||
return page.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PageData object for required fields
|
||||
*
|
||||
* @param {PageData} data
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static validate(data) {
|
||||
return Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update page with given id in the database
|
||||
*
|
||||
* @param {string} id - page id
|
||||
* @param {PageData} data
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async update(id, data) {
|
||||
const page = await Model.get(id);
|
||||
|
||||
if (!page._id) {
|
||||
throw new Error('Page with given id does not exist');
|
||||
}
|
||||
|
||||
page.data = data;
|
||||
|
||||
return page.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove page with given id from the database
|
||||
*
|
||||
* @param {string} id - page id
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async remove(id) {
|
||||
const page = await Model.get(id);
|
||||
|
||||
if (!page._id) {
|
||||
throw new Error('Page with given id does not exist');
|
||||
}
|
||||
|
||||
return page.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pages;
|
16
src/frontend/.postcssrc
Normal file
16
src/frontend/.postcssrc
Normal file
|
@ -0,0 +1,16 @@
|
|||
plugins:
|
||||
postcss-smart-import: {}
|
||||
postcss-custom-properties: {}
|
||||
postcss-apply: {}
|
||||
postcss-custom-media: {}
|
||||
postcss-media-minmax: {}
|
||||
postcss-custom-selectors: {}
|
||||
postcss-nested-ancestors: {}
|
||||
postcss-nesting: {}
|
||||
postcss-nested: {}
|
||||
postcss-color-mod-function: {}
|
||||
postcss-color-hex-alpha: {}
|
||||
postcss-font-family-system-ui: {}
|
||||
cssnano: {}
|
||||
autoprefixer:
|
||||
browsers: ['last 2 versions', '> 1%']
|
12
src/frontend/js/app.js
Normal file
12
src/frontend/js/app.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// No inspection for unused var `css` because it's used for css bundle
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import css from '../styles/main.pcss';
|
||||
|
||||
module.exports = class Docs {
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
console.log('CodeX Docs initialized');
|
||||
}
|
||||
};
|
7
src/frontend/styles/main.pcss
Normal file
7
src/frontend/styles/main.pcss
Normal file
|
@ -0,0 +1,7 @@
|
|||
body {
|
||||
font-family: system-ui, Helvetica, Arial, Verdana;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
160
src/models/page.js
Normal file
160
src/models/page.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
const {pages} = require('../utils/database/index');
|
||||
|
||||
/**
|
||||
* @typedef {Object} PageData
|
||||
* @property {string} _id - page id
|
||||
* @property {string} title - page title
|
||||
* @property {*} body - page body
|
||||
* @property {string} parent - id of parent page
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Page
|
||||
* @class Page model
|
||||
*
|
||||
* @property {string} _id - page id
|
||||
* @property {string} title - page title
|
||||
* @property {*} body - page body
|
||||
* @property {string} _parent - id of parent page
|
||||
*/
|
||||
class Page {
|
||||
/**
|
||||
* Find and return model of page with given id
|
||||
* @param {string} _id - page id
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async get(_id) {
|
||||
const data = await pages.findOne({_id});
|
||||
|
||||
return new Page(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all pages which match passed query object
|
||||
*
|
||||
* @param {Object} query
|
||||
* @returns {Promise<Page[]>}
|
||||
*/
|
||||
static async getAll(query = {}) {
|
||||
const docs = await pages.find(query);
|
||||
|
||||
return Promise.all(docs.map(doc => new Page(doc)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {PageData} data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
if (data === null) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
this.db = pages;
|
||||
|
||||
if (data._id) {
|
||||
this._id = data._id;
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set PageData object fields to internal model fields
|
||||
*
|
||||
* @param {PageData} pageData
|
||||
*/
|
||||
set data(pageData) {
|
||||
const {title, body, parent} = pageData;
|
||||
|
||||
this.title = title || this.title;
|
||||
this.body = body || this.body;
|
||||
this._parent = parent || this._parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PageData object
|
||||
*
|
||||
* @returns {PageData}
|
||||
*/
|
||||
get data() {
|
||||
return {
|
||||
_id: this._id,
|
||||
title: this.title,
|
||||
body: this.body,
|
||||
parent: this._parent
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Link given page as parent
|
||||
*
|
||||
* @param {Page} parentPage
|
||||
*/
|
||||
set parent(parentPage) {
|
||||
this._parent = parentPage._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return parent page model
|
||||
*
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
get parent() {
|
||||
return this.db.findOne({_id: this._parent})
|
||||
.then(data => new Page(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return child pages models
|
||||
*
|
||||
* @returns {Promise<Page[]>}
|
||||
*/
|
||||
get children() {
|
||||
return this.db.find({parent: this._id})
|
||||
.then(data => data.map(page => new Page(page)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update page data in the database
|
||||
*
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
async save() {
|
||||
if (!this._id) {
|
||||
const insertedRow = await this.db.insert(this.data);
|
||||
|
||||
this._id = insertedRow._id;
|
||||
} else {
|
||||
await this.db.update({_id: this._id}, this.data);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove page data from the database
|
||||
*
|
||||
* @returns {Promise<Page>}
|
||||
*/
|
||||
async destroy() {
|
||||
await this.db.remove({_id: this._id});
|
||||
|
||||
delete this._id;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return readable page data
|
||||
*
|
||||
* @returns {PageData}
|
||||
*/
|
||||
toJSON() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Page;
|
9
src/routes/home.js
Normal file
9
src/routes/home.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function (req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
10
src/routes/index.js
Normal file
10
src/routes/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const home = require('./home');
|
||||
const pages = require('./pages');
|
||||
|
||||
router.use('/', home);
|
||||
router.use('/', pages);
|
||||
|
||||
module.exports = router;
|
115
src/routes/pages.js
Normal file
115
src/routes/pages.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer')();
|
||||
const Pages = require('../controllers/pages');
|
||||
|
||||
/**
|
||||
* GET /page/:id
|
||||
*
|
||||
* Return PageData of page with given id
|
||||
*/
|
||||
router.get('/page/:id', async (req, res) => {
|
||||
try {
|
||||
const page = await Pages.get(req.params.id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: page.data
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /pages
|
||||
*
|
||||
* Return PageData for all pages
|
||||
*/
|
||||
router.get('/pages', async (req, res) => {
|
||||
try {
|
||||
const pages = await Pages.getAll();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: pages
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /page
|
||||
*
|
||||
* Create new page in the database
|
||||
*/
|
||||
router.put('/page', multer.any(), async (req, res) => {
|
||||
try {
|
||||
const {title, body, parent} = req.body;
|
||||
const page = await Pages.insert({title, body, parent});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: page
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /page/:id
|
||||
*
|
||||
* Update page data in the database
|
||||
*/
|
||||
router.post('/page/:id', multer.any(), async (req, res) => {
|
||||
const {id} = req.params;
|
||||
|
||||
try {
|
||||
const {title, body, parent} = req.body;
|
||||
const page = await Pages.update(id, {title, body, parent});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: page
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /page/:id
|
||||
*
|
||||
* Remove page from the database
|
||||
*/
|
||||
router.delete('/page/:id', async (req, res) => {
|
||||
try {
|
||||
const page = await Pages.remove(req.params.id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
result: page
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
146
src/utils/database/index.js
Normal file
146
src/utils/database/index.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
const pages = require('./pages');
|
||||
|
||||
/**
|
||||
* @class Database
|
||||
* @classdesc Simple decorator class to work with nedb datastore
|
||||
*
|
||||
* @property db - nedb Datastore object
|
||||
*/
|
||||
class Database {
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {Object} nedbInstance - nedb Datastore object
|
||||
*/
|
||||
constructor(nedbInstance) {
|
||||
this.db = nedbInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new document into the database
|
||||
* @see https://github.com/louischatriot/nedb#inserting-documents
|
||||
*
|
||||
* @param {Object} doc - object to insert
|
||||
* @returns {Promise<Object|Error>} - inserted doc or Error object
|
||||
*/
|
||||
async insert(doc) {
|
||||
return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(newDoc);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find documents that match passed query
|
||||
* @see https://github.com/louischatriot/nedb#finding-documents
|
||||
*
|
||||
* @param {Object} query - query object
|
||||
* @param {Object} projection - projection object
|
||||
* @returns {Promise<Array<Object>|Error>} - found docs or Error object
|
||||
*/
|
||||
async find(query, projection) {
|
||||
const cbk = (resolve, reject) => (err, docs) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(docs);
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (projection) {
|
||||
this.db.find(query, projection, cbk(resolve, reject));
|
||||
} else {
|
||||
this.db.find(query, cbk(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find one document matches passed query
|
||||
* @see https://github.com/louischatriot/nedb#finding-documents
|
||||
*
|
||||
* @param {Object} query - query object
|
||||
* @param {Object} projection - projection object
|
||||
* @returns {Promise<Object|Error>} - found doc or Error object
|
||||
*/
|
||||
async findOne(query, projection) {
|
||||
const cbk = (resolve, reject) => (err, doc) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(doc);
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (projection) {
|
||||
this.db.findOne(query, projection, cbk(resolve, reject));
|
||||
} else {
|
||||
this.db.findOne(query, cbk(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update document matches query
|
||||
* @see https://github.com/louischatriot/nedb#updating-documents
|
||||
*
|
||||
* @param {Object} query - query object
|
||||
* @param {Object} update - fields to update
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.multi - (false) allows update several documents
|
||||
* @param {Boolean} options.upsert - (false) if true, upsert document with update fields.
|
||||
* Method will return inserted doc or number of affected docs if doc hasn't been inserted
|
||||
* @param {Boolean} options.returnUpdatedDocs - (false) if true, returns affected docs
|
||||
* @returns {Promise<number|Object|Object[]|Error>} - number of updated rows or affected docs or Error object
|
||||
*/
|
||||
async update(query, update, options = {}) {
|
||||
return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case options.returnUpdatedDocs:
|
||||
resolve(affectedDocs);
|
||||
break;
|
||||
case options.upsert:
|
||||
if (affectedDocs) {
|
||||
resolve(affectedDocs);
|
||||
}
|
||||
resolve(result);
|
||||
break;
|
||||
default:
|
||||
resolve(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove document matches passed query
|
||||
* @see https://github.com/louischatriot/nedb#removing-documents
|
||||
*
|
||||
* @param {Object} query - query object
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.multi - (false) if true, remove several docs
|
||||
* @returns {Promise<number|Error>} - number of removed rows or Error object
|
||||
*/
|
||||
async remove(query, options = {}) {
|
||||
return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
class: Database,
|
||||
pages: new Database(pages)
|
||||
};
|
5
src/utils/database/pages.js
Normal file
5
src/utils/database/pages.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const Datastore = require('nedb');
|
||||
|
||||
const db = new Datastore({filename: './.db/pages.db', autoload: true});
|
||||
|
||||
module.exports = db;
|
7
src/views/error.twig
Normal file
7
src/views/error.twig
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{message}}</h1>
|
||||
<h2>{{error.status}}</h2>
|
||||
<pre>{{error.stack}}</pre>
|
||||
{% endblock %}
|
6
src/views/index.twig
Normal file
6
src/views/index.twig
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{title}}</h1>
|
||||
<p>Welcome to {{title}}</p>
|
||||
{% endblock %}
|
11
src/views/layout.twig
Normal file
11
src/views/layout.twig
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="/dist/bundle.css" />
|
||||
<script src="/dist/bundle.js" onload="new Docs()"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue