'use strict'; const express = require('express') , router = express.Router() , utils = require('../utils.js') , Posts = require(__dirname+'/../models/posts.js') , Boards = require(__dirname+'/../models/boards.js') , uuidv4 = require('uuid/v4') , path = require('path') , uploadDirectory = require(__dirname+'/../helpers/uploadDirectory.js') , util = require('util') , fs = require('fs') , unlink = util.promisify(fs.unlink) , fileUpload = require(__dirname+'/../helpers/file-upload.js') , fileThumbnail = require(__dirname+'/../helpers/file-thumbnail.js') , fileIdentify = require(__dirname+'/../helpers/file-identify.js') , fileCheckMimeType = require(__dirname+'/../helpers/file-check-mime-types.js'); /* (async () => { await Boards.deleteIncrement('b'); await Boards.deleteAll(); await Boards.insertOne({ _id: 'b', name: 'random', description: 'post anything here', }) await Posts.deleteAll('b'); })(); */ // make new post router.post('/board/:board', Boards.exists, async (req, res, next) => { //needs a refactor into a body validator of some sort let numFiles = 0; if (req.files && req.files.file) { if (Array.isArray(req.files.file)) { numFiles = req.files.file.length; } else { numFiles = 1; req.files.file = [req.files.file]; } } if (!req.body.message && numFiles === 0) { return res.status(400).json({ 'message': 'Must provide a message or file' }); } if (req.body.message && req.body.message.length > 2000) { return res.status(400).json({ 'message': 'Message must be 2000 characters or less' }); } if (req.body.name && req.body.name.length > 50) { return res.status(400).json({ 'message': 'Name must be 50 characters or less' }); } if (req.body.subject && req.body.subject.length > 50) { return res.status(400).json({ 'message': 'Subject must be 50 characters or less' }); } if (req.body.password && req.body.password.length > 50) { return res.status(400).json({ 'message': 'Password must be 50 characters or less' }); } // check if this is responding to an existing thread if (req.body.thread) { let thread; try { thread = await Posts.getThread(req.params.board, req.body.thread); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }); } if (!thread) { return res.status(400).json({ 'message': 'thread does not exist' }) } } let files = []; // if we got a file if (numFiles > 0) { // check all mime types befoer we try saving anything for (let i = 0; i < numFiles; i++) { if (!fileCheckMimeType(req.files.file[i].mimetype)) { return res.status(400).json({ 'message': 'Invalid file type' }); } } // then upload, thumb, get metadata, etc. for (let i = 0; i < numFiles; i++) { const file = req.files.file[i]; const filename = uuidv4() + path.extname(file.name); // try to save, thumbnail and get metadata try { await fileUpload(req, res, file, filename); const fileData = await fileIdentify(filename); await fileThumbnail(filename); const processedFile = { filename: filename, originalFilename: file.name, mimetype: file.mimetype, size: file.size, // size in bytes geometry: fileData.size, // object with width and height pixels sizeString: fileData.Filesize, // 123 Ki string geometryString: fileData.Geometry, // 123 x 123 string } //handle gifs with multiple geometry and size if (Array.isArray(processedFile.geometry)) { processedFile.geometry = processedFile.geometry[0]; } if (Array.isArray(processedFile.sizeString)) { processedFile.sizeString = processedFile.sizeString[0]; } if (Array.isArray(processedFile.geometryString)) { processedFile.geometryString = processedFile.geometryString[0]; } files.push(processedFile); } catch (err) { console.error(err); //TODO: DELETE FAILED FILES return res.status(500).json({ 'message': 'Error uploading file' }); } } } const data = { 'name': req.body.name || 'Anonymous', 'subject': req.body.subject || '', 'date': new Date(), 'message': req.body.message || '', 'thread': req.body.thread || null, 'password': req.body.password || '', 'files': files }; const post = await Posts.insertOne(req.params.board, data) const redirect = '/' + req.params.board + '/thread/' + (req.body.thread || post.insertedId); return res.redirect(redirect); }); // delete a post. using POST isntead of DELETE because of html forms supprot router.post('/board/:board/delete', Boards.exists, async (req, res, next) => { if (!req.body.password) { return res.status(400).json({ 'message': 'Must provide a password' }) } if (req.body.password.length > 50) { return res.status(400).json({ 'message': 'Password must be 50 characters or less' }) } if (!req.body.checked || req.body.checked.length === 0 || req.body.checked.length > 10) { //10 for now just for _some_ limit return res.status(400).json({ 'message': 'Must check 1-10 boxes for posts to delete' }) } //get all posts that were checked let posts; try { posts = await Posts.getPosts(req.params.board, req.body.checked); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }); } //filter it to ones that match the password posts = posts.filter(post => post.password == req.body.password); if (posts.length > 0) { const threadIds = posts.filter(x => x.thread == null).map(x => x._id); //get posts from all threads let threadPosts = [] await Promise.all(threadIds.map(async id => { const currentThreadPosts = await Posts.getThreadPosts(req.params.board, id); threadPosts = threadPosts.concat(currentThreadPosts); return; })) //combine them all into one array const allPosts = posts.concat(threadPosts) //delete posts from DB let deletedPosts = 0; try { const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x._id)); deletedPosts = result.deletedCount; } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error deleting posts from DB' }); } //get filenames from all the posts let fileNames = []; allPosts.forEach(post => { fileNames = fileNames.concat(post.files.map(x => x.filename)) }) //delete all the files using the filenames await Promise.all(fileNames.map(async filename => { //dont question it. return Promise.all([ unlink(uploadDirectory + filename), unlink(uploadDirectory + 'thumb-' + filename) ]) })); //hooray! return res.json({ 'message': `deleted ${threadIds.length} threads and ${deletedPosts} posts` }) } return res.status(403).json({ 'message': 'Password did not match any selected posts' }) }); // get recent threads and preview posts router.get('/board/:board/recent/:page(\\d+)?', Boards.exists, async (req, res, next) => { //get the recently bumped thread & preview po let threads; let threads; try { threads = await Posts.getRecent(req.params.board, req.params.page || 1); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }); } if (!threads || threads.lenth === 0) { return res.status(404).json({ 'message': 'Not found' }); } return res.json(threads); }); // get a thread router.get('/board/:board/thread/:id(\\d+)', Boards.exists, async (req, res, next) => { //get the recently bumped thread & preview posts let thread; try { thread = await Posts.getThread(req.params.board, req.params.id); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }); } if (!thread) { return res.status(404).json({ 'message': 'Not found' }); } return res.json(thread) }); // get array of threads (catalog) router.get('/board/:board/catalog', Boards.exists, async (req, res, next) => { //get the recently bumped thread & preview posts let data; try { data = await Posts.getCatalog(req.params.board); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }); } if (!data) { return res.status(404).json({ 'message': 'Not found' }); } return res.json(data) }); //get list of boards router.get('/boards', Boards.exists, async (req, res, next) => { //get a list of boards let boards; try { boards = await Boards.find(); } catch (err) { console.error(err); return res.status(500).json({ 'message': 'Error fetching from DB' }) } //render the page res.json(boards) }); module.exports = router;