1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-24 07:39: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:
Nikita Melnikov 2022-03-05 22:57:23 +04:00 committed by GitHub
parent 059cfb96f9
commit 34514761f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 3817 additions and 2249 deletions

162
src/backend/models/alias.ts Normal file
View file

@ -0,0 +1,162 @@
import crypto from '../utils/crypto';
import database from '../utils/database/index';
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
*
*/
export interface AliasData {
_id?: string;
hash?: string;
type?: string;
deprecated?: boolean;
id?: string;
}
/**
* @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
*/
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
*
* @returns {object}
*/
public static get types(): { PAGE: string } {
return {
PAGE: 'page',
};
}
/**
* Find and return alias with given alias
*
* @param {string} aliasName - alias of entity
* @returns {Promise<Alias>}
*/
public static async get(aliasName: string): Promise<Alias> {
const hash = binaryMD5(aliasName);
let data = await aliasesDb.findOne({
hash: hash,
deprecated: false,
});
if (!data) {
data = await aliasesDb.findOne({ hash: hash });
}
return new Alias(data);
}
/**
* Mark alias as deprecated
*
* @param {string} aliasName - alias of entity
* @returns {Promise<Alias>}
*/
public static async markAsDeprecated(aliasName: string): Promise<Alias> {
const alias = await Alias.get(aliasName);
alias.deprecated = true;
return alias.save();
}
/**
* Save or update alias data in the database
*
* @returns {Promise<Alias>}
*/
public async save(): Promise<Alias> {
if (!this._id) {
const insertedRow = await aliasesDb.insert(this.data) as { _id: string };
this._id = insertedRow._id;
} else {
await aliasesDb.update({ _id: this._id }, this.data);
}
return this;
}
/**
* Set AliasData object fields to internal model fields
*
* @param {AliasData} aliasData - info about alias
*/
public set data(aliasData: AliasData) {
const { id, type, hash, deprecated } = aliasData;
this.id = id || this.id;
this.type = type || this.type;
this.hash = hash || this.hash;
this.deprecated = deprecated || false;
}
/**
* Return AliasData object
*
* @returns {AliasData}
*/
public get data(): AliasData {
return {
_id: this._id,
id: this.id,
type: this.type,
hash: this.hash,
deprecated: this.deprecated,
};
}
/**
* @returns {Promise<Alias>}
*/
public async destroy(): Promise<Alias> {
await aliasesDb.remove({ _id: this._id });
delete this._id;
return this;
}
}
export default Alias;

177
src/backend/models/file.ts Normal file
View file

@ -0,0 +1,177 @@
import database from '../utils/database/index';
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
* @property {string} path - path to uploaded file
* @property {string} mimetype - file MIME type
* @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 model
*
* @property {string} _id - file id
* @property {string} name - original file name
* @property {string} filename - name of uploaded file
* @property {string} path - path to uploaded file
* @property {string} mimetype - file MIME type
* @property {number} size - size of the file in
*/
class File {
public _id?: string;
public name?: string;
public filename?: string;
public path?: string;
public mimetype?: string;
public size?: number;
/**
* @class
*
* @param {FileData} data - info about file
*/
constructor(data: FileData = {}) {
if (data === null) {
data = {};
}
if (data._id) {
this._id = data._id;
}
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
*
* @param {FileData} fileData - info about file
*/
public set data(fileData: FileData) {
const { name, filename, path, mimetype, size } = fileData;
this.name = name || this.name;
this.filename = filename || this.filename;
this.path = path ? this.processPath(path) : this.path;
this.mimetype = mimetype || this.mimetype;
this.size = size || this.size;
}
/**
* Return FileData object
*
* @returns {FileData}
*/
public get data(): FileData {
return {
_id: this._id,
name: this.name,
filename: this.filename,
path: this.path,
mimetype: this.mimetype,
size: this.size,
};
}
/**
* Save or update file data in the database
*
* @returns {Promise<File>}
*/
public async save(): Promise<File> {
if (!this._id) {
const insertedRow = await filesDb.insert(this.data) as { _id: string };
this._id = insertedRow._id;
} else {
await filesDb.update({ _id: this._id }, this.data);
}
return this;
}
/**
* Remove file data from the database
*
* @returns {Promise<File>}
*/
public async destroy(): Promise<File> {
await filesDb.remove({ _id: this._id });
delete this._id;
return this;
}
/**
* Return readable file data
*
* @returns {FileData}
*/
public toJSON(): FileData {
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/, '');
}
}
export default File;

