├── data └── .gitignore ├── .dockerignore ├── .gitignore ├── .gitmodules ├── utils ├── prototypes.js ├── log.js └── utils.js ├── hooks └── post_checkout.sh ├── .prettierrc ├── Dockerfile ├── package.json ├── LICENSE ├── scripts ├── _load.js ├── deleteDomain.js ├── calculateDomainSize.js ├── deleteModofiedExpired.js └── deleteFiles.js ├── index.js └── README.md /data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | .dockerignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | upload 3 | local_storage 4 | config/config.js 5 | .env 6 | todo 7 | tmp -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "granny-server-backend"] 2 | path = granny-server-backend 3 | url = https://github.com/mrspartak/granny-server-backend 4 | -------------------------------------------------------------------------------- /utils/prototypes.js: -------------------------------------------------------------------------------- 1 | Array.prototype.fixedPush = function(element, length) { 2 | if(this.length + 1 > length) this.shift(); 3 | this.push(element); 4 | } -------------------------------------------------------------------------------- /hooks/post_checkout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Docker hub does a recursive clone, then checks the branch out, 3 | # so when a PR adds a submodule (or updates it), it fails. 4 | git submodule update --init 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "trailingComma": "all", 6 | "singleQuote": true, 7 | "htmlWhitespaceSensitivity": "css", 8 | "vueIndentScriptAndStyle": false 9 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.16.0-alpine 2 | 3 | RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app 4 | 5 | WORKDIR /home/node/app 6 | 7 | COPY package.json ./ 8 | 9 | USER node 10 | 11 | RUN npm install --production 12 | 13 | COPY --chown=node:node . . 14 | 15 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdn-image-api-cron", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "cross-env DEBUG=1 nodemon ./index.js", 8 | "start": "node ./index.js", 9 | "submodule": "git submodule foreach git pull origin master" 10 | }, 11 | "husky": { 12 | "hooks": { 13 | "pre-commit": "pretty-quick --staged --pattern \"**/*.*(js|jsx)\"" 14 | } 15 | }, 16 | "nodemonConfig": { 17 | "ignore": [ 18 | "data/*" 19 | ] 20 | }, 21 | "author": "", 22 | "license": "MIT", 23 | "dependencies": { 24 | "lodash": "^4.17.15", 25 | "minio": "^7.0.13", 26 | "moment": "^2.26.0", 27 | "mongoose": "^5.9.17", 28 | "mongoose-autorefs": "^1.0.5", 29 | "mongoose-plugin-autoinc": "^1.1.9", 30 | "mongoose-unique-validator": "^2.0.3", 31 | "node-cron": "^2.0.3" 32 | }, 33 | "devDependencies": { 34 | "cross-env": "^7.0.2", 35 | "husky": "^4.2.3", 36 | "nodemon": "^2.0.2", 37 | "prettier": "2.0.5", 38 | "pretty-quick": "^2.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Assorium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/_load.js: -------------------------------------------------------------------------------- 1 | module.exports = async function(options) { 2 | let { __, _, log, initStats } = options; 3 | 4 | var stats = initStats 5 | 6 | async function run(scriptName, fn) { 7 | if(!stats[scriptName]) 8 | stats[scriptName] = { 9 | lastRun: {}, 10 | runs: [] 11 | } 12 | 13 | let runStats = {} 14 | 15 | log.info(`START.${scriptName}`); 16 | 17 | runStats.tsStart = new Date().getTime()/1000 18 | try { 19 | await fn() 20 | } catch(e) { 21 | runStats.error = e.message 22 | } 23 | runStats.tsFinish = new Date().getTime()/1000 24 | runStats.duration = +(runStats.tsFinish - runStats.tsStart).toFixed(1) 25 | 26 | stats[scriptName].lastRun = runStats 27 | stats[scriptName].runs.fixedPush(runStats, 10) 28 | 29 | log.info(`FINISH.${scriptName}`, `+${runStats.duration} seconds`); 30 | } 31 | 32 | //load modules dynamicaly 33 | let modules = __.modulesAt(__.path('scripts/'), { 34 | camelCase: false, 35 | capitalize: false, 36 | }); 37 | 38 | _.each(modules, module => { 39 | if (module.name == '_load') return; 40 | 41 | log.info('scripts.loaded', module.name) 42 | require(module.path)(options, {scriptName: module.name, run}) 43 | }); 44 | 45 | return stats 46 | }; 47 | -------------------------------------------------------------------------------- /scripts/deleteDomain.js: -------------------------------------------------------------------------------- 1 | module.exports = function(options, {scriptName, run}) { 2 | let { __, cron, log, config, mongo } = options; 3 | 4 | let prefix = `RUN.${scriptName}` 5 | let schedule = config.DEBUG ? '*/10 * * * * *' : '*/10 * * * *' 6 | 7 | cron.schedule(schedule, () => { 8 | run(scriptName, deleteDomain) 9 | }); 10 | 11 | async function deleteDomain() { 12 | var [err, domains] = await __.to(mongo.Domain.find({ deleted: true }).exec()) 13 | 14 | if(err) return log.error(prefix, 'mongo.select.err', err.message) 15 | 16 | log.info(prefix, 'domains_to_delete', domains.length) 17 | 18 | await __.asyncForEach(domains, async (domain) => { 19 | log.debug('deleting', domain.domain) 20 | 21 | var [err, images] = await __.to(mongo.Image.find({ domain: domain.domain }).exec()) 22 | if(images.length) { 23 | log.warn('cant_delete_domain', 'still have images to delete: '+ images.length) 24 | 25 | var [err, updated] = await __.to(mongo.Image.updateMany({ 26 | domain: domain.domain 27 | }, { 28 | deleted: true 29 | }).exec()) 30 | 31 | return; 32 | } 33 | 34 | var [err, result] = await __.to( mongo.Domain.deleteOne({ 35 | _id: domain._id 36 | }) ) 37 | if(err) { 38 | log.error(prefix, 'mongo.delete.err', err.message) 39 | return; 40 | } 41 | log.debug('deleted', domain.domain) 42 | }) 43 | 44 | 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /scripts/calculateDomainSize.js: -------------------------------------------------------------------------------- 1 | module.exports = function(options, {scriptName, run}) { 2 | let { __, cron, log, config, mongo, getMinio } = options; 3 | 4 | let prefix = `RUN.${scriptName}` 5 | let schedule = config.DEBUG ? '*/10 * * * * *' : '*/10 * * * *' 6 | 7 | cron.schedule(schedule, () => { 8 | run(scriptName, calculate) 9 | }); 10 | 11 | async function calculate() { 12 | var [err, domains] = await __.to(mongo.Domain.find()) 13 | 14 | if(err) return log.error(prefix, 'mongo.select.err', err.message) 15 | log.info(prefix, 'domains', domains.length) 16 | 17 | await __.asyncForEach(domains, async (domain) => { 18 | return new Promise((resolve) => { 19 | let minio = getMinio(domain.s3) 20 | 21 | let totalSize = 0 22 | let stream = minio.listObjectsV2(domain.s3.bucket, '', true) 23 | stream.on('data', (obj) => { 24 | if(obj.size && typeof obj.size == 'number') totalSize += obj.size 25 | }) 26 | stream.on('end', async () => { 27 | log.debug(prefix, domain.domain, 'size: '+ totalSize) 28 | 29 | var [err, result] = await __.to( mongo.Domain.updateOne({ 30 | domain: { $eq: domain.domain } 31 | },{ 32 | size: totalSize 33 | }) ) 34 | if(err) return log.error(prefix, 'mongo.update.err', err.message) 35 | 36 | return resolve() 37 | }) 38 | stream.on('error', (err) => { 39 | log.error(prefix, domain.domain, 'stream.err', err.message) 40 | return resolve() 41 | }) 42 | }) 43 | }) 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /scripts/deleteModofiedExpired.js: -------------------------------------------------------------------------------- 1 | module.exports = function(options, {scriptName, run}) { 2 | let { __, cron, log, config, mongo, getMinio } = options; 3 | 4 | let prefix = `RUN.${scriptName}` 5 | let schedule = config.DEBUG ? '*/10 * * * * *' : '*/2 * * * *' 6 | 7 | cron.schedule(schedule, () => { 8 | run(scriptName, deleteImages) 9 | }); 10 | 11 | async function deleteImages() { 12 | let currentTS = parseInt(new Date().getTime()/1000) 13 | 14 | var [err, images] = await __.to(mongo.Image.aggregate([ 15 | { 16 | $unwind: { 17 | path: "$refChildren", 18 | includeArrayIndex: "arrayIndex" 19 | } 20 | }, 21 | { 22 | $match: { 23 | "refChildren.ttl": { 24 | $lt: currentTS 25 | } 26 | } 27 | } 28 | ]).exec()) 29 | 30 | if(err) return log.error(prefix, 'mongo.select.err', err.message) 31 | 32 | log.info(prefix, 'images_to_delete', images.length) 33 | 34 | await __.asyncForEach(images, async (image) => { 35 | let domain = await mongo.Domain.findOne({ domain: image.domain }).exec() 36 | if(!domain) return log.error(prefix, 'no_domain'); 37 | 38 | let minio = getMinio(domain.s3) 39 | 40 | var [err] = await __.to( minio.removeObject(domain.s3.bucket, `${image.s3_folder}/${image.refChildren.s3_file}`) ) 41 | if(err) return log.error(prefix, 'minio.err', err.message) 42 | 43 | var [err, result] = await __.to( mongo.Image.updateOne({ 44 | _id: image._id 45 | },{ 46 | $pull: { 47 | refChildren: { 48 | _id: image.refChildren._id 49 | } 50 | } 51 | }) ) 52 | if(err) return log.error(prefix, 'mongo.delete.err', err.message) 53 | log.debug(prefix, 'images_delete_result', image._id, result) 54 | }) 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | ;(async () => { 2 | 3 | const __ = require('./utils/utils'); 4 | require(__.path('utils/prototypes')) 5 | 6 | const _ = require('lodash'); 7 | const cron = require('node-cron'); 8 | const moment = require('moment'); 9 | moment.locale('ru'); 10 | const fs = require('fs').promises; 11 | 12 | /* Config from env */ 13 | const config = { 14 | DEBUG: process.env.DEBUG || false, 15 | MONGO: process.env.MONGO || 'mongodb://localhost/js_cdn' 16 | }; 17 | 18 | /* Logger */ 19 | const log = require(__.path('utils/log'))({ 20 | prefix: '#CRON |', 21 | level: config.DEBUG ? 'debug' : 'info', 22 | }); 23 | log.info('START CONST', config); 24 | 25 | /* Initial modules pool */ 26 | const initModules = { __, _, log, moment, config, cron }; 27 | 28 | /* Minio server */ 29 | const Minio = require('minio'); 30 | function getMinio({endPoint, accessKey, secretKey, port = false, useSSL = true}) { 31 | return new Minio.Client({endPoint, accessKey, secretKey, port, useSSL}) 32 | } 33 | 34 | /* Mongo */ 35 | const mongo = await require(__.path('granny-server-backend/src/mongo/_load'))(initModules); 36 | 37 | let dataFile = __.path('data/stats.json') 38 | let initStats = await getState(dataFile) 39 | 40 | _.assign(initModules, { mongo, getMinio, initStats }); 41 | 42 | /* Script modules */ 43 | let stats = await require(__.path('scripts/_load'))(initModules); 44 | 45 | setInterval(async () => { 46 | await saveState(dataFile, stats) 47 | }, 10000) 48 | 49 | async function getState(dataFile) { 50 | try { 51 | let data = await fs.readFile(dataFile) 52 | return JSON.parse(data) 53 | } catch(ex) { 54 | console.log('getState.ex', ex.message) 55 | return {} 56 | } 57 | } 58 | 59 | async function saveState(dataFile, state) { 60 | await fs.writeFile(dataFile, JSON.stringify(state)) 61 | } 62 | })() -------------------------------------------------------------------------------- /scripts/deleteFiles.js: -------------------------------------------------------------------------------- 1 | module.exports = function(options, {scriptName, run}) { 2 | let { __, cron, log, config, mongo, getMinio } = options; 3 | 4 | let prefix = `RUN.${scriptName}` 5 | let schedule = config.DEBUG ? '*/10 * * * * *' : '*/5 * * * *' 6 | 7 | cron.schedule(schedule, () => { 8 | run(scriptName, deleteImages) 9 | }); 10 | 11 | async function deleteImages() { 12 | var [err, images] = await __.to(mongo.Image.find({ deleted: true }).exec()) 13 | 14 | if(err) return log.error(prefix, 'mongo.select.err', err.message) 15 | 16 | log.info(prefix, 'images_to_delete', images.length) 17 | await __.asyncForEach(images, async (image) => { 18 | log.debug('deleting', image._id) 19 | 20 | let domain = await mongo.Domain.findOne({ domain: image.domain }).exec() 21 | if(!domain || !domain.s3.endPoint) { 22 | log.error(prefix, 'no_domain', 'domain: '+ !!domain, 'domain.s3.endPoint: '+ !!domain.s3.endPoint); 23 | return; 24 | } 25 | 26 | let minio = getMinio(domain.s3) 27 | 28 | await __.asyncForEach(image.refChildren, async (img) => { 29 | var [err] = await __.to( minio.removeObject(domain.s3.bucket, `${image.s3_folder}/${img.s3_file}`) ) 30 | if(err) log.error(prefix, 'minio.refChildren.err', err.message) 31 | return; 32 | }) 33 | 34 | var [err] = await __.to( minio.removeObject(domain.s3.bucket, `${image.s3_folder}/${image.reference.s3_file}`) ) 35 | if(err) { 36 | log.error(prefix, 'minio.reference.err', err.message) 37 | return; 38 | } 39 | 40 | var [err] = await __.to( minio.removeObject(domain.s3.bucket, `${image.s3_folder}/${image.original.s3_file}`) ) 41 | if(err) { 42 | log.error(prefix, 'minio.original.err', err.message) 43 | return; 44 | } 45 | 46 | 47 | var [err, result] = await __.to( mongo.Image.deleteOne({ 48 | _id: image._id 49 | }) ) 50 | if(err) { 51 | log.error(prefix, 'mongo.delete.err', err.message) 52 | return; 53 | } 54 | log.debug('deleted', image._id) 55 | }) 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /utils/log.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | 3 | function log(options) { 4 | this.options = Object.assign({}, { 5 | prefix: false, 6 | level: 'info', 7 | dateformat: 'DD.MM | HH:mm:ss |' 8 | }, options) 9 | 10 | const LEVEL_HIERARCHY = { 11 | debug: 10, 12 | info: 20, 13 | warn: 30, 14 | error: 40 15 | } 16 | this._checkLevel = function(level) { 17 | return LEVEL_HIERARCHY[level] >= LEVEL_HIERARCHY[this.options.level] 18 | } 19 | this._ts = function() { 20 | return moment().format(this.options.dateformat) 21 | } 22 | 23 | if(!LEVEL_HIERARCHY[this.options.level]) throw new Error('level does not supported') 24 | 25 | 26 | this.debug = function() { 27 | if(!this._checkLevel('debug')) return; 28 | 29 | let args = Array.from(arguments) 30 | 31 | let prefixes = ['\x1b[35m%s', this._ts(), 'DEBUG |', '\x1b[0m'] 32 | if(this.options.prefix) prefixes.push(this.options.prefix) 33 | args.unshift(...prefixes) 34 | 35 | console.debug.apply(console, args) 36 | } 37 | 38 | this.info = function() { 39 | if(!this._checkLevel('info')) return; 40 | 41 | let args = Array.from(arguments) 42 | 43 | let prefixes = ['\x1b[34m%s', this._ts(), 'INFO |', '\x1b[0m'] 44 | if(this.options.prefix) prefixes.push(this.options.prefix) 45 | args.unshift(...prefixes) 46 | 47 | console.info.apply(console, args) 48 | } 49 | 50 | this.warn = function() { 51 | if(!this._checkLevel('warn')) return; 52 | 53 | let args = Array.from(arguments) 54 | 55 | let prefixes = ['\x1b[33m%s', this._ts(), 'WARN |', '\x1b[0m'] 56 | if(this.options.prefix) prefixes.push(this.options.prefix) 57 | args.unshift(...prefixes) 58 | 59 | console.warn.apply(console, args) 60 | } 61 | 62 | this.error = function() { 63 | if(!this._checkLevel('error')) return; 64 | 65 | let args = Array.from(arguments) 66 | 67 | let prefixes = ['\x1b[31m%s', this._ts(), 'ERROR |', '\x1b[0m'] 68 | if(this.options.prefix) prefixes.push(this.options.prefix) 69 | args.unshift(...prefixes) 70 | 71 | console.error.apply(console, args) 72 | } 73 | 74 | 75 | return this 76 | } 77 | 78 | module.exports = log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Utility service from Granny image delivery service 2 | 3 | [![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/assorium/granny-server-cron?style=for-the-badge "Docker Cloud Automated build")](https://hub.docker.com/r/assorium/granny-server-cron "Docker Cloud Automated build") 4 | [![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/assorium/granny-server-cron?style=for-the-badge "Docker Cloud Build Status")](https://hub.docker.com/r/assorium/granny-server-cron "Docker Cloud Build Status") 5 | [![Docker Pulls](https://img.shields.io/docker/pulls/assorium/granny-server-cron?style=for-the-badge "Docker Pulls")](https://hub.docker.com/r/assorium/granny-server-cron "Docker Pulls")
6 | 7 | [![Latest Github tag](https://img.shields.io/github/v/tag/mrspartak/granny-server-cron?sort=date&style=for-the-badge "Latest Github tag")](https://github.com/mrspartak/granny-server-cron/releases "Latest Github tag") 8 | [![Join the chat at https://gitter.im/granny-js/community](https://img.shields.io/gitter/room/granny-js/community?style=for-the-badge)](https://gitter.im/granny-js/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | ## Ecosystem 11 | ![image](https://user-images.githubusercontent.com/993910/74678258-8f250380-51cb-11ea-9b5e-1640e713380e.PNG) 12 | 13 | [granny-server-backend](https://github.com/mrspartak/granny-server-backend "granny-server-backend") - Backend service with API exposed to upload and serve/manipulate images 14 | [granny-js-client](https://github.com/mrspartak/granny-js-client "granny-js-client") - Client library that works both in nodejs and browser. Makes API calls easier 15 | [granny-server-frontend](https://github.com/mrspartak/granny-server-frontend "granny-server-frontend") - Frontend APP that uses client to manage your CDN domains and settings 16 | [granny-server-cron](https://github.com/mrspartak/granny-server-cron "granny-server-cron") - Utility app 17 | 18 | 19 | 20 | ## Environment variables 21 | #mongo connection string 22 | const MONGO = process.env.DEBUG || 'mongodb://localhost/js_cdn' 23 | #debug messages 24 | const DEBUG = process.env.DEBUG || false 25 | 26 | ## Docker 27 | ``` 28 | docker run -p 3000:3000 --name granny-server-backend \ 29 | -e MONGO='mongodb://user@password:example.com/granny' \ 30 | assorium/granny-server-backend:latest 31 | ``` -------------------------------------------------------------------------------- /utils/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const { assign } = require('lodash') 4 | const crypto = require('crypto') 5 | 6 | 7 | /* path */ 8 | exports.ROOT_DIR = path.resolve(process.cwd(), './'); 9 | exports.path = function(userPath, settings = {}) { 10 | let defaultSettings = { 11 | __dirname: false 12 | } 13 | settings = assign(defaultSettings, settings) 14 | 15 | return path.resolve(settings.__dirname ? settings.__dirname : process.cwd(), './', userPath); 16 | } 17 | exports.pathExists = function(userPath, extensions = ['']) { 18 | let filePath = exports.path(userPath) 19 | return extensions.some((extension) => { 20 | let tmpPath = extension == '' ? filePath : filePath +'.'+ extension 21 | return fs.existsSync(tmpPath) 22 | }) 23 | } 24 | exports.modulesAt = function(userPath, settings = {}) { 25 | let defaultSettings = { 26 | camelCase: true, 27 | capitalize: true, 28 | localPath: false 29 | } 30 | settings = assign(defaultSettings, settings) 31 | let filePath = settings.localPath ? userPath : exports.path(userPath) 32 | 33 | let files = fs.readdirSync(filePath), result = [] 34 | files.forEach((file) => { 35 | if(file.indexOf('.js') === -1) return 36 | let file_name = file.replace('.js', '') 37 | 38 | let moduleName = file_name 39 | if(settings.camelCase) moduleName = camelCase(moduleName) 40 | if(settings.capitalize) moduleName = capitalize(moduleName) 41 | 42 | result.push({ 43 | path: userPath +'/'+ file_name, 44 | name: moduleName 45 | }) 46 | }) 47 | 48 | return result 49 | } 50 | 51 | 52 | /* stream */ 53 | exports.streamToBuffer = function(stream) { 54 | return new Promise((resolve, reject) => { 55 | let buf = Buffer.allocUnsafe(0) 56 | stream.on('data', (chunk) => { 57 | console.log(`chunk length ${chunk.length}`) 58 | buf = Buffer.concat([buf, chunk]) 59 | }) 60 | stream.on('end', () => { 61 | return resolve(buf) 62 | }) 63 | stream.on('error', (err) => { 64 | return reject(err) 65 | }) 66 | }) 67 | } 68 | 69 | 70 | /* string */ 71 | exports.trim = function(string, char) { 72 | if (char === "]") char = "\\]"; 73 | if (char === "\\") char = "\\\\"; 74 | return string.replace(new RegExp( 75 | "^[" + char + "]+|[" + char + "]+$", "g" 76 | ), "") 77 | } 78 | 79 | exports.sanitizePath = function(path) { 80 | path = path.trim() 81 | path = path.replace(/\/+/, '/') 82 | path = exports.trim(path, '/') 83 | return exports.trim(path, '\\') 84 | } 85 | 86 | 87 | /* promise */ 88 | exports.to = function(promise) { 89 | return promise.then(data => { 90 | return [null, data] 91 | }) 92 | .catch(err => [err]) 93 | } 94 | 95 | exports.sleep = function(ms) { 96 | return new Promise(resolve => setTimeout(resolve, ms)); 97 | } 98 | 99 | exports.asyncForEach = async function(array, callback) { 100 | for (let index = 0; index < array.length; index++) { 101 | await callback(array[index], index, array); 102 | } 103 | } 104 | 105 | 106 | /* randoms */ 107 | exports.checkConstant = function(constant, value) { 108 | return Object.values(constant).indexOf(value) == -1 ? false : true 109 | } 110 | 111 | 112 | //length - 9 maximum 113 | exports.uniqueID = function(length) { 114 | return Math.random().toString(36).substr(2, length); 115 | } 116 | 117 | exports.uniqueIDv2 = function(length) { 118 | return new Promise((resolve, reject) => { 119 | crypto.randomBytes(length, (err, buf) => { 120 | if (err) return reject(err) 121 | resolve(buf.toString('hex').substr(0, length)) 122 | }) 123 | }) 124 | } 125 | 126 | 127 | //regex 128 | exports.regExpEscape = function(str) { 129 | return str.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&'); 130 | } --------------------------------------------------------------------------------