From 2feed218a0724b0667e4a2117474188591a849c1 Mon Sep 17 00:00:00 2001 From: Nikita Melnikov Date: Sat, 1 Oct 2022 23:04:03 +0800 Subject: [PATCH] fix bugs --- app-config.yaml | 4 +- docker-compose.yml | 9 ++++ nodemon.json | 2 +- src/backend/app.ts | 4 +- src/backend/controllers/pages.ts | 71 +++++++++++++++---------- src/backend/controllers/pagesOrder.ts | 14 ++--- src/backend/controllers/transport.ts | 6 +-- src/backend/models/alias.ts | 65 +++++++++++++++------- src/backend/models/file.ts | 9 ++-- src/backend/models/page.ts | 15 +++--- src/backend/models/pageOrder.ts | 34 ++++++------ src/backend/models/pagesFlatArray.ts | 11 ++-- src/backend/routes/api/pages.ts | 9 ++-- src/backend/routes/api/transport.ts | 2 +- src/backend/routes/auth.ts | 2 +- src/backend/routes/middlewares/pages.ts | 13 ++--- src/backend/routes/middlewares/token.ts | 2 +- src/backend/routes/pages.ts | 3 +- src/backend/utils/appConfig.ts | 31 +++++------ src/backend/utils/database/index.ts | 20 +++++-- src/backend/utils/database/local.ts | 39 +++++++------- src/backend/utils/database/mongodb.ts | 55 ++++++++++--------- src/backend/utils/database/types.ts | 4 ++ 23 files changed, 246 insertions(+), 178 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index ec50658..fb4282f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -24,8 +24,8 @@ hawk: # backendToken: "123" database: - driver: local + driver: mongodb local: path: ./db mongodb: - uri: mongodb://localhost:27017 + uri: mongodb://localhost:27017/docs1 diff --git a/docker-compose.yml b/docker-compose.yml index 1ca8082..8b44a7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,12 @@ services: - ./public/uploads:/uploads - ./db:/usr/src/app/db - ./app-config.yaml:/usr/src/app/app-config.yaml + mongodb: + image: mongo:6.0.1 + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + +volumes: + mongodb_data: diff --git a/nodemon.json b/nodemon.json index 70b8ccc..392bb4c 100644 --- a/nodemon.json +++ b/nodemon.json @@ -8,5 +8,5 @@ "watch": [ "**/*" ], - "ext": "js,twig" + "ext": "js,twig,ts" } diff --git a/src/backend/app.ts b/src/backend/app.ts index fd7b7ce..7df23b9 100644 --- a/src/backend/app.ts +++ b/src/backend/app.ts @@ -9,7 +9,7 @@ import * as dotenv from 'dotenv'; import HawkCatcher from '@hawk.so/nodejs'; import os from 'os'; import { downloadFavicon, FaviconData } from './utils/downloadFavicon.js'; -import appConfig from "./utils/appConfig.js"; +import appConfig from './utils/appConfig.js'; /** * The __dirname CommonJS variables are not available in ES modules. @@ -28,7 +28,7 @@ if (appConfig.hawk?.backendToken) { } // Get url to upload favicon from config -const favicon = appConfig.favicon +const favicon = appConfig.favicon; app.locals.config = localConfig; // Set client error tracking token as app local. diff --git a/src/backend/controllers/pages.ts b/src/backend/controllers/pages.ts index f5d70df..571bbc2 100644 --- a/src/backend/controllers/pages.ts +++ b/src/backend/controllers/pages.ts @@ -1,9 +1,11 @@ -import Page, { PageData } from '../models/page.js'; +import Page, {PageData} from '../models/page.js'; import Alias from '../models/alias.js'; import PagesOrder from './pagesOrder.js'; import PageOrder from '../models/pageOrder.js'; import HttpException from '../exceptions/httpException.js'; import PagesFlatArray from '../models/pagesFlatArray.js'; +import {EntityId} from '../utils/database/types.js'; +import {isEqualIds} from "../utils/database/index.js"; type PageDataFields = keyof PageData; @@ -27,7 +29,7 @@ class Pages { * @param {string} id - page id * @returns {Promise} */ - public static async get(id: string): Promise { + public static async get(id: EntityId): Promise { const page = await Page.get(id); if (!page._id) { @@ -42,7 +44,7 @@ class Pages { * * @returns {Promise} */ - public static async getAll(): Promise { + public static async getAllPages(): Promise { return Page.getAll(); } @@ -52,8 +54,8 @@ class Pages { * @param {string} parent - id of current page * @returns {Promise} */ - public static async getAllExceptChildren(parent: string): Promise { - const pagesAvailable = this.removeChildren(await Pages.getAll(), parent); + public static async getAllExceptChildren(parent: EntityId): Promise { + const pagesAvailable = this.removeChildren(await Pages.getAllPages(), parent); const nullFilteredPages: Page[] = []; @@ -66,6 +68,21 @@ class Pages { return nullFilteredPages; } + private static async getPagesMap(): Promise> { + const pages = await Pages.getAllPages(); + const pagesMap = new Map(); + + pages.forEach(page => { + if (page._id) { + pagesMap.set(page._id.toString(), page); + } else { + throw new Error('Page id is not defined'); + } + }); + + return pagesMap; + } + /** * Group all pages by their parents * If the pageId is passed, it excludes passed page from result pages @@ -73,11 +90,9 @@ class Pages { * @param {string} pageId - pageId to exclude from result pages * @returns {Page[]} */ - public static async groupByParent(pageId = ''): Promise { - const result: Page[] = []; - const orderGroupedByParent: Record = {}; - const rootPageOrder = await PagesOrder.getRootPageOrder(); - const childPageOrder = await PagesOrder.getChildPageOrder(); + public static async groupByParent(pageId: EntityId = ''): Promise { + const rootPageOrder = await PagesOrder.getRootPageOrder(); // get order of the root pages + const childPageOrder = await PagesOrder.getChildPageOrder(); // get order of the all other pages const orphanPageOrder: PageOrder[] = []; /** @@ -87,21 +102,17 @@ class Pages { return []; } - const pages = (await this.getAll()).reduce((map, _page) => { - map.set(_page._id, _page); - - return map; - }, new Map); + const pagesMap = await this.getPagesMap(); const idsOfRootPages = rootPageOrder.order; /** * It groups root pages and 1 level pages by its parent */ - idsOfRootPages.reduce((prev, curr, idx) => { - const childPages:PageOrder[] = []; + const orderGroupedByParent = idsOfRootPages.reduce((acc, curr, idx) => { + const childPages: PageOrder[] = []; childPageOrder.forEach((pageOrder, _idx) => { - if (pageOrder.page === curr) { + if (isEqualIds(pageOrder.page, curr)) { childPages.push(pageOrder); childPageOrder.splice(_idx, 1); } @@ -109,14 +120,14 @@ class Pages { const hasChildPage = childPages.length > 0; - prev[curr] = []; - prev[curr].push(curr); + acc[curr.toString()] = []; + acc[curr.toString()].push(curr); /** * It attaches 1 level page id to its parent page id */ if (hasChildPage) { - prev[curr].push(...childPages[0].order); + acc[curr.toString()].push(...childPages[0].order); } /** @@ -127,8 +138,8 @@ class Pages { orphanPageOrder.push(...childPageOrder); } - return prev; - }, orderGroupedByParent); + return acc; + }, {} as Record); let count = 0; @@ -159,9 +170,10 @@ class Pages { /** * It converts grouped pages(object) to array */ - Object.values(orderGroupedByParent).flatMap(arr => [ ...arr ]) - .forEach(arr => { - result.push(pages.get(arr)); + const result = Object.values(orderGroupedByParent) + .flatMap(arr => [ ...arr ]) + .map(arr => { + return pagesMap.get(arr.toString()) as Page; }); /** @@ -188,9 +200,9 @@ class Pages { * @param {string} parent - id of parent page * @returns {Array} */ - public static removeChildren(pagesAvailable: Array, parent: string | undefined): Array { + public static removeChildren(pagesAvailable: Array, parent: EntityId | undefined): Array { pagesAvailable.forEach(async (item, index) => { - if (item === null || item._parent !== parent) { + if (item === null || !isEqualIds(item._parent, parent)) { return; } pagesAvailable[index] = null; @@ -278,7 +290,7 @@ class Pages { * @param {string} id - page id * @returns {Promise} */ - public static async remove(id: string): Promise { + public static async remove(id: EntityId): Promise { const page = await Page.get(id); if (!page._id) { @@ -291,6 +303,7 @@ class Pages { await alias.destroy(); } const removedPage = page.destroy(); + await PagesFlatArray.regenerate(); return removedPage; diff --git a/src/backend/controllers/pagesOrder.ts b/src/backend/controllers/pagesOrder.ts index f0a287c..3b7e111 100644 --- a/src/backend/controllers/pagesOrder.ts +++ b/src/backend/controllers/pagesOrder.ts @@ -1,6 +1,7 @@ import PageOrder from '../models/pageOrder.js'; import Page from '../models/page.js'; import PagesFlatArray from '../models/pagesFlatArray.js'; +import { EntityId } from '../utils/database/types.js'; /** * @class PagesOrder @@ -15,7 +16,7 @@ class PagesOrder { * @param {string} parentId - of which page we want to get children order * @returns {Promise} */ - public static async get(parentId: string): Promise { + public static async get(parentId: EntityId): Promise { const order = await PageOrder.get(parentId); if (!order._id) { @@ -58,7 +59,7 @@ class PagesOrder { * @param {string} parentId - parent page's id * @param {string} childId - new page pushed to the order */ - public static async push(parentId: string, childId: string): Promise { + public static async push(parentId: EntityId, childId: EntityId): Promise { const order = await PageOrder.get(parentId); order.push(childId); @@ -73,7 +74,7 @@ class PagesOrder { * @param {string} newParentId - new parent page's id * @param {string} targetPageId - page's id which is changing the parent page */ - public static async move(oldParentId: string, newParentId: string, targetPageId: string): Promise { + public static async move(oldParentId: EntityId, newParentId: EntityId, targetPageId: EntityId): Promise { const oldParentOrder = await PageOrder.get(oldParentId); oldParentOrder.remove(targetPageId); @@ -96,8 +97,9 @@ class PagesOrder { * @param {boolean} ignoreSelf - should we ignore current page in list or not * @returns {Page[]} */ - public static async getOrderedChildren(pages: Page[], currentPageId: string, parentPageId: string, ignoreSelf = false): Promise { + public static async getOrderedChildren(pages: Page[], currentPageId: EntityId, parentPageId: EntityId, ignoreSelf = false): Promise { const children = await PageOrder.get(parentPageId); + console.log({children}) const unordered = pages.filter(page => page._parent === parentPageId).map(page => page._id); // Create unique array with ordered and unordered pages id @@ -122,7 +124,7 @@ class PagesOrder { * @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 */ - public static async update(unordered: string[], currentPageId: string, parentPageId: string, putAbovePageId: string): Promise { + public static async update(unordered: string[], currentPageId: EntityId, parentPageId: EntityId, putAbovePageId: EntityId): Promise { const pageOrder = await PageOrder.get(parentPageId); // Create unique array with ordered and unordered pages id @@ -136,7 +138,7 @@ class PagesOrder { * @param {string} parentId - identity of parent page * @returns {Promise} */ - public static async remove(parentId: string): Promise { + public static async remove(parentId: EntityId): Promise { const order = await PageOrder.get(parentId); if (!order._id) { diff --git a/src/backend/controllers/transport.ts b/src/backend/controllers/transport.ts index b81822c..8db5f88 100644 --- a/src/backend/controllers/transport.ts +++ b/src/backend/controllers/transport.ts @@ -5,7 +5,7 @@ import nodePath from 'path'; import File, { FileData } from '../models/file.js'; import crypto from '../utils/crypto.js'; import deepMerge from '../utils/objects.js'; -import appConfig from "../utils/appConfig.js"; +import appConfig from '../utils/appConfig.js'; const random16 = crypto.random16; @@ -29,7 +29,6 @@ class Transport { * @param {string} multerData.path - path to the uploaded file * @param {number} multerData.size - size of the uploaded file * @param {string} multerData.mimetype - MIME type of the uploaded file - * * @param {object} map - object that represents how should fields of File object should be mapped to response * @returns {Promise} */ @@ -108,11 +107,10 @@ class Transport { * * @param {File} file - file object * @param {object} map - object that represents how should fields of File object should be mapped to response - * */ public static composeResponse(file: File, map: Dict): Dict { const response: Dict = {}; - const { data } = file; + const data = file.data as Record; Object.entries(map).forEach(([name, path]) => { const fields: string[] = path.split(':'); diff --git a/src/backend/models/alias.ts b/src/backend/models/alias.ts index 5853004..172bb57 100644 --- a/src/backend/models/alias.ts +++ b/src/backend/models/alias.ts @@ -1,46 +1,71 @@ import crypto from '../utils/crypto.js'; import database from '../utils/database/index.js'; +import { EntityId } from '../utils/database/types.js'; const binaryMD5 = crypto.binaryMD5; const aliasesDb = database['aliases']; /** - * @typedef {object} AliasData - * @property {string} _id - alias id - * @property {string} hash - alias binary hash - * @property {string} type - entity type - * @property {boolean} deprecated - indicate if alias deprecated - * @property {string} id - entity id - * + * Describe an alias */ export interface AliasData { - _id?: string; + /** + * Alias id + */ + _id?: EntityId; + + /** + * Alias binary hash + */ hash?: string; + + /** + * Entity type + */ type?: string; + + /** + * Indicate if alias deprecated + */ deprecated?: boolean; - id?: string; + + /** + * Entity id + */ + id?: EntityId; } /** - * @class Alias - * @classdesc Alias model - * - * @property {string} _id - alias id - * @property {string} hash - alias binary hash - * @property {string} type - entity type - * @property {boolean} deprecated - indicate if alias deprecated - * @property {string} id - entity title + * Alias model */ class Alias { - public _id?: string; + /** + * Alias id + */ + public _id?: EntityId; + + /** + * Alias binary hash + */ public hash?: string; + + /** + * Entity type + */ public type?: string; + + /** + * Indicate if alias deprecated + */ public deprecated?: boolean; - public id?: string; + + /** + * Entity id + */ + public id?: EntityId; /** * @class - * * @param {AliasData} data - info about alias * @param {string} aliasName - alias of entity */ diff --git a/src/backend/models/file.ts b/src/backend/models/file.ts index e70d849..579c94d 100644 --- a/src/backend/models/file.ts +++ b/src/backend/models/file.ts @@ -1,10 +1,10 @@ import database from '../utils/database/index.js'; +import { EntityId } from '../utils/database/types.js'; const filesDb = database['files']; /** * @typedef {object} FileData - * * @property {string} _id - file id * @property {string} name - original file name * @property {string} filename - name of uploaded file @@ -14,20 +14,18 @@ const filesDb = database['files']; * @property {number} size - size of the file in */ export interface FileData { - _id?: string; + _id?: EntityId; name?: string; filename?: string; path?: string; mimetype?: string; url?: string; size?: number; - [key: string]: string | number | undefined; } /** * @class File * @class File model - * * @property {string} _id - file id * @property {string} name - original file name * @property {string} filename - name of uploaded file @@ -36,7 +34,7 @@ export interface FileData { * @property {number} size - size of the file in */ class File { - public _id?: string; + public _id?: EntityId; public name?: string; public filename?: string; public path?: string; @@ -46,7 +44,6 @@ class File { /** * @class - * * @param {FileData} data - info about file */ constructor(data: FileData = {}) { diff --git a/src/backend/models/page.ts b/src/backend/models/page.ts index cc99baf..ae814c3 100644 --- a/src/backend/models/page.ts +++ b/src/backend/models/page.ts @@ -1,5 +1,6 @@ import urlify from '../utils/urlify.js'; import database from '../utils/database/index.js'; +import { EntityId } from '../utils/database/types.js'; const pagesDb = database['pages']; @@ -12,17 +13,16 @@ const pagesDb = database['pages']; * @property {string} parent - id of parent page */ export interface PageData { - _id?: string; + _id?: EntityId; title?: string; uri?: string; body?: any; - parent?: string; + parent?: EntityId; } /** * @class Page * @class Page model - * * @property {string} _id - page id * @property {string} title - page title * @property {string} uri - page uri @@ -30,15 +30,14 @@ export interface PageData { * @property {string} _parent - id of parent page */ class Page { - public _id?: string; + public _id?: EntityId; public body?: any; public title?: string; public uri?: string; - public _parent?: string; + public _parent?: EntityId; /** * @class - * * @param {PageData} data - page's data */ constructor(data: PageData = {}) { @@ -59,7 +58,7 @@ class Page { * @param {string} _id - page id * @returns {Promise} */ - public static async get(_id: string): Promise { + public static async get(_id: EntityId): Promise { const data = await pagesDb.findOne({ _id }); return new Page(data); @@ -86,7 +85,7 @@ class Page { public static async getAll(query: Record = {}): Promise { const docs = await pagesDb.find(query); - return Promise.all(docs.map(doc => new Page(doc))); + return docs.map(doc => new Page(doc)); } /** diff --git a/src/backend/models/pageOrder.ts b/src/backend/models/pageOrder.ts index 24ffc17..5603378 100644 --- a/src/backend/models/pageOrder.ts +++ b/src/backend/models/pageOrder.ts @@ -1,4 +1,6 @@ import database from '../utils/database/index.js'; +import { ObjectId } from 'mongodb'; +import { EntityId } from '../utils/database/types.js'; const db = database['pagesOrder']; @@ -9,9 +11,9 @@ const db = database['pagesOrder']; * @property {Array} order - list of ordered pages */ export interface PageOrderData { - _id?: string; - page?: string; - order?: string[]; + _id?: EntityId; + page?: EntityId; + order?: EntityId[]; } /** @@ -21,14 +23,13 @@ export interface PageOrderData { * Creates order for Pages with children */ class PageOrder { - public _id?: string; - public page?: string; - private _order?: string[]; + public _id?: EntityId; + public page?: EntityId; + private _order?: EntityId[]; /** * @class - * * @param {PageOrderData} data - info about pageOrder */ constructor(data: PageOrderData = {}) { @@ -49,7 +50,7 @@ class PageOrder { * @param {string} pageId - page's id * @returns {Promise} */ - public static async get(pageId: string): Promise { + public static async get(pageId: EntityId): Promise { const order = await db.findOne({ page: pageId }); let data: PageOrderData = {}; @@ -125,8 +126,8 @@ class PageOrder { * * @param {string} pageId - page's id */ - public push(pageId: string | number): void { - if (typeof pageId === 'string') { + public push(pageId: EntityId): void { + if (typeof pageId === 'string' || pageId instanceof ObjectId) { if (this.order === undefined) { this.order = []; } @@ -141,7 +142,7 @@ class PageOrder { * * @param {string} pageId - page's id */ - public remove(pageId: string): void { + public remove(pageId: EntityId): void { if (this.order === undefined) { return; } @@ -156,10 +157,9 @@ class PageOrder { /** * @param {string} currentPageId - page's id that changes the order * @param {string} putAbovePageId - page's id above which we put the target page - * * @returns {void} */ - public putAbove(currentPageId: string, putAbovePageId: string): void { + public putAbove(currentPageId: EntityId, putAbovePageId: EntityId): void { if (this.order === undefined) { return; } @@ -182,7 +182,7 @@ class PageOrder { * * @param {string} pageId - identity of page */ - public getSubPageBefore(pageId: string): string | null { + public getSubPageBefore(pageId: EntityId): EntityId | null { if (this.order === undefined) { return null; } @@ -204,7 +204,7 @@ class PageOrder { * * @param pageId - identity of page */ - public getSubPageAfter(pageId: string): string | null { + public getSubPageAfter(pageId: EntityId): EntityId | null { if (this.order === undefined) { return null; } @@ -224,7 +224,7 @@ class PageOrder { /** * @param {string[]} order - define new order */ - public set order(order: string[]) { + public set order(order: EntityId[]) { this._order = order; } @@ -233,7 +233,7 @@ class PageOrder { * * @returns {string[]} */ - public get order(): string[] { + public get order(): EntityId[] { return this._order || []; } diff --git a/src/backend/models/pagesFlatArray.ts b/src/backend/models/pagesFlatArray.ts index e82d4c3..c72ae92 100644 --- a/src/backend/models/pagesFlatArray.ts +++ b/src/backend/models/pagesFlatArray.ts @@ -1,6 +1,7 @@ import Page from './page.js'; import PageOrder from './pageOrder.js'; import NodeCache from 'node-cache'; +import { EntityId } from '../utils/database/types.js'; // Create cache for flat array const cache = new NodeCache({ stdTTL: 120 }); @@ -14,12 +15,12 @@ export interface PagesFlatArrayData { /** * Page id */ - id: string; + id: EntityId; /** * Page parent id */ - parentId?: string; + parentId?: EntityId; /** * id of parent with parent id '0' @@ -105,7 +106,7 @@ class PagesFlatArray { * @param pageId - page id * @returns {Promise} */ - public static async getPageBefore(pageId: string): Promise { + public static async getPageBefore(pageId: EntityId): Promise { const arr = await this.get(); const pageIndex = arr.findIndex( (item) => item.id == pageId); @@ -125,7 +126,7 @@ class PagesFlatArray { * @param pageId - page id * @returns {Promise} */ - public static async getPageAfter(pageId: string): Promise { + public static async getPageAfter(pageId: EntityId): Promise { const arr = await this.get(); const pageIndex = arr.findIndex( (item) => item.id == pageId ); @@ -148,7 +149,7 @@ class PagesFlatArray { * @param orders - all page orders * @returns {Promise>} */ - private static getChildrenFlatArray(pageId: string, level: number, + private static getChildrenFlatArray(pageId: EntityId, level: number, pages: Array, orders: Array): Array { let arr: Array = new Array(); diff --git a/src/backend/routes/api/pages.ts b/src/backend/routes/api/pages.ts index f4b1673..f521614 100644 --- a/src/backend/routes/api/pages.ts +++ b/src/backend/routes/api/pages.ts @@ -2,6 +2,7 @@ import express, { Request, Response } from 'express'; import multerFunc from 'multer'; import Pages from '../../controllers/pages.js'; import PagesOrder from '../../controllers/pagesOrder.js'; +import { EntityId } from '../../utils/database/types.js'; const router = express.Router(); const multer = multerFunc(); @@ -35,7 +36,7 @@ router.get('/page/:id', async (req: Request, res: Response) => { */ router.get('/pages', async (req: Request, res: Response) => { try { - const pages = await Pages.getAll(); + const pages = await Pages.getAllPages(); res.json({ success: true, @@ -92,7 +93,7 @@ router.post('/page/:id', multer.none(), async (req: Request, res: Response) => { try { const { title, body, parent, putAbovePageId, uri } = req.body; - const pages = await Pages.getAll(); + const pages = await Pages.getAllPages(); let page = await Pages.get(id); if (page._id === undefined) { @@ -177,8 +178,8 @@ router.delete('/page/:id', async (req: Request, res: Response) => { * @param {string} startFrom - start point to delete * @returns {Promise} */ - const deleteRecursively = async (startFrom: string): Promise => { - let order: string[] = []; + const deleteRecursively = async (startFrom: EntityId): Promise => { + let order: EntityId[] = []; try { const children = await PagesOrder.get(startFrom); diff --git a/src/backend/routes/api/transport.ts b/src/backend/routes/api/transport.ts index 47187a5..a62fe12 100644 --- a/src/backend/routes/api/transport.ts +++ b/src/backend/routes/api/transport.ts @@ -4,7 +4,7 @@ import mime from 'mime'; import mkdirp from 'mkdirp'; import Transport from '../../controllers/transport.js'; import { random16 } from '../../utils/crypto.js'; -import appConfig from "../../utils/appConfig.js"; +import appConfig from '../../utils/appConfig.js'; const router = Router(); diff --git a/src/backend/routes/auth.ts b/src/backend/routes/auth.ts index e3abdb9..9b69c9a 100644 --- a/src/backend/routes/auth.ts +++ b/src/backend/routes/auth.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; import csrf from 'csurf'; -import appConfig from "../utils/appConfig.js"; +import appConfig from '../utils/appConfig.js'; const router = express.Router(); const csrfProtection = csrf({ cookie: true }); diff --git a/src/backend/routes/middlewares/pages.ts b/src/backend/routes/middlewares/pages.ts index 528ee63..4a1311b 100644 --- a/src/backend/routes/middlewares/pages.ts +++ b/src/backend/routes/middlewares/pages.ts @@ -4,6 +4,8 @@ import PagesOrder from '../../controllers/pagesOrder.js'; import Page from '../../models/page.js'; import asyncMiddleware from '../../utils/asyncMiddleware.js'; import PageOrder from '../../models/pageOrder.js'; +import { EntityId } from '../../utils/database/types.js'; +import {isEqualIds} from "../../utils/database/index.js"; /** * Process one-level pages list to parent-children list @@ -13,11 +15,10 @@ import PageOrder from '../../models/pageOrder.js'; * @param {PagesOrder[]} pagesOrder - list of pages order * @param {number} level - max level recursion * @param {number} currentLevel - current level of element - * * @returns {Page[]} */ 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 => isEqualIds(order.data.page, parentPageId)); /** * branch is a page children in tree @@ -27,12 +28,12 @@ function createMenuTree(parentPageId: string, pages: Page[], pagesOrder: PageOrd let ordered: any[] = []; if (childrenOrder) { - ordered = childrenOrder.order.map((pageId: string) => { - return pages.find(page => page._id === pageId); + ordered = childrenOrder.order.map((pageId: EntityId) => { + return pages.find(page => isEqualIds(page._id, pageId)); }); } - const unordered = pages.filter(page => page._parent === parentPageId); + const unordered = pages.filter(page => isEqualIds(page._parent, parentPageId)); const branch = Array.from(new Set([...ordered, ...unordered])); /** @@ -68,7 +69,7 @@ export default asyncMiddleware(async (req: Request, res: Response, next: NextFun const parentIdOfRootPages = '0'; try { - const pages = await Pages.getAll(); + const pages = await Pages.getAllPages(); const pagesOrder = await PagesOrder.getAll(); res.locals.menu = createMenuTree(parentIdOfRootPages, pages, pagesOrder, 2); diff --git a/src/backend/routes/middlewares/token.ts b/src/backend/routes/middlewares/token.ts index f8d4ded..d57169e 100644 --- a/src/backend/routes/middlewares/token.ts +++ b/src/backend/routes/middlewares/token.ts @@ -1,6 +1,6 @@ import { NextFunction, Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import appConfig from "../../utils/appConfig.js"; +import appConfig from '../../utils/appConfig.js'; /** diff --git a/src/backend/routes/pages.ts b/src/backend/routes/pages.ts index b4c6424..e9c09b0 100644 --- a/src/backend/routes/pages.ts +++ b/src/backend/routes/pages.ts @@ -4,6 +4,7 @@ import PagesOrder from '../controllers/pagesOrder.js'; import verifyToken from './middlewares/token.js'; import allowEdit from './middlewares/locals.js'; import PagesFlatArray from '../models/pagesFlatArray.js'; +import { toEntityId } from '../utils/database/index.js'; const router = express.Router(); @@ -28,7 +29,7 @@ router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Respon * Edit page form */ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => { - const pageId = req.params.id; + const pageId = toEntityId(req.params.id); try { const page = await Pages.get(pageId); diff --git a/src/backend/utils/appConfig.ts b/src/backend/utils/appConfig.ts index 73e241b..86297bc 100644 --- a/src/backend/utils/appConfig.ts +++ b/src/backend/utils/appConfig.ts @@ -2,7 +2,7 @@ import { loadConfig } from 'config-loader'; import * as process from 'process'; import arg from 'arg'; import path from 'path'; -import { z } from "zod"; +import { z } from 'zod'; /** * Configuration for Hawk errors catcher @@ -10,25 +10,25 @@ import { z } from "zod"; const HawkConfig = z.object({ backendToken: z.string().optional(), // Hawk backend token frontendToken: z.string().optional(), // Hawk frontend token -}) +}); const LocalDatabaseConfig = z.object({ driver: z.literal('local'), local: z.object({ - path: z.string() - }) -}) + path: z.string(), + }), +}); const MongoDatabaseConfig = z.object({ driver: z.literal('mongodb'), mongodb: z.object({ - uri: z.string() - }) -}) + uri: z.string(), + }), +}); const AuthConfig = z.object({ - secret: z.string() // Secret for JWT -}) + secret: z.string(), // Secret for JWT +}); const FrontendConfig = z.object({ title: z.string(), // Title for pages @@ -40,8 +40,9 @@ const FrontendConfig = z.object({ serve: z.string().optional(), // Carbon serve url placement: z.string().optional(), // Carbon placement }), - menu: z.array(z.union([z.string(), z.object({title: z.string(), uri: z.string()})])), // Menu for pages -}) + menu: z.array(z.union([z.string(), z.object({ title: z.string(), + uri: z.string() })])), // Menu for pages +}); /** * Application configuration @@ -56,7 +57,7 @@ const AppConfig = z.object({ frontend: FrontendConfig, // Frontend configuration auth: AuthConfig, // Auth configuration database: z.union([LocalDatabaseConfig, MongoDatabaseConfig]), // Database configuration -}) +}); export type AppConfig = z.infer; @@ -66,7 +67,7 @@ const args = arg({ /* eslint-disable @typescript-eslint/naming-convention */ }); const cwd = process.cwd(); -const paths = (args['--config'] || ['./app-config.yaml']).map((configPath) => { +const paths = (args['--config'] || [ './app-config.yaml' ]).map((configPath) => { if (path.isAbsolute(configPath)) { return configPath; } @@ -76,6 +77,6 @@ const paths = (args['--config'] || ['./app-config.yaml']).map((configPath) => { const loadedConfig = loadConfig(...paths); -const appConfig = AppConfig.parse(loadedConfig) +const appConfig = AppConfig.parse(loadedConfig); export default appConfig; diff --git a/src/backend/utils/database/index.ts b/src/backend/utils/database/index.ts index 6c0529b..63394b9 100644 --- a/src/backend/utils/database/index.ts +++ b/src/backend/utils/database/index.ts @@ -2,12 +2,26 @@ import { AliasData } from '../../models/alias.js'; import { FileData } from '../../models/file.js'; import { PageData } from '../../models/page.js'; import { PageOrderData } from '../../models/pageOrder.js'; -import appConfig from "../appConfig.js"; -import LocalDatabaseDriver from "./local.js"; -import MongoDatabaseDriver from "./mongodb.js"; +import appConfig from '../appConfig.js'; +import LocalDatabaseDriver from './local.js'; +import MongoDatabaseDriver from './mongodb.js'; +import { EntityId } from './types.js'; +import { ObjectId } from 'mongodb'; const Database = appConfig.database.driver === 'mongodb' ? MongoDatabaseDriver : LocalDatabaseDriver; +/** + * + * @param id + */ +export function toEntityId(id: string): EntityId { + return appConfig.database.driver === 'mongodb' ? new ObjectId(id) : id; +} + +export function isEqualIds(id1?: EntityId, id2?: EntityId): boolean { + return id1?.toString() === id2?.toString(); +} + export default { pages: new Database('pages'), aliases: new Database('aliases'), diff --git a/src/backend/utils/database/local.ts b/src/backend/utils/database/local.ts index c7162db..40c544c 100644 --- a/src/backend/utils/database/local.ts +++ b/src/backend/utils/database/local.ts @@ -1,7 +1,7 @@ -import Datastore from "nedb"; -import {DatabaseDriver, Options, RejectFunction, ResolveFunction} from "./types.js"; -import path from "path"; -import appConfig from "../appConfig.js"; +import Datastore from 'nedb'; +import { DatabaseDriver, Options, RejectFunction, ResolveFunction } from './types.js'; +import path from 'path'; +import appConfig from '../appConfig.js'; /** * Init function for nedb instance @@ -11,9 +11,11 @@ import appConfig from "../appConfig.js"; */ function initDb(name: string): Datastore { const dbConfig = appConfig.database.driver === 'local' ? appConfig.database.local : null; + if (!dbConfig) { throw new Error('Database config is not initialized'); } + return new Datastore({ filename: path.resolve(`${dbConfig.path}/${name}.db`), autoload: true, @@ -41,9 +43,8 @@ export default class LocalDatabaseDriver implements DatabaseDriver} - inserted doc or Error object + * @param {object} doc - object to insert + * @returns {Promise} - inserted doc or Error object */ public async insert(doc: DocType): Promise { return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => { @@ -59,10 +60,9 @@ export default class LocalDatabaseDriver implements DatabaseDriver|Error>} - found docs or Error object + * @param {object} query - query object + * @param {object} projection - projection object + * @returns {Promise | Error>} - found docs or Error object */ public async find(query: Record, projection?: DocType): Promise> { const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => { @@ -86,10 +86,9 @@ export default class LocalDatabaseDriver implements DatabaseDriver} - found doc or Error object + * @param {object} query - query object + * @param {object} projection - projection object + * @returns {Promise} - found doc or Error object */ public async findOne(query: Record, projection?: DocType): Promise { const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => { @@ -113,11 +112,10 @@ export default class LocalDatabaseDriver implements DatabaseDriver} - number of updated rows or affected docs or Error object + * @returns {Promise} - number of updated rows or affected docs or Error object */ public async update(query: Record, update: DocType, options: Options = {}): Promise> { return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => { @@ -145,8 +143,7 @@ export default class LocalDatabaseDriver implements DatabaseDriver} - number of removed rows or Error object */ diff --git a/src/backend/utils/database/mongodb.ts b/src/backend/utils/database/mongodb.ts index 5794cbd..454121b 100644 --- a/src/backend/utils/database/mongodb.ts +++ b/src/backend/utils/database/mongodb.ts @@ -1,6 +1,6 @@ -import {Collection, Filter, MongoClient, OptionalUnlessRequiredId, UpdateFilter} from 'mongodb'; -import {DatabaseDriver, Options} from "./types.js"; -import appConfig from "../appConfig.js"; +import { Collection, Filter, MongoClient, OptionalUnlessRequiredId, UpdateFilter } from 'mongodb'; +import { DatabaseDriver, Options } from './types.js'; +import appConfig from '../appConfig.js'; const mongodbUri = appConfig.database.driver === 'mongodb' ? appConfig.database.mongodb.uri : null; const mongodbClient = mongodbUri ? await MongoClient.connect(mongodbUri): null; @@ -15,6 +15,10 @@ export default class MongoDatabaseDriver implements DatabaseDriver; + /** + * + * @param collectionName + */ constructor(collectionName: string) { if (!mongodbClient) { throw new Error('MongoDB client is not initialized'); @@ -26,47 +30,46 @@ export default class MongoDatabaseDriver implements DatabaseDriver} - inserted doc or Error object + * @param {object} doc - object to insert + * @returns {Promise} - inserted doc or Error object */ public async insert(doc: DocType): Promise { const result = await this.collection.insertOne(doc as OptionalUnlessRequiredId); + return { ...doc, _id: result.insertedId, - } + }; } /** * Find documents that match passed query * - * @param {Object} query - query object - * @param {Object} projection - projection object - * @returns {Promise|Error>} - found docs or Error object + * @param {object} query - query object + * @param {object} projection - projection object + * @returns {Promise | Error>} - found docs or Error object */ public async find(query: Record, projection?: DocType): Promise> { - const cursor = this.collection.find(query as Filter) + const cursor = this.collection.find(query as Filter); if (projection) { cursor.project(projection); } const docs = await cursor.toArray(); + return docs as unknown as Array; } /** * Find one document matches passed query * - * @param {Object} query - query object - * @param {Object} projection - projection object - * @returns {Promise} - found doc or Error object + * @param {object} query - query object + * @param {object} projection - projection object + * @returns {Promise} - found doc or Error object */ public async findOne(query: Record, projection?: DocType): Promise { - const doc = await this.collection.findOne(query as Filter, {projection}); - if (!doc) { - throw new Error('Document not found'); - } + const doc = await this.collection.findOne(query as Filter, { projection }); return doc as unknown as DocType; } @@ -74,39 +77,41 @@ export default class MongoDatabaseDriver implements DatabaseDriver} - number of updated rows or affected docs or Error object + * @returns {Promise} - number of updated rows or affected docs or Error object */ public async update(query: Record, update: DocType, options: Options = {}): Promise> { const updateDocument = { - $set: update + $set: update, } as UpdateFilter; const result = await this.collection.updateMany(query as Filter, updateDocument, options); switch (true) { case options.returnUpdatedDocs: - return result.modifiedCount + return result.modifiedCount; case options.upsert: if (result.modifiedCount) { return result.modifiedCount; } - return result as DocType[] + + return result as DocType[]; default: - return result as DocType[] + return result as DocType[]; } } /** * Remove document matches passed query * - * @param {Object} query - query object + * @param {object} query - query object * @param {Options} options - optional params * @returns {Promise} - number of removed rows or Error object */ public async remove(query: Record, options: Options = {}): Promise { const result = await this.collection.deleteMany(query as Filter); + return result.deletedCount; } } diff --git a/src/backend/utils/database/types.ts b/src/backend/utils/database/types.ts index 8b32741..f26da5a 100644 --- a/src/backend/utils/database/types.ts +++ b/src/backend/utils/database/types.ts @@ -1,3 +1,5 @@ +import { ObjectId } from 'mongodb'; + export interface DatabaseDriver { insert(doc: DocType): Promise; find(query: Record, projection?: DocType): Promise>; @@ -6,6 +8,8 @@ export interface DatabaseDriver { remove(query: Record, options: Options): Promise } +export type EntityId = string | ObjectId; + /** * @typedef Options - optional params * @param {boolean} multi - (false) allows to take action to several documents