diff --git a/lib/app.js b/lib/app.js index cb3fa4923..76915f12f 100644 --- a/lib/app.js +++ b/lib/app.js @@ -257,7 +257,7 @@ function startListen () { models.sequelize.authenticate().then(function () { // check if realtime is ready if (realtime.isReady()) { - models.Revision.checkAllNotesRevision(function (err, notes) { + Revision.checkAllNotesRevision(function (err, notes) { if (err) throw new Error(err) if (!notes || notes.length <= 0) return startListen() }) diff --git a/lib/models/author.ts b/lib/models/author.ts index 8468d3d9e..e6bd78f2a 100644 --- a/lib/models/author.ts +++ b/lib/models/author.ts @@ -1,43 +1,32 @@ -import {DataTypes} from 'sequelize'; +import { AutoIncrement, Table, Column, DataType, PrimaryKey, Model, BelongsTo, createIndexDecorator, ForeignKey } from 'sequelize-typescript' +import { Note } from './note'; +import { User } from './user'; +const NoteUserIndex = createIndexDecorator({unique: true}); -function createAuthorModel(sequelize) { - const Author = sequelize.define('Author', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - color: { - type: DataTypes.STRING - } - }, { - indexes: [ - { - unique: true, - fields: ['noteId', 'userId'] - } - ] - }); +@Table +export class Author extends Model { + @PrimaryKey + @AutoIncrement + @Column(DataType.INTEGER) + id: number; - Author.associate = function (models) { - Author.belongsTo(models.Note, { - foreignKey: 'noteId', - as: 'note', - constraints: false, - onDelete: 'CASCADE', - hooks: true - }); - Author.belongsTo(models.User, { - foreignKey: 'userId', - as: 'user', - constraints: false, - onDelete: 'CASCADE', - hooks: true - }) - }; + @Column(DataType.STRING) + color: string; - return Author + @ForeignKey(() => Note) + @NoteUserIndex + @Column + noteId: string; + + @BelongsTo(() => Note, { foreignKey: 'noteId', onDelete: 'CASCADE', constraints: false, hooks: true }) + note: Note; + + @ForeignKey(() => User) + @NoteUserIndex + @Column + userId: string; + + @BelongsTo(() => User, { foreignKey: 'userId', onDelete: 'CASCADE', constraints: false, hooks: true }) + user: User; } - -export = createAuthorModel diff --git a/lib/models/index.js b/lib/models/index.ts similarity index 62% rename from lib/models/index.js rename to lib/models/index.ts index 88c1b1689..a2fcd7c13 100644 --- a/lib/models/index.js +++ b/lib/models/index.ts @@ -1,8 +1,11 @@ -'use strict' -// external modules +import {Sequelize} from 'sequelize-typescript'; +import { Author } from './author'; +import { Note } from './note'; +import { Revision } from './revision'; +import { Temp } from './temp'; +import { User } from './user'; var fs = require('fs') -var path = require('path') -var Sequelize = require('sequelize') +var path = require('path'); const { cloneDeep } = require('lodash') // core @@ -14,7 +17,7 @@ dbconfig.logging = config.debug ? (data) => { logger.info(data) } : false -var sequelize = null +var sequelize: any = null; // Heroku specific if (config.dbURL) { @@ -34,26 +37,25 @@ sequelize.stripNullByte = stripNullByte function processData (data, _default, process) { if (data === undefined) return data - else return data === null ? _default : (process ? process(data) : data) + else if (process) { + if (data === null) { + return _default + } else { + return process(data) + } + } else { + if (data === null) { + return _default + } else { + return data + } + } } sequelize.processData = processData -var db = {} +var db: any = {} -fs.readdirSync(__dirname) - .filter(function (file) { - return (file.indexOf('.') !== 0) && (file !== 'index.js') - }) - .forEach(function (file) { - var model = sequelize.import(path.join(__dirname, file)) - db[model.name] = model - }) - -Object.keys(db).forEach(function (modelName) { - if ('associate' in db[modelName]) { - db[modelName].associate(db) - } -}) +sequelize.addModels([Author, Note, Revision, Temp, User]); db.sequelize = sequelize db.Sequelize = Sequelize diff --git a/lib/models/note.js b/lib/models/note.ts similarity index 74% rename from lib/models/note.js rename to lib/models/note.ts index 141402ac0..c8777b076 100644 --- a/lib/models/note.js +++ b/lib/models/note.ts @@ -1,4 +1,15 @@ -'use strict' +import { AllowNull, AfterCreate, BeforeCreate, Default, Unique, IsUUID, Model, PrimaryKey, Column, DataType, Table, BelongsTo, HasMany, ForeignKey } from "sequelize-typescript"; +import { generate as shortIdGenerate, isValid as shortIdIsValid } from "shortid"; +import { Author } from "./author"; +import { User } from './user'; +import { Revision } from './revision'; + +// external modules +// external modules +var fs = require('fs') +var path = require('path') +var LZString = require('lz-string') + // external modules var fs = require('fs') var path = require('path') @@ -7,7 +18,6 @@ var base64url = require('base64url') var md = require('markdown-it')() var metaMarked = require('meta-marked') var cheerio = require('cheerio') -var shortId = require('shortid') var Sequelize = require('sequelize') var async = require('async') var moment = require('moment') @@ -23,156 +33,157 @@ var logger = require('../logger') var ot = require('../ot') // permission types -var permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', 'private'] +enum PermissionEnum { + freely = "freely", + editable = "editable", + limited = "limited", + locked = "locked", + protected = "protected", + private = "private" +}; -module.exports = function (sequelize, DataTypes) { - var Note = sequelize.define('Note', { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: Sequelize.UUIDV4 - }, - shortid: { - type: DataTypes.STRING, - unique: true, - allowNull: false, - defaultValue: shortId.generate - }, - alias: { - type: DataTypes.STRING, - unique: true - }, - permission: { - type: DataTypes.ENUM, - values: permissionTypes - }, - viewcount: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0 - }, - title: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('title'), '') - }, - set: function (value) { - this.setDataValue('title', sequelize.stripNullByte(value)) - } - }, - content: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('content'), '') - }, - set: function (value) { - this.setDataValue('content', sequelize.stripNullByte(value)) - } - }, - authorship: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) - }, - set: function (value) { - this.setDataValue('authorship', JSON.stringify(value)) - } - }, - lastchangeAt: { - type: DataTypes.DATE - }, - savedAt: { - type: DataTypes.DATE - } - }, { - paranoid: false, - hooks: { - beforeCreate: function (note, options) { - return new Promise(function (resolve, reject) { - // if no content specified then use default note - if (!note.content) { - var body = null - let filePath = null - if (!note.alias) { - filePath = config.defaultNotePath - } else { - filePath = path.join(config.docsPath, note.alias + '.md') - } - if (Note.checkFileExist(filePath)) { - var fsCreatedTime = moment(fs.statSync(filePath).ctime) - body = fs.readFileSync(filePath, 'utf8') - note.title = Note.parseNoteTitle(body) - note.content = body - if (filePath !== config.defaultNotePath) { - note.createdAt = fsCreatedTime - } - } - } - // if no permission specified and have owner then give default permission in config, else default permission is freely - if (!note.permission) { - if (note.ownerId) { - note.permission = config.defaultPermission - } else { - note.permission = 'freely' - } - } - return resolve(note) - }) - }, - afterCreate: function (note, options, callback) { - return new Promise(function (resolve, reject) { - sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { - if (err) { - return reject(err) - } - return resolve(note) - }) - }) - } - } - }) +@Table({ paranoid: false }) +export class Note extends Model { + @PrimaryKey + @Default(Sequelize.UUIDV4) + @Column(DataType.UUID) + id: string; - Note.associate = function (models) { - Note.belongsTo(models.User, { - foreignKey: 'ownerId', - as: 'owner', - constraints: false, - onDelete: 'CASCADE', - hooks: true - }) - Note.belongsTo(models.User, { - foreignKey: 'lastchangeuserId', - as: 'lastchangeuser', - constraints: false - }) - Note.hasMany(models.Revision, { - foreignKey: 'noteId', - constraints: false - }) - Note.hasMany(models.Author, { - foreignKey: 'noteId', - as: 'authors', - constraints: false + @AllowNull(false) + @Default(shortIdGenerate) + @Unique + @Column(DataType.STRING) + shortid: string; + + @Unique + @Column(DataType.STRING) + alias: string; + + @Column(DataType.ENUM({values: Object.keys(PermissionEnum).map(k => PermissionEnum[k as any])})) + permission: PermissionEnum; + + @AllowNull(false) + @Default(0) + @Column(DataType.INTEGER) + viewcount: number; + + @Column(DataType.TEXT) + get title(): string { + return Sequelize.processData(this.getDataValue('title'), '') + } + set title(value: string) { + this.setDataValue('title', Sequelize.stripNullByte(value)) + } + + @Column(DataType.TEXT({ length: 'long' })) + get content(): string { + return Sequelize.processData(this.getDataValue('content'), '') + } + set content(value: string) { + this.setDataValue('content', Sequelize.stripNullByte(value)) + } + + @Column(DataType.TEXT({ length: 'long' })) + get authorship(): string { + return Sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) + } + set authorship(value: string) { + this.setDataValue('authorship', JSON.stringify(value)) + } + + // ToDo: use @UpdatedAt instead? (https://www.npmjs.com/package/sequelize-typescript#createdat--updatedat--deletedat) + @Column(DataType.DATE) + lastchangeAt: Date; + + // ToDo: use @UpdatedAt instead? (https://www.npmjs.com/package/sequelize-typescript#createdat--updatedat--deletedat) + @Column(DataType.DATE) + savedAt: Date; + + @ForeignKey(() => User) + @Column + ownerId: string; + + @BelongsTo(() => User, { foreignKey: 'ownerId', constraints: false, onDelete: 'CASCADE', hooks: true }) + owner: User; + + @ForeignKey(() => User) + @Column + lastchangeuserId: string; + + @BelongsTo(() => User, { foreignKey: 'lastchangeuserId', constraints: false }) + lastchangeuser: User; + + @HasMany(() => Revision, { foreignKey: 'noteId', constraints: false }) + revisions: Revision[]; + + @HasMany(() => Author, { foreignKey: 'noteId', constraints: false }) + authors: Author[]; + + @BeforeCreate + static defaultContentAndPermissions(note: Note) { + return new Promise(function (resolve, reject) { + // if no content specified then use default note + if (!note.content) { + let filePath = null + if (!note.alias) { + filePath = config.defaultNotePath + } else { + filePath = path.join(config.docsPath, note.alias + '.md') + } + if (Note.checkFileExist(filePath)) { + var fsCreatedTime = moment(fs.statSync(filePath).ctime) + const body = fs.readFileSync(filePath, 'utf8') + note.title = Note.parseNoteTitle(body) + note.content = body + if (filePath !== config.defaultNotePath) { + note.createdAt = fsCreatedTime + } + } + } + // if no permission specified and have owner then give default permission in config, else default permission is freely + if (!note.permission) { + if (note.owner) { + note.permission = config.defaultPermission + } else { + note.permission = PermissionEnum.freely + } + } + return resolve(note) }) } - Note.checkFileExist = function (filePath) { + + @AfterCreate + static saveRevision(note) { + return new Promise(function (resolve, reject) { + Sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { + if (err) { + return reject(err) + } + return resolve(note) + }) + }) + } + + static checkFileExist(filePath) { try { return fs.statSync(filePath).isFile() } catch (err) { return false } } - Note.encodeNoteId = function (id) { + + static encodeNoteId(id) { // remove dashes in UUID and encode in url-safe base64 let str = id.replace(/-/g, '') let hexStr = Buffer.from(str, 'hex') return base64url.encode(hexStr) } - Note.decodeNoteId = function (encodedId) { + + static decodeNoteId(encodedId) { // decode from url-safe base64 - let id = base64url.toBuffer(encodedId).toString('hex') + let id: string = base64url.toBuffer(encodedId).toString('hex') // add dashes between the UUID string parts - let idParts = [] + let idParts: string[] = [] idParts.push(id.substr(0, 8)) idParts.push(id.substr(8, 4)) idParts.push(id.substr(12, 4)) @@ -180,12 +191,12 @@ module.exports = function (sequelize, DataTypes) { idParts.push(id.substr(20, 12)) return idParts.join('-') } - Note.checkNoteIdValid = function (id) { + static checkNoteIdValid(id) { var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i var result = id.match(uuidRegex) if (result && result.length === 1) { return true } else { return false } } - Note.parseNoteId = function (noteId, callback) { + static parseNoteId(noteId, callback) { async.series({ parseNoteIdByAlias: function (_callback) { // try to parse note id by alias (e.g. doc) @@ -210,7 +221,7 @@ module.exports = function (sequelize, DataTypes) { content: body, lastchangeAt: fsModifiedTime }).then(function (note) { - sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { + Sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { if (err) return _callback(err, null) // update authorship on after making revision of docs var patch = dmp.patch_fromText(revision.patch) @@ -293,7 +304,7 @@ module.exports = function (sequelize, DataTypes) { parseNoteIdByShortId: function (_callback) { // try to parse note id by shortId try { - if (shortId.isValid(noteId)) { + if (shortIdIsValid(noteId)) { Note.findOne({ where: { shortid: noteId @@ -319,7 +330,7 @@ module.exports = function (sequelize, DataTypes) { return callback(null, null) }) } - Note.parseNoteInfo = function (body) { + parseNoteInfo(body) { var parsed = Note.extractMeta(body) var $ = cheerio.load(md.render(parsed.markdown)) return { @@ -327,12 +338,12 @@ module.exports = function (sequelize, DataTypes) { tags: Note.extractNoteTags(parsed.meta, $) } } - Note.parseNoteTitle = function (body) { - var parsed = Note.extractMeta(body) + static parseNoteTitle(body) { + const parsed = Note.extractMeta(body) var $ = cheerio.load(md.render(parsed.markdown)) return Note.extractNoteTitle(parsed.meta, $) } - Note.extractNoteTitle = function (meta, $) { + static extractNoteTitle(meta, $) { var title = '' if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { title = meta.title @@ -343,23 +354,23 @@ module.exports = function (sequelize, DataTypes) { if (!title) title = 'Untitled' return title } - Note.generateDescription = function (markdown) { + static generateDescription(markdown) { return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ') } - Note.decodeTitle = function (title) { + static decodeTitle(title) { return title || 'Untitled' } - Note.generateWebTitle = function (title) { + static generateWebTitle(title) { title = !title || title === 'Untitled' ? 'CodiMD - Collaborative markdown notes' : title + ' - CodiMD' return title } - Note.extractNoteTags = function (meta, $) { - var tags = [] - var rawtags = [] + static extractNoteTags(meta, $) { + var tags: string[] = [] + var rawtags: string[] = [] if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) { var metaTags = ('' + meta.tags).split(',') for (let i = 0; i < metaTags.length; i++) { - var text = metaTags[i].trim() + var text: string = metaTags[i].trim() if (text) rawtags.push(text) } } else { @@ -386,22 +397,22 @@ module.exports = function (sequelize, DataTypes) { } return tags } - Note.extractMeta = function (content) { - var obj = null + + static extractMeta(content) { try { - obj = metaMarked(content) + var obj = metaMarked(content) if (!obj.markdown) obj.markdown = '' if (!obj.meta) obj.meta = {} + return obj; } catch (err) { - obj = { + return { markdown: content, meta: {} } } - return obj } - Note.parseMeta = function (meta) { - var _meta = {} + static parseMeta(meta): NoteMetadata { + var _meta = new NoteMetadata(); if (meta) { if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { _meta.title = meta.title } if (meta.description && (typeof meta.description === 'string' || typeof meta.description === 'number')) { _meta.description = meta.description } @@ -413,15 +424,15 @@ module.exports = function (sequelize, DataTypes) { } return _meta } - Note.parseOpengraph = function (meta, title) { - var _ogdata = {} + static parseOpengraph(meta, title) { + var _ogdata: any = {} if (meta.opengraph) { _ogdata = meta.opengraph } if (!(_ogdata.title && (typeof _ogdata.title === 'string' || typeof _ogdata.title === 'number'))) { _ogdata.title = title } if (!(_ogdata.description && (typeof _ogdata.description === 'string' || typeof _ogdata.description === 'number'))) { _ogdata.description = meta.description || '' } if (!(_ogdata.type && (typeof _ogdata.type === 'string'))) { _ogdata.type = 'website' } return _ogdata } - Note.updateAuthorshipByOperation = function (operation, userId, authorships) { + static updateAuthorshipByOperation(operation, userId, authorships) { var index = 0 var timestamp = Date.now() for (let i = 0; i < operation.length; i++) { @@ -522,8 +533,8 @@ module.exports = function (sequelize, DataTypes) { } return authorships } - Note.transformPatchToOperations = function (patch, contentLength) { - var operations = [] + static transformPatchToOperations(patch, contentLength) { + var operations: any = [] if (patch.length > 0) { // calculate original content length for (let j = patch.length - 1; j >= 0; j--) { @@ -544,7 +555,7 @@ module.exports = function (sequelize, DataTypes) { var bias = 0 var lengthBias = 0 for (let j = 0; j < patch.length; j++) { - var operation = [] + var operation: any = [] let p = patch[j] var currIndex = p.start1 var currLength = contentLength - bias @@ -581,6 +592,14 @@ module.exports = function (sequelize, DataTypes) { } return operations } - - return Note } + +export class NoteMetadata { + title: string; + description: string; + robots: string; + GA: string; + disqus: string; + slideOptions: any; + opengraph: any; +} \ No newline at end of file diff --git a/lib/models/revision.js b/lib/models/revision.ts similarity index 65% rename from lib/models/revision.js rename to lib/models/revision.ts index dbd76e4e3..dc208d839 100644 --- a/lib/models/revision.js +++ b/lib/models/revision.ts @@ -1,26 +1,25 @@ -'use strict' -// external modules +import {Table, Model, Column, DataType, BelongsTo, IsUUID, PrimaryKey, ForeignKey} from 'sequelize-typescript' var Sequelize = require('sequelize') -var async = require('async') -var moment = require('moment') -var childProcess = require('child_process') -var shortId = require('shortid') -var path = require('path') +import async = require('async') +import moment = require('moment') +import childProcess = require('child_process') +import shortId = require('shortid') +import path = require('path') var Op = Sequelize.Op // core -var logger = require('../logger') +import logger = require('../logger') +import {ChildProcess} from "child_process"; +import { Note } from './note' -var dmpWorker = createDmpWorker() +var dmpWorker : ChildProcess | null = createDmpWorker() var dmpCallbackCache = {} function createDmpWorker () { - var worker = childProcess.fork(path.resolve(__dirname, '../workers/dmpWorker.js'), { - stdio: 'ignore' - }) + var worker = childProcess.fork(path.resolve(__dirname, '../workers/dmpWorker.js'), ['ignore']) logger.debug('dmp worker process started') - worker.on('message', function (data) { + worker.on('message', function (data: any) { if (!data || !data.msg || !data.cacheKey) { return logger.error('dmp worker error: not enough data on message') } @@ -36,7 +35,7 @@ function createDmpWorker () { delete dmpCallbackCache[cacheKey] }) worker.on('close', function (code) { - dmpWorker = null + dmpWorker = null; logger.debug(`dmp worker process exited with code ${code}`) }) return worker @@ -52,75 +51,67 @@ function sendDmpWorker (data, callback) { dmpWorker.send(data) } -module.exports = function (sequelize, DataTypes) { - var Revision = sequelize.define('Revision', { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: Sequelize.UUIDV4 - }, - patch: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('patch'), '') - }, - set: function (value) { - this.setDataValue('patch', sequelize.stripNullByte(value)) - } - }, - lastContent: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('lastContent'), '') - }, - set: function (value) { - this.setDataValue('lastContent', sequelize.stripNullByte(value)) - } - }, - content: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('content'), '') - }, - set: function (value) { - this.setDataValue('content', sequelize.stripNullByte(value)) - } - }, - length: { - type: DataTypes.INTEGER - }, - authorship: { - type: DataTypes.TEXT('long'), - get: function () { - return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) - }, - set: function (value) { - this.setDataValue('authorship', value ? JSON.stringify(value) : value) - } - } - }) +@Table +export class Revision extends Model { + @IsUUID(4) + @PrimaryKey + @Column + id: string; - Revision.associate = function (models) { - Revision.belongsTo(models.Note, { - foreignKey: 'noteId', - as: 'note', - constraints: false, - onDelete: 'CASCADE', - hooks: true - }) + @Column(DataType.TEXT({ length: 'long' })) + get patch(): string { + return Sequelize.processData(this.getDataValue('patch'), '') } - Revision.getNoteRevisions = function (note, callback) { + set patch(value: string) { + this.setDataValue('patch', Sequelize.stripNullByte(value)) + } + + @Column(DataType.TEXT({ length: 'long' })) + get lastContent(): string { + return Sequelize.processData(this.getDataValue('lastContent'), '') + } + set lastContent(value: string) { + this.setDataValue('lastContent', Sequelize.stripNullByte(value)) + } + + @Column(DataType.TEXT({ length: 'long' })) + get content(): string { + return Sequelize.processData(this.getDataValue('content'), '') + } + set content(value: string) { + this.setDataValue('content', Sequelize.stripNullByte(value)) + } + + @Column(DataType.INTEGER) + length : number + + @Column(DataType.TEXT({ length: 'long' })) + get authorship(): string { + return Sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) + } + set authorship(value: string) { + this.setDataValue('authorship', value ? JSON.stringify(value) : value) + } + + @ForeignKey(() => Note) + @Column + noteId: string + + @BelongsTo(() => Note, { foreignKey: 'noteId', constraints: false, onDelete: 'CASCADE', hooks: true }) + note: Note; + + getNoteRevisions (note, callback) { Revision.findAll({ where: { noteId: note.id }, order: [['createdAt', 'DESC']] }).then(function (revisions) { - var data = [] - for (var i = 0, l = revisions.length; i < l; i++) { + var data : any[] = [] + for (var i = 0; i < revisions.length; i++) { var revision = revisions[i] data.push({ - time: moment(revision.createdAt).valueOf(), + time : moment(revision.createdAt).valueOf(), length: revision.length }) } @@ -129,7 +120,7 @@ module.exports = function (sequelize, DataTypes) { callback(err, null) }) } - Revision.getPatchedNoteRevisionByTime = function (note, time, callback) { + getPatchedNoteRevisionByTime (note, time, callback) { // find all revisions to prepare for all possible calculation Revision.findAll({ where: { @@ -146,7 +137,6 @@ module.exports = function (sequelize, DataTypes) { [Op.gte]: time } }, - order: [['createdAt', 'DESC']] }).then(function (count) { if (count <= 0) return callback(null, null) sendDmpWorker({ @@ -161,18 +151,18 @@ module.exports = function (sequelize, DataTypes) { return callback(err, null) }) } - Revision.checkAllNotesRevision = function (callback) { - Revision.saveAllNotesRevision(function (err, notes) { + checkAllNotesRevision (callback) { + this.saveAllNotesRevision(function (err, notes) { if (err) return callback(err, null) if (!notes || notes.length <= 0) { return callback(null, notes) } else { - Revision.checkAllNotesRevision(callback) + this.checkAllNotesRevision(callback) } }) } - Revision.saveAllNotesRevision = function (callback) { - sequelize.models.Note.findAll({ + saveAllNotesRevision (callback) { + Sequelize.models.Note.findAll({ // query all notes that need to save for revision where: { [Op.and]: [ @@ -182,7 +172,7 @@ module.exports = function (sequelize, DataTypes) { [Op.eq]: null, [Op.and]: { [Op.ne]: null, - [Op.gt]: sequelize.col('createdAt') + [Op.gt]: Sequelize.col('createdAt') } } } @@ -191,7 +181,7 @@ module.exports = function (sequelize, DataTypes) { savedAt: { [Op.or]: { [Op.eq]: null, - [Op.lt]: sequelize.col('lastchangeAt') + [Op.lt]: Sequelize.col('lastchangeAt') } } } @@ -199,24 +189,24 @@ module.exports = function (sequelize, DataTypes) { } }).then(function (notes) { if (notes.length <= 0) return callback(null, notes) - var savedNotes = [] - async.each(notes, function (note, _callback) { + var savedNotes : any[] = [] + async.each(notes, function (note : any, _callback) { // revision saving policy: note not been modified for 5 mins or not save for 10 mins if (note.lastchangeAt && note.savedAt) { var lastchangeAt = moment(note.lastchangeAt) var savedAt = moment(note.savedAt) if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) { savedNotes.push(note) - Revision.saveNoteRevision(note, _callback) + this.saveNoteRevision(note, _callback) } else if (lastchangeAt.isAfter(savedAt.add(10, 'minutes'))) { savedNotes.push(note) - Revision.saveNoteRevision(note, _callback) + this.saveNoteRevision(note, _callback) } else { return _callback(null, null) } } else { savedNotes.push(note) - Revision.saveNoteRevision(note, _callback) + this.saveNoteRevision(note, _callback) } }, function (err) { if (err) { @@ -230,7 +220,7 @@ module.exports = function (sequelize, DataTypes) { return callback(err, null) }) } - Revision.saveNoteRevision = function (note, callback) { + saveNoteRevision (note, callback) { Revision.findAll({ where: { noteId: note.id @@ -239,13 +229,13 @@ module.exports = function (sequelize, DataTypes) { }).then(function (revisions) { if (revisions.length <= 0) { // if no revision available - Revision.create({ + this.create({ noteId: note.id, lastContent: note.content ? note.content : '', length: note.content ? note.content.length : 0, authorship: note.authorship }).then(function (revision) { - Revision.finishSaveNoteRevision(note, revision, callback) + this.finishSaveNoteRevision(note, revision, callback) }).catch(function (err) { return callback(err, null) }) @@ -265,12 +255,12 @@ module.exports = function (sequelize, DataTypes) { latestRevision.update({ updatedAt: Date.now() }).then(function (revision) { - Revision.finishSaveNoteRevision(note, revision, callback) + this.finishSaveNoteRevision(note, revision, callback) }).catch(function (err) { return callback(err, null) }) } else { - Revision.create({ + this.create({ noteId: note.id, patch: patch, content: note.content, @@ -281,7 +271,7 @@ module.exports = function (sequelize, DataTypes) { latestRevision.update({ content: null }).then(function () { - Revision.finishSaveNoteRevision(note, revision, callback) + this.finishSaveNoteRevision(note, revision, callback) }).catch(function (err) { return callback(err, null) }) @@ -295,7 +285,7 @@ module.exports = function (sequelize, DataTypes) { return callback(err, null) }) } - Revision.finishSaveNoteRevision = function (note, revision, callback) { + finishSaveNoteRevision (note, revision, callback) { note.update({ savedAt: revision.updatedAt }).then(function () { @@ -305,5 +295,7 @@ module.exports = function (sequelize, DataTypes) { }) } - return Revision + + } + diff --git a/lib/models/temp.js b/lib/models/temp.js deleted file mode 100644 index 2ad23fb54..000000000 --- a/lib/models/temp.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' -// external modules -var shortId = require('shortid') - -module.exports = function (sequelize, DataTypes) { - var Temp = sequelize.define('Temp', { - id: { - type: DataTypes.STRING, - primaryKey: true, - defaultValue: shortId.generate - }, - data: { - type: DataTypes.TEXT - } - }) - - return Temp -} diff --git a/lib/models/temp.ts b/lib/models/temp.ts new file mode 100644 index 000000000..f7c128fdb --- /dev/null +++ b/lib/models/temp.ts @@ -0,0 +1,13 @@ +import { DataType, Model, Table, PrimaryKey, Column, Default } from 'sequelize-typescript' +import { generate as shortIdGenerate, isValid as shortIdIsValid } from "shortid"; + +@Table +export class Temp extends Model { + @Default(shortIdGenerate) + @PrimaryKey + @Column(DataType.STRING) + id: string; + + @Column(DataType.TEXT) + data: string +} diff --git a/lib/models/user.js b/lib/models/user.ts similarity index 51% rename from lib/models/user.js rename to lib/models/user.ts index 28cbc58d8..8bb9c91ce 100644 --- a/lib/models/user.js +++ b/lib/models/user.ts @@ -1,97 +1,56 @@ -'use strict' -// external modules -const Sequelize = require('sequelize') -const crypto = require('crypto') -if (!crypto.scrypt) { - // polyfill for node.js 8.0, see https://github.com/chrisveness/scrypt-kdf#openssl-implementation - const scryptAsync = require('scrypt-async') - crypto.scrypt = function (password, salt, keylen, options, callback) { - const opt = Object.assign({}, options, { dkLen: keylen }) - scryptAsync(password, salt, opt, (derivedKey) => callback(null, Buffer.from(derivedKey))) - } -} -const scrypt = require('scrypt-kdf') +import { Note } from './note'; +import { Table, BeforeCreate, BeforeUpdate, HasMany, Unique, IsUUID, IsEmail, Column, DataType, PrimaryKey, Model, Default } from 'sequelize-typescript'; +import scrypt from 'scrypt-kdf'; +import { generateAvatarURL } from '../letter-avatars'; +import logger from '../logger'; +var Sequelize = require('sequelize') // core -const logger = require('../logger') -const { generateAvatarURL } = require('../letter-avatars') -module.exports = function (sequelize, DataTypes) { - var User = sequelize.define('User', { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: Sequelize.UUIDV4 - }, - profileid: { - type: DataTypes.STRING, - unique: true - }, - profile: { - type: DataTypes.TEXT - }, - history: { - type: DataTypes.TEXT - }, - accessToken: { - type: DataTypes.TEXT - }, - refreshToken: { - type: DataTypes.TEXT - }, - deleteToken: { - type: DataTypes.UUID, - defaultValue: Sequelize.UUIDV4 - }, - email: { - type: Sequelize.TEXT, - validate: { - isEmail: true - } - }, - password: { - type: Sequelize.TEXT - } - }) +@Table +export class User extends Model { - User.prototype.verifyPassword = function (attempt) { - return scrypt.verify(Buffer.from(this.password, 'hex'), attempt) + @PrimaryKey + @Default(Sequelize.UUIDV4) + @Column(DataType.UUID) + id: string; + + @Unique + @Column(DataType.STRING) + profileid: string; + + @Column(DataType.TEXT) + profile: string; + + @Column(DataType.TEXT) + histroy: string; + + @Column(DataType.TEXT) + accessToken: string; + + @Column(DataType.TEXT) + refreshToken: string; + + @IsUUID(4) + @Column + deleteToken: string; + + @IsEmail + @Column(DataType.TEXT) + email: string; + + @Column(DataType.TEXT) + password: string; + + verifyPassword(attempt: string) { + return scrypt.verify(Buffer.from(this.password, 'hex'), attempt); } - User.associate = function (models) { - User.hasMany(models.Note, { - foreignKey: 'ownerId', - constraints: false - }) - User.hasMany(models.Note, { - foreignKey: 'lastchangeuserId', - constraints: false - }) - } - User.getProfile = function (user) { - if (!user) { - return null - } - return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null) - } - User.parseProfile = function (profile) { - try { - profile = JSON.parse(profile) - } catch (err) { - logger.error(err) - profile = null - } - if (profile) { - profile = { - name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile), - biggerphoto: User.parsePhotoByProfile(profile, true) - } - } - return profile - } - User.parsePhotoByProfile = function (profile, bigger) { - var photo = null + @HasMany(() => Note, { foreignKey: 'lastchangeuserId', constraints: false }) + @HasMany(() => Note, { foreignKey: 'ownerId', constraints: false }) + + static parsePhotoByProfile(profile: any, bigger: boolean) { + let photo: string; switch (profile.provider) { case 'facebook': photo = 'https://graph.facebook.com/' + profile.id + '/picture' @@ -137,7 +96,7 @@ module.exports = function (sequelize, DataTypes) { } return photo } - User.parseProfileByEmail = function (email) { + static parseProfileByEmail(email: string) { return { name: email.substring(0, email.lastIndexOf('@')), photo: generateAvatarURL('', email, false), @@ -145,21 +104,43 @@ module.exports = function (sequelize, DataTypes) { } } - function updatePasswordHashHook (user, options) { + @BeforeUpdate + @BeforeCreate + static async updatePasswordHashHook(user: User) { // suggested way to hash passwords to be able to do this asynchronously: // @see https://github.com/sequelize/sequelize/issues/1821#issuecomment-44265819 if (!user.changed('password')) { - return Promise.resolve() + return Promise.resolve(); } - return scrypt.kdf(user.getDataValue('password'), { logN: 15 }).then(keyBuf => { - user.setDataValue('password', keyBuf.toString('hex')) + return scrypt.kdf(user.getDataValue('password'), { logN: 15, r: 8, p: 1 }).then(keyBuf => { + user.setDataValue('password', keyBuf.toString('hex')); }) } - User.beforeCreate(updatePasswordHashHook) - User.beforeUpdate(updatePasswordHashHook) + static getProfile(user: User) { + if (!user) { + return null + } + return user.profile ? user.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null) + } + + parseProfile(profile: any) { + try { + profile = JSON.parse(profile) + } catch (err) { + logger.error(err) + profile = null + } + if (profile) { + profile = { + name: profile.displayName || profile.username, + photo: User.parsePhotoByProfile(profile, false), + biggerphoto: User.parsePhotoByProfile(profile, true) + } + } + return profile + } - return User } diff --git a/lib/web/note/actions.js b/lib/web/note/actions.js deleted file mode 100644 index d92d2443e..000000000 --- a/lib/web/note/actions.js +++ /dev/null @@ -1,89 +0,0 @@ -const models = require('../../models') -const logger = require('../../logger') -const config = require('../../config') -const errors = require('../../errors') -const shortId = require('shortid') -const moment = require('moment') -const querystring = require('querystring') - -exports.getInfo = function getInfo (req, res, note) { - const body = note.content - const extracted = models.Note.extractMeta(body) - const markdown = extracted.markdown - const meta = models.Note.parseMeta(extracted.meta) - const createtime = note.createdAt - const updatetime = note.lastchangeAt - const title = models.Note.decodeTitle(note.title) - const data = { - title: meta.title || title, - description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null), - viewcount: note.viewcount, - createtime: createtime, - updatetime: updatetime - } - res.set({ - 'Access-Control-Allow-Origin': '*', // allow CORS as API - 'Access-Control-Allow-Headers': 'Range', - 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', - 'Cache-Control': 'private', // only cache by client - 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling - }) - res.send(data) -} - -exports.createGist = function createGist (req, res, note) { - const data = { - client_id: config.github.clientID, - redirect_uri: config.serverURL + '/auth/github/callback/' + models.Note.encodeNoteId(note.id) + '/gist', - scope: 'gist', - state: shortId.generate() - } - const query = querystring.stringify(data) - res.redirect('https://github.com/login/oauth/authorize?' + query) -} - -exports.getRevision = function getRevision (req, res, note) { - const actionId = req.params.actionId - if (actionId) { - const time = moment(parseInt(actionId)) - if (time.isValid()) { - models.Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) { - if (err) { - logger.error(err) - return errors.errorInternalError(res) - } - if (!content) { - return errors.errorNotFound(res) - } - res.set({ - 'Access-Control-Allow-Origin': '*', // allow CORS as API - 'Access-Control-Allow-Headers': 'Range', - 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', - 'Cache-Control': 'private', // only cache by client - 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling - }) - res.send(content) - }) - } else { - return errors.errorNotFound(res) - } - } else { - models.Revision.getNoteRevisions(note, function (err, data) { - if (err) { - logger.error(err) - return errors.errorInternalError(res) - } - const out = { - revision: data - } - res.set({ - 'Access-Control-Allow-Origin': '*', // allow CORS as API - 'Access-Control-Allow-Headers': 'Range', - 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', - 'Cache-Control': 'private', // only cache by client - 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling - }) - res.send(out) - }) - } -} diff --git a/lib/web/note/actions.ts b/lib/web/note/actions.ts new file mode 100644 index 000000000..ac52f1fac --- /dev/null +++ b/lib/web/note/actions.ts @@ -0,0 +1,97 @@ +import {Response} from "express"; + +const models = require('../../models') +const logger = require('../../logger') +const config = require('../../config') +const errors = require('../../errors') +const shortId = require('shortid') +const moment = require('moment') +const querystring = require('querystring') + +export module ActionController { + + // TODO: Use correct type for note + export function getInfo(req: any, res: Response, note: any) { + const body = note.content + const extracted = models.Note.extractMeta(body) + const markdown = extracted.markdown + const meta = models.Note.parseMeta(extracted.meta) + const createtime = note.createdAt + const updatetime = note.lastchangeAt + const title = models.Note.decodeTitle(note.title) + const data = { + title: meta.title || title, + description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null), + viewcount: note.viewcount, + createtime: createtime, + updatetime: updatetime + } + res.set({ + 'Access-Control-Allow-Origin': '*', // allow CORS as API + 'Access-Control-Allow-Headers': 'Range', + 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', + 'Cache-Control': 'private', // only cache by client + 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling + }) + res.send(data) + } + + // TODO: Use correct type for note + export function createGist(req: any, res: Response, note: any) { + const data = { + client_id: config.github.clientID, + redirect_uri: config.serverURL + '/auth/github/callback/' + models.Note.encodeNoteId(note.id) + '/gist', + scope: 'gist', + state: shortId.generate() + } + const query = querystring.stringify(data) + res.redirect('https://github.com/login/oauth/authorize?' + query) + } + + // TODO: Use correct type for note + export function getRevision(req: any, res: Response, note: any) { + const actionId = req.params.actionId + if (actionId) { + const time = moment(parseInt(actionId)) + if (time.isValid()) { + models.Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) { + if (err) { + logger.error(err) + return errors.errorInternalError(res) + } + if (!content) { + return errors.errorNotFound(res) + } + res.set({ + 'Access-Control-Allow-Origin': '*', // allow CORS as API + 'Access-Control-Allow-Headers': 'Range', + 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', + 'Cache-Control': 'private', // only cache by client + 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling + }) + res.send(content) + }) + } else { + return errors.errorNotFound(res) + } + } else { + models.Revision.getNoteRevisions(note, function (err, data) { + if (err) { + logger.error(err) + return errors.errorInternalError(res) + } + const out = { + revision: data + } + res.set({ + 'Access-Control-Allow-Origin': '*', // allow CORS as API + 'Access-Control-Allow-Headers': 'Range', + 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range', + 'Cache-Control': 'private', // only cache by client + 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling + }) + res.send(out) + }) + } + } +} diff --git a/package.json b/package.json index 2f07f1044..54e5d4ef2 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "randomcolor": "^0.5.3", "raphael": "git+https://github.com/dmitrybaranovskiy/raphael", "readline-sync": "^1.4.7", + "reflect-metadata": "^0.1.13", "request": "^2.88.0", "reveal.js": "~3.9.2", "scrypt-async": "^2.0.1", @@ -167,8 +168,11 @@ "url": "https://github.com/codimd/server.git" }, "devDependencies": { - "@types/express": "4.17.0", - "@types/node": "^12.12.12", + "@types/express": "^4.17.6", + "@types/node": "^13.11.1", + "@types/bluebird": "^3.5.30", + "@types/passport": "^1.0.3", + "@types/validator": "^13.0.0", "awesome-typescript-loader": "^5.2.1", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", diff --git a/tsconfig.json b/tsconfig.json index b750d83da..6d88a11ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "target": "es5", "module": "commonjs", "esModuleInterop": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, "include": [ "./lib/**/*" diff --git a/yarn.lock b/yarn.lock index ca05714c8..d700532fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,6 +64,11 @@ resolved "https://registry.yarnpkg.com/@passport-next/passport-strategy/-/passport-strategy-1.1.0.tgz#4c0df069e2ec9262791b9ef1e23320c1d73bdb74" integrity sha512-2KhFjtPueJG6xVj2HnqXt9BlANOfYCVLyu+pXYjPGBDT8yk+vQwc/6tsceIj+mayKcoxMau2JimggXRPHgoc8w== +"@types/bluebird@^3.5.30": + version "3.5.30" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" + integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== + "@types/body-parser@*": version "1.19.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -85,9 +90,9 @@ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/express-serve-static-core@*": - version "4.17.2" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf" - integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg== + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.4.tgz#157c79c2d28b632d6418497c57c93185e392e444" + integrity sha512-dPs6CaRWxsfHbYDVU51VjEJaUJEcli4UI0fFMT4oWmgCvHj+j7oIxz5MLHVL0Rv++N004c21ylJNdWQvPkkb5w== dependencies: "@types/node" "*" "@types/range-parser" "*" @@ -101,13 +106,14 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/express@4.17.0": - version "4.17.0" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287" - integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw== +"@types/express@^4.17.6": + version "4.17.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e" + integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" + "@types/qs" "*" "@types/serve-static" "*" "@types/geojson@^7946.0.7": @@ -127,7 +133,12 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== -"@types/node@*", "@types/node@>=8.0.0": +"@types/node@*", "@types/node@^13.11.1": + version "13.11.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" + integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== + +"@types/node@>=8.0.0": version "13.7.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d" integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA== @@ -137,11 +148,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.15.tgz#bfff4e23e9e70be6eec450419d51e18de1daf8e7" integrity sha512-daFGV9GSs6USfPgxceDA8nlSe48XrVCJfDeYm7eokxq/ye7iuOH87hKXgMtEAVLFapkczbZsx868PMDT1Y0a6A== -"@types/node@^12.12.12": - version "12.12.28" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.28.tgz#3a2b5f8d21f96ace690a8832ae9779114612575f" - integrity sha512-g73GJYJDXgf0jqg+P9S8h2acWbDXNkoCX8DLtJVu7Fkn788pzQ/oJsrdJz/2JejRf/SjfZaAhsw+3nd1D5EWGg== - "@types/node@^12.12.17": version "12.12.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.27.tgz#d7506f73160ad30fcebbcf5b8b7d2d976e649e42" @@ -159,11 +165,23 @@ dependencies: "@types/express" "*" +"@types/passport@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.3.tgz#e459ed6c262bf0686684d1b05901be0d0b192a9c" + integrity sha512-nyztuxtDPQv9utCzU0qW7Gl8BY2Dn8BKlYAFFyxKipFxjaVd96celbkLCV/tRqqBUZ+JB8If3UfgV8347DTo3Q== + dependencies: + "@types/express" "*" + "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/qs@*": + version "6.9.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7" + integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw== + "@types/range-parser@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" @@ -192,6 +210,11 @@ dependencies: "@types/node" "*" +"@types/validator@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.0.0.tgz#365f1bf936aeaddd0856fc41aa1d6f82d88ee5b3" + integrity sha512-WAy5txG7aFX8Vw3sloEKp5p/t/Xt8jD3GRD9DacnFv6Vo8ubudAsRTXgxpQwU0mpzY/H8U4db3roDuCMjShBmw== + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -8343,6 +8366,11 @@ referrer-policy@1.2.0: resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA== +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -8868,6 +8896,13 @@ sequelize-pool@^2.3.0: resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-2.3.0.tgz#64f1fe8744228172c474f530604b6133be64993d" integrity sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA== +sequelize-typescript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.1.0.tgz#d5c2945e7fbfe55a934917b27d84589858d79123" + integrity sha512-FAPEQPeAhIaFQNLAcf9Q2IWcqWhNcvn5OZZ7BzGB0CJMtImIsGg4E/EAb7huMmPaPwDArxJUWGqk1KurphTNRA== + dependencies: + glob "7.1.2" + sequelize@^5.21.1: version "5.21.4" resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.21.4.tgz#a49597dbd7862e4e1fb8ec819de04705d06d9d17"