├── .env.example ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── README.md ├── app.js ├── bin └── server.js ├── database ├── connection.js ├── extra │ └── defualtColumns.js ├── knexfile.js ├── migrations │ ├── 20200709151446_createTestTable.js │ ├── 20200719140110_option.js │ ├── 20201022110529_user.js │ ├── 20201022111144_user_transaction.js │ ├── 20201022111643_payment_medium.js │ ├── 20201022111928_transaction_type.js │ ├── 20201022113834_job.js │ ├── 20201022115535_page.js │ ├── 20210110234212_add_rate_to_job.js │ ├── 20210115180408_add_transaction_confirmed.js │ ├── 20210121100727_add_currency_to_paymentmedium.js │ ├── 20210121102927_activation_token.js │ ├── 20210126112419_add_columns_to_user.js │ ├── 20210126113022_add_from_api_to_job.js │ └── 20210129095447_add_callback_to_job.js └── seeds │ ├── option.js │ ├── payment_medium.js │ └── transaction_type.js ├── faq.js ├── firebase └── draft.js ├── helpers ├── aws │ ├── config.js │ ├── pipeS3ObjectToExpress.js │ ├── putObjects.js │ └── removeObjects.js ├── convertNumberToArabic.js ├── email.js ├── emailTemplates.js ├── isValidAPIKey.js ├── numberWithCommas.js ├── options.js ├── ratelimit.js └── superagent.js ├── middlewares ├── jwt │ └── user.js └── validators │ ├── auth.js │ ├── blockedMailProviders.js │ ├── common │ ├── dataGrid.js │ ├── lazyLoad.js │ ├── searchQuery.js │ └── storage.js │ ├── data │ ├── idPay.js │ ├── job.js │ └── user.js │ ├── fastpay.js │ ├── idPay.js │ ├── job.js │ ├── user.js │ └── validate.js ├── package-lock.json ├── package.json ├── query ├── job.js └── user.js └── routes ├── auth.js ├── fastpay.js ├── idPay.js ├── index.js ├── job.js ├── s3interface.js └── user.js /.env.example: -------------------------------------------------------------------------------- 1 | # enviroment variables goes here 2 | USER_JWT_SECRET = "123" 3 | NODE_ENV = "development" 4 | # currenrt domain 5 | DOMAIN = "https://zhir.io" 6 | 7 | #database connection info 8 | HOST ="localhost" 9 | DB_USER ="root" 10 | DB_NAME = "zhir-ocr" 11 | DB_PASSWORD = "123" 12 | 13 | #email configuration 14 | MAILING_LIST = "" 15 | SENDER_EMAIL = "aramrafeq2@gamil.com" 16 | SENDER_PASSWORD = "123" 17 | 18 | # s3 bucket configuration 19 | AWS_ENDPOINT= "" 20 | AWS_ACCESS_KEY_ID= "" 21 | AWS_SECRET_ACCESS_KEY = "" 22 | AWS_BUCKET = "" 23 | 24 | # ID Pay ENV variables 25 | IDPAY_AUTH_KEY = "121312" 26 | IDPAY_SANDBOX = "1" 27 | 28 | #Fastpay 29 | FASTPAY_API_URL = "https://apigw-merchant.fast-pay.iq" 30 | FASTPAY_STORE_ID = "21312312" 31 | FASTPAY_MERCHANT_STORE_PASS = "$$" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "airbnb-base" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": 12 12 | }, 13 | "rules": { 14 | "linebreak-style": "off", 15 | "no-tabs": "off", 16 | "indent": ["error", "tab", { "SwitchCase": 1 }] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env 3 | /log -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | scripts-prepend-node-path=true 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cat](http://24.media.tumblr.com/ab01497a8b8060e1a5e6e16b5ec4005d/tumblr_mqnc6yryjy1sax1wjo1_500.gif) 2 | # API 3 | This acts as the main rest api for zhir.io 4 | 5 | 6 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const debug = require('debug'); 3 | const morgan = require('morgan'); 4 | const helmet = require('helmet'); 5 | const cors = require('cors'); 6 | const compression = require('compression'); 7 | const rfs = require('rotating-file-stream'); 8 | const path = require('path'); 9 | require('dotenv').config(); 10 | 11 | const app = express(); 12 | const accessLogStream = rfs.createStream('access.log', { 13 | size: '10M', // rotate every 10 MegaBytes written 14 | interval: '1d', // rotate daily 15 | path: path.join(__dirname, 'log'), 16 | }); 17 | 18 | let loggerOptions = { 19 | stream: accessLogStream, 20 | }; 21 | if (process.env.NODE_ENV === 'development') { 22 | loggerOptions = {}; 23 | } 24 | const logger = morgan('combined', loggerOptions); 25 | app.use(logger); 26 | 27 | app.use(cors()); 28 | // app.use(helmet({ 29 | // contentSecurityPolicy: false, 30 | // })); 31 | app.use(helmet()); 32 | app.use(compression()); 33 | 34 | app.use(express.json({ limit: '80mb' })); 35 | app.set('view engine', 'ejs'); 36 | 37 | const seInterfaceRouter = require('./routes/s3interface'); 38 | 39 | app.use('/files', seInterfaceRouter); 40 | 41 | app.use('/', require('./routes')); 42 | 43 | app.get('/', (req, res) => { 44 | res.json({ msg: 'Zhir.io API' }); 45 | }); 46 | // eslint-disable-next-line no-unused-vars 47 | app.use((err, req, res, next) => { 48 | debug.error(err.stack); 49 | res.status(500).send(err.toString()); 50 | }); 51 | 52 | module.exports = app; 53 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | 3 | const app = require('../app'); 4 | 5 | const port = process.env.PORT || 3000; 6 | app.listen(port, () => { 7 | debug.log(`app listening at http://localhost:${port}`); 8 | }); 9 | -------------------------------------------------------------------------------- /database/connection.js: -------------------------------------------------------------------------------- 1 | const db = require('knex'); 2 | const path = require('path'); 3 | 4 | require('dotenv').config({ path: path.join(__dirname, '../../../', '.env') }); 5 | 6 | module.exports = db({ 7 | client: 'mysql', 8 | connection: { 9 | host: process.env.HOST, 10 | user: process.env.DB_USER, 11 | password: process.env.DB_PASSWORD, 12 | database: process.env.DB_NAME, 13 | debug: false, 14 | }, 15 | port: 80, 16 | }); 17 | -------------------------------------------------------------------------------- /database/extra/defualtColumns.js: -------------------------------------------------------------------------------- 1 | module.exports = (table, excludes = []) => { 2 | if (excludes.indexOf('active') < 0) { 3 | table.boolean('active').notNullable().defaultTo(1); 4 | } 5 | if (excludes.indexOf('deleted') < 0) { 6 | table.boolean('deleted').notNullable().defaultTo(0); 7 | } 8 | if (excludes.indexOf('updated_at') < 0) { 9 | table.datetime('updated_at', { precision: 6 }); 10 | table.integer('updated_by').notNullable().defaultTo(0); 11 | } 12 | 13 | table.datetime('created_at', { precision: 6 }); 14 | table.integer('created_by').notNullable().defaultTo(0); 15 | table.charset('utf8mb4'); 16 | return table; 17 | }; 18 | -------------------------------------------------------------------------------- /database/knexfile.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config({ path: path.join(__dirname, '../../', '.env') }); 3 | 4 | const folders = { 5 | seeds: { 6 | directory: './seeds', 7 | }, 8 | migrations: { 9 | directory: './migrations', 10 | }, 11 | }; 12 | 13 | module.exports = { 14 | development: { 15 | client: 'mysql', 16 | connection: { 17 | host: process.env.HOST, 18 | user: process.env.DB_USER, 19 | password: process.env.DB_PASSWORD, 20 | database: process.env.DB_NAME, 21 | debug: false, 22 | }, 23 | port: 80, 24 | ...folders, 25 | }, 26 | production: { 27 | client: 'mysql', 28 | connection: { 29 | host: process.env.HOST, 30 | user: process.env.DB_USER, 31 | password: process.env.DB_PASSWORD, 32 | database: process.env.DB_NAME, 33 | debug: false, 34 | }, 35 | port: 80, 36 | ...folders, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /database/migrations/20200709151446_createTestTable.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.createTable('test', (table) => { 3 | table.increments('id').primary(); 4 | table.string('name', 350).notNullable().defaultTo('default name'); 5 | table.charset('utf8mb4'); 6 | // table.collate('utf8_general_ci'); 7 | }); 8 | 9 | exports.down = (knex) => knex.schema.dropTable('test'); 10 | -------------------------------------------------------------------------------- /database/migrations/20200719140110_option.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.createTable('option', (table) => { 3 | table.increments('id').primary(); 4 | table.string('key', 355).notNullable().defaultTo(''); 5 | table.string('value', 355).notNullable().defaultTo(''); 6 | table.charset('utf8mb4'); 7 | }); 8 | 9 | exports.down = (knex) => knex.schema.dropTable('option'); 10 | -------------------------------------------------------------------------------- /database/migrations/20201022110529_user.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => knex.schema.createTable('user', (table) => { 4 | table.increments('id').primary(); 5 | table.string('name', 350).notNullable().defaultTo(''); 6 | table.string('company_name', 350).notNullable().defaultTo(''); 7 | table.string('email', 350).notNullable().unique(); 8 | table.string('password', 100).notNullable().defaultTo(''); 9 | table.string('tmp_password', 100).defaultTo(''); 10 | table.string('activation_token', 100).defaultTo(''); 11 | table.string('salt', 100).notNullable().defaultTo(''); 12 | table 13 | .enu('gender', ['male', 'female', 'unspecified']) 14 | .notNullable() 15 | .defaultTo('unspecified'); 16 | table.boolean('is_admin').defaultTo(0); 17 | table.boolean('verified').defaultTo(0); 18 | table.date('birthdate'); 19 | table.string('phone_no', 100).notNullable().defaultTo(''); 20 | table.string('uid', 200).notNullable().defaultTo(''); 21 | defualts(table); 22 | table.charset('utf8mb4'); 23 | }); 24 | 25 | exports.down = (knex) => knex.schema.dropTable('user'); 26 | -------------------------------------------------------------------------------- /database/migrations/20201022111144_user_transaction.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => 4 | knex.schema.createTable('user_transaction', (table) => { 5 | table.increments('id').primary(); 6 | table.integer('user_id'); 7 | table.integer('type_id'); 8 | table.integer('payment_medium_id').defaultTo(0); // defualt payment medium we can add extra column to payment_mediums 9 | table.integer('page_count'); 10 | table.decimal('amount', 11, 3).notNullable().defaultTo(1000); // we use IQD only 11 | table.string('transaction_id', 350); 12 | table.string('user_note', 350).defaultTo(''); 13 | table.string('admin_note', 350).defaultTo(''); 14 | 15 | defualts(table, ['active', 'deleted', 'updated_at']); 16 | table.charset('utf8mb4'); 17 | }); 18 | 19 | exports.down = (knex) => knex.schema.dropTable('user_transaction'); 20 | -------------------------------------------------------------------------------- /database/migrations/20201022111643_payment_medium.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => 4 | knex.schema.createTable('payment_medium', (table) => { 5 | table.increments('id').primary(); 6 | table.string('name', 350).notNullable().defaultTo(''); 7 | table.string('code', 350).notNullable().defaultTo(''); 8 | table.decimal('min_amount', 11, 3).notNullable().defaultTo(0); // minimum amout to use for a transaction 9 | table.decimal('max_amount', 11, 3).notNullable().defaultTo(0); // maximum amout to use for a transaction 10 | defualts(table); 11 | table.charset('utf8mb4'); 12 | }); 13 | 14 | exports.down = (knex) => knex.schema.dropTable('payment_medium'); 15 | -------------------------------------------------------------------------------- /database/migrations/20201022111928_transaction_type.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.createTable('transaction_type', (table) => { 3 | table.increments('id').primary(); 4 | table.string('name', 350).notNullable().defaultTo(''); 5 | table.string('code', 350).notNullable().defaultTo(''); 6 | table.charset('utf8mb4'); 7 | }); 8 | 9 | exports.down = (knex) => knex.schema.dropTable('transaction_type'); 10 | -------------------------------------------------------------------------------- /database/migrations/20201022113834_job.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => 4 | knex.schema.createTable('job', (table) => { 5 | table.string('id', 36).primary(); 6 | table.string('name', 350).notNullable().defaultTo(''); 7 | table.string('code', 350).notNullable().defaultTo(''); 8 | table.integer('user_id'); 9 | table.integer('page_count'); 10 | table.integer('paid_page_count'); 11 | table.string('user_failing_reason', 350).notNullable().defaultTo(''); 12 | table.enu('status', [ 13 | 'pending', 14 | 'queued', 15 | 'processing', 16 | 'completed', 17 | 'failed', 18 | ]); 19 | table.string('lang', 350).defaultTo('ckb'); 20 | 21 | table.datetime('queued_at', { precision: 6 }).defaultTo(knex.fn.now(6)); 22 | table.datetime('processed_at', { precision: 6 }); 23 | 24 | table.datetime('finished_at', { precision: 6 }); 25 | // table.datetime('failed_at', { precision: 6 }); 26 | table.string('failing_reason', 450).notNullable().defaultTo(''); 27 | 28 | defualts(table, ['active', 'updated_at']); 29 | table.charset('utf8mb4'); 30 | }); 31 | 32 | exports.down = (knex) => knex.schema.dropTable('job'); 33 | -------------------------------------------------------------------------------- /database/migrations/20201022115535_page.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => 4 | knex.schema.createTable('page', (table) => { 5 | table.string('id', 36).primary(); 6 | table.string('name', 350).notNullable().defaultTo(''); 7 | table.integer('user_id'); 8 | table.string('job_id', 350); 9 | table 10 | .datetime('started_processing_at', { precision: 6 }) 11 | .defaultTo(knex.fn.now(6)); 12 | 13 | table.boolean('processed').notNullable().defaultTo(0); 14 | table.boolean('is_free').notNullable().defaultTo(0); 15 | 16 | table 17 | .datetime('finished_processing_at', { precision: 6 }) 18 | .defaultTo(knex.fn.now(6)); 19 | table.boolean('succeeded').notNullable().defaultTo(1); 20 | 21 | table.text('result', 'MEDIUMTEXT'); 22 | table.text('processed_result', 'MEDIUMTEXT'); 23 | 24 | defualts(table, ['active', 'updated_at']); 25 | table.charset('utf8mb4'); 26 | }); 27 | 28 | exports.down = (knex) => knex.schema.dropTable('page'); 29 | -------------------------------------------------------------------------------- /database/migrations/20210110234212_add_rate_to_job.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('job', (table) => { 3 | table.integer('rate', 11).defaultTo(-1000); 4 | }); 5 | 6 | exports.down = (knex) => 7 | knex.schema.alterTable('job', (table) => { 8 | table.dropColumn('rate'); 9 | }); 10 | -------------------------------------------------------------------------------- /database/migrations/20210115180408_add_transaction_confirmed.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('user_transaction', (table) => { 3 | table.boolean('confirmed').defaultTo(0); 4 | }); 5 | 6 | exports.down = (knex) => 7 | knex.schema.alterTable('user_transaction', (table) => { 8 | table.dropColumn('confirmed'); 9 | }); 10 | -------------------------------------------------------------------------------- /database/migrations/20210121100727_add_currency_to_paymentmedium.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('payment_medium', (table) => { 3 | table.string('currency_symbol', 100).defaultTo(''); 4 | }); 5 | 6 | exports.down = (knex) => 7 | knex.schema.alterTable('payment_medium', (table) => { 8 | table.dropColumn('currency_symbol'); 9 | }); 10 | -------------------------------------------------------------------------------- /database/migrations/20210121102927_activation_token.js: -------------------------------------------------------------------------------- 1 | const defualts = require('../extra/defualtColumns'); 2 | 3 | exports.up = (knex) => 4 | knex.schema.createTable('activation_token', (table) => { 5 | table.increments('id').primary(); 6 | table.integer('user_id'); 7 | table.string('token', 350).notNullable().defaultTo(''); 8 | 9 | defualts(table, ['active', 'updated_at']); 10 | table.charset('utf8mb4'); 11 | }); 12 | 13 | exports.down = (knex) => knex.schema.dropTable('activation_token'); 14 | -------------------------------------------------------------------------------- /database/migrations/20210126112419_add_columns_to_user.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('user', (table) => { 3 | table.boolean('can_use_api').defaultTo(0); 4 | table.string('api_key', 200).defaultTo(''); 5 | table.integer('monthly_recharge', 11).defaultTo(50); 6 | }); 7 | 8 | exports.down = (knex) => 9 | knex.schema.alterTable('user', (table) => { 10 | table.dropColumn('can_use_api'); 11 | table.dropColumn('api_key'); 12 | table.dropColumn('monthly_recharge'); 13 | }); 14 | -------------------------------------------------------------------------------- /database/migrations/20210126113022_add_from_api_to_job.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('job', (table) => { 3 | table.boolean('from_api').defaultTo(0); 4 | }); 5 | 6 | exports.down = (knex) => 7 | knex.schema.alterTable('job', (table) => { 8 | table.dropColumn('from_api'); 9 | }); 10 | -------------------------------------------------------------------------------- /database/migrations/20210129095447_add_callback_to_job.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex) => 2 | knex.schema.alterTable('job', (table) => { 3 | table.string('callback', 1000).defaultTo(''); 4 | }); 5 | 6 | exports.down = (knex) => 7 | knex.schema.alterTable('job', (table) => { 8 | table.dropColumn('callback'); 9 | }); 10 | -------------------------------------------------------------------------------- /database/seeds/option.js: -------------------------------------------------------------------------------- 1 | exports.seed = (knex) => 2 | knex('option') 3 | .del() 4 | .then(() => 5 | knex('option').insert([ 6 | { 7 | key: 'price_per_page', 8 | value: 150, 9 | }, 10 | ]) 11 | ); 12 | -------------------------------------------------------------------------------- /database/seeds/payment_medium.js: -------------------------------------------------------------------------------- 1 | exports.seed = (knex) => 2 | knex('payment_medium') 3 | .del() 4 | .then(() => 5 | knex('payment_medium').insert([ 6 | { 7 | id: 1, 8 | name: 'ژیر', 9 | code: 'ZHIR', 10 | min_amount: 1000, // 1000IQD 11 | max_amount: 350000, // 350000 IQD 12 | currency_symbol: '', 13 | }, 14 | { 15 | id: 2, 16 | name: 'فاستپەی', 17 | code: 'FASTPAY', 18 | min_amount: 1000, // 1000IQD 19 | max_amount: 350000, // 350000 IQD 20 | currency_symbol: 'د.ع', 21 | }, 22 | { 23 | id: 3, 24 | name: 'ئاسیا حەواڵە', 25 | code: 'ASIA_HAWALA', 26 | min_amount: 1000, // 1000IQD 27 | max_amount: 350000, // 350000 IQD 28 | currency_symbol: 'د.ع', 29 | }, 30 | { 31 | id: 4, 32 | name: 'زەین کاش', 33 | code: 'ZAIN_CASH', 34 | min_amount: 1000, // 1000IQD 35 | max_amount: 350000, // 350000 IQD 36 | currency_symbol: 'د.ع', 37 | }, 38 | { 39 | id: 5, 40 | name: 'باڵانس', 41 | code: 'BALANCE', 42 | min_amount: 0, // 1000IQD 43 | max_amount: 350000, // 350000 IQD 44 | currency_symbol: 'د.ع', 45 | }, 46 | { 47 | id: 6, 48 | name: 'ئایدی پەی', 49 | code: 'IDPAY', 50 | min_amount: 0, 51 | max_amount: 3500000, 52 | currency_symbol: 'ریال', 53 | }, 54 | ]) 55 | ); 56 | -------------------------------------------------------------------------------- /database/seeds/transaction_type.js: -------------------------------------------------------------------------------- 1 | exports.seed = (knex) => 2 | knex('transaction_type') 3 | .del() 4 | .then(() => 5 | knex('transaction_type').insert([ 6 | { 7 | id: 1, 8 | name: 'پرکرنەوەی باڵانس', 9 | code: 'RECHARGE', 10 | }, 11 | { 12 | id: 2, 13 | name: 'سکانکردن', 14 | code: 'OCR-JOB', 15 | }, 16 | { 17 | id: 3, 18 | name: 'ناردنی باڵانس', 19 | code: 'BALANCE-TRANSFER', 20 | }, 21 | ]) 22 | ); 23 | -------------------------------------------------------------------------------- /faq.js: -------------------------------------------------------------------------------- 1 | const faqs = [ 2 | { 3 | question: 'ژیر بە خۆڕاییە؟', 4 | answer: 5 | 'نەخێر، لە خزمەتگوزاری ژیر دەتوانیت بەپێی پێویستی پەرە بکڕیت و دوای ئەوە هەرکاتێک ویستت بەکاریان بهێنیت بۆ دەرهێنانی دەقەکان.', 6 | }, 7 | { 8 | question: 'ژیر دەستنووس دەناسێتەوە؟', 9 | answer: 10 | 'نەخێر، تەنها دۆکیۆمێنتی پرینتکراو یان سکانکراو دەکاتەوە بە نووسین لە داهاتوودا دەستنووسیش دەناسێتەوە.', 11 | }, 12 | { 13 | question: 'تاوەکو چەند زانیاریەکانم پارێزراون؟', 14 | answer: 15 | 'فایل و زانیاریەکانت لەسەر راژەیەکی تایبەتی هەڵدەگیرێت کە کەس ناتوانێت دەستی پێیان بگات.', 16 | }, 17 | { 18 | question: 'نووسینی تێکەڵ دەناسێتەوە بە چەند زمانێک نوسرابێت؟', 19 | answer: 20 | 'وا باشترە پەڕەی سکانکراو تەنها کوردی یان عەرەبی تێدابێت بۆ ئەوەی بەباشی دەقەکان دەربهێنێت، نوسینی ئینگلیزی دەناسێتەوە بەڵام لەوانەیە کاریگەری هەبێت لەسەر دروستی دەقی دەرهێندراو هەرچەندە ژمارە و رێکەوتی ئینگلیزی کاریگەری نیە و ئاساییە.', 21 | }, 22 | { 23 | question: 'ژیر هەموو شتێک بە دروستی دەردەهێنێت؟', 24 | answer: 25 | 'تا ڕادەیەک، هەرچەندە هەموو هەوڵی خۆمان داوە بۆ ئەوەی بە دروستترین شێوە، نووسین و دەقەکان بناسێتەوە بەردەوامیشین لە هەوڵدان بۆ باشترکردنی.', 26 | }, 27 | { 28 | question: 29 | 'پێویست دەکا ماڵپەری ژیر بە کراوەیی بهێڵمەوە لەکاتی جێبەجێکردنی کارەکان؟', 30 | answer: 31 | 'نەخێر. دوای ئەوەی وێنەکانت بارکرد، دەتوانی ماڵپەری ژیر دابخەی. ژیر کارەکان لەسەر سێرڤەر دەکات. دەتوانی هەر کات ویستت لە لیستی ناسینەوەی وێنە ئەنجامەکان وەربگریەوە.', 32 | }, 33 | { 34 | question: 'چۆن پەیوەندی بە ژیرەوە بکەین؟', 35 | answer: 36 | 'دەتوانن ڕەخنە و پێشنیارەکانتان بنێرن بۆ پۆستی ئەلیکترۆنی ژیر info@zhir.io یان لەرێگای چاتی ڕاستەوخۆ.', 37 | }, 38 | ]; 39 | export default faqs; 40 | -------------------------------------------------------------------------------- /firebase/draft.js: -------------------------------------------------------------------------------- 1 | function initApp() { 2 | firebase.auth().onAuthStateChanged(function (user) { 3 | if (user) { 4 | // User is signed in. 5 | // we dont care about signed in user 6 | // when user signs in we create custom session from out bakcend using idToken 7 | user.getIdToken().then(function (idToken) { 8 | // <------ Check this line 9 | console.log(idToken); // It shows the Firebase token now 10 | }); 11 | console.log(user); 12 | } else { 13 | // User is signed out. 14 | alert("user signed out"); 15 | } 16 | }); 17 | } 18 | let logout = () => { 19 | firebase.auth().signOut(); 20 | }; 21 | let signInWithGoogle = () => { 22 | var provider = new firebase.auth.GoogleAuthProvider(); 23 | firebase 24 | .auth() 25 | .signInWithPopup(provider) 26 | .then((result) => { 27 | console.log(result.credential); 28 | }) 29 | .catch((error) => { 30 | var errorCode = error.code; 31 | var errorMessage = error.message; 32 | // The email of the user's account used. 33 | var email = error.email; 34 | // The firebase.auth.AuthCredential type that was used. 35 | var credential = error.credential; 36 | console.log(error); 37 | }); 38 | }; 39 | 40 | onMount(async () => { 41 | initApp(); 42 | }); 43 | { 44 | /* 45 | */ 46 | } 47 | -------------------------------------------------------------------------------- /helpers/aws/config.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | AWS.config.update({ 4 | endpoint: process.env.AWS_ENDPOINT, 5 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 6 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 7 | }); 8 | 9 | const s3 = new AWS.S3(); 10 | 11 | module.exports = { 12 | AWS, 13 | s3, 14 | }; 15 | -------------------------------------------------------------------------------- /helpers/aws/pipeS3ObjectToExpress.js: -------------------------------------------------------------------------------- 1 | const { s3 } = require('./config'); 2 | 3 | module.exports = (params, res) => { 4 | s3.headObject(params, (err, headers) => { 5 | if (err) { 6 | res.status(404).json({ 7 | msg: 'ئەنجامی داواکراو بوونی نیە یان نەدۆزرایەوە', 8 | }); 9 | } else { 10 | const stream = s3.getObject(params).createReadStream(); 11 | 12 | stream.on('error', () => { 13 | res.status(404).json({ 14 | msg: 'هەڵەیەک رویدا لەکاتی ناردنەوەی ئەنجام', 15 | }); 16 | }); 17 | res.set('Content-Type', headers.ContentType); 18 | res.set('Content-Length', headers.ContentLength); 19 | res.set('Last-Modified', headers.LastModified); 20 | res.set('ETag', headers.ETag); 21 | // Pipe the s3 object to the response 22 | stream.pipe(res); 23 | } 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /helpers/aws/putObjects.js: -------------------------------------------------------------------------------- 1 | const { s3 } = require('./config'); 2 | 3 | module.exports = (objects = []) => { 4 | /* 5 | example of a single object should be something like 6 | for reguller ocr images the convention is here 7 | userid/jobid/index.jpg 8 | { 9 | index: 0, 10 | name: f.filename, 11 | base64: f.getFileEncodeDataURL(), 12 | type: "application/pdf", 13 | extension: "jpg", 14 | path: `/original/`, 15 | } 16 | */ 17 | const objectPromises = []; 18 | // const today = new Date(); 19 | // const year = today.getFullYear(); 20 | // const month = today.toLocaleString('default', { month: 'long' }); 21 | objects.forEach((object) => { 22 | objectPromises.push( 23 | new Promise((resolve, reject) => { 24 | const base64String = object.base64.substr( 25 | object.base64.indexOf(';base64,') + 8, 26 | object.length, 27 | ); 28 | // eslint-disable-next-line new-cap 29 | const binaryData = new Buffer.from(base64String, 'base64'); 30 | s3.upload( 31 | { 32 | Bucket: `${process.env.AWS_BUCKET}/${object.path}`, // process.env.AWS_BUCKET 33 | Body: binaryData, 34 | // Key: `${uid.time()}-${object.name}`, 35 | // Key: `${uuiv4()}`, 36 | Metadata: { 37 | name: Buffer.from(`${object.name}`).toString( 38 | 'base64', 39 | ), 40 | extention: `${object.extention}`, 41 | index: `${object.index}`, 42 | }, 43 | Key: `${object.index}.${object.extention}`, 44 | // ACL: 'public-read', // this should change in the future 45 | ContentType: object.type, 46 | CacheControl: 'max-age=31557600', 47 | }, 48 | (err, data) => (err == null ? resolve(data) : reject(err)), 49 | ); 50 | }), 51 | ); 52 | }); 53 | return Promise.all(objectPromises); 54 | }; 55 | -------------------------------------------------------------------------------- /helpers/aws/removeObjects.js: -------------------------------------------------------------------------------- 1 | const { s3 } = require('./config'); 2 | 3 | module.exports = async (keys = []) => { 4 | const deleteKeys = keys.map((k) => ({ 5 | Key: k, 6 | })); 7 | const params = { 8 | Bucket: `${process.env.AWS_BUCKET}`, 9 | Delete: { 10 | Objects: deleteKeys, 11 | Quiet: true, 12 | }, 13 | }; 14 | return s3.deleteObjects(params, (err, data) => { 15 | if (err) { 16 | return Promise.reject(err); 17 | } 18 | 19 | return Promise.resolve(data); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /helpers/convertNumberToArabic.js: -------------------------------------------------------------------------------- 1 | module.exports = (rawNumber) => { 2 | const number = `${rawNumber}`; 3 | let charIndex = 0; 4 | let NumericArabic = ''; 5 | 6 | while (charIndex < number.length) { 7 | switch (number[charIndex]) { 8 | case '.': 9 | NumericArabic += '.'; 10 | break; 11 | 12 | case '0': 13 | NumericArabic += '٠'; 14 | break; 15 | 16 | case '1': 17 | NumericArabic += '١'; 18 | break; 19 | 20 | case '2': 21 | NumericArabic += '٢'; 22 | break; 23 | 24 | case '3': 25 | NumericArabic += '٣'; 26 | break; 27 | 28 | case '4': 29 | NumericArabic += '٤'; 30 | break; 31 | 32 | case '5': 33 | NumericArabic += '٥'; 34 | break; 35 | 36 | case '6': 37 | NumericArabic += '٦'; 38 | break; 39 | 40 | case '7': 41 | NumericArabic += '٧'; 42 | break; 43 | 44 | case '8': 45 | NumericArabic += '٨'; 46 | break; 47 | 48 | case '9': 49 | NumericArabic += '٩'; 50 | break; 51 | 52 | default: 53 | NumericArabic += number[charIndex]; 54 | break; 55 | } 56 | 57 | charIndex += 1; 58 | } 59 | 60 | return NumericArabic; 61 | }; 62 | -------------------------------------------------------------------------------- /helpers/email.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | 3 | async function send( 4 | subject, 5 | text, 6 | to = process.env.MAILING_LIST, 7 | cb = () => {}, 8 | ) { 9 | try { 10 | const transporter = nodemailer.createTransport({ 11 | service: 'gmail', 12 | auth: { 13 | user: process.env.SENDER_EMAIL, 14 | pass: process.env.SENDER_PASSWORD, 15 | }, 16 | }); 17 | const message = { 18 | from: `<${process.env.SENDER_EMAIL}>`, 19 | to, 20 | subject, 21 | text: subject, 22 | html: text, 23 | }; 24 | transporter.sendMail(message, (err, info) => { 25 | cb(err, info); 26 | }); 27 | } catch (e) { 28 | // handle errors here 29 | } 30 | } 31 | 32 | module.exports = { 33 | send, 34 | }; 35 | -------------------------------------------------------------------------------- /helpers/emailTemplates.js: -------------------------------------------------------------------------------- 1 | function passwordResetTemplate(url) { 2 | return ` 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ژیر | گۆرینی تێپەرەوشە 11 | 12 | 13 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 108 | 109 | 110 | 119 | 120 | 121 | 158 | 159 | 160 | 164 | 165 |
98 | 99 | 100 | 105 | 106 |
101 | 102 | Logo 103 | 104 |
107 |
111 | 112 | 113 | 116 | 117 |
114 |

