├── .dockerignore ├── .editorconfig ├── .env-sample ├── .gitignore ├── Dockerfile ├── docker-compose.yaml ├── index.js ├── lib ├── call-worker.js ├── config-router.js ├── db.js ├── extractFileId.js ├── filter403Videos.js ├── get-videos.js ├── getShortUrl.js ├── handleError.js ├── log.js ├── mid │ ├── basic-auth.js │ └── is-apikey-valid.js ├── pool.js ├── possiblePromise.js ├── router.js ├── router │ ├── apikey.js │ ├── getlink-api.js │ ├── getlink-demo.js │ ├── render-homepage.js │ └── worker.js └── server.js ├── package.json ├── public └── css │ ├── grids-responsive-min.css │ ├── grids-responsive-old-ie-min.css │ ├── pure-min.css │ └── style.css ├── readme.md ├── report.js └── views ├── apikey ├── create.html ├── list.html └── remove.html ├── home.html ├── register.html └── worker ├── create.html ├── list.html └── remove.html /.dockerignore: -------------------------------------------------------------------------------- 1 | npm-debug.log.* 2 | node_modules 3 | database 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | # indent_size = 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.env-sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=6969 3 | VIRTUAL_HOST=duongtang.clgt.dev 4 | SHORT_URL_SERVICE=http://go.clgt.dev 5 | LOG_LEVEL=debug 6 | ADMIN_USERNAME=admin 7 | ADMIN_PASSWORD=1234 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log.* 2 | node_modules 3 | database 4 | .env 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:7.5-alpine 2 | 3 | MAINTAINER quocnguyen 4 | 5 | WORKDIR /src 6 | 7 | # extra tools to build native lib 8 | RUN apk add --no-cache make gcc g++ python 9 | 10 | # install and cache package.json 11 | COPY package.json /src 12 | RUN npm install --production 13 | RUN apk del make gcc g++ python 14 | 15 | # Bundle app source 16 | COPY . /src 17 | 18 | EXPOSE 6969 19 | 20 | CMD ["npm","start"] 21 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | duongtang_api: 2 | image: quocnguyen/duongtang-api 3 | mem_limit: 1024m 4 | env_file: .env 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.ROOT = __dirname 4 | require('dotenv').config({silent: true}) 5 | const log = require('./lib/log') 6 | const PORT = process.env.PORT || 6969 7 | require('./lib/server').listen(PORT, () => { 8 | log.debug(`http://0.0.0.0:${PORT}`) 9 | }) 10 | -------------------------------------------------------------------------------- /lib/call-worker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const pool = require('./pool') 3 | const got = require('got') 4 | const log = require('./log') 5 | 6 | module.exports = async (id) => { 7 | const worker = pool.next() 8 | log.info('call worker', worker) 9 | 10 | return got(`${worker.url}/drive/${id}`, { 11 | json: true, 12 | timeout: 3000, 13 | retries: 1, 14 | headers: { 15 | 'X-REQUEST-SECRET': worker.secret 16 | } 17 | }) 18 | .then(response => response.body) 19 | } 20 | -------------------------------------------------------------------------------- /lib/config-router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compression = require('compression') 4 | const serveStatic = require('serve-static') 5 | const helmet = require('helmet') 6 | const csurf = require('csurf') 7 | const cookieParser = require('cookie-parser') 8 | const qs = require('querystring') 9 | const url = require('url') 10 | const consolidate = require('consolidate') 11 | const {resolve} = require('path') 12 | 13 | const DAY_IN_MILISECOND = 24 * 60 * 60 * 1000 14 | 15 | // apply all middlewares needed for the app 16 | module.exports = (app) => { 17 | // use gzip to reduce the size of assets 18 | app.use( 19 | compression({ 20 | level: process.env.COMPRESSION_LEVEL || 1 21 | }) 22 | ) 23 | 24 | // assets 25 | let opt = {} 26 | if (process.env.NODE_ENV === 'production') { 27 | opt = { 28 | maxAge: DAY_IN_MILISECOND 29 | } 30 | } 31 | app.use( 32 | serveStatic( 33 | resolve(__dirname, '..', 'public'), 34 | opt 35 | ) 36 | ) 37 | 38 | // render 39 | app.use( 40 | (req, res, next) => { 41 | res.render = (filename, params = {}) => { 42 | const path = resolve(__dirname, '..', 'views', filename) 43 | res.locals = res.locals || {} 44 | consolidate.mustache( 45 | path, 46 | Object.assign(params, res.locals), 47 | (err, html) => { 48 | if (err) { throw err } 49 | res.setHeader('Content-Type', 'text/html; charset=utf8') 50 | res.end(html) 51 | } 52 | ) 53 | } 54 | next() 55 | } 56 | ) 57 | 58 | // query string 59 | app.use( 60 | (req, res, next) => { 61 | req.query = qs.parse( 62 | url.parse(req.url).query 63 | ) 64 | next() 65 | } 66 | ) 67 | 68 | // res.json 69 | app.use( 70 | (req, res, next) => { 71 | const json = (obj) => { 72 | return JSON.stringify(obj, null, 4) 73 | } 74 | 75 | res.json = (obj) => { 76 | res.setHeader('Content-Type', 'application/json; charset=utf8') 77 | res.end( 78 | json(obj) 79 | ) 80 | } 81 | 82 | next() 83 | } 84 | ) 85 | 86 | // parse body 87 | app.use((req, res, next) => { 88 | req.body = {} 89 | if (req.method !== 'POST') { return next() } 90 | 91 | let body = '' 92 | req.on('data', (buf) => { 93 | body += buf.toString() 94 | }) 95 | req.on('end', () => { 96 | req.body = qs.parse(body) 97 | next() 98 | }) 99 | }) 100 | 101 | // enable cookie 102 | app.use(cookieParser()) 103 | 104 | // helmet best practise protection 105 | app.use(helmet()) 106 | 107 | // csrf protection 108 | app.use( 109 | csurf({ 110 | cookie: true 111 | }) 112 | ) 113 | // assign csrfToken to view 114 | app.use( 115 | (req, res, next) => { 116 | res.locals = res.locals || {} 117 | res.locals.csrfToken = req.csrfToken() 118 | next() 119 | } 120 | ) 121 | 122 | return app 123 | } 124 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const level = require('level') 4 | const defaults = require('levelup-defaults') 5 | const bytewise = require('bytewise') 6 | const db = level(process.env.ROOT + '/database') 7 | 8 | module.exports = defaults(db, { 9 | keyEncoding: bytewise, 10 | valueEncoding: 'json' 11 | }) 12 | -------------------------------------------------------------------------------- /lib/extractFileId.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // extract file id out of google drive share link 4 | module.exports = (url) => { 5 | if (!url) return false 6 | if (url.toLowerCase().indexOf('drive.google.com') === -1) { 7 | return false 8 | } 9 | const found = url.match(/(?:https?:\/\/)?(?:[\w-]+\.)*(?:drive|docs)\.google\.com\/(?:(?:folderview|open|uc)\?(?:[\w\-%]+=[\w\-%]*&)*id=|(?:folder|file|document|presentation)\/d\/|spreadsheet\/ccc\?(?:[\w\-%]+=[\w\-%]*&)*key=)([\w-]{28,})/i) 10 | if (found === null) { 11 | return false 12 | } 13 | 14 | return found[1] 15 | } 16 | -------------------------------------------------------------------------------- /lib/filter403Videos.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const got = require('got') 4 | const log = require('./log') 5 | 6 | module.exports = async (videos) => { 7 | const test = videos.filter(video => video.provider === 'drive') 8 | try { 9 | if (test.length === 0) { throw new Error('no video for provider drive') } 10 | log.info('checkout', test[0].src) 11 | await got.head(videos[0].src) 12 | videos = videos.filter(video => video.provider === 'drive') 13 | log.info('good') 14 | } catch (err) { 15 | log.info('die, backup to proxy') 16 | videos = videos.filter(video => video.provider === 'proxy') 17 | } 18 | 19 | return Promise.resolve(videos) 20 | } 21 | -------------------------------------------------------------------------------- /lib/get-videos.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const callWorker = require('./call-worker') 4 | const filter403Videos = require('./filter403Videos') 5 | const getShortUrl = require('./getShortUrl') 6 | const mem = require('mem') 7 | const ONE_HOURS_IN_MILISECONDS = 1 * 60 * 60 * 1000 8 | 9 | let getVideos = async (id) => { 10 | const result = await callWorker(id) 11 | if (result.status !== 'OK') { 12 | throw new Error(result.reason) 13 | } 14 | 15 | let videos = result.data 16 | videos = await filter403Videos(videos) 17 | const urls = await getShortUrl( 18 | videos.map(video => video.src) 19 | ) 20 | 21 | return videos.map((video, index) => { 22 | delete video.provider 23 | video.src = urls[index] 24 | return { 25 | label: video.res, 26 | default: index === 0 ? 'true' : 'false', 27 | type: 'mp4', 28 | file: video.src 29 | } 30 | }) 31 | } 32 | 33 | const cacheFn = mem(getVideos, { 34 | maxAge: ONE_HOURS_IN_MILISECONDS 35 | }) 36 | 37 | module.exports = cacheFn 38 | 39 | -------------------------------------------------------------------------------- /lib/getShortUrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const got = require('got') 4 | const possiblePromise = require('./possiblePromise') 5 | 6 | module.exports = async (urls) => { 7 | if (Number(process.env.ALLOW_SHORT_URL) === 0) { 8 | return Promise.resolve(urls) 9 | } 10 | 11 | const shorturlService = `${process.env.SHORT_URL_SERVICE}/batch` 12 | const p = got 13 | .post(shorturlService, { 14 | timeout: 1500, 15 | retries: 1, 16 | json: true, 17 | body: {urls} 18 | }) 19 | .then(res => res.body.data) 20 | 21 | return possiblePromise(p, urls) 22 | } 23 | -------------------------------------------------------------------------------- /lib/handleError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('./log') 4 | const cuid = require('cuid') 5 | module.exports = (err) => { 6 | err.requestId = cuid() 7 | const msg = `Error number: ${err.requestId} - please contact admin \n 8 | Reason could be: ${err.toString()}` 9 | 10 | if ( 11 | err.message !== 'drive was invalid' || 12 | err.message !== 'homepage only support my demo link, please contact me to use api' 13 | ) { 14 | log.error(err) 15 | } 16 | 17 | return msg 18 | } 19 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const logger = require('pino')({ 3 | name: 'api' 4 | }) 5 | logger.level = process.env.LOG_LEVEL || 'debug' 6 | 7 | module.exports = logger 8 | -------------------------------------------------------------------------------- /lib/mid/basic-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const extractAuthFromReq = require('basic-auth') 4 | 5 | const basicAuth = (req, res, next) => { 6 | const user = extractAuthFromReq(req) 7 | if ( 8 | !user || 9 | user.name !== process.env.ADMIN_USERNAME || 10 | user.pass !== process.env.ADMIN_PASSWORD 11 | ) { 12 | res.statusCode = 401 13 | res.setHeader('WWW-Authenticate', 'Basic realm="example"') 14 | return res.end('Access denied') 15 | } 16 | 17 | next() 18 | } 19 | 20 | module.exports = basicAuth 21 | -------------------------------------------------------------------------------- /lib/mid/is-apikey-valid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const db = require('../db') 4 | 5 | module.exports = (req, res, next) => { 6 | if (!req.query.drive) return next() 7 | 8 | if (!req.query.apikey) { 9 | return res.json({ 10 | status: 'FAIL', 11 | reason: 'missing apikey' 12 | }) 13 | } 14 | 15 | db.get(['apikey', req.query.apikey], (err, value) => { 16 | if (err || value === null) { 17 | return res.json({ 18 | status: 'FAIL', 19 | reason: 'invalid apikey, contact https://www.facebook.com/quocnguyenclgt to get apikey' 20 | }) 21 | } 22 | 23 | let requestTotal = value.requestTotal || 0 24 | 25 | // update apikey stat 26 | db.put(['apikey', req.query.apikey], Object.assign(value, { 27 | requestTotal: requestTotal + 1 28 | })) 29 | 30 | next() 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Pool = require('wrr-pool') 4 | const db = require('./db') 5 | const through2 = require('through2') 6 | const log = require('./log') 7 | const pool = new Pool() 8 | 9 | db.createReadStream({ 10 | gt: ['worker', null], 11 | lt: ['worker', undefined], 12 | keys: false 13 | }) 14 | .pipe(through2.obj((worker, enc, next) => { 15 | pool.add(worker, worker.score || 1000) 16 | next() 17 | })) 18 | 19 | db.on('del', (key) => { 20 | if (key[0] !== 'worker') return 21 | log.info('remove worker', key) 22 | try { 23 | pool.remove(worker => worker.id === key[1]) 24 | } catch (err) { 25 | // ignore err since it is an external lib 26 | } 27 | }) 28 | 29 | db.on('put', (key, worker) => { 30 | if (key[0] !== 'worker') return 31 | log.info('add worker', worker) 32 | pool.add(worker, worker.score || 1000) 33 | }) 34 | 35 | module.exports = pool 36 | -------------------------------------------------------------------------------- /lib/possiblePromise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // promise that never reject 4 | // it will resolve to defaultValue on error 5 | const log = require('./log') 6 | 7 | module.exports = async (p, defaultValue = null) => { 8 | return new Promise(resolve => { 9 | p.then(resolve).catch(err => { 10 | log.error(err) 11 | resolve(defaultValue) 12 | }) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('./log') 4 | const Router = require('router') 5 | const configRouter = require('./config-router') 6 | const isAdmin = require('./mid/basic-auth') 7 | const app = configRouter(Router()) 8 | const isApikeyValid = require('./mid/is-apikey-valid') 9 | const workerRouter = require('./router/worker') 10 | const apikeyRouter = require('./router/apikey') 11 | 12 | // render homepage 13 | app.get('/', 14 | isApikeyValid, 15 | require('./router/getlink-api'), 16 | require('./router/render-homepage') 17 | ) 18 | 19 | app.post('/', 20 | require('./router/getlink-demo'), 21 | require('./router/render-homepage') 22 | ) 23 | 24 | app 25 | .use('/apikeys', isAdmin) 26 | .get('/apikeys', 27 | apikeyRouter.list 28 | ) 29 | .get('/apikeys/create', 30 | apikeyRouter.create 31 | ) 32 | .get('/apikeys/:id/remove', 33 | apikeyRouter.remove 34 | ) 35 | 36 | app 37 | .use('/workers', isAdmin) 38 | .get('/workers', 39 | workerRouter.renderList 40 | ) 41 | .get('/workers/create', 42 | workerRouter.renderCreateForm 43 | ) 44 | .post('/workers/create', 45 | workerRouter.handleCreate, 46 | workerRouter.renderCreateForm 47 | ) 48 | .get('/workers/:id/remove', 49 | workerRouter.remove 50 | ) 51 | 52 | // simple err handle 53 | app.use( 54 | (err, req, res, next) => { 55 | res.statusCode = 500 56 | log.error(err) 57 | res.end('Bần tăng đang bận đánh quái ở bãi train') 58 | } 59 | ) 60 | 61 | module.exports = app 62 | -------------------------------------------------------------------------------- /lib/router/apikey.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cuid = require('cuid') 4 | const db = require('../db') 5 | const through2 = require('through2') 6 | const isDomain = require('is-domain') 7 | 8 | const list = (req, res, next) => { 9 | const apikeys = [] 10 | db.createReadStream({ 11 | gt: ['apikey', null], 12 | lt: ['apikey', undefined], 13 | keys: false 14 | }) 15 | .on('end', () => { 16 | res.statusCode = 200 17 | res.render('apikey/list.html', { 18 | apikeys: apikeys 19 | }) 20 | }) 21 | .pipe(through2.obj((row, enc, next) => { 22 | apikeys.push(row) 23 | next() 24 | })) 25 | } 26 | 27 | const create = (req, res, next) => { 28 | if ( 29 | !req.query.domain || 30 | isDomain(req.query.domain) === false 31 | ) { 32 | return res.end('domain invalid') 33 | } 34 | 35 | // generate 25 char apikey 36 | const id = cuid() 37 | 38 | db.put(['apikey', id], { 39 | apikey: id, 40 | created: Date.now(), 41 | domain: req.query.domain 42 | }) 43 | 44 | res.render('apikey/create.html', { 45 | apikey: id 46 | }) 47 | } 48 | 49 | const remove = (req, res, next) => { 50 | const id = req.params.id 51 | db.del(['apikey', id]) 52 | res.render('apikey/remove.html', { 53 | id: id 54 | }) 55 | } 56 | 57 | exports.list = list 58 | exports.create = create 59 | exports.remove = remove 60 | -------------------------------------------------------------------------------- /lib/router/getlink-api.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const extractFileId = require('../extractFileId') 4 | const getVideos = require('../get-videos') 5 | const handleError = require('../handleError') 6 | const mem = require('mem') 7 | 8 | module.exports = async (req, res, next) => { 9 | if (!req.query.drive) { 10 | return next() 11 | } 12 | 13 | try { 14 | const id = extractFileId(req.query.drive) 15 | if (id === false) { 16 | return res.json({ 17 | status: 'FAIL', 18 | reason: 'drive invalid' 19 | }) 20 | } 21 | 22 | let videos = await getVideos(id) 23 | 24 | res.json(videos) 25 | } catch (err) { 26 | // ignore err like those 27 | const msg = err.toString() 28 | if (msg === "Error: You don't have permission to access this video" || 29 | msg === "Error: This video doesn't exist.") { 30 | return res.json({ 31 | status: 'FAIL', 32 | reason: err.toString() 33 | }) 34 | } 35 | 36 | // clear only on my own err 37 | mem.clear(getVideos) 38 | res.json({ 39 | err: handleError(err) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/router/getlink-demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const extractFileId = require('../extractFileId') 4 | const handleError = require('../handleError') 5 | let getVideos = require('../get-videos') 6 | 7 | module.exports = async (req, res) => { 8 | if (!req.body.contact_me_to_use_api_dont_do_this) { 9 | throw new Error('drive was missing') 10 | } 11 | try { 12 | const id = extractFileId(req.body.contact_me_to_use_api_dont_do_this) 13 | 14 | if (id === false) { 15 | throw new Error('drive was invalid') 16 | } 17 | 18 | if (id.toLowerCase() !== '0b7mb8rvfj1jcs2p0ty1mzutzdkk') { 19 | throw new Error('homepage only support my demo link, please contact me to use api') 20 | } 21 | 22 | const videos = await getVideos(id) 23 | res.render('home.html', { 24 | result: JSON.stringify(videos, null, 4) 25 | }) 26 | } catch (err) { 27 | const errMsg = handleError(err) 28 | 29 | res.render('home.html', { 30 | result: errMsg 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/router/render-homepage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (req, res, next) => { 4 | res.render('home.html', { 5 | logo: 'http://i.imgur.com/NP4giK1.jpg' 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /lib/router/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const db = require('../db') 4 | const through2 = require('through2') 5 | const cuid = require('cuid') 6 | 7 | const handleCreate = (req, res, next) => { 8 | try { 9 | const worker = req.body 10 | if (!worker.name) { 11 | throw new Error('name missing') 12 | } 13 | 14 | if (!worker.url) { 15 | throw new Error('url missing') 16 | } 17 | 18 | if (!worker.secret) { 19 | throw new Error('secret missing') 20 | } 21 | worker.id = cuid() 22 | delete worker._csrf 23 | db.put(['worker', worker.id], worker) 24 | res.locals.success = true 25 | } catch (err) { 26 | res.locals.err = true 27 | } 28 | 29 | next() 30 | } 31 | 32 | const renderCreateForm = (req, res, next) => { 33 | res.render('worker/create.html') 34 | } 35 | 36 | const renderList = (req, res, next) => { 37 | const workers = [] 38 | db.createReadStream({ 39 | gt: ['worker', null], 40 | lt: ['worker', undefined], 41 | keys: false 42 | }) 43 | .on('end', () => { 44 | res.render('worker/list.html', { 45 | workers: workers 46 | }) 47 | }) 48 | .pipe(through2.obj((row, enc, next) => { 49 | workers.push(row) 50 | next() 51 | })) 52 | } 53 | 54 | const remove = (req, res, next) => { 55 | const id = req.params.id 56 | db.del(['worker', id]) 57 | res.render('worker/remove.html', { 58 | id: id 59 | }) 60 | } 61 | 62 | exports.renderCreateForm = renderCreateForm 63 | exports.renderList = renderList 64 | exports.handleCreate = handleCreate 65 | exports.remove = remove 66 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const http = require('http') 4 | const router = require('./router') 5 | const finalhandler = require('finalhandler') 6 | const onRequest = (req, res) => { 7 | router(req, res, finalhandler) 8 | } 9 | const server = http.createServer(onRequest) 10 | 11 | module.exports = server 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "duongtang_server", 3 | "version": "1.0.0", 4 | "description": "api for get videoplayback from google drive", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "NODE_ENV=development nodemon --harmony-async-await index.js | pino", 8 | "start": "NODE_ENV=production node --harmony-async-await index.js | NODE_ENV=production node report.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "basic-auth": "^1.1.0", 16 | "bugsnag": "^1.9.0", 17 | "bytewise": "^1.1.0", 18 | "compression": "^1.6.2", 19 | "consolidate": "^0.14.5", 20 | "cookie-parser": "^1.4.3", 21 | "csurf": "^1.9.0", 22 | "cuid": "^1.3.8", 23 | "dev-null": "^0.1.1", 24 | "dotenv": "^4.0.0", 25 | "fast-json-parse": "^1.0.2", 26 | "finalhandler": "^0.5.1", 27 | "got": "^6.7.1", 28 | "helmet": "^3.4.0", 29 | "is-domain": "0.0.1", 30 | "level": "^1.6.0", 31 | "levelup-defaults": "^2.0.0", 32 | "mem": "^1.1.0", 33 | "mustache": "^2.3.0", 34 | "pino": "^3.3.2", 35 | "response-time": "^2.3.2", 36 | "router": "^1.1.4", 37 | "serve-static": "^1.11.2", 38 | "split2": "^2.1.1", 39 | "through2": "^2.0.3", 40 | "wrr-pool": "^1.1.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/css/grids-responsive-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.2 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | @media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-10-24,.pure-u-sm-5-12{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-14-24,.pure-u-sm-7-12{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-15-24,.pure-u-sm-5-8{width:62.5%}.pure-u-sm-16-24,.pure-u-sm-2-3{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-18-24,.pure-u-sm-3-4{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-20-24,.pure-u-sm-5-6{width:83.3333%}.pure-u-sm-21-24,.pure-u-sm-7-8{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-10-24,.pure-u-md-5-12{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-14-24,.pure-u-md-7-12{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-15-24,.pure-u-md-5-8{width:62.5%}.pure-u-md-16-24,.pure-u-md-2-3{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-18-24,.pure-u-md-3-4{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-20-24,.pure-u-md-5-6{width:83.3333%}.pure-u-md-21-24,.pure-u-md-7-8{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-10-24,.pure-u-lg-5-12{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-14-24,.pure-u-lg-7-12{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-15-24,.pure-u-lg-5-8{width:62.5%}.pure-u-lg-16-24,.pure-u-lg-2-3{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-18-24,.pure-u-lg-3-4{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-20-24,.pure-u-lg-5-6{width:83.3333%}.pure-u-lg-21-24,.pure-u-lg-7-8{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-12,.pure-u-xl-1-2,.pure-u-xl-1-24,.pure-u-xl-1-3,.pure-u-xl-1-4,.pure-u-xl-1-5,.pure-u-xl-1-6,.pure-u-xl-1-8,.pure-u-xl-10-24,.pure-u-xl-11-12,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-2-24,.pure-u-xl-2-3,.pure-u-xl-2-5,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24,.pure-u-xl-3-24,.pure-u-xl-3-4,.pure-u-xl-3-5,.pure-u-xl-3-8,.pure-u-xl-4-24,.pure-u-xl-4-5,.pure-u-xl-5-12,.pure-u-xl-5-24,.pure-u-xl-5-5,.pure-u-xl-5-6,.pure-u-xl-5-8,.pure-u-xl-6-24,.pure-u-xl-7-12,.pure-u-xl-7-24,.pure-u-xl-7-8,.pure-u-xl-8-24,.pure-u-xl-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%}.pure-u-xl-1-5{width:20%}.pure-u-xl-5-24{width:20.8333%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%}.pure-u-xl-7-24{width:29.1667%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%}.pure-u-xl-2-5{width:40%}.pure-u-xl-10-24,.pure-u-xl-5-12{width:41.6667%}.pure-u-xl-11-24{width:45.8333%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%}.pure-u-xl-13-24{width:54.1667%}.pure-u-xl-14-24,.pure-u-xl-7-12{width:58.3333%}.pure-u-xl-3-5{width:60%}.pure-u-xl-15-24,.pure-u-xl-5-8{width:62.5%}.pure-u-xl-16-24,.pure-u-xl-2-3{width:66.6667%}.pure-u-xl-17-24{width:70.8333%}.pure-u-xl-18-24,.pure-u-xl-3-4{width:75%}.pure-u-xl-19-24{width:79.1667%}.pure-u-xl-4-5{width:80%}.pure-u-xl-20-24,.pure-u-xl-5-6{width:83.3333%}.pure-u-xl-21-24,.pure-u-xl-7-8{width:87.5%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%}.pure-u-xl-23-24{width:95.8333%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-24-24,.pure-u-xl-5-5{width:100%}} -------------------------------------------------------------------------------- /public/css/grids-responsive-old-ie-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.2 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-10-24,.pure-u-sm-5-12{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-14-24,.pure-u-sm-7-12{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-15-24,.pure-u-sm-5-8{width:62.5%}.pure-u-sm-16-24,.pure-u-sm-2-3{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-18-24,.pure-u-sm-3-4{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-20-24,.pure-u-sm-5-6{width:83.3333%}.pure-u-sm-21-24,.pure-u-sm-7-8{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{width:100%}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-10-24,.pure-u-md-5-12{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-14-24,.pure-u-md-7-12{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-15-24,.pure-u-md-5-8{width:62.5%}.pure-u-md-16-24,.pure-u-md-2-3{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-18-24,.pure-u-md-3-4{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-20-24,.pure-u-md-5-6{width:83.3333%}.pure-u-md-21-24,.pure-u-md-7-8{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{width:100%}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-10-24,.pure-u-lg-5-12{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-14-24,.pure-u-lg-7-12{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-15-24,.pure-u-lg-5-8{width:62.5%}.pure-u-lg-16-24,.pure-u-lg-2-3{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-18-24,.pure-u-lg-3-4{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-20-24,.pure-u-lg-5-6{width:83.3333%}.pure-u-lg-21-24,.pure-u-lg-7-8{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{width:100%} -------------------------------------------------------------------------------- /public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.2 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */.pure-button:focus,a:active,a:hover{outline:0}.pure-table,table{border-collapse:collapse;border-spacing:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}.pure-button,input{line-height:normal}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}.pure-button,.pure-form input:not([type]),.pure-menu{box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend,td,th{padding:0}legend{border:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u,.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block;zoom:1}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;zoom:1;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:transparent;background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{filter:alpha(opacity=90);background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;margin:0;border-radius:0;border-right:1px solid #111;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=tel],.pure-form input[type=color],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=text],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px}.pure-form input[type=color]{padding:.2em .5em}.pure-form input:not([type]):focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=text]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=checkbox]:focus,.pure-form input[type=radio]:focus{outline:#129FEA auto 1px}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input:not([type])[disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=text][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input:not([type]),.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=text],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-aligned .pure-help-inline,.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=tel],.pure-form input[type=color],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=text],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=tel],.pure-group input[type=color],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=text]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-disabled,.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td,.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 960px; 3 | } 4 | .wrapper { 5 | max-width: 960px; 6 | margin: 0 auto; 7 | } 8 | html, button, input, select, textarea, 9 | .pure-g [class *= "pure-u"] { 10 | font-family: monospace; 11 | font-size: 20px; 12 | } 13 | 14 | .green { 15 | color: #88B04B; 16 | } 17 | 18 | .help { 19 | color: #ccc; 20 | font-size: 16px; 21 | } 22 | 23 | pre { 24 | display: block; 25 | padding: 9.5px; 26 | margin: 0 0 10px; 27 | font-size: 13px; 28 | line-height: 1.42857143; 29 | color: #333; 30 | word-break: break-all; 31 | word-wrap: break-word; 32 | background-color: #f5f5f5; 33 | border: 1px solid #ccc; 34 | border-radius: 4px; 35 | } 36 | 37 | .slogan { 38 | text-align: center; 39 | color: #88B04B; 40 | margin-bottom: 10px; 41 | } 42 | 43 | .pure-menu { 44 | text-align: center; 45 | height: 100px; 46 | } 47 | 48 | .main { 49 | margin-top: 100px; 50 | text-align: justify; 51 | } 52 | 53 | .apikey { 54 | text-align: center; 55 | color: #88B04B; 56 | } 57 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ``` 2 | docker run -d \ 3 | --name dt \ 4 | -v /root/database:/src/database \ 5 | -p 80:6969 \ 6 | -e VIRTUAL_HOST=duongtang.clgt.vn \ 7 | -e LETSENCRYPT_HOST=duongtang.clgt.vn \ 8 | -e LETSENCRYPT_EMAIL=quocnguyen@clgt.vn \ 9 | -e SHORT_URL_SERVICE=http://go.clgt.vn \ 10 | -e ALLOW_SHORT_URL=0 \ 11 | -e ADMIN_USERNAME=admin \ 12 | -e ADMIN_PASSWORD=denthecat \ 13 | -e BUGSNAG_API=3fab4a6fbc30d7e939102ebf055ab661 \ 14 | -e ALLOW_CACHE=1 \ 15 | quocnguyen/dt-api 16 | ``` 17 | -------------------------------------------------------------------------------- /report.js: -------------------------------------------------------------------------------- 1 | // a very simple transporter that help us 2 | // push error coming from stdin to bugsnap 3 | 'use strict' 4 | 5 | require('dotenv').config({silent: true}) 6 | const bugsnap = require('bugsnag') 7 | const split = require('split2') 8 | const through = require('through2') 9 | const Parse = require('fast-json-parse') 10 | const util = require('util') 11 | 12 | bugsnap.register(process.env.BUGSNAG_API) 13 | 14 | // test if the line is actually in pino log format 15 | // taken from https://github.com/mcollina/pino/blob/master/pretty.js#49 16 | function isPinoLine (line) { 17 | return line.hasOwnProperty('hostname') && line.hasOwnProperty('pid') && (line.hasOwnProperty('v') && line.v === 1) 18 | } 19 | 20 | function mapLine (line) { 21 | var parsed = Parse(line) 22 | var value = parsed.value 23 | if (parsed.err || !isPinoLine(value)) { 24 | // pass through 25 | return line + '\n' 26 | } 27 | return value 28 | } 29 | let tranform = through.obj(function (record, enc, cb) { 30 | // if record is not object, it properly not what we want 31 | if (!util.isObject(record)) { 32 | return cb(null, record) 33 | } 34 | 35 | // make this stream a pass through if we are not in production 36 | if (process.env.NODE_ENV !== 'production') { 37 | return cb(null, JSON.stringify(record) + '\n') 38 | } 39 | 40 | // we also do nothing if this record is not an error 41 | if (record.level < 50) { 42 | return cb(null, JSON.stringify(record) + '\n') 43 | } 44 | 45 | // create a err instance but use the stack trace from the record 46 | let err = new Error(record.msg + ' ' + record.requestId) 47 | err.stack = record.stack 48 | 49 | // kick off 50 | bugsnap.notify(err, { 51 | subsystem: { 52 | requestId: record.requestId 53 | } 54 | }) 55 | 56 | // ready to process the next one 57 | cb(null, JSON.stringify(record) + '\n') 58 | }) 59 | 60 | process.stdin 61 | .pipe(split(mapLine)) 62 | .pipe(tranform) 63 | .pipe(process.stdout) 64 | -------------------------------------------------------------------------------- /views/apikey/create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | Get link drive không bao giờ die 15 | 16 | 17 |
18 |
19 | {{#apikey}} 20 |

21 | {{.}} 22 |

23 | {{/apikey}} 24 | 25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /views/apikey/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 |
17 |
18 |
19 |
20 |

21 | Apikeys / 22 | create new one 23 |

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{#apikeys}} 37 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | {{/apikeys}} 49 | 50 |
apikeydomaincreatedrequest total
{{apikey}}{{domain}}{{created}}{{requestTotal}} 43 | 44 | remove 45 | 46 |
51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /views/apikey/remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 |

17 | Removed {{id}} 18 | back 19 |

20 | 21 | -------------------------------------------------------------------------------- /views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 |
22 |

Get link drive không bao vờ die

23 |

Contact me at: https://www.facebook.com/quocnguyenclgt

24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 | {{^result}} 34 | 35 | 36 | {{/result}} 37 | 38 | {{#result}} 39 | 40 | 41 | {{/result}} 42 |
43 |
44 |
45 |
46 | {{#csrfToken}} 47 | 48 | {{/csrfToken}} 49 | 50 | 51 |

52 | Code mới cập nhật Thứ 3 ngày 7 tháng 2 năm 2017 53 |

54 | 57 | 60 |
61 |
62 |
63 | 64 | {{#result}} 65 |
66 |
{{.}}
67 |
68 | {{/result}} 69 |
70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /views/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ bay 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |

Đã đủ người đăng ký

22 |

Thí chủ hãy chờ đợt sau hoặc dùng code VIP

23 |
24 |
25 | 26 |
27 |
28 |
29 | {{#csrfToken}} 30 | 31 | {{/csrfToken}} 32 | 33 | 34 |

35 | Code mát xa tại tây lương nữ quốc. Thó của admin. 36 | Thó ngay 37 |

38 | 39 | 40 | 41 |

Không có web thí chủ đăng ký làm gì?

42 | 43 | 44 | 45 |

Để bần tăng gởi apikey vào mail

46 | 47 | 48 | 49 |

Dùng login vào web thống kê của bần tăng

50 | 51 | 54 |
55 |
56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /views/worker/create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 |

22 | create new worker 23 | back to list 24 |

25 | {{#success}} 26 |

Done.

27 | {{/success}} 28 |
29 |
30 | {{#csrfToken}} 31 | 32 | {{/csrfToken}} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /views/worker/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 |
17 |
18 |
19 |
20 |

21 | Workers / 22 | create new one 23 |

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{#workers}} 39 | 40 | 41 | 42 | 43 | 46 | {{/workers}} 47 | 48 | 49 |
NameURLScoreCreated
{{name}}{{url}}{{score}}{{created}} 44 | Remove 45 |
50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /views/worker/remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Get link drive không bao giờ die 14 | 15 | 16 |

17 | Removed {{id}} 18 | back 19 |

20 | --------------------------------------------------------------------------------