1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-08-08 06:55:26 +02:00
This commit is contained in:
Nikita Melnikov 2022-10-01 23:04:03 +08:00
parent 6fc17913ce
commit 2feed218a0
23 changed files with 246 additions and 178 deletions

View file

@ -24,8 +24,8 @@ hawk:
# backendToken: "123" # backendToken: "123"
database: database:
driver: local driver: mongodb
local: local:
path: ./db path: ./db
mongodb: mongodb:
uri: mongodb://localhost:27017 uri: mongodb://localhost:27017/docs1

View file

@ -12,3 +12,12 @@ services:
- ./public/uploads:/uploads - ./public/uploads:/uploads
- ./db:/usr/src/app/db - ./db:/usr/src/app/db
- ./app-config.yaml:/usr/src/app/app-config.yaml - ./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:

View file

@ -8,5 +8,5 @@
"watch": [ "watch": [
"**/*" "**/*"
], ],
"ext": "js,twig" "ext": "js,twig,ts"
} }

View file

@ -9,7 +9,7 @@ import * as dotenv from 'dotenv';
import HawkCatcher from '@hawk.so/nodejs'; import HawkCatcher from '@hawk.so/nodejs';
import os from 'os'; import os from 'os';
import { downloadFavicon, FaviconData } from './utils/downloadFavicon.js'; 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. * 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 // Get url to upload favicon from config
const favicon = appConfig.favicon const favicon = appConfig.favicon;
app.locals.config = localConfig; app.locals.config = localConfig;
// Set client error tracking token as app local. // Set client error tracking token as app local.

View file