گۆرینی تێپەرەوشە

115 |
118 |
122 | 123 | 124 | 127 | 128 | 129 | 144 | 145 | 146 | 150 | 151 | 152 | 155 | 156 |
125 |

بۆ گۆرینی تێپەرەوشە کرتە لەو دوگمەیەی خوارەوە بکە ،گەر تۆ داواکاریەکەت نەناردوە دەتوانیت ئەم نامەیە بسریتەوە

126 |
130 | 131 | 132 | 141 | 142 |
133 | 134 | 135 | 138 | 139 |
136 | گۆرینی تێپەرەوشە 137 |
140 |
143 |
147 |

گەر کرتەکردن کارناکات دەتوانیت ئەم بەستەرەی خوارەوە کۆی بکەیت و بە وێبگەرەکەت راستەوخۆ پێیبگەیت

148 |

${url}

149 |
153 |

دەستەی برێوبەرایەتی
ژیر

154 |
157 |
161 | 162 |
163 |
166 | 167 | 168 | `; 169 | } 170 | function activateAccountTemplate(url) { 171 | return ` 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | ژیر | کاراکردنی هەژمار 180 | 181 | 182 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 277 | 278 | 279 | 288 | 289 | 290 | 327 | 328 | 329 | 333 | 334 |
267 | 268 | 269 | 274 | 275 |
270 | 271 | Logo 272 | 273 |
276 |
280 | 281 | 282 | 285 | 286 |
283 |