246
src/backend/models/page.ts Normal file
View file

@ -0,0 +1,246 @@
import urlify from '../utils/urlify';
import database from '../utils/database/index';
const pagesDb = database['pages'];
/**
* @typedef {object} PageData
* @property {string} _id - page id
* @property {string} title - page title
* @property {string} uri - page uri
* @property {*} body - page body
* @property {string} parent - id of parent page
*/
export interface PageData {
_id?: string;
title?: string;
uri?: string;
body?: any;
parent?: string;
}
/**
* @class Page
* @class Page model
*
* @property {string} _id - page id
* @property {string} title - page title
* @property {string} uri - page uri
* @property {*} body - page body
* @property {string} _parent - id of parent page
*/
class Page {
public _id?: string;
public body?: any;
public title?: string;
public uri?: string;
public _parent?: string;
/**
* @class
*
* @param {PageData} data - page's data
*/
constructor(data: PageData = {}) {
if (data === null) {
data = {};
}
if (data._id) {
this._id = data._id;
}
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
*
* @param {PageData} pageData - page's data
*/
public set data(pageData: PageData) {
const { body, parent, uri } = pageData;
this.body = body || this.body;
this.title = this.extractTitleFromBody();
this.uri = uri || '';
this._parent = parent || this._parent || '0';
}
/**
* Return PageData object
*
* @returns {PageData}
*/
public get data(): PageData {
return {
_id: this._id,
title: this.title,
uri: this.uri,
body: this.body,
parent: this._parent,
};
}
/**
* Link given page as parent
*
* @param {Page} parentPage - the page to be set as parent
*/
public set parent(parentPage: Page) {
this._parent = parentPage._id;
}
/**
* Return parent page model
*
* @returns {Promise<Page>}
*/
public async getParent(): Promise<Page> {
const data = await pagesDb.findOne({ _id: this._parent });
return new Page(data);
}
/**
* Return child pages models
*
* @returns {Promise<Page[]>}
*/
public get children(): Promise<Page[]> {
return pagesDb.find({ parent: this._id })
.then(data => {
return data.map(page => new Page(page));
});
}
/**
* Save or update page data in the database
*
* @returns {Promise<Page>}
*/
public async save(): Promise<Page> {
if (this.uri !== undefined) {
this.uri = await this.composeUri(this.uri);
}
if (!this._id) {
const insertedRow = await pagesDb.insert(this.data) as { _id: string };
this._id = insertedRow._id;
} else {
await pagesDb.update({ _id: this._id }, this.data);
}
return this;
}
/**
* Remove page data from the database
*
* @returns {Promise<Page>}
*/
public async destroy(): Promise<Page> {
await pagesDb.remove({ _id: this._id });
delete this._id;
return this;
}
/**
* Return readable page data
*
* @returns {PageData}
*/
public toJSON(): PageData {
return this.data;
}
/**
* Find and return available uri
*
* @returns {Promise<string>}
* @param uri - input uri to be composed
*/
private async composeUri(uri: string): Promise<string> {
let pageWithSameUriCount = 0;
if (!this._id) {
uri = this.transformTitleToUri();
}
if (uri) {
let pageWithSameUri = await Page.getByUri(uri);
while (pageWithSameUri._id && pageWithSameUri._id !== this._id) {
pageWithSameUriCount++;
pageWithSameUri = await Page.getByUri(uri + `-${pageWithSameUriCount}`);
}
}
return pageWithSameUriCount ? uri + `-${pageWithSameUriCount}` : uri;
}
/**
* Extract first header from editor data
*
* @returns {string}
*/
private extractTitleFromBody(): string {
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);
}
}
export default Page;