@ -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 Alias from '../models/alias.js';
import PagesOrder from './pagesOrder.js'; import PagesOrder from './pagesOrder.js';
import PageOrder from '../models/pageOrder.js'; import PageOrder from '../models/pageOrder.js';
import HttpException from '../exceptions/httpException.js'; import HttpException from '../exceptions/httpException.js';
import PagesFlatArray from '../models/pagesFlatArray.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; type PageDataFields = keyof PageData;
@ -27,7 +29,7 @@ class Pages {
* @param {string} id - page id * @param {string} id - page id
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
public static async get(id: string): Promise<Page> { public static async get(id: EntityId): Promise<Page> {
const page = await Page.get(id); const page = await Page.get(id);
if (!page._id) { if (!page._id) {
@ -42,7 +44,7 @@ class Pages {
* *
* @returns {Promise<Page[]>} * @returns {Promise<Page[]>}
*/ */
public static async getAll(): Promise<Page[]> { public static async getAllPages(): Promise<Page[]> {
return Page.getAll(); return Page.getAll();
} }
@ -52,8 +54,8 @@ class Pages {
* @param {string} parent - id of current page * @param {string} parent - id of current page
* @returns {Promise<Page[]>} * @returns {Promise<Page[]>}
*/ */
public static async getAllExceptChildren(parent: string): Promise<Page[]> { public static async getAllExceptChildren(parent: EntityId): Promise<Page[]> {
const pagesAvailable = this.removeChildren(await Pages.getAll(), parent); const pagesAvailable = this.removeChildren(await Pages.getAllPages(), parent);
const nullFilteredPages: Page[] = []; const nullFilteredPages: Page[] = [];
@ -66,6 +68,21 @@ class Pages {
return nullFilteredPages; return nullFilteredPages;
} }
private static async getPagesMap(): Promise<Map<string, Page>> {
const pages = await Pages.getAllPages();
const pagesMap = new Map<string, Page>();
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 * Group all pages by their parents
* If the pageId is passed, it excludes passed page from result pages * 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 * @param {string} pageId - pageId to exclude from result pages
* @returns {Page[]} * @returns {Page[]}
*/ */
public static async groupByParent(pageId = ''): Promise<Page[]> { public static async groupByParent(pageId: EntityId = ''): Promise<Page[]> {
const result: Page[] = []; const rootPageOrder = await PagesOrder.getRootPageOrder(); // get order of the root pages
const orderGroupedByParent: Record<string, string[]> = {}; const childPageOrder = await PagesOrder.getChildPageOrder(); // get order of the all other pages
const rootPageOrder = await PagesOrder.getRootPageOrder();
const childPageOrder = await PagesOrder.getChildPageOrder();
const orphanPageOrder: PageOrder[] = []; const orphanPageOrder: PageOrder[] = [];
/** /**
@ -87,21 +102,17 @@ class Pages {
return []; return [];
} }
const pages = (await this.getAll()).reduce((map, _page) => { const pagesMap = await this.getPagesMap();
map.set(_page._id, _page);
return map;
}, new Map);
const idsOfRootPages = rootPageOrder.order; const idsOfRootPages = rootPageOrder.order;
/** /**
* It groups root pages and 1 level pages by its parent * It groups root pages and 1 level pages by its parent
*/ */
idsOfRootPages.reduce((prev, curr, idx) => { const orderGroupedByParent = idsOfRootPages.reduce((acc, curr, idx) => {
const childPages:PageOrder[] = []; const childPages: PageOrder[] = [];
childPageOrder.forEach((pageOrder, _idx) => { childPageOrder.forEach((pageOrder, _idx) => {
if (pageOrder.page === curr) { if (isEqualIds(pageOrder.page, curr)) {
childPages.push(pageOrder); childPages.push(pageOrder);
childPageOrder.splice(_idx, 1); childPageOrder.splice(_idx, 1);
} }
@ -109,14 +120,14 @@ class Pages {
const hasChildPage = childPages.length > 0; const hasChildPage = childPages.length > 0;
prev[curr] = []; acc[curr.toString()] = [];
prev[curr].push(curr); acc[curr.toString()].push(curr);
/** /**
* It attaches 1 level page id to its parent page id * It attaches 1 level page id to its parent page id
*/ */
if (hasChildPage) { if (hasChildPage) {
prev[curr].push(...childPages[0].order); acc[curr.toString()].push(...childPages[0].order);
} }
/** /**
@ -127,8 +138,8 @@ class Pages {
orphanPageOrder.push(...childPageOrder); orphanPageOrder.push(...childPageOrder);
} }
return prev; return acc;
}, orderGroupedByParent); }, {} as Record<string, EntityId[]>);
let count = 0; let count = 0;
@ -159,9 +170,10 @@ class Pages {
/** /**
* It converts grouped pages(object) to array * It converts grouped pages(object) to array
*/ */
Object.values(orderGroupedByParent).flatMap(arr => [ ...arr ]) const result = Object.values(orderGroupedByParent)
.forEach(arr => { .flatMap(arr => [ ...arr ])
result.push(pages.get(arr)); .map(arr => {
return pagesMap.get(arr.toString()) as Page;
}); });
/** /**
@ -188,9 +200,9 @@ class Pages {
* @param {string} parent - id of parent page * @param {string} parent - id of parent page
* @returns {Array<?Page>} * @returns {Array<?Page>}
*/ */
public static removeChildren(pagesAvailable: Array<Page | null>, parent: string | undefined): Array<Page | null> { public static removeChildren(pagesAvailable: Array<Page | null>, parent: EntityId | undefined): Array<Page | null> {
pagesAvailable.forEach(async (item, index) => { pagesAvailable.forEach(async (item, index) => {
if (item === null || item._parent !== parent) { if (item === null || !isEqualIds(item._parent, parent)) {
return; return;
} }
pagesAvailable[index] = null; pagesAvailable[index] = null;
@ -278,7 +290,7 @@ class Pages {
* @param {string} id - page id * @param {string} id - page id
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
public static async remove(id: string): Promise<Page> { public static async remove(id: EntityId): Promise<Page> {
const page = await Page.get(id); const page = await Page.get(id);
if (!page._id) { if (!page._id) {
@ -291,6 +303,7 @@ class Pages {
await alias.destroy(); await alias.destroy();
} }
const removedPage = page.destroy(); const removedPage = page.destroy();
await PagesFlatArray.regenerate(); await PagesFlatArray.regenerate();
return removedPage; return removedPage;

View file

@ -1,6 +1,7 @@
import PageOrder from '../models/pageOrder.js'; import PageOrder from '../models/pageOrder.js';
import Page from '../models/page.js'; import Page from '../models/page.js';
import PagesFlatArray from '../models/pagesFlatArray.js'; import PagesFlatArray from '../models/pagesFlatArray.js';
import { EntityId } from '../utils/database/types.js';
/** /**
* @class PagesOrder * @class PagesOrder
@ -15,7 +16,7 @@ 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>}
*/ */
public static async get(parentId: string): Promise<PageOrder> { public static async get(parentId: EntityId): Promise<PageOrder> {
const order = await PageOrder.get(parentId); const order = await PageOrder.get(parentId);
if (!order._id) { if (!order._id) {
@ -58,7 +59,7 @@ 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
*/ */
public static async push(parentId: string, childId: string): Promise<void> { public static async push(parentId: EntityId, childId: EntityId): Promise<void> {
const order = await PageOrder.get(parentId); const order = await PageOrder.get(parentId);
order.push(childId); order.push(childId);
@ -73,7 +74,7 @@ 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
*/ */
public static async move(oldParentId: string, newParentId: string, targetPageId: string): Promise<void> { public static async move(oldParentId: EntityId, newParentId: EntityId, targetPageId: EntityId): Promise<void> {
const oldParentOrder = await PageOrder.get(oldParentId); const oldParentOrder = await PageOrder.get(oldParentId);
oldParentOrder.remove(targetPageId); oldParentOrder.remove(targetPageId);
@ -96,8 +97,9 @@ 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[]}
*/ */
public static async getOrderedChildren(pages: Page[], currentPageId: string, parentPageId: string, ignoreSelf = false): Promise<Page[]> { public static async getOrderedChildren(pages: Page[], currentPageId: EntityId, parentPageId: EntityId, ignoreSelf = false): Promise<Page[]> {
const children = await PageOrder.get(parentPageId); const children = await PageOrder.get(parentPageId);
console.log({children})
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
@ -122,7 +124,7 @@ class PagesOrder {
* @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
*/ */
public static async update(unordered: string[], currentPageId: string, parentPageId: string, putAbovePageId: string): Promise<void> { public static async update(unordered: string[], currentPageId: EntityId, parentPageId: EntityId, putAbovePageId: EntityId): Promise<void> {
const pageOrder = await PageOrder.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
@ -136,7 +138,7 @@ class PagesOrder {
* @param {string} parentId - identity of parent page * @param {string} parentId - identity of parent page
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public static async remove(parentId: string): Promise<void> { public static async remove(parentId: EntityId): Promise<void> {
const order = await PageOrder.get(parentId); const order = await PageOrder.get(parentId);
if (!order._id) { if (!order._id) {

View file

@ -5,7 +5,7 @@ import nodePath from 'path';
import File, { FileData } from '../models/file.js'; import File, { FileData } from '../models/file.js';
import crypto from '../utils/crypto.js'; import crypto from '../utils/crypto.js';
import deepMerge from '../utils/objects.js'; import deepMerge from '../utils/objects.js';
import appConfig from "../utils/appConfig.js"; import appConfig from '../utils/appConfig.js';
const random16 = crypto.random16; const random16 = crypto.random16;
@ -29,7 +29,6 @@ class Transport {
* @param {string} multerData.path - path to the uploaded file * @param {string} multerData.path - path to the uploaded file
* @param {number} multerData.size - size of the uploaded file * @param {number} multerData.size - size of the uploaded file
* @param {string} multerData.mimetype - MIME type 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 * @param {object} map - object that represents how should fields of File object should be mapped to response
* @returns {Promise<FileData>} * @returns {Promise<FileData>}
*/ */
@ -108,11 +107,10 @@ class Transport {
* *
* @param {File} file - file object * @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
*
*/ */
public static composeResponse(file: File, map: Dict): Dict { public static composeResponse(file: File, map: Dict): Dict {
const response: Dict = {}; const response: Dict = {};
const { data } = file; const data = file.data as Record<string, string | number | undefined>;
Object.entries(map).forEach(([name, path]) => { Object.entries(map).forEach(([name, path]) => {
const fields: string[] = path.split(':'); const fields: string[] = path.split(':');

View file

@ -1,46 +1,71 @@
import crypto from '../utils/crypto.js'; import crypto from '../utils/crypto.js';
import database from '../utils/database/index.js'; import database from '../utils/database/index.js';
import { EntityId } from '../utils/database/types.js';
const binaryMD5 = crypto.binaryMD5; const binaryMD5 = crypto.binaryMD5;
const aliasesDb = database['aliases']; const aliasesDb = database['aliases'];
/** /**
* @typedef {object} AliasData * Describe an alias
* @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
*
*/ */
export interface AliasData { export interface AliasData {
_id?: string; /**
* Alias id
*/
_id?: EntityId;
/**
* Alias binary hash
*/
hash?: string; hash?: string;
/**
* Entity type
*/
type?: string; type?: string;
/**
* Indicate if alias deprecated
*/
deprecated?: boolean; deprecated?: boolean;
id?: string;
/**
* Entity id
*/
id?: EntityId;
} }
/** /**
* @class Alias * Alias model
* @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
*/ */
class Alias { class Alias {
public _id?: string; /**
* Alias id
*/
public _id?: EntityId;
/**
* Alias binary hash
*/
public hash?: string; public hash?: string;
/**
* Entity type
*/
public type?: string; public type?: string;
/**
* Indicate if alias deprecated
*/
public deprecated?: boolean; public deprecated?: boolean;
public id?: string;
/**
* Entity id
*/
public id?: EntityId;
/** /**
* @class * @class
*
* @param {AliasData} data - info about alias * @param {AliasData} data - info about alias
* @param {string} aliasName - alias of entity * @param {string} aliasName - alias of entity
*/ */

View file

@ -1,10 +1,10 @@
import database from '../utils/database/index.js'; import database from '../utils/database/index.js';
import { EntityId } from '../utils/database/types.js';
const filesDb = database['files']; const filesDb = database['files'];
/** /**
* @typedef {object} FileData * @typedef {object} FileData
*
* @property {string} _id - file id * @property {string} _id - file id
* @property {string} name - original file name * @property {string} name - original file name
* @property {string} filename - name of uploaded file * @property {string} filename - name of uploaded file
@ -14,20 +14,18 @@ const filesDb = database['files'];
* @property {number} size - size of the file in * @property {number} size - size of the file in
*/ */
export interface FileData { export interface FileData {
_id?: string; _id?: EntityId;
name?: string; name?: string;
filename?: string; filename?: string;
path?: string; path?: string;
mimetype?: string; mimetype?: string;
url?: string; url?: string;
size?: number; size?: number;
[key: string]: string | number | undefined;
} }
/** /**
* @class File * @class File
* @class File model * @class File model
*
* @property {string} _id - file id * @property {string} _id - file id
* @property {string} name - original file name * @property {string} name - original file name
* @property {string} filename - name of uploaded file * @property {string} filename - name of uploaded file
@ -36,7 +34,7 @@ export interface FileData {
* @property {number} size - size of the file in * @property {number} size - size of the file in
*/ */
class File { class File {
public _id?: string; public _id?: EntityId;
public name?: string; public name?: string;
public filename?: string; public filename?: string;
public path?: string; public path?: string;
@ -46,7 +44,6 @@ class File {
/** /**
* @class * @class
*
* @param {FileData} data - info about file * @param {FileData} data - info about file
*/ */
constructor(data: FileData = {}) { constructor(data: FileData = {}) {

View file

@ -1,5 +1,6 @@
import urlify from '../utils/urlify.js'; import urlify from '../utils/urlify.js';
import database from '../utils/database/index.js'; import database from '../utils/database/index.js';
import { EntityId } from '../utils/database/types.js';
const pagesDb = database['pages']; const pagesDb = database['pages'];
@ -12,17 +13,16 @@ const pagesDb = database['pages'];
* @property {string} parent - id of parent page * @property {string} parent - id of parent page
*/ */
export interface PageData { export interface PageData {
_id?: string; _id?: EntityId;
title?: string; title?: string;
uri?: string; uri?: string;
body?: any; body?: any;
parent?: string; parent?: EntityId;
} }
/** /**
* @class Page * @class Page
* @class Page model * @class Page model
*
* @property {string} _id - page id * @property {string} _id - page id
* @property {string} title - page title * @property {string} title - page title
* @property {string} uri - page uri * @property {string} uri - page uri
@ -30,15 +30,14 @@ export interface PageData {
* @property {string} _parent - id of parent page * @property {string} _parent - id of parent page
*/ */
class Page { class Page {
public _id?: string; public _id?: EntityId;
public body?: any; public body?: any;
public title?: string; public title?: string;
public uri?: string; public uri?: string;
public _parent?: string; public _parent?: EntityId;
/** /**
* @class * @class
*
* @param {PageData} data - page's data * @param {PageData} data - page's data
*/ */
constructor(data: PageData = {}) { constructor(data: PageData = {}) {
@ -59,7 +58,7 @@ class Page {
* @param {string} _id - page id * @param {string} _id - page id
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
public static async get(_id: string): Promise<Page> { public static async get(_id: EntityId): Promise<Page> {
const data = await pagesDb.findOne({ _id }); const data = await pagesDb.findOne({ _id });
return new Page(data); return new Page(data);
@ -86,7 +85,7 @@ class Page {
public static async getAll(query: Record<string, unknown> = {}): Promise<Page[]> { public static async getAll(query: Record<string, unknown> = {}): Promise<Page[]> {
const docs = await pagesDb.find(query); const docs = await pagesDb.find(query);
return Promise.all(docs.map(doc => new Page(doc))); return docs.map(doc => new Page(doc));
} }
/** /**

View file

@ -1,4 +1,6 @@
import database from '../utils/database/index.js'; import database from '../utils/database/index.js';
import { ObjectId } from 'mongodb';
import { EntityId } from '../utils/database/types.js';
const db = database['pagesOrder']; const db = database['pagesOrder'];
@ -9,9 +11,9 @@ const db = database['pagesOrder'];
* @property {Array<string>} order - list of ordered pages * @property {Array<string>} order - list of ordered pages
*/ */
export interface PageOrderData { export interface PageOrderData {
_id?: string; _id?: EntityId;
page?: string; page?: EntityId;
order?: string[]; order?: EntityId[];
} }
/** /**
@ -21,14 +23,13 @@ export interface PageOrderData {
* Creates order for Pages with children * Creates order for Pages with children
*/ */
class PageOrder { class PageOrder {
public _id?: string; public _id?: EntityId;
public page?: string; public page?: EntityId;
private _order?: string[]; private _order?: EntityId[];
/** /**
* @class * @class
*
* @param {PageOrderData} data - info about pageOrder * @param {PageOrderData} data - info about pageOrder
*/ */
constructor(data: PageOrderData = {}) { constructor(data: PageOrderData = {}) {
@ -49,7 +50,7 @@ class PageOrder {
* @param {string} pageId - page's id * @param {string} pageId - page's id
* @returns {Promise<PageOrder>} * @returns {Promise<PageOrder>}
*/ */
public static async get(pageId: string): Promise<PageOrder> { public static async get(pageId: EntityId): Promise<PageOrder> {
const order = await db.findOne({ page: pageId }); const order = await db.findOne({ page: pageId });
let data: PageOrderData = {}; let data: PageOrderData = {};
@ -125,8 +126,8 @@ class PageOrder {
* *
* @param {string} pageId - page's id * @param {string} pageId - page's id
*/ */
public push(pageId: string | number): void { public push(pageId: EntityId): void {
if (typeof pageId === 'string') { if (typeof pageId === 'string' || pageId instanceof ObjectId) {
if (this.order === undefined) { if (this.order === undefined) {
this.order = []; this.order = [];
} }
@ -141,7 +142,7 @@ class PageOrder {
* *
* @param {string} pageId - page's id * @param {string} pageId - page's id
*/ */
public remove(pageId: string): void { public remove(pageId: EntityId): void {
if (this.order === undefined) { if (this.order === undefined) {
return; return;
} }
@ -156,10 +157,9 @@ 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}
*/ */
public putAbove(currentPageId: string, putAbovePageId: string): void { public putAbove(currentPageId: EntityId, putAbovePageId: EntityId): void {
if (this.order === undefined) { if (this.order === undefined) {
return; return;
} }
@ -182,7 +182,7 @@ class PageOrder {
* *
* @param {string} pageId - identity of page * @param {string} pageId - identity of page
*/ */
public getSubPageBefore(pageId: string): string | null { public getSubPageBefore(pageId: EntityId): EntityId | null {
if (this.order === undefined) { if (this.order === undefined) {
return null; return null;
} }
@ -204,7 +204,7 @@ class PageOrder {
* *
* @param pageId - identity of page * @param pageId - identity of page
*/ */
public getSubPageAfter(pageId: string): string | null { public getSubPageAfter(pageId: EntityId): EntityId | null {
if (this.order === undefined) { if (this.order === undefined) {
return null; return null;
} }
@ -224,7 +224,7 @@ class PageOrder {
/** /**
* @param {string[]} order - define new order * @param {string[]} order - define new order
*/ */
public set order(order: string[]) { public set order(order: EntityId[]) {
this._order = order; this._order = order;
} }
@ -233,7 +233,7 @@ class PageOrder {
* *
* @returns {string[]} * @returns {string[]}
*/ */
public get order(): string[] { public get order(): EntityId[] {
return this._order || []; return this._order || [];
} }

View file

@ -1,6 +1,7 @@
import Page from './page.js'; import Page from './page.js';
import PageOrder from './pageOrder.js'; import PageOrder from './pageOrder.js';
import NodeCache from 'node-cache'; import NodeCache from 'node-cache';
import { EntityId } from '../utils/database/types.js';
// Create cache for flat array // Create cache for flat array
const cache = new NodeCache({ stdTTL: 120 }); const cache = new NodeCache({ stdTTL: 120 });
@ -14,12 +15,12 @@ export interface PagesFlatArrayData {
/** /**
* Page id * Page id
*/ */
id: string; id: EntityId;
/** /**
* Page parent id * Page parent id
*/ */
parentId?: string; parentId?: EntityId;
/** /**
* id of parent with parent id '0' * id of parent with parent id '0'
@ -105,7 +106,7 @@ class PagesFlatArray {
* @param pageId - page id * @param pageId - page id
* @returns {Promise<PagesFlatArrayData | undefined>} * @returns {Promise<PagesFlatArrayData | undefined>}
*/ */
public static async getPageBefore(pageId: string): Promise<PagesFlatArrayData | undefined> { public static async getPageBefore(pageId: EntityId): Promise<PagesFlatArrayData | undefined> {
const arr = await this.get(); const arr = await this.get();
const pageIndex = arr.findIndex( (item) => item.id == pageId); const pageIndex = arr.findIndex( (item) => item.id == pageId);
@ -125,7 +126,7 @@ class PagesFlatArray {
* @param pageId - page id * @param pageId - page id
* @returns {Promise<PagesFlatArrayData | undefined>} * @returns {Promise<PagesFlatArrayData | undefined>}
*/ */
public static async getPageAfter(pageId: string): Promise<PagesFlatArrayData | undefined> { public static async getPageAfter(pageId: EntityId): Promise<PagesFlatArrayData | undefined> {
const arr = await this.get(); const arr = await this.get();
const pageIndex = arr.findIndex( (item) => item.id == pageId ); const pageIndex = arr.findIndex( (item) => item.id == pageId );
@ -148,7 +149,7 @@ class PagesFlatArray {
* @param orders - all page orders * @param orders - all page orders
* @returns {Promise<Array<PagesFlatArrayData>>} * @returns {Promise<Array<PagesFlatArrayData>>}
*/ */
private static getChildrenFlatArray(pageId: string, level: number, private static getChildrenFlatArray(pageId: EntityId, level: number,
pages: Array<Page>, orders: Array<PageOrder>): Array<PagesFlatArrayData> { pages: Array<Page>, orders: Array<PageOrder>): Array<PagesFlatArrayData> {
let arr: Array<PagesFlatArrayData> = new Array<PagesFlatArrayData>(); let arr: Array<PagesFlatArrayData> = new Array<PagesFlatArrayData>();

View file

@ -2,6 +2,7 @@ import express, { Request, Response } from 'express';
import multerFunc from 'multer'; import multerFunc from 'multer';
import Pages from '../../controllers/pages.js'; import Pages from '../../controllers/pages.js';
import PagesOrder from '../../controllers/pagesOrder.js'; import PagesOrder from '../../controllers/pagesOrder.js';
import { EntityId } from '../../utils/database/types.js';
const router = express.Router(); const router = express.Router();
const multer = multerFunc(); const multer = multerFunc();
@ -35,7 +36,7 @@ router.get('/page/:id', async (req: Request, res: Response) => {
*/ */
router.get('/pages', async (req: Request, res: Response) => { router.get('/pages', async (req: Request, res: Response) => {
try { try {
const pages = await Pages.getAll(); const pages = await Pages.getAllPages();
res.json({ res.json({
success: true, success: true,
@ -92,7 +93,7 @@ router.post('/page/:id', multer.none(), async (req: Request, res: Response) => {
try { try {
const { title, body, parent, putAbovePageId, uri } = req.body; const { title, body, parent, putAbovePageId, uri } = req.body;
const pages = await Pages.getAll(); const pages = await Pages.getAllPages();
let page = await Pages.get(id); let page = await Pages.get(id);
if (page._id === undefined) { if (page._id === undefined) {
@ -177,8 +178,8 @@ router.delete('/page/:id', async (req: Request, res: Response) => {
* @param {string} startFrom - start point to delete * @param {string} startFrom - start point to delete
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
const deleteRecursively = async (startFrom: string): Promise<void> => { const deleteRecursively = async (startFrom: EntityId): Promise<void> => {
let order: string[] = []; let order: EntityId[] = [];
try { try {
const children = await PagesOrder.get(startFrom); const children = await PagesOrder.get(startFrom);

View file

@ -4,7 +4,7 @@ import mime from 'mime';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import Transport from '../../controllers/transport.js'; import Transport from '../../controllers/transport.js';
import { random16 } from '../../utils/crypto.js'; import { random16 } from '../../utils/crypto.js';
import appConfig from "../../utils/appConfig.js"; import appConfig from '../../utils/appConfig.js';
const router = Router(); const router = Router();

View file

@ -1,7 +1,7 @@
import express, { Request, Response } from 'express'; import express, { Request, Response } from 'express';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import csrf from 'csurf'; import csrf from 'csurf';
import appConfig from "../utils/appConfig.js"; import appConfig from '../utils/appConfig.js';
const router = express.Router(); const router = express.Router();
const csrfProtection = csrf({ cookie: true }); const csrfProtection = csrf({ cookie: true });

View file

@ -4,6 +4,8 @@ import PagesOrder from '../../controllers/pagesOrder.js';
import Page from '../../models/page.js'; import Page from '../../models/page.js';
import asyncMiddleware from '../../utils/asyncMiddleware.js'; import asyncMiddleware from '../../utils/asyncMiddleware.js';
import PageOrder from '../../models/pageOrder.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 * 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 {PagesOrder[]} pagesOrder - list of pages order
* @param {number} level - max level recursion * @param {number} level - max level recursion
* @param {number} currentLevel - current level of element * @param {number} currentLevel - current level of element
*
* @returns {Page[]} * @returns {Page[]}
*/ */
function createMenuTree(parentPageId: string, pages: Page[], pagesOrder: PageOrder[], level = 1, currentLevel = 1): 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 * branch is a page children in tree
@ -27,12 +28,12 @@ function createMenuTree(parentPageId: string, pages: Page[], pagesOrder: PageOrd
let ordered: any[] = []; let ordered: any[] = [];
if (childrenOrder) { if (childrenOrder) {
ordered = childrenOrder.order.map((pageId: string) => { ordered = childrenOrder.order.map((pageId: EntityId) => {
return pages.find(page => page._id === pageId); 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])); 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'; const parentIdOfRootPages = '0';
try { try {
const pages = await Pages.getAll(); const pages = await Pages.getAllPages();
const pagesOrder = await PagesOrder.getAll(); const pagesOrder = await PagesOrder.getAll();
res.locals.menu = createMenuTree(parentIdOfRootPages, pages, pagesOrder, 2); res.locals.menu = createMenuTree(parentIdOfRootPages, pages, pagesOrder, 2);

View file

@ -1,6 +1,6 @@
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import appConfig from "../../utils/appConfig.js"; import appConfig from '../../utils/appConfig.js';
/** /**

View file

@ -4,6 +4,7 @@ import PagesOrder from '../controllers/pagesOrder.js';
import verifyToken from './middlewares/token.js'; import verifyToken from './middlewares/token.js';
import allowEdit from './middlewares/locals.js'; import allowEdit from './middlewares/locals.js';
import PagesFlatArray from '../models/pagesFlatArray.js'; import PagesFlatArray from '../models/pagesFlatArray.js';
import { toEntityId } from '../utils/database/index.js';
const router = express.Router(); const router = express.Router();
@ -28,7 +29,7 @@ router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Respon
* Edit page form * Edit page form
*/ */
router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => { 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 { try {
const page = await Pages.get(pageId); const page = await Pages.get(pageId);

View file

@ -2,7 +2,7 @@ import { loadConfig } from 'config-loader';
import * as process from 'process'; import * as process from 'process';
import arg from 'arg'; import arg from 'arg';
import path from 'path'; import path from 'path';
import { z } from "zod"; import { z } from 'zod';
/** /**
* Configuration for Hawk errors catcher * Configuration for Hawk errors catcher
@ -10,25 +10,25 @@ import { z } from "zod";
const HawkConfig = z.object({ const HawkConfig = z.object({
backendToken: z.string().optional(), // Hawk backend token backendToken: z.string().optional(), // Hawk backend token
frontendToken: z.string().optional(), // Hawk frontend token frontendToken: z.string().optional(), // Hawk frontend token
}) });
const LocalDatabaseConfig = z.object({ const LocalDatabaseConfig = z.object({
driver: z.literal('local'), driver: z.literal('local'),
local: z.object({ local: z.object({
path: z.string() path: z.string(),
}) }),
}) });
const MongoDatabaseConfig = z.object({ const MongoDatabaseConfig = z.object({
driver: z.literal('mongodb'), driver: z.literal('mongodb'),
mongodb: z.object({ mongodb: z.object({
uri: z.string() uri: z.string(),
}) }),
}) });
const AuthConfig = z.object({ const AuthConfig = z.object({
secret: z.string() // Secret for JWT secret: z.string(), // Secret for JWT
}) });
const FrontendConfig = z.object({ const FrontendConfig = z.object({
title: z.string(), // Title for pages title: z.string(), // Title for pages
@ -40,8 +40,9 @@ const FrontendConfig = z.object({
serve: z.string().optional(), // Carbon serve url serve: z.string().optional(), // Carbon serve url
placement: z.string().optional(), // Carbon placement 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 * Application configuration
@ -56,7 +57,7 @@ const AppConfig = z.object({
frontend: FrontendConfig, // Frontend configuration frontend: FrontendConfig, // Frontend configuration
auth: AuthConfig, // Auth configuration auth: AuthConfig, // Auth configuration
database: z.union([LocalDatabaseConfig, MongoDatabaseConfig]), // Database configuration database: z.union([LocalDatabaseConfig, MongoDatabaseConfig]), // Database configuration
}) });
export type AppConfig = z.infer<typeof AppConfig>; export type AppConfig = z.infer<typeof AppConfig>;
@ -66,7 +67,7 @@ const args = arg({ /* eslint-disable @typescript-eslint/naming-convention */
}); });
const cwd = process.cwd(); 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)) { if (path.isAbsolute(configPath)) {
return configPath; return configPath;
} }
@ -76,6 +77,6 @@ const paths = (args['--config'] || ['./app-config.yaml']).map((configPath) => {
const loadedConfig = loadConfig<AppConfig>(...paths); const loadedConfig = loadConfig<AppConfig>(...paths);
const appConfig = AppConfig.parse(loadedConfig) const appConfig = AppConfig.parse(loadedConfig);
export default appConfig; export default appConfig;

View file

@ -2,12 +2,26 @@ import { AliasData } from '../../models/alias.js';
import { FileData } from '../../models/file.js'; import { FileData } from '../../models/file.js';
import { PageData } from '../../models/page.js'; import { PageData } from '../../models/page.js';
import { PageOrderData } from '../../models/pageOrder.js'; import { PageOrderData } from '../../models/pageOrder.js';
import appConfig from "../appConfig.js"; import appConfig from '../appConfig.js';
import LocalDatabaseDriver from "./local.js"; import LocalDatabaseDriver from './local.js';
import MongoDatabaseDriver from "./mongodb.js"; import MongoDatabaseDriver from './mongodb.js';
import { EntityId } from './types.js';
import { ObjectId } from 'mongodb';
const Database = appConfig.database.driver === 'mongodb' ? MongoDatabaseDriver : LocalDatabaseDriver; 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 { export default {
pages: new Database<PageData>('pages'), pages: new Database<PageData>('pages'),
aliases: new Database<AliasData>('aliases'), aliases: new Database<AliasData>('aliases'),

View file

@ -1,7 +1,7 @@
import Datastore from "nedb"; import Datastore from 'nedb';
import {DatabaseDriver, Options, RejectFunction, ResolveFunction} from "./types.js"; import { DatabaseDriver, Options, RejectFunction, ResolveFunction } from './types.js';
import path from "path"; import path from 'path';
import appConfig from "../appConfig.js"; import appConfig from '../appConfig.js';
/** /**
* Init function for nedb instance * Init function for nedb instance
@ -11,9 +11,11 @@ import appConfig from "../appConfig.js";
*/ */
function initDb(name: string): Datastore { function initDb(name: string): Datastore {
const dbConfig = appConfig.database.driver === 'local' ? appConfig.database.local : null; const dbConfig = appConfig.database.driver === 'local' ? appConfig.database.local : null;
if (!dbConfig) { if (!dbConfig) {
throw new Error('Database config is not initialized'); throw new Error('Database config is not initialized');
} }
return new Datastore({ return new Datastore({
filename: path.resolve(`${dbConfig.path}/${name}.db`), filename: path.resolve(`${dbConfig.path}/${name}.db`),
autoload: true, autoload: true,
@ -41,9 +43,8 @@ export default class LocalDatabaseDriver<DocType> implements DatabaseDriver<DocT
* 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
*/ */
public async insert(doc: DocType): Promise<DocType> { 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) => {
@ -59,10 +60,9 @@ export default class LocalDatabaseDriver<DocType> implements DatabaseDriver<DocT
* 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
*/ */
public async find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>> { public async find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>> {
const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => { const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => {
@ -86,10 +86,9 @@ export default class LocalDatabaseDriver<DocType> implements DatabaseDriver<DocT
* 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
*/ */
public async findOne(query: Record<string, unknown>, projection?: DocType): Promise<DocType> { public async findOne(query: Record<string, unknown>, projection?: DocType): Promise<DocType> {
const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => { const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => {
@ -113,11 +112,10 @@ export default class LocalDatabaseDriver<DocType> implements DatabaseDriver<DocT
* 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 {Options} options - optional params * @param {Options} options - optional params
* @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
*/ */
public async update(query: Record<string, unknown>, update: DocType, options: Options = {}): Promise<number|boolean|Array<DocType>> { 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) => {
@ -145,8 +143,7 @@ export default class LocalDatabaseDriver<DocType> implements DatabaseDriver<DocT
* 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 {Options} options - optional params * @param {Options} options - optional params
* @returns {Promise<number|Error>} - number of removed rows or Error object * @returns {Promise<number|Error>} - number of removed rows or Error object
*/ */

View file

@ -1,6 +1,6 @@
import {Collection, Filter, MongoClient, OptionalUnlessRequiredId, UpdateFilter} from 'mongodb'; import { Collection, Filter, MongoClient, OptionalUnlessRequiredId, UpdateFilter } from 'mongodb';
import {DatabaseDriver, Options} from "./types.js"; import { DatabaseDriver, Options } from './types.js';
import appConfig from "../appConfig.js"; import appConfig from '../appConfig.js';
const mongodbUri = appConfig.database.driver === 'mongodb' ? appConfig.database.mongodb.uri : null; const mongodbUri = appConfig.database.driver === 'mongodb' ? appConfig.database.mongodb.uri : null;
const mongodbClient = mongodbUri ? await MongoClient.connect(mongodbUri): null; const mongodbClient = mongodbUri ? await MongoClient.connect(mongodbUri): null;
@ -15,6 +15,10 @@ export default class MongoDatabaseDriver<DocType> implements DatabaseDriver<DocT
private db: MongoClient; private db: MongoClient;
private collection: Collection<DocType>; private collection: Collection<DocType>;
/**
*
* @param collectionName
*/
constructor(collectionName: string) { constructor(collectionName: string) {
if (!mongodbClient) { if (!mongodbClient) {
throw new Error('MongoDB client is not initialized'); throw new Error('MongoDB client is not initialized');
@ -26,47 +30,46 @@ export default class MongoDatabaseDriver<DocType> implements DatabaseDriver<DocT
/** /**
* Insert new document into the database * Insert new document into the database
* *
* @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
*/ */
public async insert(doc: DocType): Promise<DocType> { public async insert(doc: DocType): Promise<DocType> {
const result = await this.collection.insertOne(doc as OptionalUnlessRequiredId<DocType>); const result = await this.collection.insertOne(doc as OptionalUnlessRequiredId<DocType>);
return { return {
...doc, ...doc,
_id: result.insertedId, _id: result.insertedId,
} };
} }
/** /**
* Find documents that match passed query * Find documents that match passed query
* *
* @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
*/ */
public async find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>> { public async find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>> {
const cursor = this.collection.find(query as Filter<DocType>) const cursor = this.collection.find(query as Filter<DocType>);
if (projection) { if (projection) {
cursor.project(projection); cursor.project(projection);
} }
const docs = await cursor.toArray(); const docs = await cursor.toArray();
return docs as unknown as Array<DocType>; return docs as unknown as Array<DocType>;
} }
/** /**
* Find one document matches passed query * Find one document matches passed query
* *
* @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
*/ */
public async findOne(query: Record<string, unknown>, projection?: DocType): Promise<DocType> { public async findOne(query: Record<string, unknown>, projection?: DocType): Promise<DocType> {
const doc = await this.collection.findOne(query as Filter<DocType>, {projection}); const doc = await this.collection.findOne(query as Filter<DocType>, { projection });
if (!doc) {
throw new Error('Document not found');
}
return doc as unknown as DocType; return doc as unknown as DocType;
} }
@ -74,39 +77,41 @@ export default class MongoDatabaseDriver<DocType> implements DatabaseDriver<DocT
/** /**
* Update document matches query * Update document matches query
* *
* @param {Object} query - query object * @param {object} query - query object
* @param {Object} update - fields to update * @param {object} update - fields to update
* @param {Options} options - optional params * @param {Options} options - optional params
* @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
*/ */
public async update(query: Record<string, unknown>, update: DocType, options: Options = {}): Promise<number|boolean|Array<DocType>> { public async update(query: Record<string, unknown>, update: DocType, options: Options = {}): Promise<number|boolean|Array<DocType>> {
const updateDocument = { const updateDocument = {
$set: update $set: update,
} as UpdateFilter<DocType>; } as UpdateFilter<DocType>;
const result = await this.collection.updateMany(query as Filter<DocType>, updateDocument, options); const result = await this.collection.updateMany(query as Filter<DocType>, updateDocument, options);
switch (true) { switch (true) {
case options.returnUpdatedDocs: case options.returnUpdatedDocs:
return result.modifiedCount return result.modifiedCount;
case options.upsert: case options.upsert:
if (result.modifiedCount) { if (result.modifiedCount) {
return result.modifiedCount; return result.modifiedCount;
} }
return result as DocType[]
return result as DocType[];
default: default:
return result as DocType[] return result as DocType[];
} }
} }
/** /**
* Remove document matches passed query * Remove document matches passed query
* *
* @param {Object} query - query object * @param {object} query - query object
* @param {Options} options - optional params * @param {Options} options - optional params
* @returns {Promise<number|Error>} - number of removed rows or Error object * @returns {Promise<number|Error>} - number of removed rows or Error object
*/ */
public async remove(query: Record<string, unknown>, options: Options = {}): Promise<number> { public async remove(query: Record<string, unknown>, options: Options = {}): Promise<number> {
const result = await this.collection.deleteMany(query as Filter<DocType>); const result = await this.collection.deleteMany(query as Filter<DocType>);
return result.deletedCount; return result.deletedCount;
} }
} }

View file

@ -1,3 +1,5 @@
import { ObjectId } from 'mongodb';
export interface DatabaseDriver<DocType> { export interface DatabaseDriver<DocType> {
insert(doc: DocType): Promise<DocType>; insert(doc: DocType): Promise<DocType>;
find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>>; find(query: Record<string, unknown>, projection?: DocType): Promise<Array<DocType>>;
@ -6,6 +8,8 @@ export interface DatabaseDriver<DocType> {
remove(query: Record<string, unknown>, options: Options): Promise<number> remove(query: Record<string, unknown>, options: Options): Promise<number>
} }
export type EntityId = string | ObjectId;
/** /**
* @typedef Options - optional params * @typedef Options - optional params
* @param {boolean} multi - (false) allows to take action to several documents * @param {boolean} multi - (false) allows to take action to several documents