mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-07-18 20:59:42 +02:00
Typescript rewrite (#147)
* Updated highlight.js * Update .codexdocsrc.sample remove undefined page for a fresh new install * backend rewritten in TS * test -> TS, .added dockerignore, bug fixed * Removed compiled js files, eslint codex/ts added * fixed jsdocs warning, leaving editor confirmation * use path.resolve for DB paths * db drives updated + fixed User model * redundant cleared + style fixed * explicit type fixing * fixing testing code * added body block type * compiled JS files -> dist, fixed compiling errors * fixed compiling error, re-organized ts source code * updated Dockerfile * fixed link to parent page * up nodejs version * fix package name * fix deps Co-authored-by: nvc8996 <nvc.8996@gmail.com> Co-authored-by: Taly <vitalik7tv@yandex.ru>
This commit is contained in:
parent
059cfb96f9
commit
34514761f5
99 changed files with 3817 additions and 2249 deletions
17
.eslintrc
17
.eslintrc
|
@ -1,18 +1,25 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"codex"
|
"codex/ts",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"chai-friendly"
|
"chai-friendly",
|
||||||
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"mocha": true
|
"mocha": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-expressions": 0,
|
"no-unused-expressions": 1,
|
||||||
"chai-friendly/no-unused-expressions": 2
|
"chai-friendly/no-unused-expressions": 2,
|
||||||
|
"@typescript-eslint/ban-types": 1,
|
||||||
|
"@typescript-eslint/no-magic-numbers": 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": 1
|
||||||
},
|
},
|
||||||
"parser": "babel-eslint",
|
"parser": "@typescript-eslint/parser",
|
||||||
"globals": {
|
"globals": {
|
||||||
"fetch": true,
|
"fetch": true,
|
||||||
"alert": true
|
"alert": true
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -76,3 +76,6 @@ typings/
|
||||||
# Uploads
|
# Uploads
|
||||||
/public/uploads
|
/public/uploads
|
||||||
/public/uploads_test
|
/public/uploads_test
|
||||||
|
|
||||||
|
# Compiled files
|
||||||
|
/dist/*
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
||||||
9.11.1
|
16.14.0
|
||||||
|
|
|
@ -33,6 +33,10 @@ $ yarn install --frozen-lockfile
|
||||||
```
|
```
|
||||||
|
|
||||||
### Available scripts
|
### Available scripts
|
||||||
|
#### Compile to Javascript
|
||||||
|
```
|
||||||
|
$ yarn compile
|
||||||
|
```
|
||||||
|
|
||||||
#### Start the server
|
#### Start the server
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"port": 3000,
|
"port": 3000,
|
||||||
"database": ".db",
|
"database": ".db",
|
||||||
|
"rcFile": "./.codexdocsrc",
|
||||||
"uploads": "public/uploads",
|
"uploads": "public/uploads",
|
||||||
"secret": "iamasecretstring"
|
"secret": "iamasecretstring"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
* This module reads configuration file depending on NODE_ENV
|
|
||||||
*
|
|
||||||
* @type {module}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
||||||
const configPath = `./${NODE_ENV}.json`;
|
|
||||||
let config;
|
|
||||||
|
|
||||||
if (fs.existsSync(path.resolve(__dirname, configPath))) {
|
|
||||||
config = require(configPath);
|
|
||||||
} else {
|
|
||||||
config = {
|
|
||||||
database: '.db',
|
|
||||||
port: 3000,
|
|
||||||
uploads: 'public/uploads',
|
|
||||||
secret: 'secret'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = config;
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"port": 3000,
|
"port": 3000,
|
||||||
"database": ".db",
|
"database": ".db",
|
||||||
|
"rcFile": "./.codexdocsrc",
|
||||||
"uploads": "public/uploads",
|
"uploads": "public/uploads",
|
||||||
"secret": "iamasecretstring"
|
"secret": "iamasecretstring"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"port": 3001,
|
"port": 3001,
|
||||||
"database": ".testdb",
|
"database": ".testdb",
|
||||||
"rcFile": "./test/.codexdocsrc",
|
"rcFile": "./src/test/.codexdocsrc",
|
||||||
"uploads": "public/uploads_test",
|
"uploads": "public/uploads_test",
|
||||||
"secret": "iamasecretstring"
|
"secret": "iamasecretstring"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
FROM node:12.14.1-alpine3.11
|
FROM node:16.14.0-alpine3.15
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
RUN apk add --no-cache git gcc g++ python make musl-dev
|
RUN apk add --no-cache git gcc g++ python3 make musl-dev
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN yarn install --prod
|
RUN yarn install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn compile
|
||||||
|
|
||||||
CMD ["yarn", "start"]
|
CMD ["yarn", "start"]
|
||||||
|
|
22
nodemon.json
22
nodemon.json
|
@ -1,11 +1,15 @@
|
||||||
{
|
{
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"src/frontend",
|
"src/frontend",
|
||||||
"public/dist"
|
"public/dist"
|
||||||
],
|
],
|
||||||
"events": {
|
"events": {
|
||||||
"restart": "echo \"App restarted due to: '$FILENAME'\""
|
"restart": "echo \"App restarted due to: '$FILENAME'\""
|
||||||
},
|
},
|
||||||
"ext": "js,twig"
|
"watch": ["src/"],
|
||||||
|
"execMap": {
|
||||||
|
"ts": "node -r ts-node/register"
|
||||||
|
},
|
||||||
|
"ext": "js,json,ts,twig"
|
||||||
}
|
}
|
93
package.json
93
package.json
|
@ -9,40 +9,45 @@
|
||||||
"> 1%"
|
"> 1%"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env NODE_ENV=production nodemon ./bin/www",
|
"start:ts": "cross-env NODE_ENV=production nodemon --config nodemon.json ./src/bin/server.ts",
|
||||||
"start:dev": "cross-env NODE_ENV=development nodemon ./bin/www",
|
"start:dev": "cross-env NODE_ENV=development nodemon --config nodemon.json ./src/bin/server.ts",
|
||||||
"test": "cross-env NODE_ENV=testing mocha --recursive ./test",
|
"test": "cross-env NODE_ENV=testing mocha --recursive ./dist/test --exit",
|
||||||
"lint": "eslint --fix --cache ./src/**/*.js",
|
"test:ts": "cross-env NODE_ENV=testing ts-mocha ./src/test/*.ts ./src/test/**/*.ts --exit",
|
||||||
|
"lint": "eslint --fix --cache --ext .ts ./src/backend",
|
||||||
"build": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=production",
|
"build": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=production",
|
||||||
"build:dev": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=development --watch",
|
"build:dev": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=development --watch",
|
||||||
"precommit": "yarn lint && yarn test --exit",
|
"precommit": "yarn lint && yarn test:ts",
|
||||||
"generatePassword": "node ./generatePassword.js",
|
"generatePassword:ts": "ts-node ./src/generatePassword.ts",
|
||||||
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
"generatePassword": "node ./dist/generatePassword.js",
|
||||||
|
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest",
|
||||||
|
"compile": "npx tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@editorjs/embed": "^2.5.0",
|
"@editorjs/embed": "^2.5.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"commander": "^2.19.0",
|
"commander": "^8.1.0",
|
||||||
"cookie-parser": "~1.4.3",
|
"config": "^3.3.6",
|
||||||
"cross-env": "^5.2.0",
|
"cookie-parser": "^1.4.5",
|
||||||
"csurf": "^1.9.0",
|
"cross-env": "^7.0.3",
|
||||||
"debug": "~4.1.0",
|
"csurf": "^1.11.0",
|
||||||
"dotenv": "^6.2.0",
|
"debug": "^4.3.2",
|
||||||
"express": "~4.16.0",
|
"dotenv": "^10.0.0",
|
||||||
"file-type": "^10.7.1",
|
"express": "^4.17.1",
|
||||||
"http-errors": "~1.7.1",
|
"file-type": "^16.5.2",
|
||||||
"jsonwebtoken": "^8.4.0",
|
"http-errors": "^1.8.0",
|
||||||
"mime": "^2.4.0",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^0.5.1",
|
"mime": "^2.5.2",
|
||||||
"morgan": "~1.9.0",
|
"mkdirp": "^1.0.4",
|
||||||
"multer": "^1.3.1",
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "^1.4.2",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
"nodemon": "^1.18.3",
|
"nodemon": "^2.0.12",
|
||||||
"open-graph-scraper": "^4.5.0",
|
"open-graph-scraper": "^4.9.0",
|
||||||
"twig": "~1.12.0",
|
"ts-node": "^10.1.0",
|
||||||
|
"twig": "^1.15.4",
|
||||||
"typescript-eslint": "^0.0.1-alpha.0",
|
"typescript-eslint": "^0.0.1-alpha.0",
|
||||||
"uuid4": "^1.0.0"
|
"uuid4": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.0.0",
|
"@babel/core": "^7.0.0",
|
||||||
|
@ -63,6 +68,31 @@
|
||||||
"@editorjs/raw": "^2.3.0",
|
"@editorjs/raw": "^2.3.0",
|
||||||
"@editorjs/table": "^2.0.1",
|
"@editorjs/table": "^2.0.1",
|
||||||
"@editorjs/warning": "^1.2.0",
|
"@editorjs/warning": "^1.2.0",
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/chai": "^4.2.21",
|
||||||
|
"@types/commander": "^2.12.2",
|
||||||
|
"@types/config": "^0.0.39",
|
||||||
|
"@types/cookie-parser": "^1.4.2",
|
||||||
|
"@types/csurf": "^1.11.2",
|
||||||
|
"@types/debug": "^4.1.7",
|
||||||
|
"@types/eslint": "^7.28.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/file-type": "^10.9.1",
|
||||||
|
"@types/jsonwebtoken": "^8.5.4",
|
||||||
|
"@types/mime": "^2.0.3",
|
||||||
|
"@types/mkdirp": "^1.0.2",
|
||||||
|
"@types/mocha": "^9.0.0",
|
||||||
|
"@types/morgan": "^1.9.3",
|
||||||
|
"@types/multer": "^1.4.7",
|
||||||
|
"@types/nedb": "^1.8.12",
|
||||||
|
"@types/node": "^16.4.1",
|
||||||
|
"@types/node-fetch": "^2.5.12",
|
||||||
|
"@types/open-graph-scraper": "^4.8.1",
|
||||||
|
"@types/rimraf": "^3.0.1",
|
||||||
|
"@types/sinon": "^10.0.2",
|
||||||
|
"@types/twig": "^1.12.6",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.28.5",
|
||||||
|
"@typescript-eslint/parser": "^4.28.5",
|
||||||
"autoprefixer": "^9.1.3",
|
"autoprefixer": "^9.1.3",
|
||||||
"babel": "^6.23.0",
|
"babel": "^6.23.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
|
@ -71,17 +101,16 @@
|
||||||
"chai-http": "^4.0.0",
|
"chai-http": "^4.0.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"cssnano": "^4.1.0",
|
"cssnano": "^4.1.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.31.0",
|
||||||
"eslint-config-codex": "^1.3.4",
|
"eslint-config-codex": "^1.6.2",
|
||||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||||
"eslint-plugin-import": "^2.14.0",
|
"eslint-plugin-import": "^2.14.0",
|
||||||
"eslint-plugin-node": "^8.0.1",
|
"eslint-plugin-node": "^8.0.1",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
|
||||||
"highlight.js": "^11.1.0",
|
"highlight.js": "^11.1.0",
|
||||||
"husky": "^1.1.2",
|
"husky": "^1.1.2",
|
||||||
"mini-css-extract-plugin": "^0.4.3",
|
"mini-css-extract-plugin": "^0.4.3",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"mocha-sinon": "^2.1.0",
|
"mocha-sinon": "^2.1.2",
|
||||||
"module-dispatcher": "^2.0.0",
|
"module-dispatcher": "^2.0.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"nyc": "^13.1.0",
|
"nyc": "^13.1.0",
|
||||||
|
@ -99,8 +128,10 @@
|
||||||
"postcss-nested-ancestors": "^2.0.0",
|
"postcss-nested-ancestors": "^2.0.0",
|
||||||
"postcss-nesting": "^7.0.0",
|
"postcss-nesting": "^7.0.0",
|
||||||
"postcss-smart-import": "^0.7.6",
|
"postcss-smart-import": "^0.7.6",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^3.0.2",
|
||||||
"sinon": "^7.0.0",
|
"sinon": "^11.1.2",
|
||||||
|
"ts-mocha": "^8.0.0",
|
||||||
|
"typescript": "^4.3.5",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.17.1",
|
||||||
"webpack-cli": "^3.1.0"
|
"webpack-cli": "^3.1.0"
|
||||||
}
|
}
|
||||||
|
|
42
src/app.js
42
src/app.js
|
@ -1,42 +0,0 @@
|
||||||
const createError = require('http-errors');
|
|
||||||
const express = require('express');
|
|
||||||
const path = require('path');
|
|
||||||
const cookieParser = require('cookie-parser');
|
|
||||||
const logger = require('morgan');
|
|
||||||
const rcParser = require('./utils/rcparser');
|
|
||||||
const routes = require('./routes');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const config = rcParser.getConfiguration();
|
|
||||||
|
|
||||||
app.locals.config = config;
|
|
||||||
|
|
||||||
// view engine setup
|
|
||||||
app.set('views', path.join(__dirname, 'views'));
|
|
||||||
app.set('view engine', 'twig');
|
|
||||||
require('./utils/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;
|
|
38
src/backend/app.ts
Normal file
38
src/backend/app.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
import rcParser from './utils/rcparser';
|
||||||
|
import routes from './routes';
|
||||||
|
import HttpException from './exceptions/httpException';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const config = rcParser.getConfiguration();
|
||||||
|
|
||||||
|
app.locals.config = config;
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, '../../src/backend/', 'views'));
|
||||||
|
app.set('view engine', 'twig');
|
||||||
|
require('./utils/twig');
|
||||||
|
|
||||||
|
app.use(morgan('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);
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function (err: HttpException, req: Request, res: Response) {
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
|
@ -1,4 +1,4 @@
|
||||||
const Alias = require('../models/alias');
|
import Alias from '../models/alias';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Aliases
|
* @class Aliases
|
||||||
|
@ -11,7 +11,7 @@ class Aliases {
|
||||||
* @param {string} aliasName - alias name of entity
|
* @param {string} aliasName - alias name of entity
|
||||||
* @returns {Promise<Alias>}
|
* @returns {Promise<Alias>}
|
||||||
*/
|
*/
|
||||||
static async get(aliasName) {
|
public static async get(aliasName: string): Promise<Alias> {
|
||||||
const alias = await Alias.get(aliasName);
|
const alias = await Alias.get(aliasName);
|
||||||
|
|
||||||
if (!alias.id) {
|
if (!alias.id) {
|
||||||
|
@ -22,4 +22,4 @@ class Aliases {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Aliases;
|
export default Aliases;
|
|
@ -1,5 +1,7 @@
|
||||||
const Model = require('../models/page');
|
import Page, { PageData } from '../models/page';
|
||||||
const Alias = require('../models/alias');
|
import Alias from '../models/alias';
|
||||||
|
|
||||||
|
type PageDataFields = keyof PageData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Pages
|
* @class Pages
|
||||||
|
@ -11,7 +13,7 @@ class Pages {
|
||||||
*
|
*
|
||||||
* @returns {['title', 'body']}
|
* @returns {['title', 'body']}
|
||||||
*/
|
*/
|
||||||
static get REQUIRED_FIELDS() {
|
public static get REQUIRED_FIELDS(): Array<PageDataFields> {
|
||||||
return [ 'body' ];
|
return [ 'body' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +23,8 @@ class Pages {
|
||||||
* @param {string} id - page id
|
* @param {string} id - page id
|
||||||
* @returns {Promise<Page>}
|
* @returns {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
static async get(id) {
|
public static async get(id: string): Promise<Page> {
|
||||||
const page = await Model.get(id);
|
const page = await Page.get(id);
|
||||||
|
|
||||||
if (!page._id) {
|
if (!page._id) {
|
||||||
throw new Error('Page with given id does not exist');
|
throw new Error('Page with given id does not exist');
|
||||||
|
@ -36,8 +38,8 @@ class Pages {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Page[]>}
|
* @returns {Promise<Page[]>}
|
||||||
*/
|
*/
|
||||||
static async getAll() {
|
public static async getAll(): Promise<Page[]> {
|
||||||
return Model.getAll();
|
return Page.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,20 +48,28 @@ class Pages {
|
||||||
* @param {string} parent - id of current page
|
* @param {string} parent - id of current page
|
||||||
* @returns {Promise<Page[]>}
|
* @returns {Promise<Page[]>}
|
||||||
*/
|
*/
|
||||||
static async getAllExceptChildren(parent) {
|
public static async getAllExceptChildren(parent: string): Promise<Page[]> {
|
||||||
const pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
|
const pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
|
||||||
|
|
||||||
return pagesAvailable.filter((item) => item !== null);
|
const nullFilteredPages: Page[] = [];
|
||||||
|
|
||||||
|
pagesAvailable.forEach(async item => {
|
||||||
|
if (item instanceof Page) {
|
||||||
|
nullFilteredPages.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return nullFilteredPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set all children elements to null
|
* Set all children elements to null
|
||||||
*
|
*
|
||||||
* @param {Page[]} [pagesAvailable] - Array of all pages
|
* @param {Array<Page|null>} [pagesAvailable] - Array of all pages
|
||||||
* @param {string} parent - id of parent page
|
* @param {string} parent - id of parent page
|
||||||
* @returns {Array<?Page>}
|
* @returns {Array<?Page>}
|
||||||
*/
|
*/
|
||||||
static removeChildren(pagesAvailable, parent) {
|
public static removeChildren(pagesAvailable: Array<Page|null>, parent: string | undefined): Array<Page | null> {
|
||||||
pagesAvailable.forEach(async (item, index) => {
|
pagesAvailable.forEach(async (item, index) => {
|
||||||
if (item === null || item._parent !== parent) {
|
if (item === null || item._parent !== parent) {
|
||||||
return;
|
return;
|
||||||
|
@ -74,14 +84,14 @@ class Pages {
|
||||||
/**
|
/**
|
||||||
* Create new page model and save it in the database
|
* Create new page model and save it in the database
|
||||||
*
|
*
|
||||||
* @param {PageData} data
|
* @param {PageData} data - info about page
|
||||||
* @returns {Promise<Page>}
|
* @returns {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
static async insert(data) {
|
public static async insert(data: PageData): Promise<Page> {
|
||||||
try {
|
try {
|
||||||
Pages.validate(data);
|
Pages.validate(data);
|
||||||
|
|
||||||
const page = new Model(data);
|
const page = new Page(data);
|
||||||
|
|
||||||
const insertedPage = await page.save();
|
const insertedPage = await page.save();
|
||||||
|
|
||||||
|
@ -95,18 +105,80 @@ class Pages {
|
||||||
}
|
}
|
||||||
|
|
||||||
return insertedPage;
|
return insertedPage;
|
||||||
} catch (validationError) {
|
} catch (e) {
|
||||||
throw new Error(validationError);
|
throw new Error('validationError');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update page with given id in the database
|
||||||
|
*
|
||||||
|
* @param {string} id - page id
|
||||||
|
* @param {PageData} data - info about page
|
||||||
|
* @returns {Promise<Page>}
|
||||||
|
*/
|
||||||
|
public static async update(id: string, data: PageData): Promise<Page> {
|
||||||
|
const page = await Page.get(id);
|
||||||
|
const previousUri = page.uri;
|
||||||
|
|
||||||
|
if (!page._id) {
|
||||||
|
throw new Error('Page with given id does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.uri && !data.uri.match(/^[a-z0-9'-]+$/i)) {
|
||||||
|
throw new Error('Uri has unexpected characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = data;
|
||||||
|
const updatedPage = await page.save();
|
||||||
|
|
||||||
|
if (updatedPage.uri !== previousUri) {
|
||||||
|
if (updatedPage.uri) {
|
||||||
|
const alias = new Alias({
|
||||||
|
id: updatedPage._id,
|
||||||
|
type: Alias.types.PAGE,
|
||||||
|
}, updatedPage.uri);
|
||||||
|
|
||||||
|
alias.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousUri) {
|
||||||
|
Alias.markAsDeprecated(previousUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove page with given id from the database
|
||||||
|
*
|
||||||
|
* @param {string} id - page id
|
||||||
|
* @returns {Promise<Page>}
|
||||||
|
*/
|
||||||
|
public static async remove(id: string): Promise<Page> {
|
||||||
|
const page = await Page.get(id);
|
||||||
|
|
||||||
|
if (!page._id) {
|
||||||
|
throw new Error('Page with given id does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.uri) {
|
||||||
|
const alias = await Alias.get(page.uri);
|
||||||
|
|
||||||
|
await alias.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check PageData object for required fields
|
* Check PageData object for required fields
|
||||||
*
|
*
|
||||||
* @param {PageData} data
|
* @param {PageData} data - info about page
|
||||||
* @throws {Error} - validation error
|
* @throws {Error} - validation error
|
||||||
*/
|
*/
|
||||||
static validate(data) {
|
private static validate(data: PageData): void {
|
||||||
const allRequiredFields = Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
|
const allRequiredFields = Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
|
||||||
|
|
||||||
if (!allRequiredFields) {
|
if (!allRequiredFields) {
|
||||||
|
@ -131,64 +203,6 @@ class Pages {
|
||||||
throw new Error('Please, fill page Header');
|
throw new Error('Please, fill page Header');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
const previousUri = page.uri;
|
|
||||||
|
|
||||||
if (!page._id) {
|
|
||||||
throw new Error('Page with given id does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.uri && !data.uri.match(/^[a-z0-9'-]+$/i)) {
|
|
||||||
throw new Error('Uri has unexpected characters');
|
|
||||||
}
|
|
||||||
|
|
||||||
page.data = data;
|
|
||||||
const updatedPage = await page.save();
|
|
||||||
|
|
||||||
if (updatedPage.uri !== previousUri) {
|
|
||||||
if (updatedPage.uri) {
|
|
||||||
const alias = new Alias({
|
|
||||||
id: updatedPage._id,
|
|
||||||
type: Alias.types.PAGE,
|
|
||||||
}, updatedPage.uri);
|
|
||||||
|
|
||||||
alias.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Alias.markAsDeprecated(previousUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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');
|
|
||||||
}
|
|
||||||
|
|
||||||
const alias = await Alias.get(page.uri);
|
|
||||||
|
|
||||||
await alias.destroy();
|
|
||||||
|
|
||||||
return page.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Pages;
|
export default Pages;
|
|
@ -1,4 +1,5 @@
|
||||||
const Model = require('../models/pageOrder');
|
import PageOrder from '../models/pageOrder';
|
||||||
|
import Page from '../models/page';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class PagesOrder
|
* @class PagesOrder
|
||||||
|
@ -13,8 +14,8 @@ class PagesOrder {
|
||||||
* @param {string} parentId - of which page we want to get children order
|
* @param {string} parentId - of which page we want to get children order
|
||||||
* @returns {Promise<PageOrder>}
|
* @returns {Promise<PageOrder>}
|
||||||
*/
|
*/
|
||||||
static async get(parentId) {
|
public static async get(parentId: string): Promise<PageOrder> {
|
||||||
const order = await Model.get(parentId);
|
const order = await PageOrder.get(parentId);
|
||||||
|
|
||||||
if (!order._id) {
|
if (!order._id) {
|
||||||
throw new Error('Page with given id does not contain order');
|
throw new Error('Page with given id does not contain order');
|
||||||
|
@ -26,10 +27,10 @@ class PagesOrder {
|
||||||
/**
|
/**
|
||||||
* Returns all records about page's order
|
* Returns all records about page's order
|
||||||
*
|
*
|
||||||
* @returns {Promise<PagesOrder[]>}
|
* @returns {Promise<PageOrder[]>}
|
||||||
*/
|
*/
|
||||||
static async getAll() {
|
public static async getAll(): Promise<PageOrder[]> {
|
||||||
return Model.getAll();
|
return PageOrder.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,8 +39,8 @@ class PagesOrder {
|
||||||
* @param {string} parentId - parent page's id
|
* @param {string} parentId - parent page's id
|
||||||
* @param {string} childId - new page pushed to the order
|
* @param {string} childId - new page pushed to the order
|
||||||
*/
|
*/
|
||||||
static async push(parentId, childId) {
|
public static async push(parentId: string, childId: string): Promise<void> {
|
||||||
const order = await Model.get(parentId);
|
const order = await PageOrder.get(parentId);
|
||||||
|
|
||||||
order.push(childId);
|
order.push(childId);
|
||||||
await order.save();
|
await order.save();
|
||||||
|
@ -52,13 +53,13 @@ class PagesOrder {
|
||||||
* @param {string} newParentId - new parent page's id
|
* @param {string} newParentId - new parent page's id
|
||||||
* @param {string} targetPageId - page's id which is changing the parent page
|
* @param {string} targetPageId - page's id which is changing the parent page
|
||||||
*/
|
*/
|
||||||
static async move(oldParentId, newParentId, targetPageId) {
|
public static async move(oldParentId: string, newParentId: string, targetPageId: string): Promise<void> {
|
||||||
const oldParentOrder = await Model.get(oldParentId);
|
const oldParentOrder = await PageOrder.get(oldParentId);
|
||||||
|
|
||||||
oldParentOrder.remove(targetPageId);
|
oldParentOrder.remove(targetPageId);
|
||||||
await oldParentOrder.save();
|
await oldParentOrder.save();
|
||||||
|
|
||||||
const newParentOrder = await Model.get(newParentId);
|
const newParentOrder = await PageOrder.get(newParentId);
|
||||||
|
|
||||||
newParentOrder.push(targetPageId);
|
newParentOrder.push(targetPageId);
|
||||||
await newParentOrder.save();
|
await newParentOrder.save();
|
||||||
|
@ -73,14 +74,14 @@ class PagesOrder {
|
||||||
* @param {boolean} ignoreSelf - should we ignore current page in list or not
|
* @param {boolean} ignoreSelf - should we ignore current page in list or not
|
||||||
* @returns {Page[]}
|
* @returns {Page[]}
|
||||||
*/
|
*/
|
||||||
static async getOrderedChildren(pages, currentPageId, parentPageId, ignoreSelf = false) {
|
public static async getOrderedChildren(pages: Page[], currentPageId: string, parentPageId: string, ignoreSelf = false): Promise<Page[]> {
|
||||||
const children = await Model.get(parentPageId);
|
const children = await PageOrder.get(parentPageId);
|
||||||
const unordered = pages.filter(page => page._parent === parentPageId).map(page => page._id);
|
const unordered = pages.filter(page => page._parent === parentPageId).map(page => page._id);
|
||||||
|
|
||||||
// Create unique array with ordered and unordered pages id
|
// Create unique array with ordered and unordered pages id
|
||||||
const ordered = [ ...new Set([...children.order, ...unordered]) ];
|
const ordered = Array.from(new Set([...children.order, ...unordered]));
|
||||||
|
|
||||||
const result = [];
|
const result: Page[] = [];
|
||||||
|
|
||||||
ordered.forEach(pageId => {
|
ordered.forEach(pageId => {
|
||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
|
@ -94,26 +95,26 @@ class PagesOrder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} unordered
|
* @param {string[]} unordered - list of pages
|
||||||
* @param {string} currentPageId - page's id that changes the order
|
* @param {string} currentPageId - page's id that changes the order
|
||||||
* @param {string} parentPageId - parent page's id that contains both two pages
|
* @param {string} parentPageId - parent page's id that contains both two pages
|
||||||
* @param {string} putAbovePageId - page's id above which we put the target page
|
* @param {string} putAbovePageId - page's id above which we put the target page
|
||||||
*/
|
*/
|
||||||
static async update(unordered, currentPageId, parentPageId, putAbovePageId) {
|
public static async update(unordered: string[], currentPageId: string, parentPageId: string, putAbovePageId: string): Promise<void> {
|
||||||
const pageOrder = await Model.get(parentPageId);
|
const pageOrder = await PageOrder.get(parentPageId);
|
||||||
|
|
||||||
// Create unique array with ordered and unordered pages id
|
// Create unique array with ordered and unordered pages id
|
||||||
pageOrder.order = [ ...new Set([...pageOrder.order, ...unordered]) ];
|
pageOrder.order = Array.from(new Set([...pageOrder.order, ...unordered]));
|
||||||
pageOrder.putAbove(currentPageId, putAbovePageId);
|
pageOrder.putAbove(currentPageId, putAbovePageId);
|
||||||
await pageOrder.save();
|
await pageOrder.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param parentId
|
* @param {string} parentId - identity of parent page
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async remove(parentId) {
|
public static async remove(parentId: string): Promise<void> {
|
||||||
const order = await Model.get(parentId);
|
const order = await PageOrder.get(parentId);
|
||||||
|
|
||||||
if (!order._id) {
|
if (!order._id) {
|
||||||
throw new Error('Page with given id does not contain order');
|
throw new Error('Page with given id does not contain order');
|
||||||
|
@ -123,4 +124,4 @@ class PagesOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PagesOrder;
|
export default PagesOrder;
|
|
@ -1,12 +1,17 @@
|
||||||
const fileType = require('file-type');
|
import fileType from 'file-type';
|
||||||
const fetch = require('node-fetch');
|
import fetch from 'node-fetch';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const nodePath = require('path');
|
import nodePath from 'path';
|
||||||
|
import config from 'config';
|
||||||
|
import File, { FileData } from '../models/file';
|
||||||
|
import crypto from '../utils/crypto';
|
||||||
|
import deepMerge from '../utils/objects';
|
||||||
|
|
||||||
const Model = require('../models/file');
|
const random16 = crypto.random16;
|
||||||
const { random16 } = require('../utils/crypto');
|
|
||||||
const { deepMerge } = require('../utils/objects');
|
interface Dict {
|
||||||
const config = require('../../config');
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Transport
|
* @class Transport
|
||||||
|
@ -28,10 +33,10 @@ class Transport {
|
||||||
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
||||||
* @returns {Promise<FileData>}
|
* @returns {Promise<FileData>}
|
||||||
*/
|
*/
|
||||||
static async save(multerData, map) {
|
public static async save(multerData: Dict, map: Dict): Promise<FileData> {
|
||||||
const { originalname: name, path, filename, size, mimetype } = multerData;
|
const { originalname: name, path, filename, size, mimetype } = multerData;
|
||||||
|
|
||||||
const file = new Model({
|
const file = new File({
|
||||||
name,
|
name,
|
||||||
filename,
|
filename,
|
||||||
path,
|
path,
|
||||||
|
@ -57,22 +62,33 @@ class Transport {
|
||||||
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
||||||
* @returns {Promise<FileData>}
|
* @returns {Promise<FileData>}
|
||||||
*/
|
*/
|
||||||
static async fetch(url, map) {
|
public static async fetch(url: string, map: Dict): Promise<FileData> {
|
||||||
const fetchedFile = await fetch(url);
|
const fetchedFile = await fetch(url);
|
||||||
const buffer = await fetchedFile.buffer();
|
const buffer = await fetchedFile.buffer();
|
||||||
const filename = await random16();
|
const filename = await random16();
|
||||||
|
|
||||||
const type = fileType(buffer);
|
const type = await fileType.fromBuffer(buffer);
|
||||||
const ext = type ? type.ext : nodePath.extname(url).slice(1);
|
const ext = type ? type.ext : nodePath.extname(url).slice(1);
|
||||||
|
|
||||||
fs.writeFileSync(`${config.uploads}/${filename}.${ext}`, buffer);
|
fs.writeFileSync(`${config.get('uploads')}/${filename}.${ext}`, buffer);
|
||||||
|
|
||||||
const file = new Model({
|
const fetchedContentType: string | null = fetchedFile.headers.get('content-type');
|
||||||
|
let fetchedMimeType: string|undefined;
|
||||||
|
|
||||||
|
if (fetchedContentType === null) {
|
||||||
|
fetchedMimeType = undefined;
|
||||||
|
} else {
|
||||||
|
fetchedMimeType = fetchedContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeType = type ? type.mime : fetchedMimeType;
|
||||||
|
|
||||||
|
const file = new File({
|
||||||
name: url,
|
name: url,
|
||||||
filename: `${filename}.${ext}`,
|
filename: `${filename}.${ext}`,
|
||||||
path: `${config.uploads}/${filename}.${ext}`,
|
path: `${config.get('uploads')}/${filename}.${ext}`,
|
||||||
size: buffer.length,
|
size: buffer.length,
|
||||||
mimetype: type ? type.mime : fetchedFile.headers.get('content-type'),
|
mimetype: mimeType,
|
||||||
});
|
});
|
||||||
|
|
||||||
await file.save();
|
await file.save();
|
||||||
|
@ -89,19 +105,19 @@ class Transport {
|
||||||
/**
|
/**
|
||||||
* Map fields of File object to response by provided map object
|
* Map fields of File object to response by provided map object
|
||||||
*
|
*
|
||||||
* @param {File} file
|
* @param {File} file - file object
|
||||||
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
* @param {object} map - object that represents how should fields of File object should be mapped to response
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static composeResponse(file, map) {
|
public static composeResponse(file: File, map: Dict): Dict {
|
||||||
const response = {};
|
const response: Dict = {};
|
||||||
const { data } = file;
|
const { data } = file;
|
||||||
|
|
||||||
Object.entries(map).forEach(([name, path]) => {
|
Object.entries(map).forEach(([name, path]) => {
|
||||||
const fields = path.split(':');
|
const fields: string[] = path.split(':');
|
||||||
|
|
||||||
if (fields.length > 1) {
|
if (fields.length > 1) {
|
||||||
let object = {};
|
let object: Dict = {};
|
||||||
const result = object;
|
const result = object;
|
||||||
|
|
||||||
fields.forEach((field, i) => {
|
fields.forEach((field, i) => {
|
||||||
|
@ -125,4 +141,4 @@ class Transport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Transport;
|
export default Transport;
|
20
src/backend/controllers/users.ts
Normal file
20
src/backend/controllers/users.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import User from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Users
|
||||||
|
* @classdesc Users controller
|
||||||
|
*/
|
||||||
|
class Users {
|
||||||
|
/**
|
||||||
|
* Find and return user model.
|
||||||
|
*
|
||||||
|
* @returns {Promise<User>}
|
||||||
|
*/
|
||||||
|
public static async get(): Promise<User> {
|
||||||
|
const userData: User = await User.get();
|
||||||
|
|
||||||
|
return userData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users;
|
21
src/backend/exceptions/httpException.ts
Normal file
21
src/backend/exceptions/httpException.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* HttpException class for middleware
|
||||||
|
*
|
||||||
|
* @property {number} status - exception status code
|
||||||
|
* @property {string} message - detail about the exception
|
||||||
|
*/
|
||||||
|
class HttpException extends Error {
|
||||||
|
public status: number;
|
||||||
|
public message: string;
|
||||||
|
/**
|
||||||
|
* @param status - status of the exception
|
||||||
|
* @param message - message about the exception
|
||||||
|
*/
|
||||||
|
constructor(status: number, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HttpException;
|
|
@ -1,5 +1,8 @@
|
||||||
const { aliases: aliasesDb } = require('../utils/database/index');
|
import crypto from '../utils/crypto';
|
||||||
const { binaryMD5 } = require('../utils/crypto');
|
import database from '../utils/database/index';
|
||||||
|
|
||||||
|
const binaryMD5 = crypto.binaryMD5;
|
||||||
|
const aliasesDb = database['aliases'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} AliasData
|
* @typedef {object} AliasData
|
||||||
|
@ -10,6 +13,13 @@ const { binaryMD5 } = require('../utils/crypto');
|
||||||
* @property {string} id - entity id
|
* @property {string} id - entity id
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
export interface AliasData {
|
||||||
|
_id?: string;
|
||||||
|
hash?: string;
|
||||||
|
type?: string;
|
||||||
|
deprecated?: boolean;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Alias
|
* @class Alias
|
||||||
|
@ -22,16 +32,40 @@ const { binaryMD5 } = require('../utils/crypto');
|
||||||
* @property {string} id - entity title
|
* @property {string} id - entity title
|
||||||
*/
|
*/
|
||||||
class Alias {
|
class Alias {
|
||||||
|
public _id?: string;
|
||||||
|
public hash?: string;
|
||||||
|
public type?: string;
|
||||||
|
public deprecated?: boolean;
|
||||||
|
public id?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @param {AliasData} data - info about alias
|
||||||
|
* @param {string} aliasName - alias of entity
|
||||||
|
*/
|
||||||
|
constructor(data: AliasData = {}, aliasName = '') {
|
||||||
|
if (data === null) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
if (data._id) {
|
||||||
|
this._id = data._id;
|
||||||
|
}
|
||||||
|
if (aliasName) {
|
||||||
|
this.hash = binaryMD5(aliasName);
|
||||||
|
}
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Return Alias types
|
* Return Alias types
|
||||||
*
|
*
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
static get types() {
|
public static get types(): { PAGE: string } {
|
||||||
return {
|
return {
|
||||||
PAGE: 'page',
|
PAGE: 'page',
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find and return alias with given alias
|
* Find and return alias with given alias
|
||||||
|
@ -39,7 +73,7 @@ class Alias {
|
||||||
* @param {string} aliasName - alias of entity
|
* @param {string} aliasName - alias of entity
|
||||||
* @returns {Promise<Alias>}
|
* @returns {Promise<Alias>}
|
||||||
*/
|
*/
|
||||||
static async get(aliasName) {
|
public static async get(aliasName: string): Promise<Alias> {
|
||||||
const hash = binaryMD5(aliasName);
|
const hash = binaryMD5(aliasName);
|
||||||
let data = await aliasesDb.findOne({
|
let data = await aliasesDb.findOne({
|
||||||
hash: hash,
|
hash: hash,
|
||||||
|
@ -54,22 +88,17 @@ class Alias {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* Mark alias as deprecated
|
||||||
*
|
*
|
||||||
* @param {AliasData} data
|
|
||||||
* @param {string} aliasName - alias of entity
|
* @param {string} aliasName - alias of entity
|
||||||
|
* @returns {Promise<Alias>}
|
||||||
*/
|
*/
|
||||||
constructor(data = {}, aliasName = '') {
|
public static async markAsDeprecated(aliasName: string): Promise<Alias> {
|
||||||
if (data === null) {
|
const alias = await Alias.get(aliasName);
|
||||||
data = {};
|
|
||||||
}
|
alias.deprecated = true;
|
||||||
if (data._id) {
|
|
||||||
this._id = data._id;
|
return alias.save();
|
||||||
}
|
|
||||||
if (aliasName) {
|
|
||||||
this.hash = binaryMD5(aliasName);
|
|
||||||
}
|
|
||||||
this.data = data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,9 +106,9 @@ class Alias {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Alias>}
|
* @returns {Promise<Alias>}
|
||||||
*/
|
*/
|
||||||
async save() {
|
public async save(): Promise<Alias> {
|
||||||
if (!this._id) {
|
if (!this._id) {
|
||||||
const insertedRow = await aliasesDb.insert(this.data);
|
const insertedRow = await aliasesDb.insert(this.data) as { _id: string };
|
||||||
|
|
||||||
this._id = insertedRow._id;
|
this._id = insertedRow._id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,9 +121,9 @@ class Alias {
|
||||||
/**
|
/**
|
||||||
* Set AliasData object fields to internal model fields
|
* Set AliasData object fields to internal model fields
|
||||||
*
|
*
|
||||||
* @param {AliasData} aliasData
|
* @param {AliasData} aliasData - info about alias
|
||||||
*/
|
*/
|
||||||
set data(aliasData) {
|
public set data(aliasData: AliasData) {
|
||||||
const { id, type, hash, deprecated } = aliasData;
|
const { id, type, hash, deprecated } = aliasData;
|
||||||
|
|
||||||
this.id = id || this.id;
|
this.id = id || this.id;
|
||||||
|
@ -108,7 +137,7 @@ class Alias {
|
||||||
*
|
*
|
||||||
* @returns {AliasData}
|
* @returns {AliasData}
|
||||||
*/
|
*/
|
||||||
get data() {
|
public get data(): AliasData {
|
||||||
return {
|
return {
|
||||||
_id: this._id,
|
_id: this._id,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -119,23 +148,9 @@ class Alias {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark alias as deprecated
|
|
||||||
*
|
|
||||||
* @param {string} aliasName - alias of entity
|
|
||||||
* @returns {Promise<Alias>}
|
* @returns {Promise<Alias>}
|
||||||
*/
|
*/
|
||||||
static async markAsDeprecated(aliasName) {
|
public async destroy(): Promise<Alias> {
|
||||||
const alias = await Alias.get(aliasName);
|
|
||||||
|
|
||||||
alias.deprecated = true;
|
|
||||||
|
|
||||||
return alias.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<Alias>}
|
|
||||||
*/
|
|
||||||
async destroy() {
|
|
||||||
await aliasesDb.remove({ _id: this._id });
|
await aliasesDb.remove({ _id: this._id });
|
||||||
|
|
||||||
delete this._id;
|
delete this._id;
|
||||||
|
@ -144,4 +159,4 @@ class Alias {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Alias;
|
export default Alias;
|
|
@ -1,4 +1,6 @@
|
||||||
const { files: filesDb } = require('../utils/database/index');
|
import database from '../utils/database/index';
|
||||||
|
|
||||||
|
const filesDb = database['files'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} FileData
|
* @typedef {object} FileData
|
||||||
|
@ -10,6 +12,15 @@ const { files: filesDb } = require('../utils/database/index');
|
||||||
* @property {string} mimetype - file MIME type
|
* @property {string} mimetype - file MIME type
|
||||||
* @property {number} size - size of the file in
|
* @property {number} size - size of the file in
|
||||||
*/
|
*/
|
||||||
|
export interface FileData {
|
||||||
|
_id?: string;
|
||||||
|
name?: string;
|
||||||
|
filename?: string;
|
||||||
|
path?: string;
|
||||||
|
mimetype?: string;
|
||||||
|
size?: number;
|
||||||
|
[key: string]: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class File
|
* @class File
|
||||||
|
@ -23,48 +34,19 @@ const { files: filesDb } = require('../utils/database/index');
|
||||||
* @property {number} size - size of the file in
|
* @property {number} size - size of the file in
|
||||||
*/
|
*/
|
||||||
class File {
|
class File {
|
||||||
/**
|
public _id?: string;
|
||||||
* Find and return model of file with given id
|
public name?: string;
|
||||||
*
|
public filename?: string;
|
||||||
* @param {string} _id - file id
|
public path?: string;
|
||||||
* @returns {Promise<File>}
|
public mimetype?: string;
|
||||||
*/
|
public size?: number;
|
||||||
static async get(_id) {
|
|
||||||
const data = await filesDb.findOne({ _id });
|
|
||||||
|
|
||||||
return new File(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find and return model of file with given id
|
|
||||||
*
|
|
||||||
* @param {string} filename - uploaded filename
|
|
||||||
* @returns {Promise<File>}
|
|
||||||
*/
|
|
||||||
static async getByFilename(filename) {
|
|
||||||
const data = await filesDb.findOne({ filename });
|
|
||||||
|
|
||||||
return new File(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all files which match passed query object
|
|
||||||
*
|
|
||||||
* @param {object} query
|
|
||||||
* @returns {Promise<File[]>}
|
|
||||||
*/
|
|
||||||
static async getAll(query = {}) {
|
|
||||||
const docs = await filesDb.find(query);
|
|
||||||
|
|
||||||
return Promise.all(docs.map(doc => new File(doc)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
*
|
*
|
||||||
* @param {FileData} data
|
* @param {FileData} data - info about file
|
||||||
*/
|
*/
|
||||||
constructor(data = {}) {
|
constructor(data: FileData = {}) {
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
data = {};
|
data = {};
|
||||||
}
|
}
|
||||||
|
@ -75,13 +57,48 @@ class File {
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Find and return model of file with given id
|
||||||
|
*
|
||||||
|
* @param {string} _id - file id
|
||||||
|
* @returns {Promise<File>}
|
||||||
|
*/
|
||||||
|
public static async get(_id: string): Promise<File> {
|
||||||
|
const data: FileData = await filesDb.findOne({ _id });
|
||||||
|
|
||||||
|
return new File(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and return model of file with given id
|
||||||
|
*
|
||||||
|
* @param {string} filename - uploaded filename
|
||||||
|
* @returns {Promise<File>}
|
||||||
|
*/
|
||||||
|
public static async getByFilename(filename: string): Promise<File> {
|
||||||
|
const data = await filesDb.findOne({ filename });
|
||||||
|
|
||||||
|
return new File(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all files which match passed query object
|
||||||
|
*
|
||||||
|
* @param {object} query - input query
|
||||||
|
* @returns {Promise<File[]>}
|
||||||
|
*/
|
||||||
|
public static async getAll(query: Record<string, unknown> = {}): Promise<File[]> {
|
||||||
|
const docs = await filesDb.find(query);
|
||||||
|
|
||||||
|
return Promise.all(docs.map(doc => new File(doc)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set FileData object fields to internal model fields
|
* Set FileData object fields to internal model fields
|
||||||
*
|
*
|
||||||
* @param {FileData} fileData
|
* @param {FileData} fileData - info about file
|
||||||
*/
|
*/
|
||||||
set data(fileData) {
|
public set data(fileData: FileData) {
|
||||||
const { name, filename, path, mimetype, size } = fileData;
|
const { name, filename, path, mimetype, size } = fileData;
|
||||||
|
|
||||||
this.name = name || this.name;
|
this.name = name || this.name;
|
||||||
|
@ -96,7 +113,7 @@ class File {
|
||||||
*
|
*
|
||||||
* @returns {FileData}
|
* @returns {FileData}
|
||||||
*/
|
*/
|
||||||
get data() {
|
public get data(): FileData {
|
||||||
return {
|
return {
|
||||||
_id: this._id,
|
_id: this._id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
@ -112,9 +129,9 @@ class File {
|
||||||
*
|
*
|
||||||
* @returns {Promise<File>}
|
* @returns {Promise<File>}
|
||||||
*/
|
*/
|
||||||
async save() {
|
public async save(): Promise<File> {
|
||||||
if (!this._id) {
|
if (!this._id) {
|
||||||
const insertedRow = await filesDb.insert(this.data);
|
const insertedRow = await filesDb.insert(this.data) as { _id: string };
|
||||||
|
|
||||||
this._id = insertedRow._id;
|
this._id = insertedRow._id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,7 +146,7 @@ class File {
|
||||||
*
|
*
|
||||||
* @returns {Promise<File>}
|
* @returns {Promise<File>}
|
||||||
*/
|
*/
|
||||||
async destroy() {
|
public async destroy(): Promise<File> {
|
||||||
await filesDb.remove({ _id: this._id });
|
await filesDb.remove({ _id: this._id });
|
||||||
|
|
||||||
delete this._id;
|
delete this._id;
|
||||||
|
@ -137,24 +154,24 @@ class File {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes unnecessary public folder prefix
|
|
||||||
*
|
|
||||||
* @param {string} path
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
processPath(path) {
|
|
||||||
return path.replace(/^public/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return readable file data
|
* Return readable file data
|
||||||
*
|
*
|
||||||
* @returns {FileData}
|
* @returns {FileData}
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
public toJSON(): FileData {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes unnecessary public folder prefix
|
||||||
|
*
|
||||||
|
* @param {string} path - input path to be processed
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
private processPath(path: string): string {
|
||||||
|
return path.replace(/^public/, '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = File;
|
export default File;
|
|
@ -1,5 +1,7 @@
|
||||||
const urlify = require('../utils/urlify');
|
import urlify from '../utils/urlify';
|
||||||
const { pages: pagesDb } = require('../utils/database/index');
|
import database from '../utils/database/index';
|
||||||
|
|
||||||
|
const pagesDb = database['pages'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} PageData
|
* @typedef {object} PageData
|
||||||
|
@ -9,6 +11,13 @@ const { pages: pagesDb } = require('../utils/database/index');
|
||||||
* @property {*} body - page body
|
* @property {*} body - page body
|
||||||
* @property {string} parent - id of parent page
|
* @property {string} parent - id of parent page
|
||||||
*/
|
*/
|
||||||
|
export interface PageData {
|
||||||
|
_id?: string;
|
||||||
|
title?: string;
|
||||||
|
uri?: string;
|
||||||
|
body?: any;
|
||||||
|
parent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Page
|
* @class Page
|
||||||
|
@ -21,48 +30,18 @@ const { pages: pagesDb } = require('../utils/database/index');
|
||||||
* @property {string} _parent - id of parent page
|
* @property {string} _parent - id of parent page
|
||||||
*/
|
*/
|
||||||
class Page {
|
class Page {
|
||||||
/**
|
public _id?: string;
|
||||||
* Find and return model of page with given id
|
public body?: any;
|
||||||
*
|
public title?: string;
|
||||||
* @param {string} _id - page id
|
public uri?: string;
|
||||||
* @returns {Promise<Page>}
|
public _parent?: string;
|
||||||
*/
|
|
||||||
static async get(_id) {
|
|
||||||
const data = await pagesDb.findOne({ _id });
|
|
||||||
|
|
||||||
return new Page(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find and return model of page with given uri
|
|
||||||
*
|
|
||||||
* @param {string} uri - page uri
|
|
||||||
* @returns {Promise<Page>}
|
|
||||||
*/
|
|
||||||
static async getByUri(uri) {
|
|
||||||
const data = await pagesDb.findOne({ uri });
|
|
||||||
|
|
||||||
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 pagesDb.find(query);
|
|
||||||
|
|
||||||
return Promise.all(docs.map(doc => new Page(doc)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
*
|
*
|
||||||
* @param {PageData} data
|
* @param {PageData} data - page's data
|
||||||
*/
|
*/
|
||||||
constructor(data = {}) {
|
constructor(data: PageData = {}) {
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
data = {};
|
data = {};
|
||||||
}
|
}
|
||||||
|
@ -74,12 +53,48 @@ class Page {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and return model of page with given id
|
||||||
|
*
|
||||||
|
* @param {string} _id - page id
|
||||||
|
* @returns {Promise<Page>}
|
||||||
|
*/
|
||||||
|
public static async get(_id: string): Promise<Page> {
|
||||||
|
const data = await pagesDb.findOne({ _id });
|
||||||
|
|
||||||
|
return new Page(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and return model of page with given uri
|
||||||
|
*
|
||||||
|
* @param {string} uri - page uri
|
||||||
|
* @returns {Promise<Page>}
|
||||||
|
*/
|
||||||
|
public static async getByUri(uri: string): Promise<Page> {
|
||||||
|
const data = await pagesDb.findOne({ uri });
|
||||||
|
|
||||||
|
return new Page(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all pages which match passed query object
|
||||||
|
*
|
||||||
|
* @param {object} query - input query
|
||||||
|
* @returns {Promise<Page[]>}
|
||||||
|
*/
|
||||||
|
public static async getAll(query: Record<string, unknown> = {}): Promise<Page[]> {
|
||||||
|
const docs = await pagesDb.find(query);
|
||||||
|
|
||||||
|
return Promise.all(docs.map(doc => new Page(doc)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set PageData object fields to internal model fields
|
* Set PageData object fields to internal model fields
|
||||||
*
|
*
|
||||||
* @param {PageData} pageData
|
* @param {PageData} pageData - page's data
|
||||||
*/
|
*/
|
||||||
set data(pageData) {
|
public set data(pageData: PageData) {
|
||||||
const { body, parent, uri } = pageData;
|
const { body, parent, uri } = pageData;
|
||||||
|
|
||||||
this.body = body || this.body;
|
this.body = body || this.body;
|
||||||
|
@ -93,7 +108,7 @@ class Page {
|
||||||
*
|
*
|
||||||
* @returns {PageData}
|
* @returns {PageData}
|
||||||
*/
|
*/
|
||||||
get data() {
|
public get data(): PageData {
|
||||||
return {
|
return {
|
||||||
_id: this._id,
|
_id: this._id,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
|
@ -103,32 +118,12 @@ class Page {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract first header from editor data
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
extractTitleFromBody() {
|
|
||||||
const headerBlock = this.body ? this.body.blocks.find(block => block.type === 'header') : '';
|
|
||||||
|
|
||||||
return headerBlock ? headerBlock.data.text : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform title for uri
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
transformTitleToUri() {
|
|
||||||
return urlify(this.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link given page as parent
|
* Link given page as parent
|
||||||
*
|
*
|
||||||
* @param {Page} parentPage
|
* @param {Page} parentPage - the page to be set as parent
|
||||||
*/
|
*/
|
||||||
set parent(parentPage) {
|
public set parent(parentPage: Page) {
|
||||||
this._parent = parentPage._id;
|
this._parent = parentPage._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,9 +132,10 @@ class Page {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Page>}
|
* @returns {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
get parent() {
|
public async getParent(): Promise<Page> {
|
||||||
return pagesDb.findOne({ _id: this._parent })
|
const data = await pagesDb.findOne({ _id: this._parent });
|
||||||
.then(data => new Page(data));
|
|
||||||
|
return new Page(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,9 +143,11 @@ class Page {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Page[]>}
|
* @returns {Promise<Page[]>}
|
||||||
*/
|
*/
|
||||||
get children() {
|
public get children(): Promise<Page[]> {
|
||||||
return pagesDb.find({ parent: this._id })
|
return pagesDb.find({ parent: this._id })
|
||||||
.then(data => data.map(page => new Page(page)));
|
.then(data => {
|
||||||
|
return data.map(page => new Page(page));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,11 +155,13 @@ class Page {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Page>}
|
* @returns {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
async save() {
|
public async save(): Promise<Page> {
|
||||||
this.uri = await this.composeUri(this.uri);
|
if (this.uri !== undefined) {
|
||||||
|
this.uri = await this.composeUri(this.uri);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._id) {
|
if (!this._id) {
|
||||||
const insertedRow = await pagesDb.insert(this.data);
|
const insertedRow = await pagesDb.insert(this.data) as { _id: string };
|
||||||
|
|
||||||
this._id = insertedRow._id;
|
this._id = insertedRow._id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -176,7 +176,7 @@ class Page {
|
||||||
*
|
*
|
||||||
* @returns {Promise<Page>}
|
* @returns {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
async destroy() {
|
public async destroy(): Promise<Page> {
|
||||||
await pagesDb.remove({ _id: this._id });
|
await pagesDb.remove({ _id: this._id });
|
||||||
|
|
||||||
delete this._id;
|
delete this._id;
|
||||||
|
@ -184,13 +184,22 @@ class Page {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return readable page data
|
||||||
|
*
|
||||||
|
* @returns {PageData}
|
||||||
|
*/
|
||||||
|
public toJSON(): PageData {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find and return available uri
|
* Find and return available uri
|
||||||
*
|
*
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @param uri
|
* @param uri - input uri to be composed
|
||||||
*/
|
*/
|
||||||
async composeUri(uri) {
|
private async composeUri(uri: string): Promise<string> {
|
||||||
let pageWithSameUriCount = 0;
|
let pageWithSameUriCount = 0;
|
||||||
|
|
||||||
if (!this._id) {
|
if (!this._id) {
|
||||||
|
@ -210,13 +219,28 @@ class Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return readable page data
|
* Extract first header from editor data
|
||||||
*
|
*
|
||||||
* @returns {PageData}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
private extractTitleFromBody(): string {
|
||||||
return this.data;
|
const headerBlock = this.body ? this.body.blocks.find((block: Record<string, unknown>) => block.type === 'header') : '';
|
||||||
|
|
||||||
|
return headerBlock ? headerBlock.data.text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform title for uri
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
private transformTitleToUri(): string {
|
||||||
|
if (this.title === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlify(this.title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Page;
|
export default Page;
|
|
@ -1,4 +1,6 @@
|
||||||
const { pagesOrder: db } = require('../utils/database/index');
|
import database from '../utils/database/index';
|
||||||
|
|
||||||
|
const db = database['pagesOrder'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} PageOrderData
|
* @typedef {object} PageOrderData
|
||||||
|
@ -6,6 +8,11 @@ const { pagesOrder: db } = require('../utils/database/index');
|
||||||
* @property {string} page - page id
|
* @property {string} page - page id
|
||||||
* @property {Array<string>} order - list of ordered pages
|
* @property {Array<string>} order - list of ordered pages
|
||||||
*/
|
*/
|
||||||
|
export interface PageOrderData {
|
||||||
|
_id?: string;
|
||||||
|
page?: string;
|
||||||
|
order?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class PageOrder
|
* @class PageOrder
|
||||||
|
@ -14,44 +21,17 @@ const { pagesOrder: db } = require('../utils/database/index');
|
||||||
* Creates order for Pages with children
|
* Creates order for Pages with children
|
||||||
*/
|
*/
|
||||||
class PageOrder {
|
class PageOrder {
|
||||||
/**
|
public _id?: string;
|
||||||
* Returns current Page's children order
|
public page?: string;
|
||||||
*
|
private _order?: string[];
|
||||||
* @param {string} pageId - page's id
|
|
||||||
* @returns {PageOrder}
|
|
||||||
*/
|
|
||||||
static async get(pageId) {
|
|
||||||
const order = await db.findOne({ page: pageId });
|
|
||||||
|
|
||||||
let data = {};
|
|
||||||
|
|
||||||
if (!order) {
|
|
||||||
data.page = pageId;
|
|
||||||
} else {
|
|
||||||
data = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PageOrder(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all pages which match passed query object
|
|
||||||
*
|
|
||||||
* @param {object} query
|
|
||||||
* @returns {Promise<Page[]>}
|
|
||||||
*/
|
|
||||||
static async getAll(query = {}) {
|
|
||||||
const docs = await db.find(query);
|
|
||||||
|
|
||||||
return Promise.all(docs.map(doc => new PageOrder(doc)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
*
|
*
|
||||||
* @param {PageOrderData} data
|
* @param {PageOrderData} data - info about pageOrder
|
||||||
*/
|
*/
|
||||||
constructor(data = {}) {
|
constructor(data: PageOrderData = {}) {
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
data = {};
|
data = {};
|
||||||
}
|
}
|
||||||
|
@ -63,14 +43,46 @@ class PageOrder {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current Page's children order
|
||||||
|
*
|
||||||
|
* @param {string} pageId - page's id
|
||||||
|
* @returns {Promise<PageOrder>}
|
||||||
|
*/
|
||||||
|
public static async get(pageId: string): Promise<PageOrder> {
|
||||||
|
const order = await db.findOne({ page: pageId });
|
||||||
|
|
||||||
|
let data: PageOrderData = {};
|
||||||
|
|
||||||
|
if (order === null) {
|
||||||
|
data.page = pageId;
|
||||||
|
} else {
|
||||||
|
data = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PageOrder(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all pages which match passed query object
|
||||||
|
*
|
||||||
|
* @param {object} query - input query
|
||||||
|
* @returns {Promise<PageOrder[]>}
|
||||||
|
*/
|
||||||
|
public static async getAll(query: Record<string, unknown> = {}): Promise<PageOrder[]> {
|
||||||
|
const docs = await db.find(query);
|
||||||
|
|
||||||
|
return Promise.all(docs.map(doc => new PageOrder(doc)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor data setter
|
* constructor data setter
|
||||||
*
|
*
|
||||||
* @param {PageOrderData} pageOrderData
|
* @param {PageOrderData} pageOrderData - info about pageOrder
|
||||||
*/
|
*/
|
||||||
set data(pageOrderData) {
|
public set data(pageOrderData: PageOrderData) {
|
||||||
this._page = pageOrderData.page || 0;
|
this.page = pageOrderData.page || '0';
|
||||||
this._order = pageOrderData.order || [];
|
this.order = pageOrderData.order || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,11 +90,11 @@ class PageOrder {
|
||||||
*
|
*
|
||||||
* @returns {PageOrderData}
|
* @returns {PageOrderData}
|
||||||
*/
|
*/
|
||||||
get data() {
|
public get data(): PageOrderData {
|
||||||
return {
|
return {
|
||||||
_id: this._id,
|
_id: this._id,
|
||||||
page: '' + this._page,
|
page: '' + this.page,
|
||||||
order: this._order,
|
order: this.order,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +103,12 @@ class PageOrder {
|
||||||
*
|
*
|
||||||
* @param {string} pageId - page's id
|
* @param {string} pageId - page's id
|
||||||
*/
|
*/
|
||||||
push(pageId) {
|
public push(pageId: string | number): void {
|
||||||
if (typeof pageId === 'string') {
|
if (typeof pageId === 'string') {
|
||||||
this._order.push(pageId);
|
if (this.order === undefined) {
|
||||||
|
this.order = [];
|
||||||
|
}
|
||||||
|
this.order.push(pageId);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('given id is not string');
|
throw new Error('given id is not string');
|
||||||
}
|
}
|
||||||
|
@ -104,11 +119,15 @@ class PageOrder {
|
||||||
*
|
*
|
||||||
* @param {string} pageId - page's id
|
* @param {string} pageId - page's id
|
||||||
*/
|
*/
|
||||||
remove(pageId) {
|
public remove(pageId: string): void {
|
||||||
const found = this._order.indexOf(pageId);
|
if (this.order === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = this.order.indexOf(pageId);
|
||||||
|
|
||||||
if (found >= 0) {
|
if (found >= 0) {
|
||||||
this._order.splice(found, 1);
|
this.order.splice(found, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,9 +135,13 @@ class PageOrder {
|
||||||
* @param {string} currentPageId - page's id that changes the order
|
* @param {string} currentPageId - page's id that changes the order
|
||||||
* @param {string} putAbovePageId - page's id above which we put the target page
|
* @param {string} putAbovePageId - page's id above which we put the target page
|
||||||
*
|
*
|
||||||
* @returns void
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
putAbove(currentPageId, putAbovePageId) {
|
public putAbove(currentPageId: string, putAbovePageId: string): void {
|
||||||
|
if (this.order === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const found1 = this.order.indexOf(putAbovePageId);
|
const found1 = this.order.indexOf(putAbovePageId);
|
||||||
const found2 = this.order.indexOf(currentPageId);
|
const found2 = this.order.indexOf(currentPageId);
|
||||||
|
|
||||||
|
@ -135,16 +158,20 @@ class PageOrder {
|
||||||
/**
|
/**
|
||||||
* Returns page before passed page with id
|
* Returns page before passed page with id
|
||||||
*
|
*
|
||||||
* @param {string} pageId
|
* @param {string} pageId - identity of page
|
||||||
*/
|
*/
|
||||||
getPageBefore(pageId) {
|
public getPageBefore(pageId: string): string | null {
|
||||||
|
if (this.order === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const currentPageInOrder = this.order.indexOf(pageId);
|
const currentPageInOrder = this.order.indexOf(pageId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If page not found or first return nothing
|
* If page not found or first return nothing
|
||||||
*/
|
*/
|
||||||
if (currentPageInOrder <= 0) {
|
if (currentPageInOrder <= 0) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.order[currentPageInOrder - 1];
|
return this.order[currentPageInOrder - 1];
|
||||||
|
@ -153,16 +180,20 @@ class PageOrder {
|
||||||
/**
|
/**
|
||||||
* Returns page before passed page with id
|
* Returns page before passed page with id
|
||||||
*
|
*
|
||||||
* @param pageId
|
* @param pageId - identity of page
|
||||||
*/
|
*/
|
||||||
getPageAfter(pageId) {
|
public getPageAfter(pageId: string): string | null {
|
||||||
|
if (this.order === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const currentPageInOrder = this.order.indexOf(pageId);
|
const currentPageInOrder = this.order.indexOf(pageId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If page not found or is last
|
* If page not found or is last
|
||||||
*/
|
*/
|
||||||
if (currentPageInOrder === -1 || currentPageInOrder === this.order.length - 1) {
|
if (currentPageInOrder === -1 || currentPageInOrder === this.order.length - 1) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.order[currentPageInOrder + 1];
|
return this.order[currentPageInOrder + 1];
|
||||||
|
@ -171,7 +202,7 @@ class PageOrder {
|
||||||
/**
|
/**
|
||||||
* @param {string[]} order - define new order
|
* @param {string[]} order - define new order
|
||||||
*/
|
*/
|
||||||
set order(order) {
|
public set order(order: string[]) {
|
||||||
this._order = order;
|
this._order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,16 +211,18 @@ class PageOrder {
|
||||||
*
|
*
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
get order() {
|
public get order(): string[] {
|
||||||
return this._order;
|
return this._order || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save or update page data in the database
|
* Save or update page data in the database
|
||||||
|
*
|
||||||
|
* @returns {Promise<PageOrder>}
|
||||||
*/
|
*/
|
||||||
async save() {
|
public async save(): Promise<PageOrder> {
|
||||||
if (!this._id) {
|
if (!this._id) {
|
||||||
const insertedRow = await db.insert(this.data);
|
const insertedRow = await db.insert(this.data) as { _id: string};
|
||||||
|
|
||||||
this._id = insertedRow._id;
|
this._id = insertedRow._id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -201,14 +234,14 @@ class PageOrder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove page data from the database
|
* Remove page data from the database
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async destroy() {
|
public async destroy(): Promise<void> {
|
||||||
await db.remove({ _id: this._id });
|
await db.remove({ _id: this._id });
|
||||||
|
|
||||||
delete this._id;
|
delete this._id;
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PageOrder;
|
export default PageOrder;
|
40
src/backend/models/user.ts
Normal file
40
src/backend/models/user.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import database from '../utils/database/index';
|
||||||
|
|
||||||
|
const db = database['password'];
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
passHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class User
|
||||||
|
* @class User model
|
||||||
|
*
|
||||||
|
* @property {string} passHash - hashed password
|
||||||
|
*/
|
||||||
|
class User {
|
||||||
|
public passHash?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @param {UserData} userData - user data for construct new object
|
||||||
|
*/
|
||||||
|
constructor(userData: UserData) {
|
||||||
|
this.passHash = userData.passHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and return model of user.
|
||||||
|
* User is only one.
|
||||||
|
*
|
||||||
|
* @returns {Promise<User>}
|
||||||
|
*/
|
||||||
|
public static async get(): Promise<User> {
|
||||||
|
const userData: UserData = await db.findOne({});
|
||||||
|
|
||||||
|
return new User(userData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default User;
|
|
@ -1,16 +1,17 @@
|
||||||
const express = require('express');
|
import express, { Request, Response } from 'express';
|
||||||
|
import Aliases from '../controllers/aliases';
|
||||||
|
import Pages from '../controllers/pages';
|
||||||
|
import Alias from '../models/alias';
|
||||||
|
import verifyToken from './middlewares/token';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const Aliases = require('../controllers/aliases');
|
|
||||||
const Pages = require('../controllers/pages');
|
|
||||||
const Alias = require('../models/alias');
|
|
||||||
const verifyToken = require('./middlewares/token');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /*
|
* GET /*
|
||||||
*
|
*
|
||||||
* Return document with given alias
|
* Return document with given alias
|
||||||
*/
|
*/
|
||||||
router.get('*', verifyToken, async (req, res) => {
|
router.get('*', verifyToken, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
let url = req.originalUrl.slice(1); // Cuts first '/' character
|
let url = req.originalUrl.slice(1); // Cuts first '/' character
|
||||||
const queryParamsIndex = url.indexOf('?');
|
const queryParamsIndex = url.indexOf('?');
|
||||||
|
@ -21,11 +22,15 @@ router.get('*', verifyToken, async (req, res) => {
|
||||||
|
|
||||||
const alias = await Aliases.get(url);
|
const alias = await Aliases.get(url);
|
||||||
|
|
||||||
|
if (alias.id === undefined) {
|
||||||
|
throw new Error('Alias not found');
|
||||||
|
}
|
||||||
|
|
||||||
switch (alias.type) {
|
switch (alias.type) {
|
||||||
case Alias.types.PAGE: {
|
case Alias.types.PAGE: {
|
||||||
const page = await Pages.get(alias.id);
|
const page = await Pages.get(alias.id);
|
||||||
|
|
||||||
const pageParent = await page.parent;
|
const pageParent = await page.getParent();
|
||||||
|
|
||||||
res.render('pages/page', {
|
res.render('pages/page', {
|
||||||
page,
|
page,
|
||||||
|
@ -37,9 +42,9 @@ router.get('*', verifyToken, async (req, res) => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message,
|
error: err,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
12
src/backend/routes/api/index.ts
Normal file
12
src/backend/routes/api/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import express from 'express';
|
||||||
|
import pagesAPI from './pages';
|
||||||
|
import transportAPI from './transport';
|
||||||
|
import linksAPI from './links';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use('/', pagesAPI);
|
||||||
|
router.use('/', transportAPI);
|
||||||
|
router.use('/', linksAPI);
|
||||||
|
|
||||||
|
export default router;
|
62
src/backend/routes/api/links.ts
Normal file
62
src/backend/routes/api/links.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import ogs from 'open-graph-scraper';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
interface ResponseData {
|
||||||
|
success: number;
|
||||||
|
meta?: {
|
||||||
|
title: string | undefined;
|
||||||
|
description: string | undefined;
|
||||||
|
siteName: string | undefined;
|
||||||
|
image: { url: string | undefined }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept file url to fetch
|
||||||
|
*/
|
||||||
|
router.get('/fetchUrl', async (req: Request, res: Response) => {
|
||||||
|
const response: ResponseData = {
|
||||||
|
success: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!req.query.url) {
|
||||||
|
res.status(400).json(response);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof req.query.url !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const linkData = (await ogs({ url: req.query.url })).result;
|
||||||
|
|
||||||
|
if (!linkData.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.success = 1;
|
||||||
|
response.meta = {
|
||||||
|
title: linkData.ogTitle,
|
||||||
|
description: linkData.ogDescription,
|
||||||
|
siteName: linkData.ogSiteName,
|
||||||
|
image: {
|
||||||
|
url: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (linkData.ogImage !== undefined) {
|
||||||
|
response.meta.image = { url: linkData.ogImage.toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(response);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500).json(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -1,8 +1,10 @@
|
||||||
const express = require('express');
|
import express, { Request, Response } from 'express';
|
||||||
|
import multerFunc from 'multer';
|
||||||
|
import Pages from '../../controllers/pages';
|
||||||
|
import PagesOrder from '../../controllers/pagesOrder';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const multer = require('multer')();
|
const multer = multerFunc();
|
||||||
const Pages = require('../../controllers/pages');
|
|
||||||
const PagesOrder = require('../../controllers/pagesOrder');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /page/:id
|
* GET /page/:id
|
||||||
|
@ -10,18 +12,18 @@ const PagesOrder = require('../../controllers/pagesOrder');
|
||||||
* Return PageData of page with given id
|
* Return PageData of page with given id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get('/page/:id', async (req, res) => {
|
router.get('/page/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const page = await Pages.get(req.params.id);
|
const page = await Pages.get(req.params.id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: page.data
|
result: page.data,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message
|
error: (err as Error).message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -31,18 +33,18 @@ router.get('/page/:id', async (req, res) => {
|
||||||
*
|
*
|
||||||
* Return PageData for all pages
|
* Return PageData for all pages
|
||||||
*/
|
*/
|
||||||
router.get('/pages', async (req, res) => {
|
router.get('/pages', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const pages = await Pages.getAll();
|
const pages = await Pages.getAll();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: pages
|
result: pages,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message
|
error: (err as Error).message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -52,22 +54,30 @@ router.get('/pages', async (req, res) => {
|
||||||
*
|
*
|
||||||
* Create new page in the database
|
* Create new page in the database
|
||||||
*/
|
*/
|
||||||
router.put('/page', multer.none(), async (req, res) => {
|
router.put('/page', multer.none(), async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { title, body, parent } = req.body;
|
const { title, body, parent } = req.body;
|
||||||
const page = await Pages.insert({ title, body, parent });
|
const page = await Pages.insert({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (page._id === undefined) {
|
||||||
|
throw new Error('Page not found');
|
||||||
|
}
|
||||||
|
|
||||||
/** push to the orders array */
|
/** push to the orders array */
|
||||||
await PagesOrder.push(parent, page._id);
|
await PagesOrder.push(parent, page._id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: page
|
result: page,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message
|
error: (err as Error).message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -77,7 +87,7 @@ router.put('/page', multer.none(), async (req, res) => {
|
||||||
*
|
*
|
||||||
* Update page data in the database
|
* Update page data in the database
|
||||||
*/
|
*/
|
||||||
router.post('/page/:id', multer.none(), async (req, res) => {
|
router.post('/page/:id', multer.none(), async (req: Request, res: Response) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -85,25 +95,46 @@ router.post('/page/:id', multer.none(), async (req, res) => {
|
||||||
const pages = await Pages.getAll();
|
const pages = await Pages.getAll();
|
||||||
let page = await Pages.get(id);
|
let page = await Pages.get(id);
|
||||||
|
|
||||||
|
if (page._id === undefined) {
|
||||||
|
throw new Error('Page not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!page._parent) {
|
||||||
|
throw new Error('Parent not found');
|
||||||
|
}
|
||||||
|
|
||||||
if (page._parent !== parent) {
|
if (page._parent !== parent) {
|
||||||
await PagesOrder.move(page._parent, parent, id);
|
await PagesOrder.move(page._parent, parent, id);
|
||||||
} else {
|
} else {
|
||||||
if (putAbovePageId && putAbovePageId !== '0') {
|
if (putAbovePageId && putAbovePageId !== '0') {
|
||||||
const unordered = pages.filter(_page => _page._parent === page._parent).map(_page => _page._id);
|
const unordered = pages.filter(_page => _page._parent === page._parent).map(_page => _page._id);
|
||||||
|
|
||||||
await PagesOrder.update(unordered, page._id, page._parent, putAbovePageId);
|
const unOrdered: string[] = [];
|
||||||
|
|
||||||
|
unordered.forEach(item => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
unOrdered.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await PagesOrder.update(unOrdered, page._id, page._parent, putAbovePageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
page = await Pages.update(id, { title, body, parent, uri });
|
page = await Pages.update(id, {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
parent,
|
||||||
|
uri,
|
||||||
|
});
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: page
|
result: page,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message
|
error: (err as Error).message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -113,10 +144,19 @@ router.post('/page/:id', multer.none(), async (req, res) => {
|
||||||
*
|
*
|
||||||
* Remove page from the database
|
* Remove page from the database
|
||||||
*/
|
*/
|
||||||
router.delete('/page/:id', async (req, res) => {
|
router.delete('/page/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const pageId = req.params.id;
|
const pageId = req.params.id;
|
||||||
const page = await Pages.get(pageId);
|
const page = await Pages.get(pageId);
|
||||||
|
|
||||||
|
if (page._id === undefined) {
|
||||||
|
throw new Error('Page not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!page._parent) {
|
||||||
|
throw new Error('Parent not found');
|
||||||
|
}
|
||||||
|
|
||||||
const parentPageOrder = await PagesOrder.get(page._parent);
|
const parentPageOrder = await PagesOrder.get(page._parent);
|
||||||
const pageBeforeId = parentPageOrder.getPageBefore(page._id);
|
const pageBeforeId = parentPageOrder.getPageBefore(page._id);
|
||||||
const pageAfterId = parentPageOrder.getPageAfter(page._id);
|
const pageAfterId = parentPageOrder.getPageAfter(page._id);
|
||||||
|
@ -134,17 +174,19 @@ router.delete('/page/:id', async (req, res) => {
|
||||||
/**
|
/**
|
||||||
* remove current page and go deeper to remove children with orders
|
* remove current page and go deeper to remove children with orders
|
||||||
*
|
*
|
||||||
* @param startFrom
|
* @param {string} startFrom - start point to delete
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const deleteRecursively = async (startFrom) => {
|
const deleteRecursively = async (startFrom: string): Promise<void> => {
|
||||||
let order = [];
|
let order: string[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const children = await PagesOrder.get(startFrom);
|
const children = await PagesOrder.get(startFrom);
|
||||||
|
|
||||||
order = children.order;
|
order = children.order;
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
order = [];
|
||||||
|
}
|
||||||
|
|
||||||
order.forEach(async id => {
|
order.forEach(async id => {
|
||||||
await deleteRecursively(id);
|
await deleteRecursively(id);
|
||||||
|
@ -153,7 +195,9 @@ router.delete('/page/:id', async (req, res) => {
|
||||||
await Pages.remove(startFrom);
|
await Pages.remove(startFrom);
|
||||||
try {
|
try {
|
||||||
await PagesOrder.remove(startFrom);
|
await PagesOrder.remove(startFrom);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
order = [];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await deleteRecursively(req.params.id);
|
await deleteRecursively(req.params.id);
|
||||||
|
@ -164,14 +208,14 @@ router.delete('/page/:id', async (req, res) => {
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: pageToRedirect
|
result: pageToRedirect,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message
|
error: (err as Error).message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
|
@ -1,59 +1,79 @@
|
||||||
const express = require('express');
|
import { Request, Response, Router } from 'express';
|
||||||
const router = express.Router();
|
import multer, { StorageEngine } from 'multer';
|
||||||
const multer = require('multer');
|
import mime from 'mime';
|
||||||
const mime = require('mime');
|
import mkdirp from 'mkdirp';
|
||||||
const mkdirp = require('mkdirp');
|
import config from 'config';
|
||||||
const Transport = require('../../controllers/transport');
|
import Transport from '../../controllers/transport';
|
||||||
const { random16 } = require('../../utils/crypto');
|
import { random16 } from '../../utils/crypto';
|
||||||
const config = require('../../../config');
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multer storage for uploaded files and images
|
* Multer storage for uploaded files and images
|
||||||
* @type {DiskStorage|DiskStorage}
|
*
|
||||||
|
* @type {StorageEngine}
|
||||||
*/
|
*/
|
||||||
const storage = multer.diskStorage({
|
const storage: StorageEngine = multer.diskStorage({
|
||||||
destination: (req, file, cb) => {
|
destination: (req, file, cb) => {
|
||||||
const dir = config.uploads || 'public/uploads';
|
const dir: string = config.get('uploads') || 'public/uploads';
|
||||||
|
|
||||||
mkdirp(dir, err => cb(err, dir));
|
mkdirp(dir);
|
||||||
|
cb(null, dir);
|
||||||
},
|
},
|
||||||
filename: async (req, file, cb) => {
|
filename: async (req, file, cb) => {
|
||||||
const filename = await random16();
|
const filename = await random16();
|
||||||
|
|
||||||
cb(null, `${filename}.${mime.getExtension(file.mimetype)}`);
|
cb(null, `${filename}.${mime.getExtension(file.mimetype)}`);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multer middleware for image uploading
|
* Multer middleware for image uploading
|
||||||
*/
|
*/
|
||||||
const imageUploader = multer({
|
const imageUploader = multer({
|
||||||
storage,
|
storage: storage,
|
||||||
fileFilter: (req, file, cb) => {
|
fileFilter: (req, file, cb) => {
|
||||||
if (!/image/.test(file.mimetype) && !/video\/mp4/.test(file.mimetype)) {
|
if (!/image/.test(file.mimetype) && !/video\/mp4/.test(file.mimetype)) {
|
||||||
cb(null, false);
|
cb(null, false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, true);
|
cb(null, true);
|
||||||
}
|
},
|
||||||
}).fields([ { name: 'image', maxCount: 1 } ]);
|
}).fields([ {
|
||||||
|
name: 'image',
|
||||||
|
maxCount: 1,
|
||||||
|
} ]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multer middleware for file uploading
|
* Multer middleware for file uploading
|
||||||
*/
|
*/
|
||||||
const fileUploader = multer({
|
const fileUploader = multer({
|
||||||
storage
|
storage: storage,
|
||||||
}).fields([ { name: 'file', maxCount: 1 } ]);
|
}).fields([ {
|
||||||
|
name: 'file',
|
||||||
|
maxCount: 1,
|
||||||
|
} ]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts images to upload
|
* Accepts images to upload
|
||||||
*/
|
*/
|
||||||
router.post('/transport/image', imageUploader, async (req, res) => {
|
router.post('/transport/image', imageUploader, async (req: Request, res: Response) => {
|
||||||
let response = { success: 0 };
|
const response = {
|
||||||
|
success: 0,
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
|
||||||
if (!req.files || !req.files.image) {
|
if (req.files === undefined) {
|
||||||
|
response.message = 'No files found';
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!('image' in req.files)) {
|
||||||
|
res.status(400).json(response);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +93,17 @@ router.post('/transport/image', imageUploader, async (req, res) => {
|
||||||
/**
|
/**
|
||||||
* Accepts files to upload
|
* Accepts files to upload
|
||||||
*/
|
*/
|
||||||
router.post('/transport/file', fileUploader, async (req, res) => {
|
router.post('/transport/file', fileUploader, async (req: Request, res: Response) => {
|
||||||
let response = { success: 0 };
|
const response = { success: 0 };
|
||||||
|
|
||||||
if (!req.files || !req.files.file) {
|
if (req.files === undefined) {
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!('file' in req.files)) {
|
||||||
|
res.status(400).json(response);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +123,12 @@ router.post('/transport/file', fileUploader, async (req, res) => {
|
||||||
/**
|
/**
|
||||||
* Accept file url to fetch
|
* Accept file url to fetch
|
||||||
*/
|
*/
|
||||||
router.post('/transport/fetch', multer().none(), async (req, res) => {
|
router.post('/transport/fetch', multer().none(), async (req: Request, res: Response) => {
|
||||||
let response = { success: 0 };
|
const response = { success: 0 };
|
||||||
|
|
||||||
if (!req.body.url) {
|
if (!req.body.url) {
|
||||||
res.status(400).json(response);
|
res.status(400).json(response);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,4 +143,4 @@ router.post('/transport/fetch', multer().none(), async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
78
src/backend/routes/auth.ts
Normal file
78
src/backend/routes/auth.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import config from 'config';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import csrf from 'csurf';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import Users from '../controllers/users';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const csrfProtection = csrf({ cookie: true });
|
||||||
|
const parseForm = express.urlencoded({ extended: false });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorization page
|
||||||
|
*/
|
||||||
|
router.get('/auth', csrfProtection, function (req: Request, res: Response) {
|
||||||
|
res.render('auth', {
|
||||||
|
title: 'Login page',
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process given password
|
||||||
|
*/
|
||||||
|
router.post('/auth', parseForm, csrfProtection, async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const userDoc = await Users.get();
|
||||||
|
const passHash = userDoc.passHash;
|
||||||
|
|
||||||
|
if (!passHash) {
|
||||||
|
res.render('auth', {
|
||||||
|
title: 'Login page',
|
||||||
|
header: 'Password not set',
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bcrypt.compare(req.body.password, passHash, async (err, result) => {
|
||||||
|
if (err || result === false) {
|
||||||
|
res.render('auth', {
|
||||||
|
title: 'Login page',
|
||||||
|
header: 'Wrong password',
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign({
|
||||||
|
iss: 'Codex Team',
|
||||||
|
sub: 'auth',
|
||||||
|
iat: Date.now(),
|
||||||
|
}, passHash + config.get('secret'));
|
||||||
|
|
||||||
|
res.cookie('authToken', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
|
||||||
|
});
|
||||||
|
|
||||||
|
res.redirect('/');
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.render('auth', {
|
||||||
|
title: 'Login page',
|
||||||
|
header: 'Password not set',
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -1,9 +1,10 @@
|
||||||
const express = require('express');
|
import express, { Request, Response } from 'express';
|
||||||
const verifyToken = require('./middlewares/token');
|
import verifyToken from './middlewares/token';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
/* GET home page. */
|
/* GET home page. */
|
||||||
router.get('/', verifyToken, async (req, res) => {
|
router.get('/', verifyToken, async (req: Request, res: Response) => {
|
||||||
const config = req.app.locals.config;
|
const config = req.app.locals.config;
|
||||||
|
|
||||||
if (config.startPage) {
|
if (config.startPage) {
|
||||||
|
@ -12,4 +13,4 @@ router.get('/', verifyToken, async (req, res) => {
|
||||||
res.render('pages/index', { isAuthorized: res.locals.isAuthorized });
|
res.render('pages/index', { isAuthorized: res.locals.isAuthorized });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
17
src/backend/routes/index.ts
Normal file
17
src/backend/routes/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import express from 'express';
|
||||||
|
import home from './home';
|
||||||
|
import pages from './pages';
|
||||||
|
import auth from './auth';
|
||||||
|
import aliases from './aliases';
|
||||||
|
import api from './api';
|
||||||
|
import pagesMiddleware from './middlewares/pages';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use('/', pagesMiddleware, home);
|
||||||
|
router.use('/', pagesMiddleware, pages);
|
||||||
|
router.use('/', pagesMiddleware, auth);
|
||||||
|
router.use('/api', api);
|
||||||
|
router.use('/', aliases);
|
||||||
|
|
||||||
|
export default router;
|
16
src/backend/routes/middlewares/locals.ts
Normal file
16
src/backend/routes/middlewares/locals.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for checking locals.isAuthorized property, which allows to edit/create pages
|
||||||
|
*
|
||||||
|
* @param req - request object
|
||||||
|
* @param res - response object
|
||||||
|
* @param next - next function
|
||||||
|
*/
|
||||||
|
export default function allowEdit(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
if (res.locals.isAuthorized) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect('/auth');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
const Pages = require('../../controllers/pages');
|
import { NextFunction, Request, Response } from 'express';
|
||||||
const PagesOrder = require('../../controllers/pagesOrder');
|
import Pages from '../../controllers/pages';
|
||||||
const asyncMiddleware = require('../../utils/asyncMiddleware');
|
import PagesOrder from '../../controllers/pagesOrder';
|
||||||
|
import Page from '../../models/page';
|
||||||
|
import asyncMiddleware from '../../utils/asyncMiddleware';
|
||||||
|
import PageOrder from '../../models/pageOrder';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process one-level pages list to parent-children list
|
* Process one-level pages list to parent-children list
|
||||||
|
@ -8,12 +11,12 @@ const asyncMiddleware = require('../../utils/asyncMiddleware');
|
||||||
* @param {string} parentPageId - parent page id
|
* @param {string} parentPageId - parent page id
|
||||||
* @param {Page[]} pages - list of all available pages
|
* @param {Page[]} pages - list of all available pages
|
||||||
* @param {PagesOrder[]} pagesOrder - list of pages order
|
* @param {PagesOrder[]} pagesOrder - list of pages order
|
||||||
* @param {number} level
|
* @param {number} level - max level recursion
|
||||||
* @param {number} currentLevel
|
* @param {number} currentLevel - current level of element
|
||||||
*
|
*
|
||||||
* @return {Page[]}
|
* @returns {Page[]}
|
||||||
*/
|
*/
|
||||||
function createMenuTree(parentPageId, pages, pagesOrder, level = 1, currentLevel = 1) {
|
function createMenuTree(parentPageId: string, pages: Page[], pagesOrder: PageOrder[], level = 1, currentLevel = 1): Page[] {
|
||||||
const childrenOrder = pagesOrder.find(order => order.data.page === parentPageId);
|
const childrenOrder = pagesOrder.find(order => order.data.page === parentPageId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,16 +24,16 @@ function createMenuTree(parentPageId, pages, pagesOrder, level = 1, currentLevel
|
||||||
* if we got some children order on parents tree, then we push found pages in order sequence
|
* if we got some children order on parents tree, then we push found pages in order sequence
|
||||||
* otherwise just find all pages includes parent tree
|
* otherwise just find all pages includes parent tree
|
||||||
*/
|
*/
|
||||||
let ordered = [];
|
let ordered: any[] = [];
|
||||||
|
|
||||||
if (childrenOrder) {
|
if (childrenOrder) {
|
||||||
ordered = childrenOrder.order.map(pageId => {
|
ordered = childrenOrder.order.map((pageId: string) => {
|
||||||
return pages.find(page => page._id === pageId);
|
return pages.find(page => page._id === pageId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const unordered = pages.filter(page => page._parent === parentPageId);
|
const unordered = pages.filter(page => page._parent === parentPageId);
|
||||||
const branch = [ ...new Set([...ordered, ...unordered]) ];
|
const branch = Array.from(new Set([...ordered, ...unordered]));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* stop recursion when we got the passed max level
|
* stop recursion when we got the passed max level
|
||||||
|
@ -44,20 +47,22 @@ function createMenuTree(parentPageId, pages, pagesOrder, level = 1, currentLevel
|
||||||
*/
|
*/
|
||||||
return branch.filter(page => page && page._id).map(page => {
|
return branch.filter(page => page && page._id).map(page => {
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
children: createMenuTree(page._id, pages, pagesOrder, level, currentLevel + 1)
|
children: createMenuTree(page._id, pages, pagesOrder, level, currentLevel + 1),
|
||||||
}, page.data);
|
}, page.data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware for all /page/... routes
|
* Middleware for all /page/... routes
|
||||||
* @param req
|
*
|
||||||
* @param res
|
* @param {Request} req
|
||||||
* @param next
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
module.exports = asyncMiddleware(async (req, res, next) => {
|
export default asyncMiddleware(async (req: Request, res: Response, next: NextFunction) => {
|
||||||
/**
|
/**
|
||||||
* Pages without parent
|
* Pages without parent
|
||||||
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
const parentIdOfRootPages = '0';
|
const parentIdOfRootPages = '0';
|
38
src/backend/routes/middlewares/token.ts
Normal file
38
src/backend/routes/middlewares/token.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import config from 'config';
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import Users from '../../controllers/users';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for checking jwt token
|
||||||
|
*
|
||||||
|
* @param req - request object
|
||||||
|
* @param res - response object
|
||||||
|
* @param next - next function
|
||||||
|
*/
|
||||||
|
export default async function verifyToken(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const token = req.cookies.authToken;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userDoc = await Users.get();
|
||||||
|
|
||||||
|
if (!userDoc.passHash) {
|
||||||
|
res.locals.isAuthorized = false;
|
||||||
|
next();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedToken = jwt.verify(token, userDoc.passHash + config.get('secret'));
|
||||||
|
|
||||||
|
res.locals.isAuthorized = !!decodedToken;
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
res.locals.isAuthorized = false;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
75
src/backend/routes/pages.ts
Normal file
75
src/backend/routes/pages.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import express, { NextFunction, Request, Response } from 'express';
|
||||||
|
import Pages from '../controllers/pages';
|
||||||
|
import PagesOrder from '../controllers/pagesOrder';
|
||||||
|
import verifyToken from './middlewares/token';
|
||||||
|
import allowEdit from './middlewares/locals';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new page form
|
||||||
|
*/
|
||||||
|
router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const pagesAvailable = await Pages.getAll();
|
||||||
|
|
||||||
|
res.render('pages/form', {
|
||||||
|
pagesAvailable,
|
||||||
|
page: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit page form
|
||||||
|
*/
|
||||||
|
router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const pageId = req.params.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await Pages.get(pageId);
|
||||||
|
const pagesAvailable = await Pages.getAllExceptChildren(pageId);
|
||||||
|
|
||||||
|
if (!page._parent) {
|
||||||
|
throw new Error('Parent not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentsChildrenOrdered = await PagesOrder.getOrderedChildren(pagesAvailable, pageId, page._parent, true);
|
||||||
|
|
||||||
|
res.render('pages/form', {
|
||||||
|
page,
|
||||||
|
parentsChildrenOrdered,
|
||||||
|
pagesAvailable,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View page
|
||||||
|
*/
|
||||||
|
router.get('/page/:id', verifyToken, async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const pageId = req.params.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await Pages.get(pageId);
|
||||||
|
|
||||||
|
const pageParent = await page.parent;
|
||||||
|
|
||||||
|
res.render('pages/page', {
|
||||||
|
page,
|
||||||
|
pageParent,
|
||||||
|
config: req.app.locals.config,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
18
src/backend/utils/asyncMiddleware.ts
Normal file
18
src/backend/utils/asyncMiddleware.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
|
interface InputFunction {
|
||||||
|
(req: Request, res: Response, next: NextFunction): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for making async middlewares for express router
|
||||||
|
*
|
||||||
|
* @param {Function} fn - input function
|
||||||
|
* @returns {function(*=, *=, *=)}
|
||||||
|
*/
|
||||||
|
export default function asyncMiddleware(fn: InputFunction): (req: Request, res: Response, next: NextFunction) => void {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
Promise.resolve(fn(req, res, next))
|
||||||
|
.catch(next);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,14 @@
|
||||||
const crypto = require('crypto');
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} hexStr - input hex string
|
||||||
|
* @returns {string} - output binary string
|
||||||
|
*/
|
||||||
|
function hexToBinary(hexStr: string): string {
|
||||||
|
return (parseInt(hexStr, 16).toString(2))
|
||||||
|
.padStart(8, '0');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create binary md5
|
* Create binary md5
|
||||||
|
@ -6,10 +16,10 @@ const crypto = require('crypto');
|
||||||
* @param stringToHash - string to hash
|
* @param stringToHash - string to hash
|
||||||
* @returns {string} - binary hash of argument
|
* @returns {string} - binary hash of argument
|
||||||
*/
|
*/
|
||||||
function binaryMD5(stringToHash) {
|
export function binaryMD5(stringToHash: string): string {
|
||||||
return crypto.createHash('md5')
|
return hexToBinary(crypto.createHash('md5')
|
||||||
.update(stringToHash)
|
.update(stringToHash)
|
||||||
.digest('binary');
|
.digest('hex'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +27,7 @@ function binaryMD5(stringToHash) {
|
||||||
*
|
*
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
function random16() {
|
export function random16(): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
crypto.randomBytes(16, (err, raw) => {
|
crypto.randomBytes(16, (err, raw) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -29,7 +39,7 @@ function random16() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
binaryMD5,
|
binaryMD5,
|
||||||
random16,
|
random16,
|
||||||
};
|
};
|
|
@ -1,33 +1,58 @@
|
||||||
const pages = require('./pages');
|
import Datastore from 'nedb';
|
||||||
const files = require('./files');
|
import { AliasData } from '../../models/alias';
|
||||||
const password = require('./password');
|
import { FileData } from '../../models/file';
|
||||||
const aliases = require('./aliases');
|
import { PageData } from '../../models/page';
|
||||||
const pagesOrder = require('./pagesOrder');
|
import { PageOrderData } from '../../models/pageOrder';
|
||||||
|
import { UserData } from '../../models/user';
|
||||||
|
import initDb from './initDb';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef Options - optional params
|
||||||
|
* @param {boolean} multi - (false) allows to take action to several documents
|
||||||
|
* @param {boolean} 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} returnUpdatedDocs - (false) if true, returns affected docs
|
||||||
|
*/
|
||||||
|
interface Options {
|
||||||
|
multi?: boolean;
|
||||||
|
upsert?: boolean;
|
||||||
|
returnUpdatedDocs?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResolveFunction {
|
||||||
|
(value: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RejectFunction {
|
||||||
|
(reason?: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Database
|
* @class Database
|
||||||
* @classdesc Simple decorator class to work with nedb datastore
|
* @classdesc Simple decorator class to work with nedb datastore
|
||||||
*
|
*
|
||||||
* @property db - nedb Datastore object
|
* @property {Datastore} db - nedb Datastore object
|
||||||
*/
|
*/
|
||||||
class Database {
|
export class Database<DocType> {
|
||||||
|
private db: Datastore;
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @class
|
||||||
*
|
*
|
||||||
* @param {Object} nedbInstance - nedb Datastore object
|
* @param {Object} nedbInstance - nedb Datastore object
|
||||||
*/
|
*/
|
||||||
constructor(nedbInstance) {
|
constructor(nedbInstance: Datastore) {
|
||||||
this.db = nedbInstance;
|
this.db = nedbInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert new document into the database
|
* Insert new document into the database
|
||||||
|
*
|
||||||
* @see https://github.com/louischatriot/nedb#inserting-documents
|
* @see https://github.com/louischatriot/nedb#inserting-documents
|
||||||
*
|
*
|
||||||
* @param {Object} doc - object to insert
|
* @param {Object} doc - object to insert
|
||||||
* @returns {Promise<Object|Error>} - inserted doc or Error object
|
* @returns {Promise<Object|Error>} - inserted doc or Error object
|
||||||
*/
|
*/
|
||||||
async insert(doc) {
|
public async insert(doc: DocType): Promise<DocType> {
|
||||||
return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => {
|
return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -39,14 +64,15 @@ class Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find documents that match passed query
|
* Find documents that match passed query
|
||||||
|
*
|
||||||
* @see https://github.com/louischatriot/nedb#finding-documents
|
* @see https://github.com/louischatriot/nedb#finding-documents
|
||||||
*
|
*
|
||||||
* @param {Object} query - query object
|
* @param {Object} query - query object
|
||||||
* @param {Object} projection - projection object
|
* @param {Object} projection - projection object
|
||||||
* @returns {Promise<Array<Object>|Error>} - found docs or Error object
|
* @returns {Promise<Array<Object>|Error>} - found docs or Error object
|
||||||
*/
|
*/
|
||||||
async find(query, projection) {
|
public async find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>> {
|
||||||
const cbk = (resolve, reject) => (err, docs) => {
|
const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
@ -65,14 +91,15 @@ class Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find one document matches passed query
|
* Find one document matches passed query
|
||||||
|
*
|
||||||
* @see https://github.com/louischatriot/nedb#finding-documents
|
* @see https://github.com/louischatriot/nedb#finding-documents
|
||||||
*
|
*
|
||||||
* @param {Object} query - query object
|
* @param {Object} query - query object
|
||||||
* @param {Object} projection - projection object
|
* @param {Object} projection - projection object
|
||||||
* @returns {Promise<Object|Error>} - found doc or Error object
|
* @returns {Promise<Object|Error>} - found doc or Error object
|
||||||
*/
|
*/
|
||||||
async findOne(query, projection) {
|
public async findOne(query: Record<string, unknown>, projection?: DocType): Promise<DocType> {
|
||||||
const cbk = (resolve, reject) => (err, doc) => {
|
const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
@ -91,18 +118,15 @@ class Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update document matches query
|
* Update document matches query
|
||||||
|
*
|
||||||
* @see https://github.com/louischatriot/nedb#updating-documents
|
* @see https://github.com/louischatriot/nedb#updating-documents
|
||||||
*
|
*
|
||||||
* @param {Object} query - query object
|
* @param {Object} query - query object
|
||||||
* @param {Object} update - fields to update
|
* @param {Object} update - fields to update
|
||||||
* @param {Object} options
|
* @param {Options} options - optional params
|
||||||
* @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
|
* @returns {Promise<number|Object|Object[]|Error>} - number of updated rows or affected docs or Error object
|
||||||
*/
|
*/
|
||||||
async update(query, update, options = {}) {
|
public async update(query: Record<string, unknown>, update: DocType, options: Options = {}): Promise<number|boolean|Array<DocType>> {
|
||||||
return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => {
|
return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -126,14 +150,14 @@ class Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove document matches passed query
|
* Remove document matches passed query
|
||||||
|
*
|
||||||
* @see https://github.com/louischatriot/nedb#removing-documents
|
* @see https://github.com/louischatriot/nedb#removing-documents
|
||||||
*
|
*
|
||||||
* @param {Object} query - query object
|
* @param {Object} query - query object
|
||||||
* @param {Object} options
|
* @param {Options} options - optional params
|
||||||
* @param {Boolean} options.multi - (false) if true, remove several docs
|
|
||||||
* @returns {Promise<number|Error>} - number of removed rows or Error object
|
* @returns {Promise<number|Error>} - number of removed rows or Error object
|
||||||
*/
|
*/
|
||||||
async remove(query, options = {}) {
|
public async remove(query: Record<string, unknown>, options: Options = {}): Promise<number> {
|
||||||
return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => {
|
return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -144,11 +168,10 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
class: Database,
|
pages: new Database<PageData>(initDb('pages')),
|
||||||
pages: new Database(pages),
|
password: new Database<UserData>(initDb('password')),
|
||||||
password: new Database(password),
|
aliases: new Database<AliasData>(initDb('aliases')),
|
||||||
aliases: new Database(aliases),
|
pagesOrder: new Database<PageOrderData>(initDb('pagesOrder')),
|
||||||
pagesOrder: new Database(pagesOrder),
|
files: new Database<FileData>(initDb('files')),
|
||||||
files: new Database(files)
|
|
||||||
};
|
};
|
16
src/backend/utils/database/initDb.ts
Normal file
16
src/backend/utils/database/initDb.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import Datastore from 'nedb';
|
||||||
|
import config from 'config';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function for nedb instance
|
||||||
|
*
|
||||||
|
* @param {string} name - name of the data file
|
||||||
|
* @returns {Datastore} db - nedb instance
|
||||||
|
*/
|
||||||
|
export default function initDb(name: string): Datastore {
|
||||||
|
return new Datastore({
|
||||||
|
filename: path.resolve(`./${config.get('database')}/${name}.db`),
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,8 +5,13 @@
|
||||||
* @param {object[]} sources
|
* @param {object[]} sources
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
function deepMerge(target, ...sources) {
|
|
||||||
const isObject = item => item && typeof item === 'object' && !Array.isArray(item);
|
/**
|
||||||
|
* @param {Record<string, any>} target - target to merge into
|
||||||
|
* @param {...any[]} sources - sources to merge from
|
||||||
|
*/
|
||||||
|
function deepMerge(target: Record<string, any>, ...sources: any[]): Record<string, unknown> {
|
||||||
|
const isObject = (item: unknown): boolean => !!item && typeof item === 'object' && !Array.isArray(item);
|
||||||
|
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
|
@ -30,6 +35,4 @@ function deepMerge(target, ...sources) {
|
||||||
return deepMerge(target, ...sources);
|
return deepMerge(target, ...sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default deepMerge;
|
||||||
deepMerge,
|
|
||||||
};
|
|
|
@ -1,28 +1,43 @@
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const config = require('../../config');
|
import config from 'config';
|
||||||
const rcPath = path.resolve(__dirname, '../../', config.rcFile || './.codexdocsrc');
|
|
||||||
|
const rcPath = path.resolve(__dirname, '../../../', config.get('rcFile') || './.codexdocsrc');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} menu
|
||||||
|
* @property {string} title - menu option title
|
||||||
|
* @property {string} uri - menu option href
|
||||||
|
*/
|
||||||
|
interface Menu {
|
||||||
|
title: string;
|
||||||
|
uri: string;
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} RCData
|
* @typedef {object} RCData
|
||||||
* @property {string} title - website title
|
* @property {string} title - website title
|
||||||
* @property {object[]} menu - options for website menu
|
* @property {Menu[]} menu - options for website menu
|
||||||
* @property {string} menu[].title - menu option title
|
|
||||||
* @property {string} menu[].uri - menu option href
|
|
||||||
*/
|
*/
|
||||||
|
interface RCData {
|
||||||
|
title: string;
|
||||||
|
menu: Menu[];
|
||||||
|
[key: string]: string | Menu[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class RCParser
|
* @class RCParser
|
||||||
* @classdesc Class to parse runtime configuration file for CodeX Docs engine
|
* @classdesc Class to parse runtime configuration file for CodeX Docs engine
|
||||||
*/
|
*/
|
||||||
module.exports = class RCParser {
|
export default class RCParser {
|
||||||
/**
|
/**
|
||||||
* Default CodeX Docs configuration
|
* Default CodeX Docs configuration
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @returns {{title: string, menu: Array}}
|
* @returns {{title: string, menu: Array}}
|
||||||
*/
|
*/
|
||||||
static get DEFAULTS() {
|
public static get DEFAULTS():RCData {
|
||||||
return {
|
return {
|
||||||
title: 'CodeX Docs',
|
title: 'CodeX Docs',
|
||||||
menu: [],
|
menu: [],
|
||||||
|
@ -35,12 +50,12 @@ module.exports = class RCParser {
|
||||||
* @static
|
* @static
|
||||||
* @returns {{title: string, menu: []}}
|
* @returns {{title: string, menu: []}}
|
||||||
*/
|
*/
|
||||||
static getConfiguration() {
|
public static getConfiguration(): RCData {
|
||||||
if (!fs.existsSync(rcPath)) {
|
if (!fs.existsSync(rcPath)) {
|
||||||
return RCParser.DEFAULTS;
|
return RCParser.DEFAULTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = fs.readFileSync(rcPath, { encoding: 'UTF-8' });
|
const file = fs.readFileSync(rcPath, 'utf-8');
|
||||||
const rConfig = RCParser.DEFAULTS;
|
const rConfig = RCParser.DEFAULTS;
|
||||||
let userConfig;
|
let userConfig;
|
||||||
|
|
||||||
|
@ -63,7 +78,7 @@ module.exports = class RCParser {
|
||||||
rConfig.menu = RCParser.DEFAULTS.menu;
|
rConfig.menu = RCParser.DEFAULTS.menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
rConfig.menu = rConfig.menu.filter((option, i) => {
|
rConfig.menu = rConfig.menu.filter((option: string | Menu, i:number) => {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -92,7 +107,7 @@ module.exports = class RCParser {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
rConfig.menu = rConfig.menu.map(option => {
|
rConfig.menu = rConfig.menu.map((option: string | Menu) => {
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
return {
|
return {
|
||||||
title: option,
|
title: option,
|
||||||
|
@ -106,4 +121,4 @@ module.exports = class RCParser {
|
||||||
|
|
||||||
return rConfig;
|
return rConfig;
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
const translationTable = {
|
interface TransTable {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
const translationTable: TransTable = {
|
||||||
а: 'a',
|
а: 'a',
|
||||||
б: 'b',
|
б: 'b',
|
||||||
в: 'v',
|
в: 'v',
|
||||||
|
@ -73,6 +76,10 @@ const translationTable = {
|
||||||
* @returns {string} - translated string
|
* @returns {string} - translated string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function translateString(string) {
|
/**
|
||||||
|
* @param {string} string - input text to be translated
|
||||||
|
* @returns {string} text - translated text
|
||||||
|
*/
|
||||||
|
export default function translateString(string: string): string {
|
||||||
return string.replace(/[А-яёЁ]/g, (char) => translationTable[char] || char);
|
return string.replace(/[А-яёЁ]/g, (char) => translationTable[char] || char);
|
||||||
};
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* Twig extensions
|
* Twig extensions
|
||||||
*/
|
*/
|
||||||
const twig = require('twig');
|
import twig from 'twig';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const urlify = require('./urlify');
|
import urlify from './urlify';
|
||||||
|
|
||||||
module.exports = (function () {
|
export default (function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function for include svg on page
|
* Function for include svg on page
|
||||||
*
|
*
|
||||||
* @example svg('path/from/root/dir')
|
* @example svg('path/from/root/dir')
|
||||||
* @param filename - name of icon
|
* @param {string} filename - name of icon
|
||||||
* @returns {string} - svg code
|
* @returns {string} - svg code
|
||||||
*/
|
*/
|
||||||
twig.extendFunction('svg', function (filename) {
|
twig.extendFunction('svg', function (filename: string) {
|
||||||
return fs.readFileSync(`${__dirname}/../frontend/svg/${filename}.svg`, 'utf-8');
|
return fs.readFileSync(`./src/frontend/svg/${filename}.svg`, 'utf-8');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ module.exports = (function () {
|
||||||
* @param {string} string - source string with HTML
|
* @param {string} string - source string with HTML
|
||||||
* @returns {string} alias-like string
|
* @returns {string} alias-like string
|
||||||
*/
|
*/
|
||||||
twig.extendFilter('urlify', function (string) {
|
twig.extendFilter('urlify', function (string: string) {
|
||||||
return urlify(string);
|
return urlify(string);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,13 +34,15 @@ module.exports = (function () {
|
||||||
* Parse link as URL object
|
* Parse link as URL object
|
||||||
*
|
*
|
||||||
* @param {string} linkUrl - link to be processed
|
* @param {string} linkUrl - link to be processed
|
||||||
* @returns {UrlWithStringQuery} — url data
|
* @returns {string} url — url data
|
||||||
*/
|
*/
|
||||||
twig.extendFunction('parseLink', function (linkUrl) {
|
twig.extendFunction('parseLink', function (linkUrl: string): string {
|
||||||
try {
|
try {
|
||||||
return new URL(linkUrl);
|
return new URL(linkUrl).toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}());
|
}());
|
|
@ -1,4 +1,4 @@
|
||||||
const translateString = require('./translation');
|
import translateString from './translation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert text to URL-like string
|
* Convert text to URL-like string
|
||||||
|
@ -7,7 +7,7 @@ const translateString = require('./translation');
|
||||||
* @param {string} string - source string with HTML
|
* @param {string} string - source string with HTML
|
||||||
* @returns {string} alias-like string
|
* @returns {string} alias-like string
|
||||||
*/
|
*/
|
||||||
module.exports = function urlify(string) {
|
export default function urlify(string: string): string {
|
||||||
// strip tags
|
// strip tags
|
||||||
string = string.replace(/(<([^>]+)>)/ig, '');
|
string = string.replace(/(<([^>]+)>)/ig, '');
|
||||||
|
|
||||||
|
@ -30,4 +30,4 @@ module.exports = function urlify(string) {
|
||||||
string = translateString(string);
|
string = translateString(string);
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
};
|
}
|
39
bin/www → src/bin/server.ts
Executable file → Normal file
39
bin/www → src/bin/server.ts
Executable file → Normal file
|
@ -1,17 +1,17 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
const app = require('../src/app');
|
import app from '../backend/app';
|
||||||
const debug = require('debug')('codex.editor.docs:server');
|
import http from 'http';
|
||||||
const http = require('http');
|
import config from 'config';
|
||||||
const config = require('../config');
|
import Debug from 'debug';
|
||||||
|
|
||||||
|
const debug = Debug.debug('codex.editor.docs:server');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
*/
|
*/
|
||||||
const port = normalizePort(config.port || '3000');
|
const port = normalizePort(config.get('port') || '3000');
|
||||||
|
|
||||||
app.set('port', port);
|
app.set('port', port);
|
||||||
|
|
||||||
|
@ -29,8 +29,9 @@ server.on('listening', onListening);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a port into a number, string, or false.
|
* Normalize a port into a number, string, or false.
|
||||||
|
* @param val
|
||||||
*/
|
*/
|
||||||
function normalizePort(val) {
|
function normalizePort(val: string): number | string | false {
|
||||||
const value = parseInt(val, 10);
|
const value = parseInt(val, 10);
|
||||||
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
|
@ -47,9 +48,10 @@ function normalizePort(val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event listener for HTTP server "error" event.
|
* Event listener for HTTP server 'error' event.
|
||||||
|
* @param error
|
||||||
*/
|
*/
|
||||||
function onError(error) {
|
function onError(error: NodeJS.ErrnoException): void {
|
||||||
if (error.syscall !== 'listen') {
|
if (error.syscall !== 'listen') {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -63,19 +65,27 @@ function onError(error) {
|
||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
console.error(bind + ' requires elevated privileges');
|
console.error(bind + ' requires elevated privileges');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
break;
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
console.error(bind + ' is already in use');
|
console.error(bind + ' is already in use');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event listener for HTTP server "listening" event.
|
* Event listener for HTTP server 'listening' event.
|
||||||
*/
|
*/
|
||||||
function onListening() {
|
function onListening(): void {
|
||||||
const addr = server.address();
|
const addr = server.address();
|
||||||
|
|
||||||
|
if (addr === null) {
|
||||||
|
debug('Address not found');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const bind = typeof addr === 'string'
|
const bind = typeof addr === 'string'
|
||||||
? 'pipe ' + addr
|
? 'pipe ' + addr
|
||||||
: 'port ' + addr.port;
|
: 'port ' + addr.port;
|
||||||
|
@ -83,4 +93,7 @@ function onListening() {
|
||||||
debug('Listening on ' + bind);
|
debug('Listening on ' + bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {server, app};
|
export default {
|
||||||
|
server,
|
||||||
|
app,
|
||||||
|
};
|
|
@ -1,20 +0,0 @@
|
||||||
const Model = require('../models/user');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Users
|
|
||||||
* @classdesc Users controller
|
|
||||||
*/
|
|
||||||
class Users {
|
|
||||||
/**
|
|
||||||
* Find and return user model.
|
|
||||||
*
|
|
||||||
* @returns {Promise<User>}
|
|
||||||
*/
|
|
||||||
static async get() {
|
|
||||||
const userDoc = await Model.get();
|
|
||||||
|
|
||||||
return userDoc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Users;
|
|
|
@ -52,11 +52,16 @@ export default class Writing {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.onbeforeunload = (e) => {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate form elements
|
* Activate form elements
|
||||||
*/
|
*/
|
||||||
this.nodes.saveButton = moduleEl.querySelector('[name="js-submit-save"]');
|
this.nodes.saveButton = moduleEl.querySelector('[name="js-submit-save"]');
|
||||||
this.nodes.saveButton.addEventListener('click', () => {
|
this.nodes.saveButton.addEventListener('click', () => {
|
||||||
|
window.onbeforeunload = null;
|
||||||
this.saveButtonClicked();
|
this.saveButtonClicked();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,7 +74,7 @@ export default class Writing {
|
||||||
if (!isUserAgree) {
|
if (!isUserAgree) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
window.onbeforeunload = null;
|
||||||
this.removeButtonClicked();
|
this.removeButtonClicked();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
let { password: db } = require('./src/utils/database');
|
import database from './backend/utils/database';
|
||||||
const program = require('commander');
|
import commander from 'commander';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
const bcrypt = require('bcrypt');
|
const db = database['password'];
|
||||||
|
const program = commander.program;
|
||||||
const saltRounds = 12;
|
const saltRounds = 12;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Script for generating password, that will be used to create and edit pages in CodeX.Docs.
|
* Script for generating password, that will be used to create and edit pages in CodeX.Docs.
|
||||||
* Hashes password with bcrypt and inserts it to the database.
|
* Hashes password with bcrypt and inserts it to the database.
|
||||||
|
*
|
||||||
* @see {https://github.com/tj/commander.js | CommanderJS}
|
* @see {https://github.com/tj/commander.js | CommanderJS}
|
||||||
*/
|
*/
|
||||||
program
|
program
|
||||||
|
@ -23,7 +26,7 @@ program
|
||||||
|
|
||||||
const userDoc = { passHash: hash };
|
const userDoc = { passHash: hash };
|
||||||
|
|
||||||
await db.remove({}, {multi: true});
|
await db.remove({}, { multi: true });
|
||||||
await db.insert(userDoc);
|
await db.insert(userDoc);
|
||||||
|
|
||||||
console.log('Password was successfully generated');
|
console.log('Password was successfully generated');
|
|
@ -1,36 +0,0 @@
|
||||||
const { password: db } = require('../utils/database/index');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class User
|
|
||||||
* @class User model
|
|
||||||
*
|
|
||||||
* @property {string} passHash - hashed password
|
|
||||||
*/
|
|
||||||
class User {
|
|
||||||
/**
|
|
||||||
* Find and return model of user.
|
|
||||||
* User is only one.
|
|
||||||
*
|
|
||||||
* @returns {Promise<User>}
|
|
||||||
*/
|
|
||||||
static async get() {
|
|
||||||
const data = await db.findOne({});
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new User(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
*
|
|
||||||
* @param {object} userData
|
|
||||||
*/
|
|
||||||
constructor(userData) {
|
|
||||||
this.passHash = userData.passHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = User;
|
|
|
@ -1,12 +0,0 @@
|
||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const pagesAPI = require('./pages');
|
|
||||||
const transportAPI = require('./transport');
|
|
||||||
const linksAPI = require('./links');
|
|
||||||
|
|
||||||
router.use('/', pagesAPI);
|
|
||||||
router.use('/', transportAPI);
|
|
||||||
router.use('/', linksAPI);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
|
@ -1,38 +0,0 @@
|
||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
const ogs = require('open-graph-scraper');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept file url to fetch
|
|
||||||
*/
|
|
||||||
router.get('/fetchUrl', async (req, res) => {
|
|
||||||
const response = {
|
|
||||||
success: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!req.query.url) {
|
|
||||||
res.status(400).json(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const linkData = (await ogs({ url: req.query.url })).result;
|
|
||||||
|
|
||||||
response.success = 1;
|
|
||||||
response.meta = {
|
|
||||||
title: linkData.ogTitle,
|
|
||||||
description: linkData.ogDescription,
|
|
||||||
site_name: linkData.ogSiteName,
|
|
||||||
image: {
|
|
||||||
url: linkData.ogImage.url
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
res.status(200).json(response);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
res.status(500).json(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
|
@ -1,63 +0,0 @@
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
const express = require('express');
|
|
||||||
const jwt = require('jsonwebtoken');
|
|
||||||
const router = express.Router();
|
|
||||||
const Users = require('../controllers/users');
|
|
||||||
const config = require('../../config/index');
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const csrf = require('csurf');
|
|
||||||
const csrfProtection = csrf({ cookie: true });
|
|
||||||
const parseForm = express.urlencoded({ extended: false });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorization page
|
|
||||||
*/
|
|
||||||
router.get('/auth', csrfProtection, function (req, res) {
|
|
||||||
res.render('auth', {
|
|
||||||
title: 'Login page',
|
|
||||||
csrfToken: req.csrfToken(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process given password
|
|
||||||
*/
|
|
||||||
router.post('/auth', parseForm, csrfProtection, async (req, res) => {
|
|
||||||
const userDoc = await Users.get();
|
|
||||||
|
|
||||||
if (!userDoc) {
|
|
||||||
res.render('auth', {
|
|
||||||
title: 'Login page',
|
|
||||||
header: 'Password not set',
|
|
||||||
csrfToken: req.csrfToken(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const passHash = userDoc.passHash;
|
|
||||||
|
|
||||||
bcrypt.compare(req.body.password, passHash, async (err, result) => {
|
|
||||||
if (err || result === false) {
|
|
||||||
res.render('auth', {
|
|
||||||
title: 'Login page',
|
|
||||||
header: 'Wrong password',
|
|
||||||
csrfToken: req.csrfToken(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = jwt.sign({
|
|
||||||
iss: 'Codex Team',
|
|
||||||
sub: 'auth',
|
|
||||||
iat: Date.now(),
|
|
||||||
}, passHash + config.secret);
|
|
||||||
|
|
||||||
res.cookie('authToken', token, {
|
|
||||||
httpOnly: true,
|
|
||||||
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
|
|
||||||
});
|
|
||||||
|
|
||||||
res.redirect('/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
|
@ -1,18 +0,0 @@
|
||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const home = require('./home');
|
|
||||||
const pages = require('./pages');
|
|
||||||
const auth = require('./auth');
|
|
||||||
const aliases = require('./aliases');
|
|
||||||
const api = require('./api');
|
|
||||||
|
|
||||||
const pagesMiddleware = require('./middlewares/pages');
|
|
||||||
|
|
||||||
router.use('/', pagesMiddleware, home);
|
|
||||||
router.use('/', pagesMiddleware, pages);
|
|
||||||
router.use('/', pagesMiddleware, auth);
|
|
||||||
router.use('/api', api);
|
|
||||||
router.use('/', aliases);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* Middleware for checking locals.isAuthorized property, which allows to edit/create pages
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @param next
|
|
||||||
*/
|
|
||||||
module.exports = function allowEdit(req, res, next) {
|
|
||||||
if (res.locals.isAuthorized) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.redirect('/auth');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
require('dotenv').config();
|
|
||||||
const config = require('../../../config/index');
|
|
||||||
const jwt = require('jsonwebtoken');
|
|
||||||
const Users = require('../../controllers/users');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware for checking jwt token
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @param next
|
|
||||||
*/
|
|
||||||
module.exports = async function verifyToken(req, res, next) {
|
|
||||||
let token = req.cookies.authToken;
|
|
||||||
const userDoc = await Users.get();
|
|
||||||
|
|
||||||
if (!userDoc) {
|
|
||||||
res.locals.isAuthorized = false;
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt.verify(token, userDoc.passHash + config.secret, (err, decodedToken) => {
|
|
||||||
res.locals.isAuthorized = !(err || !decodedToken);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,65 +0,0 @@
|
||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
const Pages = require('../controllers/pages');
|
|
||||||
const PagesOrder = require('../controllers/pagesOrder');
|
|
||||||
|
|
||||||
const verifyToken = require('./middlewares/token');
|
|
||||||
const allowEdit = require('./middlewares/locals');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new page form
|
|
||||||
*/
|
|
||||||
router.get('/page/new', verifyToken, allowEdit, async (req, res, next) => {
|
|
||||||
const pagesAvailable = await Pages.getAll();
|
|
||||||
|
|
||||||
res.render('pages/form', {
|
|
||||||
pagesAvailable,
|
|
||||||
page: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit page form
|
|
||||||
*/
|
|
||||||
router.get('/page/edit/:id', verifyToken, allowEdit, async (req, res, next) => {
|
|
||||||
const pageId = req.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const page = await Pages.get(pageId);
|
|
||||||
const pagesAvailable = await Pages.getAllExceptChildren(pageId);
|
|
||||||
const parentsChildrenOrdered = await PagesOrder.getOrderedChildren(pagesAvailable, pageId, page._parent, true);
|
|
||||||
|
|
||||||
res.render('pages/form', {
|
|
||||||
page,
|
|
||||||
parentsChildrenOrdered,
|
|
||||||
pagesAvailable,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
res.status(404);
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View page
|
|
||||||
*/
|
|
||||||
router.get('/page/:id', verifyToken, async (req, res, next) => {
|
|
||||||
const pageId = req.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const page = await Pages.get(pageId);
|
|
||||||
|
|
||||||
const pageParent = await page.parent;
|
|
||||||
|
|
||||||
res.render('pages/page', {
|
|
||||||
page,
|
|
||||||
pageParent,
|
|
||||||
config: req.app.locals.config,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
res.status(404);
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
196
src/test/database.ts
Normal file
196
src/test/database.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import config from 'config';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import Datastore from 'nedb';
|
||||||
|
|
||||||
|
import { Database } from '../backend/utils/database';
|
||||||
|
|
||||||
|
interface Document {
|
||||||
|
data?: any;
|
||||||
|
_id?: string;
|
||||||
|
update?: boolean;
|
||||||
|
no?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Database', () => {
|
||||||
|
const pathToDB = `./${config.get('database')}/test.db`;
|
||||||
|
let nedbInstance;
|
||||||
|
let db: Database<any>;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
if (fs.existsSync(pathToDB)) {
|
||||||
|
fs.unlinkSync(pathToDB);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Creating db instance', async () => {
|
||||||
|
nedbInstance = new Datastore({ filename: pathToDB, autoload: true });
|
||||||
|
db = new Database(nedbInstance);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Inserting document', async () => {
|
||||||
|
const data = 'Text data';
|
||||||
|
|
||||||
|
const insertedDoc = await db.insert({ data }) as Document;
|
||||||
|
|
||||||
|
expect(insertedDoc).to.be.a('object');
|
||||||
|
expect(insertedDoc.data).to.equal(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Finding document', async () => {
|
||||||
|
const data = 'Text data';
|
||||||
|
|
||||||
|
const insertedDoc = await db.insert({ data }) as Document;
|
||||||
|
|
||||||
|
expect(insertedDoc).to.be.a('object');
|
||||||
|
expect(insertedDoc.data).to.equal(data);
|
||||||
|
|
||||||
|
const foundDoc = await db.findOne({ _id: insertedDoc._id }) as Document;
|
||||||
|
|
||||||
|
expect(foundDoc).not.be.null;
|
||||||
|
expect(foundDoc._id).to.equal(insertedDoc._id);
|
||||||
|
expect(foundDoc.data).to.equal(data);
|
||||||
|
|
||||||
|
const projectedDoc = await db.findOne({ _id: insertedDoc._id }, { data: 1, _id: 0 });
|
||||||
|
|
||||||
|
expect(Object.keys(projectedDoc).length).to.equal(1);
|
||||||
|
expect(Object.keys(projectedDoc).pop()).to.equal('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updating document', async () => {
|
||||||
|
const data = 'Text data';
|
||||||
|
|
||||||
|
const insertedDoc = await db.insert({ data }) as Document;
|
||||||
|
|
||||||
|
expect(insertedDoc).to.be.a('object');
|
||||||
|
expect(insertedDoc.data).to.equal(data);
|
||||||
|
|
||||||
|
const updatedData = 'Updated text data';
|
||||||
|
|
||||||
|
await db.update({ _id: insertedDoc._id }, { data: updatedData });
|
||||||
|
|
||||||
|
const updatedDoc = await db.findOne({ _id: insertedDoc._id }) as Document;
|
||||||
|
|
||||||
|
expect(updatedDoc).not.be.null;
|
||||||
|
expect(updatedDoc.data).not.equal(data);
|
||||||
|
expect(updatedDoc.data).to.equal(updatedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updating documents with options', async () => {
|
||||||
|
const data = {
|
||||||
|
update: true,
|
||||||
|
data: 'Text data',
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.insert(data);
|
||||||
|
await db.insert(data);
|
||||||
|
|
||||||
|
let numberOfUpdatedDocs = await db.update({ update: true }, { $set: { data: 'First update' } }, { multi: true });
|
||||||
|
|
||||||
|
expect(numberOfUpdatedDocs).to.equal(2);
|
||||||
|
|
||||||
|
const affectedDocs = await db.update(
|
||||||
|
{ update: true },
|
||||||
|
{ $set: { data: 'Second update' } },
|
||||||
|
{
|
||||||
|
multi: true,
|
||||||
|
returnUpdatedDocs: true,
|
||||||
|
}
|
||||||
|
) as Array<Document>;
|
||||||
|
|
||||||
|
expect(affectedDocs).to.be.a('array');
|
||||||
|
affectedDocs.forEach((doc: Document) => {
|
||||||
|
expect(doc.data).to.equal('Second update');
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsertedDoc = await db.update({ update: true, data: 'First update' }, { $set: { data: 'Third update' } }, { upsert: true }) as Document;
|
||||||
|
|
||||||
|
expect(upsertedDoc.update).to.be.true;
|
||||||
|
expect(upsertedDoc.data).to.equal('Third update');
|
||||||
|
|
||||||
|
numberOfUpdatedDocs = await db.update({ data: 'Third update' }, { $set: { data: 'Fourth update' } }, { upsert: true });
|
||||||
|
|
||||||
|
expect(numberOfUpdatedDocs).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Finding documents', async () => {
|
||||||
|
const data1 = 'Text data 1';
|
||||||
|
const data2 = 'Text data 2';
|
||||||
|
|
||||||
|
const insertedDoc1 = await db.insert({ data: data1, flag: true, no: 1 }) as Document;
|
||||||
|
const insertedDoc2 = await db.insert({ data: data2, flag: true, no: 2 }) as Document;
|
||||||
|
|
||||||
|
const foundDocs = await db.find({ flag: true }) as Array<Document>;
|
||||||
|
|
||||||
|
expect(foundDocs).to.be.a('array');
|
||||||
|
expect(foundDocs.length).to.equal(2);
|
||||||
|
|
||||||
|
foundDocs.sort(({ no: a }, { no: b }) => a - b);
|
||||||
|
|
||||||
|
expect(foundDocs[0]._id).to.equal(insertedDoc1._id);
|
||||||
|
expect(foundDocs[0].data).to.equal(insertedDoc1.data);
|
||||||
|
expect(foundDocs[1]._id).to.equal(insertedDoc2._id);
|
||||||
|
expect(foundDocs[1].data).to.equal(insertedDoc2.data);
|
||||||
|
|
||||||
|
const projectedDocs = await db.find({ flag: true }, { no: 1, _id: 0 }) as Array<Document>;
|
||||||
|
|
||||||
|
expect(projectedDocs.length).to.equal(2);
|
||||||
|
projectedDocs.forEach(data => {
|
||||||
|
expect(Object.keys(data).length).to.equal(1);
|
||||||
|
expect(Object.keys(data).pop()).to.equal('no');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Removing document', async () => {
|
||||||
|
const data = 'Text data';
|
||||||
|
|
||||||
|
const insertedDoc = await db.insert({ data }) as Document;
|
||||||
|
|
||||||
|
expect(insertedDoc).to.be.a('object');
|
||||||
|
expect(insertedDoc.data).to.equal(data);
|
||||||
|
|
||||||
|
await db.remove({ _id: insertedDoc._id });
|
||||||
|
|
||||||
|
const deletedDoc = await db.findOne({ _id: insertedDoc._id });
|
||||||
|
|
||||||
|
expect(deletedDoc).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test invalid database queries', async () => {
|
||||||
|
try {
|
||||||
|
await db.insert({});
|
||||||
|
} catch (err) {
|
||||||
|
expect((err as Error).message).to.equal('Cannot read property \'_id\' of undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.find({ size: { $invalidComparator: 1 } });
|
||||||
|
} catch (err) {
|
||||||
|
expect((err as Error).message).to.equal('Unknown comparison function $invalidComparator');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.findOne({ field: { $invalidComparator: 1 } });
|
||||||
|
} catch (err) {
|
||||||
|
expect((err as Error).message).to.equal('Unknown comparison function $invalidComparator');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.update({ field: { $undefinedComparator: 1 } }, {});
|
||||||
|
} catch (err) {
|
||||||
|
expect((err as Error).message).to.equal('Unknown comparison function $undefinedComparator');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.remove({ field: { $undefinedComparator: 1 } });
|
||||||
|
} catch (err) {
|
||||||
|
expect((err as Error).message).to.equal('Unknown comparison function $undefinedComparator');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
if (fs.existsSync(pathToDB)) {
|
||||||
|
fs.unlinkSync(pathToDB);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,13 +1,15 @@
|
||||||
const { app } = require('../bin/www');
|
import chaiHTTP from 'chai-http';
|
||||||
const chai = require('chai');
|
import chai, { expect } from 'chai';
|
||||||
const chaiHTTP = require('chai-http');
|
|
||||||
const { expect } = chai;
|
import server from '../bin/server';
|
||||||
|
|
||||||
|
const app = server.app;
|
||||||
|
|
||||||
chai.use(chaiHTTP);
|
chai.use(chaiHTTP);
|
||||||
|
|
||||||
describe('Express app', () => {
|
describe('Express app', () => {
|
||||||
it('App is available', async () => {
|
it('App is available', async () => {
|
||||||
let agent = chai.request.agent(app);
|
const agent = chai.request.agent(app);
|
||||||
|
|
||||||
const result = await agent
|
const result = await agent
|
||||||
.get('/');
|
.get('/');
|
|
@ -1,14 +1,16 @@
|
||||||
const {expect} = require('chai');
|
import { expect } from 'chai';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const config = require('../../config');
|
import config from 'config';
|
||||||
const Alias = require('../../src/models/alias');
|
import Alias from '../../backend/models/alias';
|
||||||
const {binaryMD5} = require('../../src/utils/crypto');
|
import { binaryMD5 } from '../../backend/utils/crypto';
|
||||||
const {aliases} = require('../../src/utils/database');
|
import database from '../../backend/utils/database';
|
||||||
|
|
||||||
|
const aliases = database['aliases'];
|
||||||
|
|
||||||
describe('Alias model', () => {
|
describe('Alias model', () => {
|
||||||
after(() => {
|
after(() => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './aliases.db');
|
const pathToDB = path.resolve(__dirname, '../../../', config.get('database'), './aliases.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
|
@ -108,7 +110,7 @@ describe('Alias model', () => {
|
||||||
expect(savedAlias.id).to.equal(initialData.id);
|
expect(savedAlias.id).to.equal(initialData.id);
|
||||||
expect(savedAlias.deprecated).to.equal(false);
|
expect(savedAlias.deprecated).to.equal(false);
|
||||||
|
|
||||||
const insertedAlias = await aliases.findOne({_id: savedAlias._id});
|
const insertedAlias = await aliases.findOne({_id: savedAlias._id}) as Alias;
|
||||||
|
|
||||||
expect(insertedAlias._id).to.equal(savedAlias._id);
|
expect(insertedAlias._id).to.equal(savedAlias._id);
|
||||||
expect(insertedAlias.hash).to.equal(savedAlias.hash);
|
expect(insertedAlias.hash).to.equal(savedAlias.hash);
|
||||||
|
@ -128,7 +130,7 @@ describe('Alias model', () => {
|
||||||
|
|
||||||
expect(alias._id).to.equal(insertedAlias._id);
|
expect(alias._id).to.equal(insertedAlias._id);
|
||||||
|
|
||||||
const updatedAlias = await aliases.findOne({_id: alias._id});
|
const updatedAlias = await aliases.findOne({_id: alias._id}) as Alias;
|
||||||
|
|
||||||
expect(updatedAlias._id).to.equal(savedAlias._id);
|
expect(updatedAlias._id).to.equal(savedAlias._id);
|
||||||
expect(updatedAlias.hash).to.equal(updateData.hash);
|
expect(updatedAlias.hash).to.equal(updateData.hash);
|
|
@ -1,14 +1,16 @@
|
||||||
const {expect} = require('chai');
|
import { expect } from 'chai';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const config = require('../../config');
|
import config from 'config';
|
||||||
const File = require('../../src/models/file');
|
import File from '../../backend/models/file';
|
||||||
const {files} = require('../../src/utils/database');
|
import database from '../../backend/utils/database';
|
||||||
|
|
||||||
|
const files = database['files'];
|
||||||
|
|
||||||
describe('File model', () => {
|
describe('File model', () => {
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './files.db');
|
const pathToDB = path.resolve(__dirname, '../../../', config.get('database'), './files.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
|
@ -20,7 +22,7 @@ describe('File model', () => {
|
||||||
|
|
||||||
expect(file.data).to.be.a('object');
|
expect(file.data).to.be.a('object');
|
||||||
|
|
||||||
let {data} = file;
|
let { data } = file;
|
||||||
|
|
||||||
expect(data._id).to.be.undefined;
|
expect(data._id).to.be.undefined;
|
||||||
expect(data.name).to.be.undefined;
|
expect(data.name).to.be.undefined;
|
||||||
|
@ -29,7 +31,7 @@ describe('File model', () => {
|
||||||
expect(data.size).to.be.undefined;
|
expect(data.size).to.be.undefined;
|
||||||
expect(data.mimetype).to.be.undefined;
|
expect(data.mimetype).to.be.undefined;
|
||||||
|
|
||||||
file = new File(null);
|
file = new File();
|
||||||
|
|
||||||
data = file.data;
|
data = file.data;
|
||||||
|
|
||||||
|
@ -51,8 +53,6 @@ describe('File model', () => {
|
||||||
|
|
||||||
file = new File(initialData);
|
file = new File(initialData);
|
||||||
|
|
||||||
const json = file.toJSON();
|
|
||||||
|
|
||||||
data = file.data;
|
data = file.data;
|
||||||
|
|
||||||
expect(data._id).to.equal(initialData._id);
|
expect(data._id).to.equal(initialData._id);
|
||||||
|
@ -63,7 +63,7 @@ describe('File model', () => {
|
||||||
expect(data.mimetype).to.equal(initialData.mimetype);
|
expect(data.mimetype).to.equal(initialData.mimetype);
|
||||||
|
|
||||||
const update = {
|
const update = {
|
||||||
_id: 12345,
|
_id: '12345',
|
||||||
name: 'updated filename',
|
name: 'updated filename',
|
||||||
filename: 'updated randomname',
|
filename: 'updated randomname',
|
||||||
path: '/uploads/updated randomname',
|
path: '/uploads/updated randomname',
|
||||||
|
@ -94,7 +94,7 @@ describe('File model', () => {
|
||||||
|
|
||||||
const file = new File(initialData);
|
const file = new File(initialData);
|
||||||
|
|
||||||
let savedFile = await file.save();
|
const savedFile = await file.save();
|
||||||
|
|
||||||
expect(savedFile._id).not.be.undefined;
|
expect(savedFile._id).not.be.undefined;
|
||||||
expect(savedFile.name).to.equal(initialData.name);
|
expect(savedFile.name).to.equal(initialData.name);
|
||||||
|
@ -103,7 +103,7 @@ describe('File model', () => {
|
||||||
expect(savedFile.size).to.equal(initialData.size);
|
expect(savedFile.size).to.equal(initialData.size);
|
||||||
expect(savedFile.mimetype).to.equal(initialData.mimetype);
|
expect(savedFile.mimetype).to.equal(initialData.mimetype);
|
||||||
|
|
||||||
const insertedFile = await files.findOne({_id: file._id});
|
const insertedFile = await files.findOne({ _id: file._id });
|
||||||
|
|
||||||
expect(insertedFile._id).to.equal(file._id);
|
expect(insertedFile._id).to.equal(file._id);
|
||||||
expect(insertedFile.name).to.equal(file.name);
|
expect(insertedFile.name).to.equal(file.name);
|
||||||
|
@ -113,7 +113,7 @@ describe('File model', () => {
|
||||||
expect(insertedFile.mimetype).to.equal(file.mimetype);
|
expect(insertedFile.mimetype).to.equal(file.mimetype);
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
_id: 12345,
|
_id: '12345',
|
||||||
name: 'updated filename',
|
name: 'updated filename',
|
||||||
filename: 'updated randomname',
|
filename: 'updated randomname',
|
||||||
path: '/uploads/updated randomname',
|
path: '/uploads/updated randomname',
|
||||||
|
@ -126,7 +126,7 @@ describe('File model', () => {
|
||||||
|
|
||||||
expect(file._id).to.equal(insertedFile._id);
|
expect(file._id).to.equal(insertedFile._id);
|
||||||
|
|
||||||
const updatedFile = await files.findOne({_id: file._id});
|
const updatedFile = await files.findOne({ _id: file._id });
|
||||||
|
|
||||||
expect(updatedFile._id).to.equal(savedFile._id);
|
expect(updatedFile._id).to.equal(savedFile._id);
|
||||||
expect(updatedFile.name).to.equal(updateData.name);
|
expect(updatedFile.name).to.equal(updateData.name);
|
||||||
|
@ -139,7 +139,7 @@ describe('File model', () => {
|
||||||
|
|
||||||
expect(file._id).to.be.undefined;
|
expect(file._id).to.be.undefined;
|
||||||
|
|
||||||
const removedFile = await files.findOne({_id: updatedFile._id});
|
const removedFile = await files.findOne({ _id: updatedFile._id });
|
||||||
|
|
||||||
expect(removedFile).to.be.null;
|
expect(removedFile).to.be.null;
|
||||||
});
|
});
|
||||||
|
@ -157,16 +157,18 @@ describe('File model', () => {
|
||||||
|
|
||||||
const savedFile = await file.save();
|
const savedFile = await file.save();
|
||||||
|
|
||||||
const foundFile = await File.get(savedFile._id);
|
if (savedFile._id !== undefined){
|
||||||
|
const foundFile = await File.get(savedFile._id);
|
||||||
|
|
||||||
const {data} = foundFile;
|
const { data } = foundFile;
|
||||||
|
|
||||||
expect(data._id).to.equal(savedFile._id);
|
expect(data._id).to.equal(savedFile._id);
|
||||||
expect(data.name).to.equal(savedFile.name);
|
expect(data.name).to.equal(savedFile.name);
|
||||||
expect(data.filename).to.equal(savedFile.filename);
|
expect(data.filename).to.equal(savedFile.filename);
|
||||||
expect(data.path).to.equal(savedFile.path);
|
expect(data.path).to.equal(savedFile.path);
|
||||||
expect(data.size).to.equal(savedFile.size);
|
expect(data.size).to.equal(savedFile.size);
|
||||||
expect(data.mimetype).to.equal(savedFile.mimetype);
|
expect(data.mimetype).to.equal(savedFile.mimetype);
|
||||||
|
}
|
||||||
|
|
||||||
await file.destroy();
|
await file.destroy();
|
||||||
});
|
});
|
||||||
|
@ -184,16 +186,18 @@ describe('File model', () => {
|
||||||
|
|
||||||
const savedFile = await file.save();
|
const savedFile = await file.save();
|
||||||
|
|
||||||
const foundFile = await File.getByFilename(savedFile.filename);
|
if (savedFile.filename !== undefined){
|
||||||
|
const foundFile = await File.getByFilename(savedFile.filename);
|
||||||
|
|
||||||
const {data} = foundFile;
|
const { data } = foundFile;
|
||||||
|
|
||||||
expect(data._id).to.equal(savedFile._id);
|
expect(data._id).to.equal(savedFile._id);
|
||||||
expect(data.name).to.equal(savedFile.name);
|
expect(data.name).to.equal(savedFile.name);
|
||||||
expect(data.filename).to.equal(savedFile.filename);
|
expect(data.filename).to.equal(savedFile.filename);
|
||||||
expect(data.path).to.equal(savedFile.path);
|
expect(data.path).to.equal(savedFile.path);
|
||||||
expect(data.size).to.equal(savedFile.size);
|
expect(data.size).to.equal(savedFile.size);
|
||||||
expect(data.mimetype).to.equal(savedFile.mimetype);
|
expect(data.mimetype).to.equal(savedFile.mimetype);
|
||||||
|
}
|
||||||
|
|
||||||
await file.destroy();
|
await file.destroy();
|
||||||
});
|
});
|
||||||
|
@ -218,7 +222,7 @@ describe('File model', () => {
|
||||||
|
|
||||||
const savedFiles = await Promise.all(filesToSave.map(file => file.save()));
|
const savedFiles = await Promise.all(filesToSave.map(file => file.save()));
|
||||||
|
|
||||||
const foundFiles = await File.getAll({_id: {$in: savedFiles.map(file => file._id)}});
|
const foundFiles = await File.getAll({ _id: { $in: savedFiles.map(file => file._id) } });
|
||||||
|
|
||||||
expect(foundFiles.length).to.equal(2);
|
expect(foundFiles.length).to.equal(2);
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
const {expect} = require('chai');
|
import { expect } from 'chai';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const config = require('../../config');
|
import config from 'config';
|
||||||
const Page = require('../../src/models/page');
|
import Page from '../../backend/models/page';
|
||||||
const {pages} = require('../../src/utils/database');
|
import translateString from '../../backend/utils/translation';
|
||||||
const translateString = require('../../src/utils/translation');
|
import database from '../../backend/utils/database';
|
||||||
|
|
||||||
|
const pages = database['pages'];
|
||||||
|
|
||||||
describe('Page model', () => {
|
describe('Page model', () => {
|
||||||
|
const transformToUri = (text: string): string => {
|
||||||
const transformToUri = (string) => {
|
return translateString(text
|
||||||
return translateString(string
|
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, ' ')
|
||||||
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
||||||
.replace(/ +/g, ' ')
|
.replace(/ +/g, ' ')
|
||||||
|
@ -20,7 +21,7 @@ describe('Page model', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './pages.db');
|
const pathToDB = path.resolve(__dirname, '../../../', config.get('database'), './pages.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
|
@ -40,7 +41,7 @@ describe('Page model', () => {
|
||||||
expect(data.body).to.be.undefined;
|
expect(data.body).to.be.undefined;
|
||||||
expect(data.parent).to.be.equal('0');
|
expect(data.parent).to.be.equal('0');
|
||||||
|
|
||||||
page = new Page(null);
|
page = new Page();
|
||||||
|
|
||||||
data = page.data;
|
data = page.data;
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ describe('Page model', () => {
|
||||||
expect(json.parent).to.be.equal('0');
|
expect(json.parent).to.be.equal('0');
|
||||||
|
|
||||||
const update = {
|
const update = {
|
||||||
_id: 12345,
|
_id: '12345',
|
||||||
body: {
|
body: {
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
|
@ -122,7 +123,7 @@ describe('Page model', () => {
|
||||||
};
|
};
|
||||||
const page = new Page(initialData);
|
const page = new Page(initialData);
|
||||||
|
|
||||||
let savedPage = await page.save();
|
const savedPage = await page.save();
|
||||||
|
|
||||||
expect(savedPage._id).not.be.undefined;
|
expect(savedPage._id).not.be.undefined;
|
||||||
expect(savedPage.title).to.equal(initialData.body.blocks[0].data.text);
|
expect(savedPage.title).to.equal(initialData.body.blocks[0].data.text);
|
||||||
|
@ -188,7 +189,7 @@ describe('Page model', () => {
|
||||||
const firstPage = new Page(initialData);
|
const firstPage = new Page(initialData);
|
||||||
let firstSavedPage = await firstPage.save();
|
let firstSavedPage = await firstPage.save();
|
||||||
const secondPage = new Page(initialData);
|
const secondPage = new Page(initialData);
|
||||||
let secondSavedPage = await secondPage.save();
|
const secondSavedPage = await secondPage.save();
|
||||||
|
|
||||||
expect(secondSavedPage.uri).to.equal(transformToUri(initialData.body.blocks[0].data.text) + '-1');
|
expect(secondSavedPage.uri).to.equal(transformToUri(initialData.body.blocks[0].data.text) + '-1');
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ describe('Page model', () => {
|
||||||
expect(firstSavedPage.uri).to.equal(newUri);
|
expect(firstSavedPage.uri).to.equal(newUri);
|
||||||
|
|
||||||
const thirdPage = new Page(initialData);
|
const thirdPage = new Page(initialData);
|
||||||
let thirdSavedPage = await thirdPage.save();
|
const thirdSavedPage = await thirdPage.save();
|
||||||
|
|
||||||
expect(thirdSavedPage.uri).to.equal(transformToUri(initialData.body.blocks[0].data.text));
|
expect(thirdSavedPage.uri).to.equal(transformToUri(initialData.body.blocks[0].data.text));
|
||||||
});
|
});
|
||||||
|
@ -222,6 +223,12 @@ describe('Page model', () => {
|
||||||
|
|
||||||
const savedPage = await page.save();
|
const savedPage = await page.save();
|
||||||
|
|
||||||
|
if (savedPage._id == undefined) {
|
||||||
|
await page.destroy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const foundPage = await Page.get(savedPage._id);
|
const foundPage = await Page.get(savedPage._id);
|
||||||
|
|
||||||
const {data} = foundPage;
|
const {data} = foundPage;
|
||||||
|
@ -302,26 +309,29 @@ describe('Page model', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
parent: parentId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
child.parent = parent;
|
|
||||||
|
|
||||||
const {_id: childId} = await child.save();
|
const {_id: childId} = await child.save();
|
||||||
|
|
||||||
const testedParent = await child.parent;
|
const testedParent = await child.getParent();
|
||||||
|
|
||||||
expect(testedParent._id).to.equal(parentId);
|
expect(testedParent).to.be.not.null;
|
||||||
expect(testedParent.title).to.equal(parent.body.blocks[0].data.text);
|
if (testedParent) {
|
||||||
expect(testedParent.uri).to.equal(transformToUri(parent.body.blocks[0].data.text));
|
expect(testedParent._id).to.equal(parentId);
|
||||||
expect(testedParent.body).to.deep.equal(parent.body);
|
expect(testedParent.title).to.equal(parent.body.blocks[0].data.text);
|
||||||
|
expect(testedParent.uri).to.equal(transformToUri(parent.body.blocks[0].data.text));
|
||||||
|
expect(testedParent.body).to.deep.equal(parent.body);
|
||||||
|
}
|
||||||
|
|
||||||
const children = await parent.children;
|
const children = await parent.children;
|
||||||
|
|
||||||
expect(children.length).to.equal(1);
|
expect(children.length).to.equal(1);
|
||||||
|
|
||||||
const testedChild = children.pop();
|
const temp: Page|undefined = children.pop();
|
||||||
|
const testedChild: Page = !temp ? new Page({}) : temp;
|
||||||
|
|
||||||
expect(testedChild._id).to.equal(childId);
|
expect(testedChild._id).to.equal(childId);
|
||||||
expect(testedChild.title).to.equal(child.body.blocks[0].data.text);
|
expect(testedChild.title).to.equal(child.body.blocks[0].data.text);
|
||||||
|
@ -354,20 +364,22 @@ describe('Page model', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test deletion', async () => {
|
it('test deletion', async () => {
|
||||||
|
const pageIndexes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
const pages = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
||||||
const orders = {
|
const orders = {
|
||||||
'0' : ['1', '2', '3'],
|
'0' : ['1', '2', '3'],
|
||||||
'1' : ['4', '5'],
|
'1' : ['4', '5'],
|
||||||
'5' : ['6', '7', '8'],
|
'5' : ['6', '7', '8'],
|
||||||
'3' : ['9']
|
'3' : ['9'],
|
||||||
};
|
} as { [key: string]: string[] };
|
||||||
|
|
||||||
|
function deleteRecursively(startFrom: string): void {
|
||||||
|
const order: string[] = orders[startFrom];
|
||||||
|
|
||||||
function deleteRecursively(startFrom) {
|
|
||||||
const order = orders[startFrom];
|
|
||||||
if (!order) {
|
if (!order) {
|
||||||
const found = pages.indexOf(startFrom);
|
const found = pageIndexes.indexOf(startFrom);
|
||||||
pages.splice(found, 1);
|
|
||||||
|
pageIndexes.splice(found, 1);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,8 +387,8 @@ describe('Page model', () => {
|
||||||
deleteRecursively(id);
|
deleteRecursively(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const found = pages.indexOf(startFrom);
|
const found = pageIndexes.indexOf(startFrom);
|
||||||
pages.splice(found, 1);
|
pageIndexes.splice(found, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,13 +1,15 @@
|
||||||
const {expect} = require('chai');
|
import { expect } from 'chai';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const config = require('../../config');
|
import config from 'config';
|
||||||
const PageOrder = require('../../src/models/pageOrder');
|
import PageOrder from '../../backend/models/pageOrder';
|
||||||
const {pagesOrder} = require('../../src/utils/database');
|
import database from '../../backend/utils/database';
|
||||||
|
|
||||||
|
const pagesOrder = database['pagesOrder'];
|
||||||
|
|
||||||
describe('PageOrder model', () => {
|
describe('PageOrder model', () => {
|
||||||
after(() => {
|
after(() => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './pagesOrder.db');
|
const pathToDB = path.resolve(__dirname, '../../../', config.get('database'), './pagesOrder.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
|
@ -15,7 +17,7 @@ describe('PageOrder model', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Empty Model', async () => {
|
it('Empty Model', async () => {
|
||||||
let pageOrder = new PageOrder();
|
const pageOrder = new PageOrder();
|
||||||
|
|
||||||
expect(pageOrder.data).to.be.a('object');
|
expect(pageOrder.data).to.be.a('object');
|
||||||
|
|
||||||
|
@ -25,7 +27,7 @@ describe('PageOrder model', () => {
|
||||||
expect(data.page).to.be.to.equal('0');
|
expect(data.page).to.be.to.equal('0');
|
||||||
expect(data.order).to.be.an('array').that.is.empty;
|
expect(data.order).to.be.an('array').that.is.empty;
|
||||||
|
|
||||||
page = new PageOrder(null);
|
let page = new PageOrder();
|
||||||
|
|
||||||
data = page.data;
|
data = page.data;
|
||||||
|
|
||||||
|
@ -53,13 +55,13 @@ describe('PageOrder model', () => {
|
||||||
order: ['1', '2']
|
order: ['1', '2']
|
||||||
};
|
};
|
||||||
const pageOrder = new PageOrder(testData);
|
const pageOrder = new PageOrder(testData);
|
||||||
let {data} = await pageOrder.save();
|
const {data} = await pageOrder.save();
|
||||||
|
|
||||||
expect(data._id).not.be.undefined;
|
expect(data._id).not.be.undefined;
|
||||||
expect(data.page).to.equal(testData.page);
|
expect(data.page).to.equal(testData.page);
|
||||||
expect(data.order).to.deep.equals(testData.order);
|
expect(data.order).to.deep.equals(testData.order);
|
||||||
|
|
||||||
const insertedPageOrder = await pagesOrder.findOne({_id: data._id});
|
const insertedPageOrder = await pagesOrder.findOne({_id: data._id}) as PageOrder;
|
||||||
expect(insertedPageOrder._id).to.equal(data._id);
|
expect(insertedPageOrder._id).to.equal(data._id);
|
||||||
expect(insertedPageOrder.page).to.equal(data.page);
|
expect(insertedPageOrder.page).to.equal(data.page);
|
||||||
expect(insertedPageOrder.order).to.deep.equal(data.order);
|
expect(insertedPageOrder.order).to.deep.equal(data.order);
|
||||||
|
@ -74,7 +76,7 @@ describe('PageOrder model', () => {
|
||||||
|
|
||||||
expect(pageOrder.data._id).to.equal(insertedPageOrder._id);
|
expect(pageOrder.data._id).to.equal(insertedPageOrder._id);
|
||||||
|
|
||||||
const updatedData = await pagesOrder.findOne({_id: insertedPageOrder._id});
|
const updatedData = await pagesOrder.findOne({_id: insertedPageOrder._id}) as PageOrder;
|
||||||
|
|
||||||
expect(updatedData.page).to.equal(updateData.page);
|
expect(updatedData.page).to.equal(updateData.page);
|
||||||
expect(updatedData.order).to.deep.equal(updateData.order);
|
expect(updatedData.order).to.deep.equal(updateData.order);
|
||||||
|
@ -97,9 +99,11 @@ describe('PageOrder model', () => {
|
||||||
await pageOrder.save();
|
await pageOrder.save();
|
||||||
pageOrder.push('3');
|
pageOrder.push('3');
|
||||||
expect(pageOrder.data.order).to.be.an('array').that.is.not.empty;
|
expect(pageOrder.data.order).to.be.an('array').that.is.not.empty;
|
||||||
pageOrder.data.order.forEach((el) => {
|
if (pageOrder.data.order !== undefined) {
|
||||||
expect(el).to.be.an('string')
|
pageOrder.data.order.forEach((el) => {
|
||||||
});
|
expect(el).to.be.an('string');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
expect(pageOrder.data.order).to.deep.equals(['1', '2', '3']);
|
expect(pageOrder.data.order).to.deep.equals(['1', '2', '3']);
|
||||||
|
|
||||||
|
@ -130,11 +134,13 @@ describe('PageOrder model', () => {
|
||||||
const pageOrder = new PageOrder(testData);
|
const pageOrder = new PageOrder(testData);
|
||||||
const insertedData = await pageOrder.save();
|
const insertedData = await pageOrder.save();
|
||||||
|
|
||||||
const insertedPageOrder = await PageOrder.get(insertedData.data.page);
|
if (insertedData.data.page !== undefined) {
|
||||||
expect(insertedPageOrder).to.instanceOf(PageOrder);
|
const insertedPageOrder = await PageOrder.get(insertedData.data.page);
|
||||||
expect(insertedPageOrder.data._id).to.be.equal(insertedData.data._id);
|
expect(insertedPageOrder).to.instanceOf(PageOrder);
|
||||||
|
expect(insertedPageOrder.data._id).to.be.equal(insertedData.data._id);
|
||||||
|
}
|
||||||
|
|
||||||
const emptyInstance = await PageOrder.get(null);
|
const emptyInstance = await PageOrder.get('');
|
||||||
expect(emptyInstance.data.page).to.be.equal('0');
|
expect(emptyInstance.data.page).to.be.equal('0');
|
||||||
expect(emptyInstance.data.order).to.be.an('array').that.is.empty;
|
expect(emptyInstance.data.order).to.be.an('array').that.is.empty;
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
const {expect} = require('chai');
|
import { expect } from 'chai';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import config from 'config';
|
||||||
|
import sinon = require('sinon');
|
||||||
|
|
||||||
require('mocha-sinon');
|
import rcParser from '../backend/utils/rcparser';
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('../config');
|
|
||||||
const rcParser = require('../src/utils/rcparser');
|
|
||||||
|
|
||||||
const rcPath = path.resolve(process.cwd(), config.rcFile);
|
const rcPath = path.resolve(process.cwd(), config.get('rcFile'));
|
||||||
|
|
||||||
describe('RC file parser test', () => {
|
describe('RC file parser test', () => {
|
||||||
beforeEach(function () {
|
|
||||||
this.sinon.stub(console, 'log');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (fs.existsSync(rcPath)) {
|
if (fs.existsSync(rcPath)) {
|
||||||
fs.unlinkSync(rcPath);
|
fs.unlinkSync(rcPath);
|
||||||
|
@ -27,25 +23,27 @@ describe('RC file parser test', () => {
|
||||||
|
|
||||||
it('Invalid JSON formatted config', () => {
|
it('Invalid JSON formatted config', () => {
|
||||||
const invalidJson = '{title: "Codex Docs"}';
|
const invalidJson = '{title: "Codex Docs"}';
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, invalidJson, 'utf8');
|
fs.writeFileSync(rcPath, invalidJson, 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('CodeX Docs rc file should be in JSON format.')).to.be.true;
|
expect(spy.calledWith('CodeX Docs rc file should be in JSON format.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig).to.be.deep.equal(rcParser.DEFAULTS);
|
expect(parsedConfig).to.be.deep.equal(rcParser.DEFAULTS);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Normal config', () => {
|
it('Normal config', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
{title: 'Option 1', uri: '/option1'},
|
{ title: 'Option 1', uri: '/option1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
@ -58,10 +56,10 @@ describe('RC file parser test', () => {
|
||||||
it('Missed title', () => {
|
it('Missed title', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
menu: [
|
menu: [
|
||||||
{title: 'Option 1', uri: '/option1'},
|
{ title: 'Option 1', uri: '/option1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
@ -74,7 +72,7 @@ describe('RC file parser test', () => {
|
||||||
|
|
||||||
it('Missed menu', () => {
|
it('Missed menu', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation'
|
title: 'Documentation',
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
@ -89,21 +87,23 @@ describe('RC file parser test', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: {
|
menu: {
|
||||||
0: {title: 'Option 1', uri: '/option1'},
|
0: { title: 'Option 1', uri: '/option1' },
|
||||||
1: {title: 'Option 2', uri: '/option2'},
|
1: { title: 'Option 2', uri: '/option2' },
|
||||||
2: {title: 'Option 3', uri: '/option3'}
|
2: { title: 'Option 3', uri: '/option3' },
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu section in the rc file must be an array.')).to.be.true;
|
expect(spy.calledWith('Menu section in the rc file must be an array.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(rcParser.DEFAULTS.menu);
|
expect(parsedConfig.menu).to.be.deep.equal(rcParser.DEFAULTS.menu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Menu option is a string', () => {
|
it('Menu option is a string', () => {
|
||||||
|
@ -111,15 +111,15 @@ describe('RC file parser test', () => {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
'Option 1',
|
'Option 1',
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 1', uri: '/option-1'},
|
{ title: 'Option 1', uri: '/option-1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
@ -134,129 +134,139 @@ describe('RC file parser test', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
[ {title: 'Option 1', uri: '/option1'} ],
|
[ { title: 'Option 1', uri: '/option1' } ],
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu option #1 in rc file must be a string or an object')).to.be.true;
|
expect(spy.calledWith('Menu option #1 in rc file must be a string or an object')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Menu option title is undefined', () => {
|
it('Menu option title is undefined', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
{uri: '/option1'},
|
{ uri: '/option1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu option #1 title must be a string.')).to.be.true;
|
expect(spy.calledWith('Menu option #1 title must be a string.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Menu option title is not a string', () => {
|
it('Menu option title is not a string', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
{title: [], uri: '/option1'},
|
{ title: [], uri: '/option1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu option #1 title must be a string.')).to.be.true;
|
expect(spy.calledWith('Menu option #1 title must be a string.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Menu option uri is undefined', () => {
|
it('Menu option uri is undefined', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
{title: 'Option 1'},
|
{ title: 'Option 1' },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu option #1 uri must be a string.')).to.be.true;
|
expect(spy.calledWith('Menu option #1 uri must be a string.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Menu option title is not a string', () => {
|
it('Menu option title is not a string', () => {
|
||||||
const normalConfig = {
|
const normalConfig = {
|
||||||
title: 'Documentation',
|
title: 'Documentation',
|
||||||
menu: [
|
menu: [
|
||||||
{title: 'Option 1', uri: []},
|
{ title: 'Option 1', uri: [] },
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedMenu = [
|
const expectedMenu = [
|
||||||
{title: 'Option 2', uri: '/option2'},
|
{ title: 'Option 2', uri: '/option2' },
|
||||||
{title: 'Option 3', uri: '/option3'}
|
{ title: 'Option 3', uri: '/option3' },
|
||||||
];
|
];
|
||||||
|
const spy = sinon.spy(console, 'log');
|
||||||
|
|
||||||
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
fs.writeFileSync(rcPath, JSON.stringify(normalConfig), 'utf8');
|
||||||
|
|
||||||
const parsedConfig = rcParser.getConfiguration();
|
const parsedConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
expect(console.log.calledOnce).to.be.true;
|
expect(spy.calledOnce).to.be.true;
|
||||||
expect(console.log.calledWith('Menu option #1 uri must be a string.')).to.be.true;
|
expect(spy.calledWith('Menu option #1 uri must be a string.')).to.be.true;
|
||||||
|
|
||||||
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
expect(parsedConfig.title).to.be.equal(normalConfig.title);
|
||||||
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
expect(parsedConfig.menu).to.be.deep.equal(expectedMenu);
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,29 +1,30 @@
|
||||||
const {app} = require('../../bin/www');
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import config from 'config';
|
||||||
|
import chai from 'chai';
|
||||||
|
import chaiHTTP from 'chai-http';
|
||||||
|
import server from '../../bin/server';
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('../../config');
|
|
||||||
const chai = require('chai');
|
|
||||||
const chaiHTTP = require('chai-http');
|
|
||||||
const {expect} = chai;
|
const {expect} = chai;
|
||||||
|
const app = server.app;
|
||||||
|
|
||||||
chai.use(chaiHTTP);
|
chai.use(chaiHTTP);
|
||||||
|
|
||||||
describe('Aliases REST: ', () => {
|
describe('Aliases REST: ', () => {
|
||||||
let agent;
|
let agent: ChaiHttp.Agent;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
agent = chai.request.agent(app);
|
agent = chai.request.agent(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './pages.db');
|
const pathToDB = path.resolve(__dirname, '../../', config.get('database'), './pages.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathToAliasDB = path.resolve(__dirname, '../../', config.database, './aliases.db');
|
const pathToAliasDB = path.resolve(__dirname, '../../', config.get('database'), './aliases.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToAliasDB)) {
|
if (fs.existsSync(pathToAliasDB)) {
|
||||||
fs.unlinkSync(pathToAliasDB);
|
fs.unlinkSync(pathToAliasDB);
|
|
@ -1,22 +1,23 @@
|
||||||
const {app} = require('../../bin/www');
|
import fs from 'fs';
|
||||||
const model = require('../../src/models/page');
|
import path from 'path';
|
||||||
const Page = require('../../src/models/page');
|
import config from 'config';
|
||||||
const PageOrder = require('../../src/models/pageOrder');
|
import chai from 'chai';
|
||||||
const translateString = require('../../src/utils/translation');
|
import chaiHTTP from 'chai-http';
|
||||||
|
import server from '../../bin/server';
|
||||||
|
import model from '../../backend/models/page';
|
||||||
|
import Page from '../../backend/models/page';
|
||||||
|
import PageOrder from '../../backend/models/pageOrder';
|
||||||
|
import translateString from '../../backend/utils/translation';
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('../../config');
|
|
||||||
const chai = require('chai');
|
|
||||||
const chaiHTTP = require('chai-http');
|
|
||||||
const {expect} = chai;
|
const {expect} = chai;
|
||||||
|
const app = server.app;
|
||||||
|
|
||||||
chai.use(chaiHTTP);
|
chai.use(chaiHTTP);
|
||||||
|
|
||||||
describe('Pages REST: ', () => {
|
describe('Pages REST: ', () => {
|
||||||
let agent;
|
let agent: ChaiHttp.Agent;
|
||||||
const transformToUri = (string) => {
|
const transformToUri = (text: string):string => {
|
||||||
return translateString(string
|
return translateString(text
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, ' ')
|
||||||
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
||||||
.replace(/ +/g, ' ')
|
.replace(/ +/g, ' ')
|
||||||
|
@ -31,9 +32,9 @@ describe('Pages REST: ', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
const pathToPagesDB = path.resolve(__dirname, '../../', config.database, './pages.db');
|
const pathToPagesDB = path.resolve(__dirname, '../../../', config.get('database'), './pages.db');
|
||||||
const pathToPagesOrderDB = path.resolve(__dirname, '../../', config.database, './pagesOrder.db');
|
const pathToPagesOrderDB = path.resolve(__dirname, '../../../', config.get('database'), './pagesOrder.db');
|
||||||
const pathToAliasesDB = path.resolve(__dirname, '../../', config.database, './aliases.db');
|
const pathToAliasesDB = path.resolve(__dirname, '../../../', config.get('database'), './aliases.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToPagesDB)) {
|
if (fs.existsSync(pathToPagesDB)) {
|
||||||
fs.unlinkSync(pathToPagesDB);
|
fs.unlinkSync(pathToPagesDB);
|
||||||
|
@ -93,15 +94,15 @@ describe('Pages REST: ', () => {
|
||||||
it('Page data validation on create', async () => {
|
it('Page data validation on create', async () => {
|
||||||
const res = await agent
|
const res = await agent
|
||||||
.put('/api/page')
|
.put('/api/page')
|
||||||
.send({someField: 'Some text'});
|
.send({ someField: 'Some text' });
|
||||||
|
|
||||||
expect(res).to.have.status(400);
|
expect(res).to.have.status(400);
|
||||||
expect(res).to.be.json;
|
expect(res).to.be.json;
|
||||||
|
|
||||||
const {success, error} = res.body;
|
const { success, error } = res.body;
|
||||||
|
|
||||||
expect(success).to.be.false;
|
expect(success).to.be.false;
|
||||||
expect(error).to.equal('Error: Some of required fields is missed');
|
expect(error).to.equal('validationError');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Finding page', async () => {
|
it('Finding page', async () => {
|
||||||
|
@ -363,7 +364,7 @@ describe('Pages REST: ', () => {
|
||||||
expect(error).to.equal('Page with given id does not exist');
|
expect(error).to.equal('Page with given id does not exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createPageTree() {
|
async function createPageTree():Promise<string[]> {
|
||||||
/**
|
/**
|
||||||
* Creating page tree
|
* Creating page tree
|
||||||
*
|
*
|
||||||
|
@ -474,7 +475,7 @@ describe('Pages REST: ', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('Removing a page and its children', async () => {
|
it('Removing a page and its children', async () => {
|
||||||
let pages = await createPageTree();
|
const pages = await createPageTree();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deleting from tree page1
|
* Deleting from tree page1
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -1,44 +1,44 @@
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const fileType = require('file-type');
|
import fileType from 'file-type';
|
||||||
const chai = require('chai');
|
import chai from 'chai';
|
||||||
const chaiHTTP = require('chai-http');
|
import chaiHTTP from 'chai-http';
|
||||||
const rimraf = require('rimraf');
|
import rimraf from 'rimraf';
|
||||||
|
import config from 'config';
|
||||||
|
import server from '../../bin/server';
|
||||||
|
import model from '../../backend/models/file';
|
||||||
|
|
||||||
const {expect} = chai;
|
const {expect} = chai;
|
||||||
|
const app = server.app;
|
||||||
const {app} = require('../../bin/www');
|
|
||||||
const model = require('../../src/models/file');
|
|
||||||
|
|
||||||
const config = require('../../config');
|
|
||||||
|
|
||||||
chai.use(chaiHTTP);
|
chai.use(chaiHTTP);
|
||||||
|
|
||||||
describe('Transport routes: ', () => {
|
describe('Transport routes: ', () => {
|
||||||
let agent;
|
let agent: ChaiHttp.Agent;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
agent = chai.request.agent(app);
|
agent = chai.request.agent(app);
|
||||||
|
|
||||||
if (!fs.existsSync('./' + config.uploads)) {
|
if (!fs.existsSync('./' + config.get('uploads'))) {
|
||||||
fs.mkdirSync('./' + config.uploads);
|
fs.mkdirSync('./' + config.get('uploads'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
const pathToDB = path.resolve(__dirname, '../../', config.database, './files.db');
|
const pathToDB = path.resolve(__dirname, '../../../', config.get('database'), './files.db');
|
||||||
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
if (fs.existsSync(pathToDB)) {
|
||||||
fs.unlinkSync(pathToDB);
|
fs.unlinkSync(pathToDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync('./' + config.uploads)) {
|
if (fs.existsSync('./' + config.get('uploads'))) {
|
||||||
rimraf.sync('./' + config.uploads);
|
rimraf.sync('./' + config.get('uploads'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uploading an image', async () => {
|
it('Uploading an image', async () => {
|
||||||
const name = 'test_image.png';
|
const name = 'test_image.png';
|
||||||
const image = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const image = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
const res = await agent
|
const res = await agent
|
||||||
.post('/api/transport/image')
|
.post('/api/transport/image')
|
||||||
.attach('image', image, name);
|
.attach('image', image, name);
|
||||||
|
@ -55,19 +55,27 @@ describe('Transport routes: ', () => {
|
||||||
expect(file.name).to.equal(name);
|
expect(file.name).to.equal(name);
|
||||||
expect(file.filename).to.equal(body.filename);
|
expect(file.filename).to.equal(body.filename);
|
||||||
expect(file.path).to.equal(body.path);
|
expect(file.path).to.equal(body.path);
|
||||||
expect(file.mimetype).to.equal(fileType(image).mime);
|
|
||||||
expect(file.size).to.equal(image.byteLength);
|
|
||||||
|
|
||||||
const getRes = await agent
|
const type = await fileType.fromBuffer(image);
|
||||||
.get(file.path);
|
expect(type).to.be.not.undefined;
|
||||||
|
if (type !== undefined) {
|
||||||
|
expect(file.mimetype).to.equal(type.mime);
|
||||||
|
expect(file.size).to.equal(image.byteLength);
|
||||||
|
|
||||||
expect(getRes).to.have.status(200);
|
expect(file.path).to.be.not.undefined;
|
||||||
expect(getRes).to.have.header('content-type', fileType(image).mime);
|
if (file.path !== undefined) {
|
||||||
|
const getRes = await agent
|
||||||
|
.get(file.path);
|
||||||
|
|
||||||
|
expect(getRes).to.have.status(200);
|
||||||
|
expect(getRes).to.have.header('content-type', type.mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uploading an image with map option', async () => {
|
it('Uploading an image with map option', async () => {
|
||||||
const name = 'test_image.png';
|
const name = 'test_image.png';
|
||||||
const image = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const image = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
const res = await agent
|
const res = await agent
|
||||||
.post('/api/transport/image')
|
.post('/api/transport/image')
|
||||||
.attach('image', image, name)
|
.attach('image', image, name)
|
||||||
|
@ -88,7 +96,7 @@ describe('Transport routes: ', () => {
|
||||||
|
|
||||||
it('Uploading a file', async () => {
|
it('Uploading a file', async () => {
|
||||||
const name = 'test_file.json';
|
const name = 'test_file.json';
|
||||||
const json = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const json = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
const res = await agent
|
const res = await agent
|
||||||
.post('/api/transport/file')
|
.post('/api/transport/file')
|
||||||
.attach('file', json, name);
|
.attach('file', json, name);
|
||||||
|
@ -107,16 +115,19 @@ describe('Transport routes: ', () => {
|
||||||
expect(file.path).to.equal(body.path);
|
expect(file.path).to.equal(body.path);
|
||||||
expect(file.size).to.equal(json.byteLength);
|
expect(file.size).to.equal(json.byteLength);
|
||||||
|
|
||||||
const getRes = await agent
|
expect(file.path).to.be.not.undefined;
|
||||||
.get(file.path);
|
if (file.path !== undefined){
|
||||||
|
const getRes = await agent
|
||||||
|
.get(file.path);
|
||||||
|
|
||||||
expect(getRes).to.have.status(200);
|
expect(getRes).to.have.status(200);
|
||||||
expect(getRes).to.have.header('content-type', new RegExp(`^${file.mimetype}`));
|
expect(getRes).to.have.header('content-type', new RegExp(`^${file.mimetype}`));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uploading a file with map option', async () => {
|
it('Uploading a file with map option', async () => {
|
||||||
const name = 'test_file.json';
|
const name = 'test_file.json';
|
||||||
const json = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const json = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
const res = await agent
|
const res = await agent
|
||||||
.post('/api/transport/file')
|
.post('/api/transport/file')
|
||||||
.attach('file', json, name)
|
.attach('file', json, name)
|
||||||
|
@ -155,11 +166,14 @@ describe('Transport routes: ', () => {
|
||||||
expect(file.path).to.equal(body.path);
|
expect(file.path).to.equal(body.path);
|
||||||
expect(file.size).to.equal(body.size);
|
expect(file.size).to.equal(body.size);
|
||||||
|
|
||||||
const getRes = await agent
|
expect(file.path).to.be.not.undefined;
|
||||||
.get(file.path);
|
if (file.path !== undefined){
|
||||||
|
const getRes = await agent
|
||||||
|
.get(file.path);
|
||||||
|
|
||||||
expect(getRes).to.have.status(200);
|
expect(getRes).to.have.status(200);
|
||||||
expect(getRes).to.have.header('content-type', file.mimetype);
|
expect(getRes).to.have.header('content-type', file.mimetype);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Send an file URL to fetch with map option', async () => {
|
it('Send an file URL to fetch with map option', async () => {
|
||||||
|
@ -193,7 +207,7 @@ describe('Transport routes: ', () => {
|
||||||
expect(body.success).to.equal(0);
|
expect(body.success).to.equal(0);
|
||||||
|
|
||||||
const name = 'test_file.json';
|
const name = 'test_file.json';
|
||||||
const json = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const json = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
res = await agent
|
res = await agent
|
||||||
.post('/api/transport/file')
|
.post('/api/transport/file')
|
||||||
.attach('file', json, name)
|
.attach('file', json, name)
|
||||||
|
@ -216,7 +230,7 @@ describe('Transport routes: ', () => {
|
||||||
expect(body.success).to.equal(0);
|
expect(body.success).to.equal(0);
|
||||||
|
|
||||||
let name = 'test_file.json';
|
let name = 'test_file.json';
|
||||||
const json = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const json = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
res = await agent
|
res = await agent
|
||||||
.post('/api/transport/image')
|
.post('/api/transport/image')
|
||||||
.attach('image', json, name);
|
.attach('image', json, name);
|
||||||
|
@ -224,7 +238,7 @@ describe('Transport routes: ', () => {
|
||||||
expect(res).to.have.status(400);
|
expect(res).to.have.status(400);
|
||||||
|
|
||||||
name = 'test_image.png';
|
name = 'test_image.png';
|
||||||
const image = fs.readFileSync(path.resolve(`./test/rest/${name}`));
|
const image = fs.readFileSync(path.resolve(`./src/test/rest/${name}`));
|
||||||
res = await agent
|
res = await agent
|
||||||
.post('/api/transport/image')
|
.post('/api/transport/image')
|
||||||
.attach('image', image, name)
|
.attach('image', image, name)
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* Helper for making async middlewares for express router
|
|
||||||
*
|
|
||||||
* @param fn
|
|
||||||
* @returns {function(*=, *=, *=)}
|
|
||||||
*/
|
|
||||||
module.exports = function asyncMiddleware(fn) {
|
|
||||||
return (req, res, next) => {
|
|
||||||
Promise.resolve(fn(req, res, next))
|
|
||||||
.catch(next);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
const Datastore = require('nedb');
|
|
||||||
const config = require('../../../config');
|
|
||||||
|
|
||||||
const db = new Datastore({ filename: `./${config.database}/aliases.db`, autoload: true });
|
|
||||||
|
|
||||||
module.exports = db;
|
|
|
@ -1,6 +0,0 @@
|
||||||
const Datastore = require('nedb');
|
|
||||||
const config = require('../../../config');
|
|
||||||
|
|
||||||
const db = new Datastore({ filename: `./${config.database}/files.db`, autoload: true });
|
|
||||||
|
|
||||||
module.exports = db;
|
|
|
@ -1,6 +0,0 @@
|
||||||
const Datastore = require('nedb');
|
|
||||||
const config = require('../../../config');
|
|
||||||
|
|
||||||
const db = new Datastore({ filename: `./${config.database}/pages.db`, autoload: true });
|
|
||||||
|
|
||||||
module.exports = db;
|
|
|
@ -1,5 +0,0 @@
|
||||||
const Datastore = require('nedb');
|
|
||||||
const config = require('../../../config');
|
|
||||||
const db = new Datastore({ filename: `./${config.database}/pagesOrder.db`, autoload: true });
|
|
||||||
|
|
||||||
module.exports = db;
|
|
|
@ -1,6 +0,0 @@
|
||||||
const Datastore = require('nedb');
|
|
||||||
const config = require('../../../config');
|
|
||||||
|
|
||||||
const db = new Datastore({ filename: `./${config.database}/password.db`, autoload: true });
|
|
||||||
|
|
||||||
module.exports = db;
|
|
179
test/database.js
179
test/database.js
|
@ -1,179 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const config = require('../config');
|
|
||||||
const {expect} = require('chai');
|
|
||||||
|
|
||||||
const {class: Database} = require('../src/utils/database');
|
|
||||||
const Datastore = require('nedb');
|
|
||||||
|
|
||||||
describe('Database', () => {
|
|
||||||
const pathToDB = `./${config.database}/test.db`;
|
|
||||||
let nedbInstance;
|
|
||||||
let db;
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
|
||||||
fs.unlinkSync(pathToDB);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Creating db instance', async () => {
|
|
||||||
nedbInstance = new Datastore({filename: pathToDB, autoload: true});
|
|
||||||
db = new Database(nedbInstance);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Inserting document', async () => {
|
|
||||||
const data = 'Text data';
|
|
||||||
|
|
||||||
const insertedDoc = await db.insert({data});
|
|
||||||
|
|
||||||
expect(insertedDoc).to.be.a('object');
|
|
||||||
expect(insertedDoc.data).to.equal(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Finding document', async () => {
|
|
||||||
const data = 'Text data';
|
|
||||||
|
|
||||||
const insertedDoc = await db.insert({data});
|
|
||||||
|
|
||||||
expect(insertedDoc).to.be.a('object');
|
|
||||||
expect(insertedDoc.data).to.equal(data);
|
|
||||||
|
|
||||||
const foundDoc = await db.findOne({_id: insertedDoc._id});
|
|
||||||
|
|
||||||
expect(foundDoc).not.be.null;
|
|
||||||
expect(foundDoc._id).to.equal(insertedDoc._id);
|
|
||||||
expect(foundDoc.data).to.equal(data);
|
|
||||||
|
|
||||||
const projectedDoc = await db.findOne({_id: insertedDoc._id}, {data: 1, _id: 0});
|
|
||||||
|
|
||||||
expect(Object.keys(projectedDoc).length).to.equal(1);
|
|
||||||
expect(Object.keys(projectedDoc).pop()).to.equal('data');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Updating document', async () => {
|
|
||||||
const data = 'Text data';
|
|
||||||
|
|
||||||
const insertedDoc = await db.insert({data});
|
|
||||||
|
|
||||||
expect(insertedDoc).to.be.a('object');
|
|
||||||
expect(insertedDoc.data).to.equal(data);
|
|
||||||
|
|
||||||
const updatedData = 'Updated text data';
|
|
||||||
|
|
||||||
await db.update({_id: insertedDoc._id}, {data: updatedData});
|
|
||||||
|
|
||||||
const updatedDoc = await db.findOne({_id: insertedDoc._id});
|
|
||||||
|
|
||||||
expect(updatedDoc).not.be.null;
|
|
||||||
expect(updatedDoc.data).not.equal(data);
|
|
||||||
expect(updatedDoc.data).to.equal(updatedData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Updating documents with options', async () => {
|
|
||||||
const data = {update: true, data: 'Text data'};
|
|
||||||
|
|
||||||
await db.insert(data);
|
|
||||||
await db.insert(data);
|
|
||||||
|
|
||||||
let numberOfUpdatedDocs = await db.update({update: true}, {$set: {data: 'First update'}}, {multi: true});
|
|
||||||
|
|
||||||
expect(numberOfUpdatedDocs).to.equal(2);
|
|
||||||
|
|
||||||
const affectedDocs = await db.update({update: true}, {$set: {data: 'Second update'}}, {multi: true, returnUpdatedDocs: true});
|
|
||||||
|
|
||||||
expect(affectedDocs).to.be.a('array');
|
|
||||||
affectedDocs.forEach(doc => {
|
|
||||||
expect(doc.data).to.equal('Second update');
|
|
||||||
});
|
|
||||||
|
|
||||||
const upsertedDoc = await db.update({update: true, data: 'First update'}, {$set: {data: 'Third update'}}, {upsert: true});
|
|
||||||
|
|
||||||
expect(upsertedDoc.update).to.be.true;
|
|
||||||
expect(upsertedDoc.data).to.equal('Third update');
|
|
||||||
|
|
||||||
numberOfUpdatedDocs = await db.update({data: 'Third update'}, {$set: {data: 'Fourth update'}}, {upsert: true});
|
|
||||||
|
|
||||||
expect(numberOfUpdatedDocs).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Finding documents', async () => {
|
|
||||||
const data1 = 'Text data 1';
|
|
||||||
const data2 = 'Text data 2';
|
|
||||||
|
|
||||||
const insertedDoc1 = await db.insert({data: data1, flag: true, no: 1});
|
|
||||||
const insertedDoc2 = await db.insert({data: data2, flag: true, no: 2});
|
|
||||||
|
|
||||||
const foundDocs = await db.find({flag: true});
|
|
||||||
|
|
||||||
expect(foundDocs).to.be.a('array');
|
|
||||||
expect(foundDocs.length).to.equal(2);
|
|
||||||
|
|
||||||
foundDocs.sort(({no: a}, {no: b}) => a - b);
|
|
||||||
|
|
||||||
expect(foundDocs[0]._id).to.equal(insertedDoc1._id);
|
|
||||||
expect(foundDocs[0].data).to.equal(insertedDoc1.data);
|
|
||||||
expect(foundDocs[1]._id).to.equal(insertedDoc2._id);
|
|
||||||
expect(foundDocs[1].data).to.equal(insertedDoc2.data);
|
|
||||||
|
|
||||||
const projectedDocs = await db.find({flag: true}, {no: 1, _id: 0});
|
|
||||||
|
|
||||||
expect(projectedDocs.length).to.equal(2);
|
|
||||||
projectedDocs.forEach(data => {
|
|
||||||
expect(Object.keys(data).length).to.equal(1);
|
|
||||||
expect(Object.keys(data).pop()).to.equal('no');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Removing document', async () => {
|
|
||||||
const data = 'Text data';
|
|
||||||
|
|
||||||
const insertedDoc = await db.insert({data});
|
|
||||||
|
|
||||||
expect(insertedDoc).to.be.a('object');
|
|
||||||
expect(insertedDoc.data).to.equal(data);
|
|
||||||
|
|
||||||
await db.remove({_id: insertedDoc._id});
|
|
||||||
|
|
||||||
const deletedDoc = await db.findOne({_id: insertedDoc._id});
|
|
||||||
|
|
||||||
expect(deletedDoc).to.be.null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Test invalid database queries', async () => {
|
|
||||||
try {
|
|
||||||
await db.insert();
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).to.equal('Cannot read property \'_id\' of undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.find({size: {$invalidComparator: 1}});
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).to.equal('Unknown comparison function $invalidComparator');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.findOne({field: {$invalidComparator: 1}});
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).to.equal('Unknown comparison function $invalidComparator');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.update({field: {$undefinedComparator: 1}});
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).to.equal('Unknown comparison function $undefinedComparator');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.remove({field: {$undefinedComparator: 1}});
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).to.equal('Unknown comparison function $undefinedComparator');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
if (fs.existsSync(pathToDB)) {
|
|
||||||
fs.unlinkSync(pathToDB);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
72
tsconfig.json
Normal file
72
tsconfig.json
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./dist/", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
// "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. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue