const express = require('express') const multiparty = require('multiparty') const path = require('path') const fsa = require('fs') const fs = fsa.promises const sqlite = require('sqlite') const crypto = require('crypto') const router = express.Router() const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), { database: 'index.db', root: './files', expiry: 2246400, gateway: 'https://distributed.icynet.eu', tokens: {} }) function asyncForm (req, form) { return new Promise(function (resolve, reject) { form.parse(req, function(err, fields, files) { if (err) return reject(err) resolve({ fields, files }) }) }) } async function clearDatabase (config, dbPromise) { let db = await dbPromise // Remove expired files let files = await db.all('SELECT * FROM File WHERE upload < ?', new Date() - (config.expiry * 1000)) if (files.length > 0) { for (let i in files) { let f = files[i] try { await fs.unlink(path.join(config.root, f.path)) } catch (e) {} await db.run('DELETE FROM File WHERE path = ?', f.path) } } // IPFS hashes let hashes = await db.all('SELECT * FROM Translation WHERE timeat < ?', new Date() - (config.expiry * 1000)) if (hashes.length > 0) { for (let i in hashes) { await db.run('DELETE FROM Translation WHERE file_hash = ?', hashes[i].file_hash) } } console.log('Database was cleared of %d files and %d IPFS hashes.', files.length, hashes.length) } async function init () { // Load configuration let config = await cfgLoader let root = path.resolve(config.root) // Check for root directory await fs.access(root, fsa.constants.F_OK) // Initialize database const dbPromise = Promise.resolve() .then(() => sqlite.open(path.join(__dirname, config.database), { Promise, cache: true })) .then(db => db.migrate({ migrationsPath: path.join(__dirname, 'migrations') })) await clearDatabase(config, dbPromise) // Serve a file or a hash // Files should be served from an external web server (such as nginx) whenever possible. router.get('/:hash', async (req, res, next) => { if (!req.params.hash) return res.status(400).send('Invalid request') let db = await dbPromise let file = await db.get('SELECT * FROM File WHERE path = ?', req.params.hash) let translation = await db.get('SELECT * FROM Translation WHERE translation = ?', req.params.hash) if (!file && !translation) return res.status(404).end() res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000) if (translation) { return res.redirect(config.gateway + '/ipfs/' + translation.file_hash) } res.sendFile(path.join(root, file.path)) }) // Upload a file or publish a hash router.post('/publish', async (req, res, next) => { let ip = req.ip let token = req.header('token') || req.body.token if (!token || !config.tokens[token]) return res.status(402).send('Forbidden') let baseurl = config.tokens[token] // Handle IPFS hash let hash = req.query.hash || req.body.hash if (hash) { let filename = req.query.filename || req.body.filename if (!filename) filename = crypto.randomBytes(8).toString('hex') let db = await dbPromise await db.run('INSERT INTO Translation (translation,file_hash,timeat) VALUES (?,?,?)', filename, hash, new Date()) return res.send(baseurl + filename) } // Handle multipart data let form = new multiparty.Form() let { fields, files } = await asyncForm(req, form) // Detect all files let allFiles = [] for (let i in files) { for (let j in files[i]) { allFiles.push(files[i][j]) } } if (!allFiles.length) return res.status(400).send('Invalid request') console.log('[%s] from %s request to upload %d file(s)', new Date(), ip, allFiles.length) // Handle all files provided let db = await dbPromise let uploadedFiles = [] for (let i in allFiles) { let file = allFiles[i] let fname = file.originalFilename let target = path.join(root, fname) // Handle already exists case (overwrite) try { await fs.access(target, fsa.constants.F_OK) await fs.unlink(target) await fs.copyFile(file.path, target) await fs.unlink(file.path) await db.run('UPDATE File SET ip = ?, upload = ? WHERE path = ?', ip, new Date(), fname) uploadedFiles.push(baseurl + fname) continue } catch (e) { if (e.code !== 'ENOENT') throw e } // Copy to target and unlink temporary file await fs.copyFile(file.path, target) await fs.unlink(file.path) await db.run('INSERT INTO File (path,ip,upload) VALUES (?,?,?)', fname, ip, new Date()) uploadedFiles.push(baseurl + fname) } if (uploadedFiles.length === 0) return res.status(400).send('No files were uploaded') res.send(uploadedFiles.join('\n')) }) return router } module.exports = init