کاراکردنی هەژمار

284 |
287 |
291 | 292 | 293 | 296 | 297 | 298 | 313 | 314 | 315 | 319 | 320 | 321 | 324 | 325 |
294 |

بۆ کاراکردنی هەژمارەکەت کرتە لەم بەستەرەی خوارەوە بکە

295 |
299 | 300 | 301 | 310 | 311 |
302 | 303 | 304 | 307 | 308 |
305 | کاراکردنی هەژمار 306 |
309 |
312 |
316 |

گەر کرتەکردن کارناکات دەتوانیت ئەم بەستەرەی خوارەوە کۆی بکەیت و بە وێبگەرەکەت راستەوخۆ پێیبگەیت

317 |

${url}

318 |
322 |

دەستەی برێوبەرایەتی
ژیر

323 |
326 |
330 | 331 |
332 |
335 | 336 | 337 | `; 338 | } 339 | 340 | module.exports = { 341 | passwordResetTemplate, 342 | activateAccountTemplate, 343 | }; 344 | -------------------------------------------------------------------------------- /helpers/isValidAPIKey.js: -------------------------------------------------------------------------------- 1 | const db = require('../database/connection'); 2 | 3 | function isValidAPIKey(req, res, next) { 4 | const apiKey = req.headers['x-api-key']; 5 | if (!apiKey) { 6 | res.status(401).json({ 7 | msg: 'API Key is missing please set x-api-key header ', 8 | }); 9 | } else { 10 | db('user') 11 | .select() 12 | .where('api_key', apiKey) 13 | .where('active', 1) 14 | .where('deleted', 0) 15 | .limit(1) 16 | .then(([user]) => { 17 | const mutatedUser = user; 18 | if (user) { 19 | req.user = user; 20 | mutatedUser.from_api = 1; 21 | const isSandbox = req.headers['is-sandbox']; 22 | if (isSandbox === 1) mutatedUser.is_sandbox = 1; 23 | next(); 24 | } else { 25 | res.status(401).json({ 26 | msg: 27 | 'Invalid API key or the user is blocked from using the api contact support or call 0750 7665935', 28 | }); 29 | } 30 | }); 31 | } 32 | } 33 | module.exports = isValidAPIKey; 34 | -------------------------------------------------------------------------------- /helpers/numberWithCommas.js: -------------------------------------------------------------------------------- 1 | module.exports = (x) => (`${x}`).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); 2 | -------------------------------------------------------------------------------- /helpers/options.js: -------------------------------------------------------------------------------- 1 | const options = { 2 | price_per_page: 200, 3 | }; 4 | 5 | module.exports = options; 6 | -------------------------------------------------------------------------------- /helpers/ratelimit.js: -------------------------------------------------------------------------------- 1 | const { RateLimiterMySQL } = require('rate-limiter-flexible'); 2 | const db = require('../database/connection'); 3 | 4 | const rateLimiter = new RateLimiterMySQL({ 5 | blockDuration: 7200, // 2h 6 | storeClient: db, 7 | storeType: 'knex', 8 | keyPrefix: 'middleware', 9 | dbName: process.env.DB_NAME, 10 | tableName: 'rate_limit', 11 | points: 20, // 10 requests 12 | duration: 2, // per 1 second by IP 13 | }); 14 | 15 | const rateLimiterMiddleware = (req, res, next) => { 16 | rateLimiter 17 | .consume(req.ip, 1) 18 | .then(() => { 19 | next(); 20 | }) 21 | .catch(() => { 22 | res.status(429).json({ 23 | msg: 24 | 'بۆ ماوەی ٢ کاتژمێر بڵۆک کرایت بەهۆی ناردنی داواکاری زۆرەوە', 25 | }); 26 | }); 27 | }; 28 | module.exports = rateLimiterMiddleware; 29 | -------------------------------------------------------------------------------- /helpers/superagent.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); 2 | 3 | function constructHtmlError(err) { 4 | const { response } = err; 5 | if (response) { 6 | const { status, body } = response; 7 | let lis = ''; 8 | if (status === 422) { 9 | const { errors } = body; 10 | (errors || []).forEach((e) => { 11 | lis += `
  • ${e.param} : ${e.msg}
  • `; 12 | }); 13 | } else if (status === 400 || status === 401 || status === 429) { 14 | lis += `
  • ${body.msg}
  • `; 15 | } else if (status === 500) { 16 | lis += `
  • ${body.msg}
  • `; 17 | } else { 18 | lis += '
  • ببورە هەڵەیەک لە راژە رویدا
  • '; 19 | } 20 | const html = ` 21 | 24 | `; 25 | return html; 26 | } 27 | return ''; 28 | } 29 | const agent = superagent.agent(); 30 | agent.on('error', (err) => { 31 | // eslint-disable-next-line no-param-reassign 32 | err.htmlError = constructHtmlError(err); 33 | return err; 34 | }); 35 | export default agent; 36 | -------------------------------------------------------------------------------- /middlewares/jwt/user.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const db = require('../../database/connection'); 3 | 4 | module.exports = async (req, res, next) => { 5 | try { 6 | const { authorization } = req.headers; 7 | const parts = (authorization) ? authorization.split(' ') : []; 8 | if (parts[1]) { 9 | try { 10 | const decoded = jwt.verify(parts[1], process.env.USER_JWT_SECRET); 11 | // req.customer = decoded; 12 | req.user = { 13 | ...decoded.data, 14 | token: parts[1], 15 | }; 16 | const [user] = await db('user').select() 17 | .where('active', 1) 18 | .where('deleted', 0) 19 | .andWhere('id', req.user.id) 20 | .limit(1); 21 | if (user) { 22 | next(); 23 | } else { 24 | res.status(401).json({ 25 | msg: 'Your account is not active, contact customer support ', 26 | }); 27 | } 28 | } catch (err) { 29 | res.status(401).json({ 30 | msg: err.toString(), 31 | }); 32 | } 33 | } else { 34 | res.status(401).json({ 35 | msg: 'invalid token provided', 36 | }); 37 | } 38 | } catch (err) { 39 | res.status(401).json({ 40 | msg: err.toString(), 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /middlewares/validators/auth.js: -------------------------------------------------------------------------------- 1 | const { body, query } = require('express-validator'); 2 | const validate = require('./validate'); 3 | const { emailExisitsValidator } = require('./data/user'); 4 | const blockedEmails = require('./blockedMailProviders'); 5 | 6 | function blockedEmailValidator(v, bEmails = blockedEmails) { 7 | let passed = true; 8 | // eslint-disable-next-line no-restricted-syntax 9 | for (const blockedEmail of bEmails) { 10 | if (`${v}`.includes(blockedEmail)) { 11 | passed = false; 12 | break; 13 | } 14 | } 15 | if (passed) return Promise.resolve(true); 16 | return Promise.reject(new Error('ئەم جۆرە ئیمەیڵە بلۆک کراوە')); 17 | } 18 | module.exports = { 19 | emailPassValidator: [ 20 | body('email') 21 | .exists() 22 | .withMessage('key does not exist') 23 | .trim() 24 | .custom((v) => blockedEmailValidator(v, blockedEmails)), 25 | body('password').exists().withMessage('key does not exist'), 26 | validate, 27 | ], 28 | passwordResetValidator: [ 29 | body('token') 30 | .exists() 31 | .isHash('sha1') 32 | .withMessage('تۆکنی گۆرینی تێپەرەوشە هەڵەیە'), 33 | body('password_retype').optional().isString(), 34 | body('password') 35 | .optional() 36 | .isString() 37 | 38 | .withMessage('input is not valid string') 39 | .isLength({ min: 3 }) 40 | .withMessage(' minimum length for user password is 2 chars') 41 | .custom((value, { req }) => { 42 | if (value === req.body.password_retype && value !== '') { 43 | return Promise.resolve('success'); 44 | } 45 | return Promise.reject( 46 | new Error( 47 | 'user passwords dont match or both fields are empty', 48 | ), 49 | ); 50 | }) 51 | .withMessage('passwords dont match or bothe fields are empty'), 52 | validate, 53 | ], 54 | activateAccountValidator: [ 55 | query('token') 56 | .exists() 57 | .isHash('sha1') 58 | .withMessage('تۆکنی کاراکردنی هەژمار هەڵەیە'), 59 | validate, 60 | ], 61 | phoneNoValidator: [ 62 | body('phone_no') 63 | .exists() 64 | .matches(/^964[0-9][1-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/i) 65 | .withMessage( 66 | 'phone number should be an iraqi phone and in this format 964 *** *******', 67 | ), 68 | validate, 69 | ], 70 | phoneNoWithCodeValidator: [ 71 | body('phone_no') 72 | .exists() 73 | .matches(/^964[0-9][1-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/i) 74 | .withMessage( 75 | 'phone number should be an iraqi phone and in this format 964 *** *******', 76 | ), 77 | body('code') 78 | .exists() 79 | .isInt({ min: 6 }) 80 | .withMessage('invalid verification code'), 81 | validate, 82 | ], 83 | requestPasswordResetValidator: [ 84 | body('email') 85 | .exists() 86 | .withMessage('بوونی نیە') 87 | .trim() 88 | .isString() 89 | .withMessage('input is not a valid string') 90 | .custom((v) => emailExisitsValidator(v)) 91 | .custom((v) => blockedEmailValidator(v, blockedEmails)), 92 | validate, 93 | ], 94 | }; 95 | -------------------------------------------------------------------------------- /middlewares/validators/blockedMailProviders.js: -------------------------------------------------------------------------------- 1 | const blockedEmailProviders = [ 2 | 'aramidth.com', 3 | '990ys.com', 4 | 'macosnine.com', 5 | 'timothyjsilverman.com', 6 | 'geeky83.com', 7 | 'upived.online', 8 | '250hz.com', 9 | 'edultry.com', 10 | 'lidte.com', 11 | 'mohmal.in', 12 | '559ai.com', 13 | 'fironia.com', 14 | 'combcub.com', 15 | 'fironia.com', 16 | 'boldhut.com', 17 | 'laklica.com', 18 | 'loopsnow.com', 19 | 'firemailbox.club', 20 | 'eamarian.com', 21 | 'mailna.co', 22 | 'jmail.com', 23 | 'dashseat.com', 24 | 'febula.com', 25 | 'izzum.com', 26 | 'is35.com', 27 | 'fmail.com', 28 | 'zher.com', 29 | 'vss6.com', 30 | 'majorsww.com', 31 | 'vipwxb.com', 32 | 'liaphoto.com', 33 | 'orxy.tech', 34 | 'burpcollaborator.net', 35 | '28woman.com', 36 | 'girtipo.com', 37 | 'cuoly.com', 38 | 'gail.com', 39 | 'z-er.com', 40 | 'cocyo.com', 41 | 'chomagor.com', 42 | 'tobuso.com', 43 | 'aomrock.com', 44 | 'nowdigit.com', 45 | 'hebgsw.com', 46 | 'firemailbox.club', 47 | 'wii999.com', 48 | 'otozuz.com', 49 | '0815.ru', 50 | '0wnd.net', 51 | '0wnd.org', 52 | '10minutemail.co.za', 53 | '10minutemail.com', 54 | '123-m.com', 55 | '1fsdfdsfsdf.tk', 56 | '1pad.de', 57 | '20minutemail.com', 58 | '21cn.com', 59 | '2fdgdfgdfgdf.tk', 60 | '2prong.com', 61 | '30minutemail.com', 62 | '33mail.com', 63 | '3trtretgfrfe.tk', 64 | '4gfdsgfdgfd.tk', 65 | '4warding.com', 66 | '5ghgfhfghfgh.tk', 67 | '6hjgjhgkilkj.tk', 68 | '6paq.com', 69 | '7tags.com', 70 | '9ox.net', 71 | 'a-bc.net', 72 | 'agedmail.com', 73 | 'ama-trade.de', 74 | 'amilegit.com', 75 | 'amiri.net', 76 | 'amiriindustries.com', 77 | 'anonmails.de', 78 | 'anonymbox.com', 79 | 'antichef.com', 80 | 'antichef.net', 81 | 'antireg.ru', 82 | 'antispam.de', 83 | 'antispammail.de', 84 | 'armyspy.com', 85 | 'artman-conception.com', 86 | 'azmeil.tk', 87 | 'baxomale.ht.cx', 88 | 'beefmilk.com', 89 | 'bigstring.com', 90 | 'binkmail.com', 91 | 'bio-muesli.net', 92 | 'bobmail.info', 93 | 'bodhi.lawlita.com', 94 | 'bofthew.com', 95 | 'bootybay.de', 96 | 'boun.cr', 97 | 'bouncr.com', 98 | 'breakthru.com', 99 | 'brefmail.com', 100 | 'bsnow.net', 101 | 'bspamfree.org', 102 | 'bugmenot.com', 103 | 'bund.us', 104 | 'burstmail.info', 105 | 'buymoreplays.com', 106 | 'byom.de', 107 | 'c2.hu', 108 | 'card.zp.ua', 109 | 'casualdx.com', 110 | 'cek.pm', 111 | 'centermail.com', 112 | 'centermail.net', 113 | 'chammy.info', 114 | 'childsavetrust.org', 115 | 'chogmail.com', 116 | 'choicemail1.com', 117 | 'clixser.com', 118 | 'cmail.net', 119 | 'cmail.org', 120 | 'coldemail.info', 121 | 'cool.fr.nf', 122 | 'courriel.fr.nf', 123 | 'courrieltemporaire.com', 124 | 'crapmail.org', 125 | 'cust.in', 126 | 'cuvox.de', 127 | 'd3p.dk', 128 | 'dacoolest.com', 129 | 'dandikmail.com', 130 | 'dayrep.com', 131 | 'dcemail.com', 132 | 'deadaddress.com', 133 | 'deadspam.com', 134 | 'delikkt.de', 135 | 'despam.it', 136 | 'despammed.com', 137 | 'devnullmail.com', 138 | 'dfgh.net', 139 | 'digitalsanctuary.com', 140 | 'dingbone.com', 141 | 'disposableaddress.com', 142 | 'disposableemailaddresses.com', 143 | 'disposableinbox.com', 144 | 'dispose.it', 145 | 'dispostable.com', 146 | 'dodgeit.com', 147 | 'dodgit.com', 148 | 'donemail.ru', 149 | 'dontreg.com', 150 | 'dontsendmespam.de', 151 | 'drdrb.net', 152 | 'dump-email.info', 153 | 'dumpandjunk.com', 154 | 'dumpyemail.com', 155 | 'e-mail.com', 156 | 'e-mail.org', 157 | 'e4ward.com', 158 | 'easytrashmail.com', 159 | 'einmalmail.de', 160 | 'einrot.com', 161 | 'eintagsmail.de', 162 | 'emailgo.de', 163 | 'emailias.com', 164 | 'emaillime.com', 165 | 'emailsensei.com', 166 | 'emailtemporanea.com', 167 | 'emailtemporanea.net', 168 | 'emailtemporar.ro', 169 | 'emailtemporario.com.br', 170 | 'emailthe.net', 171 | 'emailtmp.com', 172 | 'emailwarden.com', 173 | 'emailx.at.hm', 174 | 'emailxfer.com', 175 | 'emeil.in', 176 | 'emeil.ir', 177 | 'emz.net', 178 | 'ero-tube.org', 179 | 'evopo.com', 180 | 'explodemail.com', 181 | 'express.net.ua', 182 | 'eyepaste.com', 183 | 'fakeinbox.com', 184 | 'fakeinformation.com', 185 | 'fansworldwide.de', 186 | 'fantasymail.de', 187 | 'fightallspam.com', 188 | 'filzmail.com', 189 | 'fivemail.de', 190 | 'fleckens.hu', 191 | 'frapmail.com', 192 | 'friendlymail.co.uk', 193 | 'fuckingduh.com', 194 | 'fudgerub.com', 195 | 'fyii.de', 196 | 'garliclife.com', 197 | 'gehensiemirnichtaufdensack.de', 198 | 'get2mail.fr', 199 | 'getairmail.com', 200 | 'getmails.eu', 201 | 'getonemail.com', 202 | 'giantmail.de', 203 | 'girlsundertheinfluence.com', 204 | 'gishpuppy.com', 205 | 'gmial.com', 206 | 'goemailgo.com', 207 | 'gotmail.net', 208 | 'gotmail.org', 209 | 'gotti.otherinbox.com', 210 | 'great-host.in', 211 | 'greensloth.com', 212 | 'grr.la', 213 | 'gsrv.co.uk', 214 | 'guerillamail.biz', 215 | 'guerillamail.com', 216 | 'guerrillamail.biz', 217 | 'guerrillamail.com', 218 | 'guerrillamail.de', 219 | 'guerrillamail.info', 220 | 'guerrillamail.net', 221 | 'guerrillamail.org', 222 | 'guerrillamailblock.com', 223 | 'gustr.com', 224 | 'harakirimail.com', 225 | 'hat-geld.de', 226 | 'hatespam.org', 227 | 'herp.in', 228 | 'hidemail.de', 229 | 'hidzz.com', 230 | 'hmamail.com', 231 | 'hopemail.biz', 232 | 'ieh-mail.de', 233 | 'ikbenspamvrij.nl', 234 | 'imails.info', 235 | 'inbax.tk', 236 | 'inbox.si', 237 | 'inboxalias.com', 238 | 'inboxclean.com', 239 | 'inboxclean.org', 240 | 'infocom.zp.ua', 241 | 'instant-mail.de', 242 | 'ip6.li', 243 | 'irish2me.com', 244 | 'iwi.net', 245 | 'jetable.com', 246 | 'jetable.fr.nf', 247 | 'jetable.net', 248 | 'jetable.org', 249 | 'jnxjn.com', 250 | 'jourrapide.com', 251 | 'jsrsolutions.com', 252 | 'kasmail.com', 253 | 'kaspop.com', 254 | 'killmail.com', 255 | 'killmail.net', 256 | 'klassmaster.com', 257 | 'klzlk.com', 258 | 'koszmail.pl', 259 | 'kurzepost.de', 260 | 'lawlita.com', 261 | 'letthemeatspam.com', 262 | 'lhsdv.com', 263 | 'lifebyfood.com', 264 | 'link2mail.net', 265 | 'litedrop.com', 266 | 'lol.ovpn.to', 267 | 'lolfreak.net', 268 | 'lookugly.com', 269 | 'lortemail.dk', 270 | 'lr78.com', 271 | 'lroid.com', 272 | 'lukop.dk', 273 | 'm21.cc', 274 | 'mail-filter.com', 275 | 'mail-temporaire.fr', 276 | 'mail.by', 277 | 'mail.mezimages.net', 278 | 'mail.zp.ua', 279 | 'mail1a.de', 280 | 'mail21.cc', 281 | 'mail2rss.org', 282 | 'mail333.com', 283 | 'mailbidon.com', 284 | 'mailbiz.biz', 285 | 'mailblocks.com', 286 | 'mailbucket.org', 287 | 'mailcat.biz', 288 | 'mailcatch.com', 289 | 'mailde.de', 290 | 'mailde.info', 291 | 'maildrop.cc', 292 | 'maileimer.de', 293 | 'mailexpire.com', 294 | 'mailfa.tk', 295 | 'mailforspam.com', 296 | 'mailfreeonline.com', 297 | 'mailguard.me', 298 | 'mailin8r.com', 299 | 'mailinater.com', 300 | 'mailinator.com', 301 | 'mailinator.net', 302 | 'mailinator.org', 303 | 'mailinator2.com', 304 | 'mailincubator.com', 305 | 'mailismagic.com', 306 | 'mailme.lv', 307 | 'mailme24.com', 308 | 'mailmetrash.com', 309 | 'mailmoat.com', 310 | 'mailms.com', 311 | 'mailnesia.com', 312 | 'mailnull.com', 313 | 'mailorg.org', 314 | 'mailpick.biz', 315 | 'mailrock.biz', 316 | 'mailscrap.com', 317 | 'mailshell.com', 318 | 'mailsiphon.com', 319 | 'mailtemp.info', 320 | 'mailtome.de', 321 | 'mailtothis.com', 322 | 'mailtrash.net', 323 | 'mailtv.net', 324 | 'mailtv.tv', 325 | 'mailzilla.com', 326 | 'makemetheking.com', 327 | 'manybrain.com', 328 | 'mbx.cc', 329 | 'mega.zik.dj', 330 | 'meinspamschutz.de', 331 | 'meltmail.com', 332 | 'messagebeamer.de', 333 | 'mezimages.net', 334 | 'ministry-of-silly-walks.de', 335 | 'mintemail.com', 336 | 'misterpinball.de', 337 | 'moncourrier.fr.nf', 338 | 'monemail.fr.nf', 339 | 'monmail.fr.nf', 340 | 'monumentmail.com', 341 | 'mt2009.com', 342 | 'mt2014.com', 343 | 'mycard.net.ua', 344 | 'mycleaninbox.net', 345 | 'mymail-in.net', 346 | 'mypacks.net', 347 | 'mypartyclip.de', 348 | 'myphantomemail.com', 349 | 'mysamp.de', 350 | 'mytempemail.com', 351 | 'mytempmail.com', 352 | 'mytrashmail.com', 353 | 'nabuma.com', 354 | 'neomailbox.com', 355 | 'nepwk.com', 356 | 'nervmich.net', 357 | 'nervtmich.net', 358 | 'netmails.com', 359 | 'netmails.net', 360 | 'neverbox.com', 361 | 'nice-4u.com', 362 | 'nincsmail.hu', 363 | 'nnh.com', 364 | 'no-spam.ws', 365 | 'noblepioneer.com', 366 | 'nomail.pw', 367 | 'nomail.xl.cx', 368 | 'nomail2me.com', 369 | 'nomorespamemails.com', 370 | 'nospam.ze.tc', 371 | 'nospam4.us', 372 | 'nospamfor.us', 373 | 'nospammail.net', 374 | 'notmailinator.com', 375 | 'nowhere.org', 376 | 'nowmymail.com', 377 | 'nurfuerspam.de', 378 | 'nus.edu.sg', 379 | 'objectmail.com', 380 | 'obobbo.com', 381 | 'odnorazovoe.ru', 382 | 'oneoffemail.com', 383 | 'onewaymail.com', 384 | 'onlatedotcom.info', 385 | 'online.ms', 386 | 'opayq.com', 387 | 'ordinaryamerican.net', 388 | 'otherinbox.com', 389 | 'ovpn.to', 390 | 'owlpic.com', 391 | 'pancakemail.com', 392 | 'pcusers.otherinbox.com', 393 | 'pjjkp.com', 394 | 'plexolan.de', 395 | 'poczta.onet.pl', 396 | 'politikerclub.de', 397 | 'poofy.org', 398 | 'pookmail.com', 399 | 'privacy.net', 400 | 'privatdemail.net', 401 | 'proxymail.eu', 402 | 'prtnx.com', 403 | 'putthisinyourspamdatabase.com', 404 | 'putthisinyourspamdatabase.com', 405 | 'quickinbox.com', 406 | 'rcpt.at', 407 | 'reallymymail.com', 408 | 'realtyalerts.ca', 409 | 'recode.me', 410 | 'recursor.net', 411 | 'reliable-mail.com', 412 | 'rhyta.com', 413 | 'rmqkr.net', 414 | 'royal.net', 415 | 'rtrtr.com', 416 | 's0ny.net', 417 | 'safe-mail.net', 418 | 'safersignup.de', 419 | 'safetymail.info', 420 | 'safetypost.de', 421 | 'saynotospams.com', 422 | 'schafmail.de', 423 | 'schrott-email.de', 424 | 'secretemail.de', 425 | 'secure-mail.biz', 426 | 'senseless-entertainment.com', 427 | 'services391.com', 428 | 'sharklasers.com', 429 | 'shieldemail.com', 430 | 'shiftmail.com', 431 | 'shitmail.me', 432 | 'shitware.nl', 433 | 'shmeriously.com', 434 | 'shortmail.net', 435 | 'sibmail.com', 436 | 'sinnlos-mail.de', 437 | 'slapsfromlastnight.com', 438 | 'slaskpost.se', 439 | 'smashmail.de', 440 | 'smellfear.com', 441 | 'snakemail.com', 442 | 'sneakemail.com', 443 | 'sneakmail.de', 444 | 'snkmail.com', 445 | 'sofimail.com', 446 | 'solvemail.info', 447 | 'sogetthis.com', 448 | 'soodonims.com', 449 | 'spam4.me', 450 | 'spamail.de', 451 | 'spamarrest.com', 452 | 'spambob.net', 453 | 'spambog.ru', 454 | 'spambox.us', 455 | 'spamcannon.com', 456 | 'spamcannon.net', 457 | 'spamcon.org', 458 | 'spamcorptastic.com', 459 | 'spamcowboy.com', 460 | 'spamcowboy.net', 461 | 'spamcowboy.org', 462 | 'spamday.com', 463 | 'spamex.com', 464 | 'spamfree.eu', 465 | 'spamfree24.com', 466 | 'spamfree24.de', 467 | 'spamfree24.org', 468 | 'spamgoes.in', 469 | 'spamgourmet.com', 470 | 'spamgourmet.net', 471 | 'spamgourmet.org', 472 | 'spamherelots.com', 473 | 'spamherelots.com', 474 | 'spamhereplease.com', 475 | 'spamhereplease.com', 476 | 'spamhole.com', 477 | 'spamify.com', 478 | 'spaml.de', 479 | 'spammotel.com', 480 | 'spamobox.com', 481 | 'spamslicer.com', 482 | 'spamspot.com', 483 | 'spamthis.co.uk', 484 | 'spamtroll.net', 485 | 'speed.1s.fr', 486 | 'spoofmail.de', 487 | 'stuffmail.de', 488 | 'super-auswahl.de', 489 | 'supergreatmail.com', 490 | 'supermailer.jp', 491 | 'superrito.com', 492 | 'superstachel.de', 493 | 'suremail.info', 494 | 'talkinator.com', 495 | 'teewars.org', 496 | 'teleworm.com', 497 | 'teleworm.us', 498 | 'temp-mail.org', 499 | 'temp-mail.ru', 500 | 'tempe-mail.com', 501 | 'tempemail.co.za', 502 | 'tempemail.com', 503 | 'tempemail.net', 504 | 'tempemail.net', 505 | 'tempinbox.co.uk', 506 | 'tempinbox.com', 507 | 'tempmail.eu', 508 | 'tempmaildemo.com', 509 | 'tempmailer.com', 510 | 'tempmailer.de', 511 | 'tempomail.fr', 512 | 'temporaryemail.net', 513 | 'temporaryforwarding.com', 514 | 'temporaryinbox.com', 515 | 'temporarymailaddress.com', 516 | 'tempthe.net', 517 | 'thankyou2010.com', 518 | 'thc.st', 519 | 'thelimestones.com', 520 | 'thisisnotmyrealemail.com', 521 | 'thismail.net', 522 | 'throwawayemailaddress.com', 523 | 'tilien.com', 524 | 'tittbit.in', 525 | 'tizi.com', 526 | 'tmailinator.com', 527 | 'toomail.biz', 528 | 'topranklist.de', 529 | 'tradermail.info', 530 | 'trash-mail.at', 531 | 'trash-mail.com', 532 | 'trash-mail.de', 533 | 'trash2009.com', 534 | 'trashdevil.com', 535 | 'trashemail.de', 536 | 'trashmail.at', 537 | 'trashmail.com', 538 | 'trashmail.de', 539 | 'trashmail.me', 540 | 'trashmail.net', 541 | 'trashmail.org', 542 | 'trashymail.com', 543 | 'trialmail.de', 544 | 'trillianpro.com', 545 | 'twinmail.de', 546 | 'tyldd.com', 547 | 'uggsrock.com', 548 | 'umail.net', 549 | 'uroid.com', 550 | 'us.af', 551 | 'venompen.com', 552 | 'veryrealemail.com', 553 | 'viditag.com', 554 | 'viralplays.com', 555 | 'vpn.st', 556 | 'vsimcard.com', 557 | 'vubby.com', 558 | 'wasteland.rfc822.org', 559 | 'webemail.me', 560 | 'weg-werf-email.de', 561 | 'wegwerf-emails.de', 562 | 'wegwerfadresse.de', 563 | 'wegwerfemail.com', 564 | 'wegwerfemail.de', 565 | 'wegwerfmail.de', 566 | 'wegwerfmail.info', 567 | 'wegwerfmail.net', 568 | 'wegwerfmail.org', 569 | 'wh4f.org', 570 | 'whyspam.me', 571 | 'willhackforfood.biz', 572 | 'willselfdestruct.com', 573 | 'winemaven.info', 574 | 'wronghead.com', 575 | 'www.e4ward.com', 576 | 'www.mailinator.com', 577 | 'wwwnew.eu', 578 | 'x.ip6.li', 579 | 'xagloo.com', 580 | 'xemaps.com', 581 | 'xents.com', 582 | 'xmaily.com', 583 | 'yep.it', 584 | 'yogamaven.com', 585 | 'yopmail.com', 586 | 'yopmail.fr', 587 | 'yopmail.net', 588 | 'yourdomain.com', 589 | 'yuurok.com', 590 | 'z1p.biz', 591 | 'za.com', 592 | 'zehnminuten.de', 593 | 'zehnminutenmail.de', 594 | 'zippymail.info', 595 | 'zoemail.net', 596 | 'zomg.info', 597 | '126.com', 598 | '163.com', 599 | 'yeah.net', 600 | '139.com', 601 | 'mailed.ro', 602 | 'maildx.com', 603 | 'yopmail.pp.ua', 604 | 'mozej.com', 605 | 'pay-mon.com', 606 | 'zep-hyr.com', 607 | 'travala10.com', 608 | 'zippiex.com', 609 | 'poly-swarm.com', 610 | '1shivom.com', 611 | 'fidelium10.com', 612 | 'hubii-network.com', 613 | 'hurify1.com', 614 | 'bit-degree.com', 615 | 'Hatmail.ir', 616 | 'Gmeil.site', 617 | 'Outlok.site', 618 | 'sika3.com', 619 | 'tapi.re', 620 | 'kagi.be', 621 | 'nagi.be', 622 | 'heisei.be', 623 | 'honeys.be', 624 | 'mbox.re', 625 | 'kbox.li', 626 | 'ponp.be', 627 | 'risu.be', 628 | 'fuwa.be', 629 | 'usako.net', 630 | 'eay.jp', 631 | 'via.tokyo.jp', 632 | 'ichigo.me', 633 | 'choco.la', 634 | 'cream.pink', 635 | 'merry.pink', 636 | 'neko2.net', 637 | 'fuwamofu.com', 638 | 'ruru.be', 639 | 'macr2.com', 640 | 'f5.si', 641 | 'ahk.jp', 642 | 'svk.jp', 643 | 'moimoi.re', 644 | 'kksm.be', 645 | 'tensi.org', 646 | 'test.com', 647 | 'niekie.com', 648 | 'd3ff.com', 649 | 'datakop.com', 650 | ]; 651 | 652 | module.exports = blockedEmailProviders; 653 | -------------------------------------------------------------------------------- /middlewares/validators/common/dataGrid.js: -------------------------------------------------------------------------------- 1 | const { query } = require('express-validator'); 2 | const validate = require('../validate'); 3 | 4 | module.exports = [ 5 | query('pageSize') 6 | .customSanitizer((value) => ((value) || 10)) 7 | .isInt({ gt: 0 }) 8 | .withMessage('input should be a valid integer'), 9 | query('page') 10 | .customSanitizer((value) => ((value) || 0)) 11 | .isInt({ gt: -1 }).withMessage('input should be a valid integer'), 12 | query('sorted') 13 | .customSanitizer((value) => ((value) || [])) 14 | .isArray() 15 | .withMessage('input must be an array') 16 | .custom((value) => { 17 | if (value.length > 5) { return Promise.reject(new Error('array must contain less than 5 items ')); } 18 | return Promise.resolve(true); 19 | }), 20 | query('sorted.*') 21 | .isString() 22 | .matches(/^([a-zA-Z0-9._ . _])+:([a-zA-Z0-9 % \- ء-ي قوەرتیئحۆپڕاسدفگهژکلزخجڤبێنمضچى . _ - ـ = + * & $ %])+$/i) 23 | .withMessage('invalid format should be columnName:value') 24 | .customSanitizer((value) => { 25 | const splitedValue = value.split(':'); 26 | return { 27 | column: splitedValue[0], 28 | value: splitedValue[1], 29 | }; 30 | }), 31 | query('sorted.*.column') 32 | .exists(), 33 | query('sorted.*.value') 34 | .isIn(['asc', 'desc']) 35 | .withMessage('allowed values for sorted column value is `asc` or `desc`'), 36 | query('filtered') 37 | .customSanitizer((value) => ((value) || [])) 38 | .isArray() 39 | .withMessage('input must be an array') 40 | .custom((value) => { 41 | if (value.length > 11) { return Promise.reject(new Error('array must contain less than 10 items ')); } 42 | return Promise.resolve(true); 43 | }), 44 | query('filtered.*') 45 | .isString() 46 | // eslint-disable-next-line no-useless-escape 47 | .matches(/^([a-zA-Z0-9._])+:([a-zA-Z0-9 % \- ء-ي قوەرتیئحۆپڕاسدفگهژکلزخجڤبێنمضچى . - ـ = + * & $ %])+$/i) 48 | .withMessage('invalid format should be columnName:value') 49 | .customSanitizer((value) => { 50 | const splitedValue = value.split(':'); 51 | return { 52 | column: splitedValue[0], 53 | value: splitedValue[1], 54 | }; 55 | }), 56 | query('filtered.*.column') 57 | .matches(/^([a-zA-Z0-9. _ـ - #])+$/i) 58 | .withMessage('column name is not alphanumeric'), 59 | query('filtered.*.value') 60 | .escape(), 61 | validate, 62 | ]; 63 | -------------------------------------------------------------------------------- /middlewares/validators/common/lazyLoad.js: -------------------------------------------------------------------------------- 1 | const { query } = require('express-validator'); 2 | const validate = require('../validate'); 3 | 4 | module.exports = [ 5 | query('limit') 6 | .exists() 7 | .withMessage('limit value is required') 8 | .isInt({ gt: 0 }) 9 | .withMessage('invalid limit value') 10 | .customSanitizer((value) => { 11 | if (Number.isNaN(value)) { 12 | return 10; 13 | } 14 | return Number.parseInt(value, 10); 15 | }), 16 | query('offset') 17 | .exists() 18 | .withMessage('offset value is required') 19 | .isInt({ gt: -1 }) 20 | .withMessage('invalid offset value') 21 | .customSanitizer((value) => { 22 | if (Number.isNaN(value)) { 23 | return 0; 24 | } 25 | return Number.parseInt(value, 10); 26 | }), 27 | validate, 28 | ]; 29 | -------------------------------------------------------------------------------- /middlewares/validators/common/searchQuery.js: -------------------------------------------------------------------------------- 1 | const { query } = require('express-validator'); 2 | const validate = require('../validate'); 3 | 4 | module.exports = [ 5 | query('q') 6 | .optional() 7 | .isLength({ min: 1, max: 255 }) 8 | .withMessage('invalid query length must be between 1->255'), 9 | query('city_id') 10 | .optional() 11 | .isInt({ gt: -1 }) 12 | .withMessage('invalid city_id must ba a valid integer'), 13 | validate, 14 | ]; 15 | -------------------------------------------------------------------------------- /middlewares/validators/common/storage.js: -------------------------------------------------------------------------------- 1 | const { body } = require('express-validator'); 2 | const validate = require('../validate'); 3 | 4 | const maxFileSize = 4e+6; 5 | module.exports = { 6 | putObjectsValidator: [ 7 | body('files') 8 | .exists().withMessage('key does not exist') 9 | .isArray({ min: 1 }) 10 | .withMessage('input should be an array with at least one elemnt'), 11 | body('files.*.name') 12 | .exists() 13 | .trim() 14 | .isString(), 15 | body('files.*.type') 16 | .exists() 17 | .trim() 18 | .isString() 19 | .isIn([ 20 | 'image/png', 21 | 'image/jpeg', 22 | 'image/pjpeg', 23 | 'image/gif', 24 | 'application/x-compressed', 25 | 'application/x-zip-compressed', 26 | 'application/zip', 27 | 'multipart/x-zip', 28 | 'audio/mpeg3', 29 | 'audio/x-mpeg-3', 30 | 'video/mpeg', 31 | 'video/x-mpeg', 32 | 'audio/mpeg', 33 | 'application/pdf', 34 | 'application/msword', 35 | 'application/excel', 36 | ]) 37 | .withMessage('file type not allowed'), 38 | body('files.*.base64') 39 | .exists() 40 | .trim() 41 | .isString() 42 | .isLength({ max: maxFileSize }) 43 | .withMessage('maximum file size excceded 6mb'), 44 | validate, 45 | ], 46 | deleteObjectsValidator: [ 47 | body('files') 48 | .exists().withMessage('key does not exist') 49 | .isArray({ min: 1 }) 50 | .withMessage('input should be an array with at least one elemnt'), 51 | body('files.*') 52 | .exists() 53 | .trim() 54 | .isString(), 55 | validate, 56 | ], 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /middlewares/validators/data/idPay.js: -------------------------------------------------------------------------------- 1 | const db = require('../../../database/connection'); 2 | 3 | async function checkDuplicateTransactionId( 4 | transactionId = 0, 5 | paymentTransactionId = 0, 6 | ) { 7 | const [transaction] = await db('user_transaction') 8 | .count('id as count') 9 | .where('id', transactionId) 10 | .andWhere('transaction_id', paymentTransactionId) 11 | .andWhere('confirmed', 1); 12 | if (transaction && transaction.count > 1) { 13 | return Promise.reject( 14 | new Error('ناتوانیت هەمان ID بەکارببەیت بۆ دووبارە پارەدانەوە'), 15 | ); 16 | } 17 | return Promise.resolve(true); 18 | } 19 | 20 | module.exports = { 21 | checkDuplicateTransactionId, 22 | }; 23 | -------------------------------------------------------------------------------- /middlewares/validators/data/job.js: -------------------------------------------------------------------------------- 1 | const db = require('../../../database/connection'); 2 | 3 | async function balanceValidator(userId = 0, files = []) { 4 | const [pages] = await db('user_transaction') 5 | .select(db.raw('SUM(COALESCE(page_count, 0)) as totalPages')) 6 | .andWhere('user_id', userId) 7 | .andWhere('confirmed', 1); 8 | if (pages && pages.totalPages > 0 && pages.totalPages >= files.length) { 9 | return Promise.resolve(true); 10 | } 11 | return Promise.reject( 12 | new Error( 13 | 'تکایە باڵانس پربکەرەوە باڵانسی ماوە کەمترە لە ژمارەی فایلەکان', 14 | ), 15 | ); 16 | } 17 | async function checkUserActiveJobs(userId = 0) { 18 | const [jobs] = await db('job') 19 | .count('id as count') 20 | .whereIn('status', ['pending', 'queued', 'processing']) 21 | .andWhere('user_id', userId); 22 | if (jobs && jobs.count < 3) { 23 | return Promise.resolve(true); 24 | } 25 | return Promise.reject( 26 | new Error('تکایە چاوەروان بە تاوەکو ٣ کردارەکەی دیکەت تەواو دەبن'), 27 | ); 28 | } 29 | async function rateJobValidator(userId = 0, jobId = 0) { 30 | const [job] = await db('job') 31 | .select() 32 | .andWhere('user_id', userId) 33 | .andWhere('id', jobId) 34 | .limit(1); 35 | if (job) { 36 | if (job.status === 'completed') { 37 | if (job.rate > 0) { 38 | return Promise.reject( 39 | new Error( 40 | 'ئەم سکانە هەڵسەنگانی بۆ کراوە ناتوانیت دوبارە کردارەکە ئەنجام بدەیتەوە', 41 | ), 42 | ); 43 | } 44 | } else { 45 | return Promise.reject( 46 | new Error( 47 | 'ناتوانیت هەڵسەنگاندن بۆ کردارێک بکەیت کە تەواو نەبووە', 48 | ), 49 | ); 50 | } 51 | return Promise.resolve(true); 52 | } 53 | return Promise.reject( 54 | new Error('تۆ ئەم سکانەت دروست نەکردوە یان هەڵەیەک رویدا'), 55 | ); 56 | } 57 | 58 | module.exports = { 59 | rateJobValidator, 60 | checkUserActiveJobs, 61 | balanceValidator, 62 | }; 63 | -------------------------------------------------------------------------------- /middlewares/validators/data/user.js: -------------------------------------------------------------------------------- 1 | const db = require('../../../database/connection'); 2 | 3 | async function duplicateEmailValidator(email) { 4 | return db('user') 5 | .count('id as count') 6 | .where('email', email) 7 | .then(([d]) => { 8 | if (d.count > 0) { 9 | return Promise.reject(new Error('پۆستی ئەلیکترۆنی بوونی هەیە')); 10 | } 11 | return Promise.resolve(true); 12 | }); 13 | } 14 | async function duplicateEmailUserUpdateValidator(email, user) { 15 | return db('user') 16 | .count('id as count') 17 | .where('email', email) 18 | .andWhere('id', '<>', user.id) 19 | .then(([d]) => { 20 | if (d.count > 0) { 21 | return Promise.reject( 22 | new Error('پۆستی ئەلئکترۆنی بەکارهاتووە'), 23 | ); 24 | } 25 | return Promise.resolve(true); 26 | }); 27 | } 28 | async function duplicatePhoneNumberUserUpdateValidator(phone, user) { 29 | if (phone === '' || phone == null) { 30 | return Promise.resolve(true); 31 | } 32 | return db('user') 33 | .count('id as count') 34 | .where('phone_no', phone) 35 | .andWhere('id', '<>', user.id) 36 | .then(([d]) => { 37 | if (d.count > 0) { 38 | return Promise.reject( 39 | new Error( 40 | 'ژمارەی مۆبایل لە هەژمارێکی دیکە بەکارهاتووە تکایە پەیوەندیمان پێوەبکە', 41 | ), 42 | ); 43 | } 44 | return Promise.resolve(true); 45 | }); 46 | } 47 | async function emailExisitsValidator(email) { 48 | return db('user') 49 | .count('id as count') 50 | .where('email', email) 51 | .then(([d]) => { 52 | if (d.count > 0) { 53 | return Promise.resolve(true); 54 | } 55 | return Promise.reject(new Error('پۆستی ئەلیکترۆنی بوونی نیە')); 56 | }); 57 | } 58 | 59 | module.exports = { 60 | duplicateEmailValidator, 61 | duplicateEmailUserUpdateValidator, 62 | duplicatePhoneNumberUserUpdateValidator, 63 | emailExisitsValidator, 64 | }; 65 | -------------------------------------------------------------------------------- /middlewares/validators/fastpay.js: -------------------------------------------------------------------------------- 1 | const { query } = require('express-validator'); 2 | 3 | const validate = require('./validate'); 4 | 5 | module.exports = { 6 | fastpayRechargeValidator: [ 7 | query('amount').exists() 8 | .isIn(['8000', '30000', '50000', 5000, 30000, 50000]) 9 | .withMessage('invalid purchase amount'), 10 | validate, 11 | ], 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /middlewares/validators/idPay.js: -------------------------------------------------------------------------------- 1 | const { body, param } = require('express-validator'); 2 | 3 | const { checkDuplicateTransactionId } = require('./data/idPay'); 4 | const validate = require('./validate'); 5 | 6 | module.exports = { 7 | payValidator: [ 8 | param('amount') 9 | .exists() 10 | .isIn([400000, 2500000, 700000, 4000000]) 11 | .withMessage('بڕی پارەی نێردراو هەڵەیە'), 12 | 13 | validate, 14 | ], 15 | verifyValidator: [ 16 | body('id').exists(), 17 | body('order_id') 18 | .exists() 19 | .custom((v, { req }) => checkDuplicateTransactionId(v, req.body.id)), 20 | validate, 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /middlewares/validators/job.js: -------------------------------------------------------------------------------- 1 | const { body, param } = require('express-validator'); 2 | 3 | const prettyBytes = require('pretty-bytes'); 4 | const validate = require('./validate'); 5 | 6 | const { 7 | checkUserActiveJobs, 8 | balanceValidator, 9 | rateJobValidator: rateJobDataValidator, 10 | } = require('./data/job'); 11 | 12 | const maxFileSize = 1.2e+7; // file limit is now 12mb 13 | const allowedImageTypes = [ 14 | 'image/jpg', 15 | 'image/jpeg', 16 | 'image/jfif', 17 | 'image/png', 18 | 'image/webp', 19 | 'image/bmp', 20 | 'image/tiff', 21 | 'image/pjpeg', 22 | 'image/tif', 23 | 'image/png', 24 | ]; 25 | module.exports = { 26 | createValidator: [ 27 | body('job_id') 28 | .exists() 29 | .isUUID() 30 | .withMessage('ئایدی دروستکراو هەڵەیە') 31 | .custom((v, { req }) => checkUserActiveJobs(req.user.id)), 32 | body('group_name').exists().isString(), 33 | body('lang').exists().isString(), 34 | body('files') 35 | .exists() 36 | .withMessage('key does not exist') 37 | .isArray({ min: 1 }) 38 | .withMessage('دەبێت بەلایەنی کەم فایلێک بنێریت') 39 | .isArray({ max: 100 }) 40 | .withMessage('دەبێت کەمتر لە 100 فایل هەڵبژێریت بۆ هەر جارێک') 41 | .custom((v, { req }) => balanceValidator(req.user.id, v)), 42 | body('files.*.name').exists().trim().isString(), 43 | body('files.*.index').exists().isInt(), 44 | body('files.*.extention').exists().isString(), 45 | body('files.*.type') 46 | .exists() 47 | .trim() 48 | .isString() 49 | .isIn(allowedImageTypes) 50 | .withMessage('ئەم جۆرە فایلە رێگەپێدراو نیە'), 51 | body('files.*.base64') 52 | .exists() 53 | .trim() 54 | .isString() 55 | .isLength({ max: maxFileSize }) 56 | .withMessage( 57 | `گەورەترین قەبارەی فایل بریتیە لە ${prettyBytes(maxFileSize)}`, 58 | ), 59 | validate, 60 | ], 61 | createExternalValidator: [ 62 | body('job_id').exists().isUUID().withMessage('ئایدی دروستکراو هەڵەیە'), 63 | body('group_name').exists().isString(), 64 | body('lang').exists().isString(), 65 | body('files') 66 | .exists() 67 | .withMessage('key does not exist') 68 | .isArray({ min: 1 }) 69 | .withMessage('دەبێت بەلایەنی کەم فایلێک بنێریت') 70 | .isArray({ max: 100 }) 71 | .withMessage('دەبێت کەمتر لە 100 فایل هەڵبژێریت بۆ هەر جارێک') 72 | .custom((v, { req }) => balanceValidator(req.user.id, v)), 73 | body('files.*.name').exists().trim().isString(), 74 | body('files.*.index').exists().isInt(), 75 | body('files.*.extention').exists().isString(), 76 | body('files.*.type') 77 | .exists() 78 | .trim() 79 | .isString() 80 | .isIn(allowedImageTypes) 81 | .withMessage('ئەم جۆرە فایلە رێگەپێدراو نیە'), 82 | body('files.*.base64') 83 | .exists() 84 | .trim() 85 | .isString() 86 | .isLength({ max: maxFileSize }) 87 | .withMessage( 88 | `گەورەترین قەبارەی فایل بریتیە لە ${prettyBytes(maxFileSize)}`, 89 | ), 90 | body('callback') 91 | .exists() 92 | .isString() 93 | .withMessage('callback must be a valid url'), 94 | validate, 95 | ], 96 | readSingleJobValidator: [param('job_id').exists().isUUID(), validate], 97 | rateJobValidator: [ 98 | param('job_id') 99 | .exists() 100 | .isUUID() 101 | .custom((v, { req }) => rateJobDataValidator(req.user.id, v)), 102 | body('rate') 103 | .exists() 104 | .isIn([1, 2, 3, 4, 5]) 105 | .withMessage( 106 | 'هەڵەیەک رویدا لەکاتی دەنگدان، ژمارەیەکی هەڵە نێردراوە', 107 | ), 108 | validate, 109 | ], 110 | }; 111 | -------------------------------------------------------------------------------- /middlewares/validators/user.js: -------------------------------------------------------------------------------- 1 | const { body, param } = require('express-validator'); 2 | 3 | // const moment = require('moment'); 4 | const validate = require('./validate'); 5 | const blockedEmails = require('./blockedMailProviders'); 6 | 7 | const { 8 | duplicateEmailValidator, 9 | duplicateEmailUserUpdateValidator, 10 | duplicatePhoneNumberUserUpdateValidator, 11 | } = require('./data/user'); 12 | 13 | // moment.suppressDeprecationWarnings = true; 14 | function blockedEmailValidator(v, bEmails = blockedEmails) { 15 | let passed = true; 16 | // eslint-disable-next-line no-restricted-syntax 17 | for (const blockedEmail of bEmails) { 18 | if (`${v}`.includes(blockedEmail)) { 19 | passed = false; 20 | break; 21 | } 22 | } 23 | if (passed) return Promise.resolve(true); 24 | return Promise.reject(new Error('ئەم جۆرە ئیمەیڵە بلۆک کراوە')); 25 | } 26 | module.exports = { 27 | createValidator: [ 28 | body('name') 29 | .exists() 30 | .withMessage('بوونی نیە') 31 | .trim() 32 | .isString() 33 | .withMessage('input is not a valid string'), 34 | body('company_name') 35 | .exists() 36 | .withMessage('بوونی نیە') 37 | .trim() 38 | .isString() 39 | .withMessage('input is not a valid string'), 40 | body('email') 41 | .exists() 42 | .withMessage('بوونی نیە') 43 | .trim() 44 | .isString() 45 | .withMessage('input is not a valid string') 46 | .custom((v) => duplicateEmailValidator(v)) 47 | .custom((v) => blockedEmailValidator(v, blockedEmails)), 48 | 49 | body('password_retype').exists().withMessage('بوونی نیە'), 50 | body('password') 51 | .exists() 52 | .isString() 53 | .withMessage('input is not valid string') 54 | .isLength({ min: 3 }) 55 | .withMessage('تێپەرەوشە دەبێت ٢ پیت بێت بەلایەنی کەم') 56 | .custom((value, { req }) => { 57 | if (value === req.body.password_retype && value !== '') { 58 | return Promise.resolve('success'); 59 | } 60 | return Promise.reject(new Error('تێپەرەوشەکان جیاوازن')); 61 | }), 62 | validate, 63 | ], 64 | updateValidator: [ 65 | // param('user_id') 66 | // .exists() 67 | // .withMessage('key does not exisit') 68 | // .isInt() 69 | // .withMessage('input must be a valid integer'), 70 | body('name') 71 | .exists() 72 | .withMessage('بوونی نیە') 73 | .trim() 74 | .isString() 75 | .withMessage('input is not a valid string') 76 | .isLength({ min: 2 }) 77 | .withMessage('ناوی بەکارهێنەر دەبێت بەلایەنی کەمەوە ٢ پیت بێت'), 78 | // body('gender') 79 | // .exists() 80 | // .withMessage('بوونی نیە') 81 | // .isIn(['male', 'female', 'unspecified']), 82 | // body('birthdate') 83 | // .exists() 84 | // .withMessage('بوونی نیە') 85 | // .custom((value) => { 86 | // const date = moment(value); 87 | // if (date.isValid()) { 88 | // if (moment(value).format('YYYY-MM-DD') === value) { 89 | // return Promise.resolve(true); 90 | // } 91 | // return Promise.reject(new Error('invalid date format')); 92 | // } 93 | // return Promise.reject(new Error('invalid date')); 94 | // }), 95 | // body('active') 96 | // .exists() 97 | // .withMessage('بوونی نیە') 98 | // .isBoolean() 99 | // .withMessage('input should be a valid boolean'), 100 | body('email') 101 | .exists() 102 | .withMessage('بوونی نیە') 103 | .trim() 104 | .isString() 105 | .custom((v, { req }) => duplicateEmailUserUpdateValidator(v, req.user)) 106 | .custom((v) => blockedEmailValidator(v, blockedEmails)), 107 | body('phone_no') 108 | .exists() 109 | .trim() 110 | .isString() 111 | .matches( 112 | /^\+964[0-9][1-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/i, 113 | ) 114 | .withMessage( 115 | `ژمارەی مۆبایل دەبێت لەم شێوەیە بێت بۆ نموونە 116 | 9647727903366+`, 117 | ) 118 | .custom((v, { req }) => duplicatePhoneNumberUserUpdateValidator(v, req.user)), 119 | body('password_retype').optional().isString(), 120 | body('password') 121 | .optional() 122 | .isString() 123 | .isLength({ min: 3 }) 124 | .withMessage('تێپەرەوشە دەبێت بەلایەنی کەم ٣ پیت بێت') 125 | .custom((value, { req }) => { 126 | if (value === req.body.password_retype && value !== '') { 127 | return Promise.resolve('success'); 128 | } 129 | return Promise.reject( 130 | new Error( 131 | 'تێپەرەوشەکان لەیەک ناکەن یاخود هەردووکیان بەتاڵن', 132 | ), 133 | ); 134 | }) 135 | .withMessage('تێپەرەوشەکان لەیەک ناکەن یاخود هەردووکیان بەتاڵن'), 136 | validate, 137 | ], 138 | readSingleUserValidator: [ 139 | param('user_id') 140 | .exists() 141 | .isInt() 142 | .withMessage('value should be valid integer'), 143 | validate, 144 | ], 145 | }; 146 | -------------------------------------------------------------------------------- /middlewares/validators/validate.js: -------------------------------------------------------------------------------- 1 | const { validationResult } = require('express-validator'); 2 | 3 | module.exports = (req, res, next) => { 4 | const errors = validationResult(req); 5 | if (!errors.isEmpty()) { 6 | return res.status(422).json({ errors: errors.array() }); 7 | } 8 | next(); 9 | return null; 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zhir-api", 3 | "version": "1.0.0", 4 | "description": "this acts as the fron rest api for zhir.io", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node ./bin/server.js", 8 | "dev": "npx nodemon ./bin/server.js", 9 | "db:migrate": "npx knex migrate:latest --knexfile ./database/knexfile.js", 10 | "db:seed": "npx knex seed:run --knexfile ./database/knexfile.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/zhir-io/API.git" 16 | }, 17 | "keywords": [ 18 | "zhir.io", 19 | "zhir" 20 | ], 21 | "author": "aramrafeq2@gmail.com", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/zhir-io/API/issues" 25 | }, 26 | "homepage": "https://github.com/zhir-io/API#readme", 27 | "devDependencies": { 28 | "eslint": "^7.32.0", 29 | "eslint-config-airbnb-base": "^14.2.1", 30 | "eslint-plugin-import": "^2.24.2", 31 | "nodemon": "^2.0.13" 32 | }, 33 | "dependencies": { 34 | "aws-sdk": "^2.995.0", 35 | "chalk": "^4.1.2", 36 | "compression": "^1.7.4", 37 | "cors": "^2.8.5", 38 | "dayjs": "^1.10.7", 39 | "debug": "^4.3.2", 40 | "dotenv": "^10.0.0", 41 | "express": "^4.17.1", 42 | "express-validator": "^6.12.1", 43 | "helmet": "^4.6.0", 44 | "jsonwebtoken": "^8.5.1", 45 | "knex": "^0.95.11", 46 | "md5": "^2.3.0", 47 | "moment": "^2.29.1", 48 | "morgan": "^1.10.0", 49 | "mysql": "^2.18.1", 50 | "nodemailer": "^6.6.5", 51 | "pretty-bytes": "^5.6.0", 52 | "rate-limiter-flexible": "^2.2.4", 53 | "rotating-file-stream": "^2.1.5", 54 | "sha1": "^1.1.1", 55 | "superagent": "^6.1.0", 56 | "uniqid": "^5.4.0", 57 | "uuid": "^8.3.2" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /query/job.js: -------------------------------------------------------------------------------- 1 | const uniqid = require('uniqid'); 2 | const dayjs = require('dayjs'); 3 | const utc = require('dayjs/plugin/utc'); 4 | const db = require('../database/connection'); 5 | const uploader = require('../helpers/aws/putObjects'); 6 | const options = require('../helpers/options'); 7 | 8 | dayjs.extend(utc); 9 | const pricePerPage = options.price_per_page; 10 | 11 | async function createJobQuery(body, user) { 12 | const filesWithPath = body.files.map((f) => ({ 13 | ...f, 14 | path: `original/${user ? user.id : -1}/${body.job_id}`, 15 | })); 16 | // const imageRequests = await uploader(filesWithPath); 17 | // const pages = filesWithPath.map((f) => { 18 | // let id = uuidV4(); 19 | // return { 20 | // id, 21 | // name: `${f.index}.${f.extention}`, 22 | // job_id: body.job_id, 23 | // user_id: req.session.user ? req.user.id : -1, 24 | // created_by: req.user ? req.user.id : -1, 25 | // created_at: db.fn.now(), 26 | // }; 27 | // }); 28 | 29 | return uploader(filesWithPath) 30 | .then(() => db('job') 31 | .insert({ 32 | id: body.job_id, 33 | code: uniqid.time(), 34 | name: body.group_name || uniqid.time(), 35 | lang: body.lang || 'ckb', 36 | status: 'pending', 37 | user_id: user ? user.id : -1, 38 | page_count: body.files.length, 39 | price_per_page: pricePerPage, 40 | from_api: user.from_api ? 1 : 0, 41 | callback: body.callback, 42 | created_at: dayjs.utc().format('YYYY-MM-D H:m:s'), 43 | created_by: user ? user.id : -1, 44 | }) 45 | // return db.batchInsert('page', pages, 200); 46 | .then(([id]) => id) 47 | .catch(() => Promise.reject( 48 | new Error('هەڵەیەک رویدا لەکاتی دروستکرنی کردار'), 49 | ))) 50 | .catch((e) => Promise.reject(new Error(e.toString()))); 51 | } 52 | async function rateJobQuery(body, req) { 53 | const { params } = req; 54 | return db('job') 55 | .update({ 56 | rate: body.rate, 57 | }) 58 | .where('id', params.job_id) 59 | .then(() => params.job_id); 60 | } 61 | async function readJobListQuery(limit, offset, userId = 0, q = '') { 62 | await db.raw('set transaction isolation level read uncommitted;'); 63 | 64 | const query = db('job') 65 | .select( 66 | 'job.id', 67 | 'job.name', 68 | 'job.code', 69 | 'job.user_id', 70 | 'job.page_count', 71 | 'job.paid_page_count', 72 | 'job.user_failing_reason', 73 | 'job.status', 74 | 'job.lang', 75 | 'job.queued_at', 76 | 'job.processed_at', 77 | 'job.finished_at', 78 | 'job.created_at', 79 | 'job.deleted', 80 | 'job.created_by', 81 | 'job.rate', 82 | 'job.from_api', 83 | 'job.callback', 84 | ) 85 | .where('user_id', userId) 86 | .andWhereRaw(' created_at BETWEEN NOW() - INTERVAL 30 DAY AND NOW() ') 87 | .orderBy('created_at', 'desc') 88 | .limit(limit) 89 | .offset(offset); 90 | 91 | if (q) { 92 | query.where('job.name', 'LIKE', `%${q}%`); 93 | } 94 | return query; 95 | } 96 | async function readSingleJobQuery(jobId = 0, userId = 0) { 97 | return db('job') 98 | .select( 99 | 'job.id', 100 | 'job.name', 101 | 'job.code', 102 | 'job.user_id', 103 | 'job.page_count', 104 | 'job.paid_page_count', 105 | 'job.user_failing_reason', 106 | 'job.status', 107 | 'job.lang', 108 | 'job.queued_at', 109 | 'job.processed_at', 110 | 'job.finished_at', 111 | 'job.created_at', 112 | 'job.deleted', 113 | 'job.created_by', 114 | 'job.rate', 115 | 'job.from_api', 116 | 'job.callback', 117 | ) 118 | .where('user_id', userId) 119 | .andWhere('id', jobId) 120 | .limit(1) 121 | .then(async ([job]) => { 122 | if (job) { 123 | const mutatedJob = job; 124 | const pages = await db('page').select().where('job_id', job.id); 125 | mutatedJob.pages = pages; 126 | return Promise.resolve(mutatedJob); 127 | } 128 | return Promise.resolve(null); 129 | }); 130 | } 131 | 132 | module.exports = { 133 | createJobQuery, 134 | rateJobQuery, 135 | readJobListQuery, 136 | readSingleJobQuery, 137 | }; 138 | -------------------------------------------------------------------------------- /query/user.js: -------------------------------------------------------------------------------- 1 | const sha1 = require('sha1'); 2 | const { v4: uuidv4 } = require('uuid'); 3 | const db = require('../database/connection'); 4 | 5 | async function readUserBalanceQuery(userId = 0) { 6 | return db('user_transaction') 7 | .sum('page_count as page_count') 8 | .where('user_id', userId) 9 | .where('confirmed', 1) 10 | .then(([d]) => d); 11 | } 12 | function readTransactionListQuery(userId = 0, limit = 20, offset = 0) { 13 | return db('user_transaction') 14 | .select( 15 | 'user_transaction.type_id', 16 | 'user_transaction.payment_medium_id', 17 | 'user_transaction.page_count', 18 | 'user_transaction.amount', 19 | 'user_transaction.transaction_id', 20 | 'user_transaction.user_note', 21 | 'user_transaction.created_at', 22 | 'payment_medium.name as payment_medium_name', 23 | 'payment_medium.code as payment_medium_code', 24 | 'payment_medium.currency_symbol as payment_medium_currency_symbol', 25 | ) 26 | .leftJoin( 27 | 'payment_medium', 28 | 'payment_medium.id', 29 | 'user_transaction.payment_medium_id', 30 | ) 31 | .andWhere('user_transaction.user_id', userId) 32 | .andWhere('user_transaction.type_id', 1) // only recharges are considered 33 | .andWhere('user_transaction.confirmed', 1) // only confirmed recharges are considered 34 | .orderBy('user_transaction.id', 'desc') 35 | .limit(limit) 36 | .offset(offset); 37 | } 38 | function readSingleUserQuery(userId = 0) { 39 | return db('user') 40 | .select( 41 | 'id', 42 | 'name', 43 | 'company_name', 44 | 'email', 45 | 'gender', 46 | 'birthdate', 47 | 'phone_no', 48 | 'uid', 49 | 'api_key', 50 | 'can_use_api', 51 | 'monthly_recharge', 52 | ) 53 | .where('id', userId) 54 | .then(([data]) => data); 55 | } 56 | function updateUserQuery(body, userId = 0) { 57 | const updateObject = { 58 | name: body.name, 59 | email: body.email, 60 | phone_no: body.phone_no, 61 | company_name: body.company_name, 62 | }; 63 | if (body.password) { 64 | const salt = uuidv4(); 65 | updateObject.password = sha1(`${salt}${body.password}`); 66 | updateObject.salt = salt; 67 | } 68 | return db('user').update(updateObject).where('id', userId); 69 | } 70 | 71 | module.exports = { 72 | readUserBalanceQuery, 73 | readTransactionListQuery, 74 | readSingleUserQuery, 75 | updateUserQuery, 76 | }; 77 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | const { v4: uuidv4 } = require('uuid'); 5 | const sha1 = require('sha1'); 6 | const utc = require('dayjs/plugin/utc'); 7 | const dayjs = require('dayjs'); 8 | 9 | const allowedMethods = ['bradost']; 10 | 11 | const { createValidator } = require('../middlewares/validators/user'); 12 | const { 13 | passwordResetValidator, 14 | requestPasswordResetValidator, 15 | emailPassValidator, 16 | activateAccountValidator, 17 | } = require('../middlewares/validators/auth'); 18 | const { 19 | passwordResetTemplate, 20 | activateAccountTemplate, 21 | } = require('../helpers/emailTemplates'); 22 | const { send } = require('../helpers/email'); 23 | 24 | const db = require('../database/connection'); 25 | 26 | dayjs.extend(utc); 27 | 28 | const router = express(); 29 | 30 | function register(data) { 31 | const salt = uuidv4(); 32 | const user = { 33 | name: data.name, 34 | company_name: data.company_name, 35 | api_key: uuidv4(), 36 | email: data.email, 37 | password: sha1(`${salt}${data.password}`), 38 | salt, 39 | active: 1, 40 | verified: data.method && allowedMethods.indexOf(data.method) > -1 ? 1 : 0, 41 | gender: 'unspecified', 42 | created_at: db.fn.now(), 43 | updated_at: db.fn.now(), 44 | }; 45 | return db('user').insert(user); 46 | } 47 | function login(email, password) { 48 | return db.transaction(async (trx) => { 49 | const [checkEmail] = await trx('user') 50 | .select() 51 | .where('email', email) 52 | .andWhere('active', 1) 53 | .andWhere('deleted', 0) 54 | .limit(1); 55 | 56 | if (checkEmail && checkEmail.verified === 0) { 57 | return { 58 | status: 400, 59 | success: false, 60 | msg: 61 | 'هەژمارەکەت کارا نیە تکایە سەیری ئیمێڵەکەت بکە بۆ وەرگرتنی بەستەری کاراکردن یان پەیوەندیمان پێوەبکە.', 62 | user: null, 63 | }; 64 | } 65 | 66 | if (!checkEmail || !checkEmail.id) { 67 | return { 68 | status: 400, 69 | success: false, 70 | // msg: ' پۆستی ئەلئکترۆنی نەدۆزرایەوە یان هەژمارەکەت راگیراوە', 71 | msg: 'پۆستی ئەلیکترۆنی یان تێپەڕەوشە هەڵەیە', 72 | user: null, 73 | }; 74 | } 75 | 76 | const [checkPassword] = await trx('user') 77 | .select('user.*') 78 | .where('user.id', checkEmail.id) 79 | .andWhere('password', sha1(checkEmail.salt + password)) 80 | .limit(1); 81 | if (!checkPassword || !checkPassword.id) { 82 | return { 83 | status: 400, 84 | success: false, 85 | msg: 'پۆستی ئەلیکترۆنی یان تێپەڕەوشە هەڵەیە', 86 | user: null, 87 | }; 88 | } 89 | delete checkPassword.salt; 90 | delete checkPassword.password; 91 | delete checkPassword.tmp_password; 92 | const token = jwt.sign({ data: checkPassword }, process.env.USER_JWT_SECRET, { expiresIn: '362d' }); 93 | 94 | return { 95 | status: 200, 96 | success: true, 97 | msg: 'سەرکەوتووبوو', 98 | user: checkPassword, 99 | token, 100 | }; 101 | }); 102 | } 103 | function generateTmpPass(email) { 104 | return db('user') 105 | .select() 106 | .where('email', email) 107 | .where('active', 1) 108 | .where('verified', 1) 109 | .limit(1) 110 | .then(([user]) => { 111 | if (user) { 112 | const mutatedUser = user; 113 | // we will not validate this after 5 mins you must request another one 114 | const tmpPass = sha1(uuidv4()); 115 | return db('user') 116 | .update({ tmp_password: tmpPass }) 117 | .where('id', mutatedUser.id) 118 | .then(() => { 119 | mutatedUser.tmp_password = tmpPass; 120 | return Promise.resolve(mutatedUser); 121 | }); 122 | } 123 | return Promise.reject(new Error('بەکارهێنەر نەدۆزرایەوە')); 124 | }); 125 | } 126 | 127 | function passwordReset(data) { 128 | return db('user') 129 | .select() 130 | .where('tmp_password', data.token) 131 | .andWhere('tmp_password', '<>', '') 132 | .limit(1) 133 | .then(([user]) => { 134 | if (user) { 135 | const salt = uuidv4(); 136 | const updateObject = { 137 | password: sha1(`${salt}${data.password}`), 138 | salt, 139 | tmp_password: '', 140 | }; 141 | return db('user') 142 | .update(updateObject) 143 | .where('id', user.id) 144 | .then(() => Promise.resolve(user)); 145 | } 146 | return Promise.reject( 147 | new Error('ئەم بەستەرە بەسەرچووە ناتواندرێت بەکاربهێندرێت'), 148 | ); 149 | }); 150 | } 151 | 152 | function generateActivationToken(email) { 153 | return db('user') 154 | .select() 155 | .where('email', email) 156 | .limit(1) 157 | .then(([user]) => { 158 | if (user) { 159 | const activationToken = sha1(uuidv4()); 160 | const mutatedUser = user; 161 | return Promise.all([ 162 | db('user') 163 | .update({ activation_token: activationToken }) 164 | .where('id', user.id) 165 | .then(() => { 166 | mutatedUser.activation_token = activationToken; 167 | return Promise.resolve(mutatedUser); 168 | }), 169 | db('activation_token').insert({ 170 | token: activationToken, 171 | user_id: mutatedUser.id, 172 | created_at: dayjs.utc().format('YYYY-MM-D H:m:s'), 173 | }), 174 | ]).then((values) => values[0]); 175 | } 176 | return Promise.reject(new Error('بەکارهێنەر نەدۆزرایەوە')); 177 | }); 178 | } 179 | function activateAccount(data) { 180 | return db('user') 181 | .select() 182 | .where('activation_token', data.token) 183 | .andWhere('activation_token', '<>', '') 184 | .limit(1) 185 | .then(([user]) => { 186 | if (user) { 187 | const updateObject = { 188 | verified: 1, 189 | activation_token: '', 190 | }; 191 | return db('user') 192 | .update(updateObject) 193 | .where('id', user.id) 194 | .then(async () => { 195 | await db('user_transaction').insert({ 196 | user_id: user.id, 197 | type_id: 1, // for recharge 198 | payment_medium_id: 1, // for zhir.io 199 | page_count: 50, 200 | confirmed: 1, 201 | amount: 0, 202 | transaction_id: uuidv4(), 203 | user_note: 204 | 'دیاری دروستکردنی هەژمار هەموو مانگێک خۆکارانە ٥٠ لاپەڕە وەردەگریت', 205 | admin_note: 'Account Activation Present', 206 | created_at: db.fn.now(), 207 | }); 208 | return Promise.resolve(user); 209 | }); 210 | } 211 | return db('activation_token') 212 | .select() 213 | .where('token', data.token) 214 | .limit(1) 215 | .then(([userToken]) => { 216 | if (userToken) { 217 | return Promise.resolve(userToken); 218 | } 219 | return Promise.reject( 220 | new Error( 221 | 'ئەم بەستەرە بەسەرچووە ناتواندرێت بەکاربهێندرێت', 222 | ), 223 | ); 224 | }); 225 | }); 226 | } 227 | 228 | router.post('/login', emailPassValidator, (req, res) => { 229 | const { email, password } = req.body; 230 | login(email, password) 231 | .then((r) => { 232 | if (r.success) { 233 | res.json({ 234 | ...r.user, 235 | token: r.token, 236 | }); 237 | } else { 238 | res.status(r.status).json({ msg: r.msg }); 239 | } 240 | }) 241 | .catch(() => { 242 | res.status(401).json({ 243 | msg: 'هەڵەیەک ڕوویدا لەکاتی چوونەژوورەوە', 244 | }); 245 | }); 246 | }); 247 | router.post('/register', createValidator, (req, res) => { 248 | const { body } = req; 249 | register(body) 250 | .then(async ([userId]) => { 251 | if (allowedMethods.indexOf(body.method) > -1) { 252 | await db('user_transaction').insert({ 253 | user_id: userId, 254 | type_id: 1, // for recharge 255 | payment_medium_id: 1, // for zhir.io 256 | page_count: 50, 257 | confirmed: 1, 258 | amount: 0, 259 | transaction_id: uuidv4(), 260 | user_note: 261 | 'دیاری دروستکردنی هەژمار هەموو مانگێک خۆکارانە ٥٠ لاپەڕە وەردەگریت', 262 | admin_note: 'Account Activation Present', 263 | created_at: db.fn.now(), 264 | }); 265 | } 266 | generateActivationToken(body.email) 267 | .then((user) => { 268 | const activationToken = user.activation_token; 269 | const url = `${process.env.DOMAIN}/api/auth/activate-account?token=${activationToken}`; 270 | const htmlMsg = activateAccountTemplate(url); 271 | send('ژیر | کاراکردنی هەژمار', htmlMsg, user.email, () => { 272 | res.json({ msg: 'هەژمار دروستکرا' }); 273 | }); 274 | }) 275 | .catch(() => { 276 | res.status(500).json({ 277 | msg: 278 | 'ناردنی بەستەری کاراکردنی هەژمار سەرکەوتوو نەبوو تکایە پەیوەندیمان پێوەبکە', 279 | }); 280 | }); 281 | }) 282 | .catch(() => { 283 | res.status(500).json({ 284 | msg: 'خۆتۆمارکردن سەرکەوتوو نەبوو', 285 | }); 286 | }); 287 | }); 288 | router.post( 289 | '/request-password-recovery', 290 | requestPasswordResetValidator, 291 | (req, res) => { 292 | const { body } = req; 293 | generateTmpPass(body.email) 294 | .then((user) => { 295 | const tmpPass = user.tmp_password; 296 | const url = `${process.env.DOMAIN}/auth/password-recovery?token=${tmpPass}`; 297 | const htmlMsg = passwordResetTemplate(url); 298 | send('ژیر | گۆڕینی تێپەڕەوشە', htmlMsg, user.email, () => { 299 | res.json({ msg: 'yayy' }); 300 | }); 301 | }) 302 | .catch(() => { 303 | res.status(500).json({ 304 | msg: 'ناردنی تێپەڕەوشە سەرکەوتوونەبوو', 305 | }); 306 | }); 307 | }, 308 | ); 309 | router.post('/password-recovery', passwordResetValidator, (req, res) => { 310 | const { body } = req; 311 | passwordReset(body) 312 | .then((user) => { 313 | const mutatedUser = { 314 | ...user, 315 | salt: undefined, 316 | password: undefined, 317 | tmp_password: undefined, 318 | }; 319 | const token = jwt.sign({ data: mutatedUser }, process.env.USER_JWT_SECRET, { expiresIn: '362d' }); 320 | mutatedUser.token = token; 321 | res.json(mutatedUser); 322 | }) 323 | .catch((e) => { 324 | res.status(400).json({ 325 | msg: e.toString(), 326 | }); 327 | }); 328 | }); 329 | 330 | // this route should be called and generated once per account only in registration 331 | // router.post( 332 | // '/request-activation-link', 333 | // requestPasswordResetValidator, 334 | // (req, res) => { 335 | // const { body } = req; 336 | // generateActivationToken(body.email) 337 | // .then((user) => { 338 | // const activationToken = user.activation_token; 339 | // const url = `${process.env.DOMAIN}/activate-account?token=${activationToken}`; 340 | // const htmlMsg = activateAccountTemplate(url); 341 | // send('ژیر | کاراکردنی هەژمار', htmlMsg, user.email, () => { 342 | // res.json({ msg: 'yayy' }); 343 | // }); 344 | // }) 345 | // .catch((e) => { 346 | // res.status(500).json({ 347 | // msg: 'ناردنی بەستەری کاراکردنی هەژمار سەرکەوتوو نەبوو تکایە پەیوەندیمان پێوەبکە', 348 | // }); 349 | // }); 350 | // } 351 | // ); 352 | 353 | router.get('/activate-account', activateAccountValidator, (req, res) => { 354 | const { query } = req; 355 | activateAccount(query) 356 | .then((user) => { 357 | if (user.user_id) { 358 | res.status(200).json({ state: 'already-activated' }); 359 | } else { 360 | res.status(200).json({ state: 'activated' }); 361 | } 362 | }) 363 | .catch((e) => { 364 | res.status(500).json({ 365 | msg: e.toString(), 366 | }); 367 | }); 368 | }); 369 | 370 | // router.get('/logout', (req, res) => { 371 | // req.session.destroy(() => { 372 | // res.redirect('/'); 373 | // }); 374 | // }); 375 | module.exports = router; 376 | -------------------------------------------------------------------------------- /routes/fastpay.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const superagent = require('superagent'); 3 | 4 | const md5 = require('md5'); 5 | const customerJWTValidator = require('../middlewares/jwt/user'); 6 | const { 7 | fastpayRechargeValidator, 8 | } = require('../middlewares/validators/fastpay'); 9 | const db = require('../database/connection'); 10 | 11 | const router = express.Router(); 12 | 13 | /* -------------------- Fastpay Related Functions----------------- */ 14 | const fastpayPaymentMediumId = 2; 15 | const fastpayTransactionTypeId = 1; // Balance Recharge 16 | 17 | async function generateDraftTransaction(customerId = 0, body = {}) { 18 | const pages = { 19 | 8000: 100, 20 | 30000: 500, 21 | 50000: 1000, 22 | }; 23 | return db('user_transaction').insert({ 24 | type_id: fastpayTransactionTypeId, 25 | payment_medium_id: fastpayPaymentMediumId, 26 | user_id: customerId, 27 | page_count: pages[body.amount] || 0, 28 | amount: body.amount, 29 | transaction_id: -1, 30 | user_note: 'پڕکردنەوەی باڵانس', 31 | confirmed: 0, // this is eqevelant to draft 32 | created_at: db.fn.now(), 33 | created_by: customerId, 34 | 35 | }).then(([id]) => id) 36 | .catch(() => -1); 37 | } 38 | 39 | async function updateDraftTransaction(transactionId = 0, paymentTransactionId, customerAccountNo = '') { 40 | return db('user_transaction').update({ 41 | transaction_id: paymentTransactionId, 42 | admin_note: `Via Fastpay Account:${customerAccountNo}`, 43 | }).where(db.raw('md5(id)'), transactionId).then(() => 'Success') 44 | .catch(() => '/payment/fastpay/fail'); 45 | } 46 | 47 | async function loadSingleTransaction(transactionId = 0) { 48 | // transactionId is md5 hashed due to new fastpay credentials 49 | return db('user_transaction').select() 50 | .where(db.raw('md5(id)'), transactionId) 51 | .limit(1) 52 | .then(([transaction]) => transaction); 53 | } 54 | async function completeDraftTransaction(transactionId = 0, fastpayTransactionId = 0) { 55 | return db('user_transaction') 56 | .update({ 57 | confirmed: 1, 58 | }) 59 | .where(db.raw('md5(id)'), transactionId) 60 | .andWhere('transaction_id', fastpayTransactionId) 61 | .then(() => transactionId); 62 | } 63 | 64 | async function initFastpayTransaction(orderId = '0', amount = '0') { 65 | return superagent.post(`${process.env.FASTPAY_API_URL}/api/v1/public/pgw/payment/initiation`) 66 | .set('Content-Type', 'application/json') 67 | .send({ 68 | store_id: process.env.FASTPAY_STORE_ID, 69 | store_password: process.env.FASTPAY_MERCHANT_STORE_PASS, 70 | order_id: md5(orderId), 71 | cart: JSON.stringify([ 72 | { 73 | name: 'Account Recharge', 74 | qty: 1, 75 | unit_price: amount, 76 | sub_total: amount, 77 | }, 78 | ]), 79 | bill_amount: Math.round(amount), 80 | currency: 'IQD', 81 | }) 82 | .then((res) => { 83 | const { body } = res; 84 | if (body.code === 200) { 85 | return body.data.redirect_uri; 86 | } 87 | return -1; 88 | }) 89 | .catch(() => -1); 90 | } 91 | async function validateFastpayTransaction(customerTransactionId = '0') { 92 | return superagent.post(`${process.env.FASTPAY_API_URL}/api/v1/public/pgw/payment/validate`) 93 | .set('Content-Type', 'application/json') 94 | .send({ 95 | store_id: process.env.FASTPAY_STORE_ID, 96 | store_password: process.env.FASTPAY_MERCHANT_STORE_PASS, 97 | order_id: customerTransactionId, 98 | }) 99 | .then(async (res) => { 100 | const { body } = res; 101 | 102 | if (`${body.code}` === '200') { 103 | await completeDraftTransaction(customerTransactionId, body.data.transaction_id); 104 | } 105 | return 1; 106 | }) 107 | .catch(() => -1); 108 | } 109 | 110 | /* ----------------------------------------------------------------- */ 111 | 112 | router.all('/fail', async (req, res) => { 113 | // we can remove the draft transaction 114 | res.redirect(`${process.env.DOMAIN}?recharge-status=fail`); 115 | }); 116 | router.all('/success', (req, res) => { 117 | res.redirect(`${process.env.DOMAIN}?recharge-status=success`); 118 | }); 119 | router.all('/cancel', (req, res) => { 120 | res.redirect(`${process.env.DOMAIN}?recharge-status=fail`); 121 | }); 122 | 123 | router.post('/ipn', async (req, res) => { 124 | /* 125 | gw_transaction_id: CUL1NUB731, 126 | merchant_order_id: 1, 127 | received_amount: 1000, 128 | currency: IQD, 129 | status: Success, 130 | customer_name: Aram Rafeq, 131 | customer_mobile_number: +9647507665935, 132 | at: 2020-11-26 13:54:01 133 | */ 134 | const { body } = req; 135 | const { 136 | gw_transaction_id: fastpayTransactionId, 137 | merchant_order_id: customerTransactionId, 138 | received_amount: billAmount, 139 | customer_mobile_number: fastpayCustomerAccountno, 140 | status, 141 | } = body; 142 | if (status === 'Success') { 143 | const transaction = await loadSingleTransaction(customerTransactionId); 144 | if (transaction) { 145 | if (Math.abs(Math.round(transaction.amount) - Math.round(billAmount)) < 10) { 146 | await updateDraftTransaction( 147 | customerTransactionId, 148 | fastpayTransactionId, 149 | fastpayCustomerAccountno, 150 | ); 151 | 152 | await validateFastpayTransaction(customerTransactionId); 153 | res.send('draft transaction updated'); 154 | } else { 155 | res.send('transaction bill fails to validate'); 156 | } 157 | } else { 158 | res.send('transaction fails'); 159 | } 160 | } else { 161 | res.send('payment failed'); 162 | } 163 | }); 164 | router.get('/recharge', customerJWTValidator, fastpayRechargeValidator, async (req, res) => { 165 | const transactionId = await generateDraftTransaction(req.user.id, req.query); 166 | const paymentURL = await initFastpayTransaction(`${transactionId}`, parseInt(req.query.amount, 10)); 167 | if (transactionId > -1) { 168 | if (typeof paymentURL === 'number' && paymentURL === -1) { 169 | res.redirect('/payment/fastpay/fail'); 170 | } else { 171 | res.redirect(paymentURL); 172 | } 173 | } else { 174 | res.redirect('/payment/fastpay/fail'); 175 | } 176 | // res.json({ msg: 'hello world' }); 177 | }); 178 | 179 | module.exports = router; 180 | -------------------------------------------------------------------------------- /routes/idPay.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eqeqeq */ 2 | const express = require('express'); 3 | const superagent = require('superagent'); 4 | const userJWT = require('../middlewares/jwt/user'); 5 | const { payValidator, verifyValidator } = require('../middlewares/validators/idPay'); 6 | 7 | const db = require('../database/connection'); 8 | 9 | let agent = superagent.agent(); 10 | agent = agent.set('Content-Type', 'application/json'); 11 | agent = agent.set('X-API-KEY', process.env.IDPAY_AUTH_KEY); 12 | if (process.env.IDPAY_SANDBOX === 1) { 13 | agent = agent.set('X-SANDBOX', 1); 14 | } 15 | 16 | function generateTransaction(object = {}) { 17 | return db('user_transaction') 18 | .insert(object) 19 | .then(([d]) => d); 20 | } 21 | function setPaymentTransactionId(id, paymentTransactionId) { 22 | return db('user_transaction') 23 | .update({ transaction_id: paymentTransactionId }) 24 | .where('id', id) 25 | .then(() => id); 26 | } 27 | function confirmTransaction(id, paymentTransactionId) { 28 | return db('user_transaction') 29 | .update({ confirmed: 1, admin_note: 'confirmed' }) 30 | .where('id', id) 31 | .andWhere('transaction_id', paymentTransactionId) 32 | .then(() => db('user') 33 | .select('user.*') 34 | .innerJoin( 35 | 'user_transaction', 36 | 'user_transaction.user_id', 37 | 'user.id', 38 | ) 39 | .andWhere( 40 | 'user_transaction.transaction_id', 41 | paymentTransactionId, 42 | ) 43 | .andWhere('user_transaction.id', id) 44 | .andWhere('user.active', 1) 45 | .andWhere('user.deleted', 0) 46 | .limit(1)); 47 | } 48 | // offical currency is rial which have one more 0 that tuman 49 | const prices = { 50 | 400000: 50, 51 | 700000: 100, 52 | 2500000: 500, 53 | 4000000: 1000, 54 | }; 55 | const router = express(); 56 | router.post('/verify', verifyValidator, (req, res) => { 57 | // Transaction Status link https://idpay.ir/web-service/v1.1/?javascript#ad39f18522 58 | const { body } = req; 59 | if (body.status === 10) { 60 | // do transaction confirmation here 61 | agent 62 | .post('https://api.idpay.ir/v1.1/payment/verify') 63 | .send({ 64 | id: body.id, 65 | order_id: body.order_id, 66 | }) 67 | .end(async (err) => { 68 | if (!err) { 69 | const [user] = await confirmTransaction( 70 | body.order_id, 71 | body.id, 72 | ); 73 | if (user) { 74 | delete user.salt; 75 | delete user.password; 76 | delete user.tmp_password; 77 | req.session.user = user; 78 | res.redirect('/app/balance?success=1'); 79 | } else { 80 | res.redirect('/app/balance?fail=1'); 81 | } 82 | } else { 83 | res.json({ 84 | msg: 'هەڵەیەک رویدا لەکاتی دڵنیابوونەوە لە پارەدان', 85 | }); 86 | } 87 | }); 88 | } else if (body.status == 1) { 89 | res.json({ msg: 'Payment not maid' }); 90 | } else if (body.status == 2) { 91 | res.json({ msg: 'Payment failed' }); 92 | } else if (body.status == 3) { 93 | res.json({ msg: 'An error has occurred' }); 94 | } else if (body.status == 4) { 95 | res.json({ msg: 'Blocked' }); 96 | } else if (body.status == 5) { 97 | res.json({ msg: 'Return to payer' }); 98 | } else if (body.status == 6) { 99 | res.json({ msg: 'Reversed system' }); 100 | } else if (body.status == 7) { 101 | res.redirect('/app/recharge'); 102 | } else if (body.status == 8) { 103 | res.json({ msg: 'Moved to payment gateway' }); 104 | } else if (body.status == 100) { 105 | res.redirect('/app/balance'); 106 | } else if (body.status == 101) { 107 | res.json({ msg: 'Payment has already been approved' }); 108 | } else if (body.status == 200) { 109 | res.json({ msg: 'Deposited to the recipient' }); 110 | } 111 | }); 112 | router.get('/pay/:amount', userJWT, payValidator, async (req, res) => { 113 | const { params } = req; 114 | const { user } = req.session; 115 | const orderId = await generateTransaction({ 116 | user_id: user.id, 117 | type_id: 1, 118 | payment_medium_id: 6, // IDPay medium 119 | page_count: prices[params.amount], 120 | amount: params.amount, 121 | transaction_id: null, 122 | created_by: user.id, 123 | confirmed: 0, 124 | created_at: db.fn.now(), 125 | admin_note: 'draft', 126 | }); 127 | agent 128 | .post('https://api.idpay.ir/v1.1/payment') 129 | .send({ 130 | order_id: orderId, 131 | amount: params.amount, 132 | name: user.name, 133 | phone: user.phone_no, 134 | mail: user.email, 135 | desc: ' ', 136 | callback: `${process.env.DOMAIN}/api/payment/idpay/verify`, 137 | }) 138 | .end(async (err, r) => { 139 | if (!err) { 140 | const { body } = r; 141 | await setPaymentTransactionId(orderId, body.id); 142 | res.redirect(body.link); 143 | } else { 144 | res.json({ 145 | msg: 146 | 'هەڵەیەک رویدا لەکاتی جێبەجێکردنی کردارەکان، دووبارە هەوڵبدەرەوە', 147 | }); 148 | } 149 | }); 150 | }); 151 | 152 | module.exports = router; 153 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | // const withUserAuth = require('../controller/withUserAuth'); 3 | const jwtVerify = require('../middlewares/jwt/user'); 4 | 5 | const authRouter = require('./auth'); 6 | const jobRouter = require('./job'); 7 | const s3interface = require('./s3interface'); 8 | const userRouter = require('./user'); 9 | const idPayRouter = require('./idPay'); 10 | const fastpayRouter = require('./fastpay'); 11 | 12 | const router = express(); 13 | router.use('/auth', authRouter); 14 | router.use('/job', jobRouter); 15 | router.use('/user', jwtVerify, userRouter); 16 | router.use('/payment/fastpay', fastpayRouter); 17 | router.use('/payment/idpay', idPayRouter); 18 | // router.use('/assets', withUserAuth(s3interface)); 19 | router.use('/assets', s3interface); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /routes/job.js: -------------------------------------------------------------------------------- 1 | // import { v4: uuidV4 } from 'uuid'; 2 | 3 | const express = require('express'); 4 | 5 | const { 6 | createValidator, 7 | readSingleJobValidator, 8 | rateJobValidator, 9 | createExternalValidator, 10 | } = require('../middlewares/validators/job'); 11 | const { 12 | createJobQuery, 13 | rateJobQuery, 14 | readJobListQuery, 15 | readSingleJobQuery, 16 | } = require('../query/job'); 17 | 18 | const userJWT = require('../middlewares/jwt/user'); 19 | const isValidAPIKey = require('../helpers/isValidAPIKey'); 20 | 21 | const router = express(); 22 | 23 | router.post('/', userJWT, createValidator, async (req, res) => { 24 | createJobQuery(req.body, req.user) 25 | .then(() => { 26 | res.json({ 27 | msg: 'کارەکە بە سەرکەوتووی نێردرا', 28 | }); 29 | }) 30 | .catch(() => { 31 | res.status(500).json({ 32 | msg: 'هەڵەیەک لە ڕاژە ڕوویدا', 33 | }); 34 | }); 35 | }); 36 | router.post( 37 | '/external', 38 | isValidAPIKey, 39 | createExternalValidator, 40 | async (req, res) => { 41 | createJobQuery(req.body, req.user) 42 | .then(() => { 43 | res.status(200).json({ 44 | success: true, 45 | status: 200, 46 | msg: 'کارەکە بە سەرکەوتووی نێردرا', 47 | }); 48 | }) 49 | .catch((e) => { 50 | res.status(500).json({ 51 | success: false, 52 | status: 500, 53 | msg: e.toString(), 54 | }); 55 | }); 56 | }, 57 | ); 58 | 59 | router.get('/external/list', isValidAPIKey, (req, res) => { 60 | readJobListQuery(100, 0, req.user.id, req.query.q) 61 | .then((data) => { 62 | res.json(data); 63 | }) 64 | .catch(() => { 65 | res.status(500).json({ 66 | msg: 'هەڵەیەک ڕوویدا لەکاتی گەراندنەوەی فایلەکان', 67 | }); 68 | }); 69 | }); 70 | router.get( 71 | '/external/:job_id', 72 | isValidAPIKey, 73 | readSingleJobValidator, 74 | (req, res) => { 75 | readSingleJobQuery(req.params.job_id, req.user.id) 76 | .then((data) => { 77 | if (data) { 78 | res.json(data); 79 | } else { 80 | res.status(404).json({ 81 | msg: 'کرداری داواکراو نەدۆزرایەوە', 82 | }); 83 | } 84 | }) 85 | .catch(() => { 86 | res.status(500).json({ 87 | msg: 'هەڵەیەک ڕوویدا لەکاتی گەراندنەوەی فایلەکان', 88 | }); 89 | }); 90 | }, 91 | ); 92 | router.put( 93 | '/rate/:job_id', 94 | userJWT, 95 | rateJobValidator, 96 | async (req, res) => { 97 | rateJobQuery(req.body, req) 98 | .then(() => { 99 | res.json({ 100 | msg: 'کارەکە بە سەرکەوتووی نێردرا', 101 | }); 102 | }) 103 | .catch(() => { 104 | res.status(500).json({ 105 | msg: 'هەڵەیەک لە ڕاژە ڕوویدا', 106 | }); 107 | }); 108 | }, 109 | ); 110 | router.get('/list', userJWT, (req, res) => { 111 | readJobListQuery(100, 0, req.user.id, req.query.q) 112 | .then((data) => { 113 | res.json(data); 114 | }) 115 | .catch(() => { 116 | res.status(500).json({ 117 | msg: 'هەڵەیەک ڕوویدا لەکاتی گەراندنەوەی فایلەکان', 118 | }); 119 | }); 120 | }); 121 | 122 | router.get('/:job_id', userJWT, readSingleJobValidator, (req, res) => { 123 | readSingleJobQuery(req.params.job_id, req.user.id) 124 | .then((data) => { 125 | if (data) { 126 | res.json(data); 127 | } else { 128 | res.status(404).json({ msg: 'کرداری داواکراو نەدۆزرایەوە' }); 129 | } 130 | }) 131 | .catch(() => { 132 | res.status(500).json({ 133 | msg: 'هەڵەیەک ڕوویدا لەکاتی گەراندنەوەی فایلەکان', 134 | }); 135 | }); 136 | }); 137 | 138 | module.exports = router; 139 | -------------------------------------------------------------------------------- /routes/s3interface.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const pipeS3ObjectToExpress = require('../helpers/aws/pipeS3ObjectToExpress'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get('*', (req, res) => { 7 | const rawKey = `${req.url}`.substring(1); 8 | const key = rawKey.split('?')[0]; 9 | // const splitedKey = key.split('/'); 10 | // const userId = splitedKey[1] || -1; 11 | const params = { 12 | Bucket: process.env.AWS_BUCKET, 13 | Key: key, 14 | }; 15 | pipeS3ObjectToExpress(params, res); 16 | // if (userId === req.user.id || req.user.is_admin) { 17 | // const params = { 18 | // Bucket: process.env.AWS_BUCKET, 19 | // Key: key, 20 | // }; 21 | // pipeS3ObjectToExpress(params, res); 22 | // } else if (userId > -1) { 23 | // res.json({ msg: 'مافی گەشتن بەم فایلەت نیە' }); 24 | // } else { 25 | // res.json({ msg: 'فایلی داواکراو بوونی نیە یان نەدۆزرایەوە.' }); 26 | // } 27 | }); 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const { updateValidator } = require('../middlewares/validators/user'); 4 | const { 5 | readUserBalanceQuery, 6 | readTransactionListQuery, 7 | readSingleUserQuery, 8 | updateUserQuery, 9 | } = require('../query/user'); 10 | 11 | const router = express(); 12 | router.put('/', updateValidator, (req, res) => { 13 | updateUserQuery(req.body, req.user.id) 14 | .then(() => { 15 | res.json({ msg: 'پرۆفایلەکەت بە سەرکەوتووی نوێکرایەوە' }); 16 | }) 17 | .catch(() => { 18 | res.status(500).json({ 19 | msg: 'هەڵەیەک ڕوویدا لە کاتی نوێکردنەوەی پرۆفایلەکەت', 20 | }); 21 | }); 22 | }); 23 | 24 | router.get('/', (req, res) => { 25 | readSingleUserQuery(req.user.id) 26 | .then((data) => { 27 | res.json(data); 28 | }) 29 | .catch(() => { 30 | res.status(500).json({ 31 | msg: 'هەڵەیەک ڕوویدا لەکاتی وەرگرتنەوەی زانیاری بەکارهێنەر', 32 | }); 33 | }); 34 | }); 35 | router.get('/balance', (req, res) => { 36 | readUserBalanceQuery(req.user.id) 37 | .then((data) => { 38 | res.json(data); 39 | }) 40 | .catch(() => { 41 | res.status(500).json({ 42 | msg: 'هەڵەیەک ڕوویدا لەکاتی گەراندنەوەی باڵانسی بەکارهێنەر', 43 | }); 44 | }); 45 | }); 46 | router.get('/transactions', (req, res) => { 47 | readTransactionListQuery(req.user.id, 20, 0) 48 | .then((data) => { 49 | res.json(data); 50 | }) 51 | .catch(() => { 52 | res.status(500).json({ 53 | msg: 'هەڵەیەک رویدا لەکاتی وەرگرتنەوەی دواین پارەدانەکان', 54 | }); 55 | }); 56 | }); 57 | 58 | module.exports = router; 59 | --------------------------------------------------------------------------------