View file

@ -0,0 +1,247 @@
import database from '../utils/database/index';
const db = database['pagesOrder'];
/**
* @typedef {object} PageOrderData
* @property {string} _id - row unique id
* @property {string} page - page id
* @property {Array<string>} order - list of ordered pages
*/
export interface PageOrderData {
_id?: string;
page?: string;
order?: string[];
}
/**
* @class PageOrder
* @classdesc PageOrder
*
* Creates order for Pages with children
*/
class PageOrder {
public _id?: string;
public page?: string;
private _order?: string[];
/**
* @class
*
* @param {PageOrderData} data - info about pageOrder
*/
constructor(data: PageOrderData = {}) {
if (data === null) {
data = {};
}
if (data._id) {
this._id = data._id;
}
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
*
* @param {PageOrderData} pageOrderData - info about pageOrder
*/
public set data(pageOrderData: PageOrderData) {
this.page = pageOrderData.page || '0';
this.order = pageOrderData.order || [];
}
/**
* Return Page Children order
*
* @returns {PageOrderData}
*/
public get data(): PageOrderData {
return {
_id: this._id,
page: '' + this.page,
order: this.order,
};
}
/**
* Pushes page id to the orders array
*
* @param {string} pageId - page's id
*/
public push(pageId: string | number): void {
if (typeof pageId === 'string') {
if (this.order === undefined) {
this.order = [];
}
this.order.push(pageId);
} else {
throw new Error('given id is not string');
}
}
/**
* Removes page id from orders array
*
* @param {string} pageId - page's id
*/
public remove(pageId: string): void {
if (this.order === undefined) {
return;
}
const found = this.order.indexOf(pageId);
if (found >= 0) {
this.order.splice(found, 1);
}
}
/**
* @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 {
if (this.order === undefined) {
return;
}
const found1 = this.order.indexOf(putAbovePageId);
const found2 = this.order.indexOf(currentPageId);
if (found1 === -1 || found2 === -1) {
return;
}
const margin = found1 < found2 ? 1 : 0;
this.order.splice(found1, 0, currentPageId);
this.order.splice(found2 + margin, 1);
}
/**
* Returns page before passed page with id
*
* @param {string} pageId - identity of page
*/
public getPageBefore(pageId: string): string | null {
if (this.order === undefined) {
return null;
}
const currentPageInOrder = this.order.indexOf(pageId);
/**
* If page not found or first return nothing
*/
if (currentPageInOrder <= 0) {
return null;
}
return this.order[currentPageInOrder - 1];
}
/**
* Returns page before passed page with id
*
* @param pageId - identity of page
*/
public getPageAfter(pageId: string): string | null {
if (this.order === undefined) {
return null;
}
const currentPageInOrder = this.order.indexOf(pageId);
/**
* If page not found or is last
*/
if (currentPageInOrder === -1 || currentPageInOrder === this.order.length - 1) {
return null;
}
return this.order[currentPageInOrder + 1];
}
/**
* @param {string[]} order - define new order
*/
public set order(order: string[]) {
this._order = order;
}
/**
* Returns ordered list
*
* @returns {string[]}
*/
public get order(): string[] {
return this._order || [];
}
/**
* Save or update page data in the database
*
* @returns {Promise<PageOrder>}
*/
public async save(): Promise<PageOrder> {
if (!this._id) {
const insertedRow = await db.insert(this.data) as { _id: string};
this._id = insertedRow._id;
} else {
await db.update({ _id: this._id }, this.data);
}
return this;
}
/**
* Remove page data from the database
*
* @returns {Promise<void>}
*/
public async destroy(): Promise<void> {
await db.remove({ _id: this._id });
delete this._id;
}
}
export default PageOrder;

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