globalmanage live posts

todo:
- handle iphashpermlevel to not send IP for users without perms (separate room? seems easiest)
- make sure lastpostId, reply adding, etc doesnt get all fucked up because of multi threads on one page
merge-requests/208/head
Thomas Lynch 4 years ago
parent d7de091809
commit 40d17e360d
  1. 1
      controllers/pages.js
  2. 25
      gulp/res/js/live.js
  3. 1
      gulp/res/js/localstorage.js
  4. 3
      helpers/sessionrefresh.js
  5. 2
      models/forms/addban.js
  6. 28
      models/forms/makepost.js
  7. 16
      socketio.js
  8. 2
      views/includes/post.pug
  9. 7
      views/pages/globalmanagerecent.pug
  10. 2
      views/pages/thread.pug

@ -61,6 +61,7 @@ router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, session
router.get('/globalmanage/reports.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageReports);
router.get('/globalmanage/bans.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageBans);
router.get('/globalmanage/recent.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageRecent);
router.get('/globalmanage/recent.json', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, (req, res, next) => { res.json({}) })
router.get('/globalmanage/boards.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), globalManageBoards);
router.get('/globalmanage/globallogs.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageLogs);
router.get('/globalmanage/news.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageNews);

@ -7,8 +7,8 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
let supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window;
const livecolor = document.getElementById('livecolor');
const livetext = isThread && document.getElementById('livetext') ? document.getElementById('livetext').childNodes[1] : null;
const updateButton = livetext ? livetext.nextSibling : null;
const livetext = (isThread || isGlobalRecent) && document.getElementById('livetext') ? document.getElementById('livetext').childNodes[1] : null;
const updateButton = document.getElementById('updatepostsbutton');
const updateLive = (message, color, showRelativeTime) => {
livecolor.style.backgroundColor = color;
livetext.nodeValue = `${message}`;
@ -19,16 +19,27 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
if (anchors.length > 0) {
lastPostId = anchors[anchors.length - 1].id;
}
const thread = document.querySelector('.thread');
const newPost = (data) => {
//insert at end of thread, but insert at top for globalmanage
console.log('got new post');
lastPostId = data.postId;
const postData = data;
//create a new post
const postHtml = post({ post: postData, modview:isModView, upLevel:isThread });
//add it to the end of the thread
thread.insertAdjacentHTML('beforeend', postHtml);
const postHtml = post({ post: postData, modview:isModView, globalmanage:isGlobalRecent, upLevel:isThread });
let insertPoint;
if (isGlobalRecent) {
const firstHr = document.querySelector('hr');
const newHr = document.createElement('hr');
const threadWrapper = document.createElement('div');
threadWrapper.classList.add('thread');
insertPoint = threadWrapper;
firstHr.insertAdjacentElement('beforebegin', newHr);
newHr.insertAdjacentElement('afterend', threadWrapper);
} else {
insertPoint = document.querySelector('.thread');
}
insertPoint.insertAdjacentHTML('beforeend', postHtml);
for (let j = 0; j < postData.quotes.length; j++) {
const quoteData = postData.quotes[j];
//add backlink to quoted posts
@ -221,7 +232,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
scrollSetting.checked = scrollEnabled;
scrollSetting.addEventListener('change', toggleScroll, false);
if (isThread) {
if (isThread || isGlobalRecent) {
updateButton.addEventListener('click', forceUpdate);
liveEnabled ? enableLive() : disableLive();
}

@ -2,6 +2,7 @@ const isCatalog = window.location.pathname.endsWith('catalog.html');
const isThread = /\/\w+\/thread\/\d+.html/.test(window.location.pathname);
const isModView = /\/\w+\/manage\/(thread\/)?(index|\d+).html/.test(window.location.pathname);
const isManage = /\/(\w+\/manage|globalmanage)\/(recent|reports|bans|boards|logs|settings|banners|accounts|news).html/.test(window.location.pathname);
const isGlobalRecent = window.location.pathname === '/globalmanage/recent.html';
function setLocalStorage(key, value) {
try {

@ -5,6 +5,9 @@ const { Accounts } = require(__dirname+'/../db/')
module.exports = async (req, res, next) => {
if (req.session && req.session.user) {
if (!res.locals) {
res.locals = {};
}
res.locals.user = await cache.get(`users:${req.session.user}`);
if (!res.locals.user) {

@ -5,7 +5,7 @@ const { Bans, Modlogs } = require(__dirname+'/../../db/')
, hashIp = require(__dirname+'/../../helpers/dynamic.js')
, buildQueue = require(__dirname+'/../../queue.js')
, { isIP } = require('net')
, { ipHashPermLevel, defaultBanDuration } = require(__dirname+'/../../configs/main.js');
, { defaultBanDuration } = require(__dirname+'/../../configs/main.js');
module.exports = async (req, res, redirect) => {

@ -24,7 +24,7 @@ const path = require('path')
, timeUtils = require(__dirname+'/../../helpers/timeutils.js')
, deletePosts = require(__dirname+'/deletepost.js')
, spamCheck = require(__dirname+'/../../helpers/checks/spamcheck.js')
, { checkRealMimeTypes, thumbSize, thumbExtension, videoThumbPercentage,
, { ipHashPermLevel, checkRealMimeTypes, thumbSize, thumbExtension, videoThumbPercentage,
postPasswordSecret, strictFiltering, animatedGifThumbnails } = require(__dirname+'/../../configs/main.js')
, buildQueue = require(__dirname+'/../../queue.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
@ -48,9 +48,9 @@ module.exports = async (req, res, next) => {
let salt = null;
let thread = null;
const { filterBanDuration, filterMode, filters, blockedCountries, resetTrigger,
maxFiles, sageOnlyEmail, forceAnon, replyLimit, disableReplySubject,
threadLimit, ids, userPostSpoiler, pphTrigger, tphTrigger, triggerAction,
captchaMode, lockMode, allowedFileTypes, flags, fileR9KMode, messageR9KMode } = res.locals.board.settings;
maxFiles, sageOnlyEmail, forceAnon, replyLimit, disableReplySubject,
threadLimit, ids, userPostSpoiler, pphTrigger, tphTrigger, triggerAction,
captchaMode, lockMode, allowedFileTypes, flags, fileR9KMode, messageR9KMode } = res.locals.board.settings;
if (res.locals.permLevel >= 4
&& res.locals.country
&& blockedCountries.includes(res.locals.country.code)) {
@ -180,6 +180,8 @@ module.exports = async (req, res, next) => {
let files = [];
// if we got a file
if (res.locals.numFiles > 0) {
//unique files check
if (res.locals.permLevel >= 4 && (req.body.thread && fileR9KMode === 1) || fileR9KMode === 2) {
const filesHashes = req.files.file.map(f => f.sha256);
const postWithExistingFiles = await Posts.checkExistingFiles(res.locals.board._id, (fileR9KMode === 2 ? null : req.body.thread), filesHashes);
@ -196,7 +198,8 @@ module.exports = async (req, res, next) => {
});
}
}
// check all mime types before we try saving anything
//basic mime type check
for (let i = 0; i < res.locals.numFiles; i++) {
if (!mimeTypes.allowed(req.files.file[i].mimetype, allowedFileTypes)) {
await deleteTempFiles(req).catch(e => console.error);
@ -207,7 +210,8 @@ module.exports = async (req, res, next) => {
});
}
}
// check for any mismatching supposed mimetypes from the actual file mimetype
//validate mime type properly
if (checkRealMimeTypes) {
for (let i = 0; i < res.locals.numFiles; i++) {
if (!(await mimeTypes.realMimeCheck(req.files.file[i]))) {
@ -220,7 +224,8 @@ module.exports = async (req, res, next) => {
}
}
}
// then upload, thumb, get metadata, etc.
//upload, create thumbnails, get metadata, etc.
for (let i = 0; i < res.locals.numFiles; i++) {
const file = req.files.file[i];
let extension = path.extname(file.name) || file.name.substring(file.name.indexOf('.'));
@ -590,6 +595,15 @@ module.exports = async (req, res, next) => {
'cyclic': data.cyclic,
}
Socketio.emitRoom(`${res.locals.board._id}-${data.thread}`, 'newPost', projectedPost);
const { raw, single, qrange, hrange } = data.ip;
const projectedWithIp = {
...projectedPost,
ip: {
single, qrange, hrange,
raw: null, //TODO: this would need to be thought about more because of ipHashPermLevel
}
}
Socketio.emitRoom('globalmanage-recent', 'newPost', projectedWithIp);
}
//now add other pages to be built in background

@ -1,6 +1,7 @@
'use strict';
const configs = require(__dirname+'/configs/main.js');
const { redis: redisConfig } = require(__dirname+'/configs/main.js')
, roomRegex = /[a-z0-9]+-\d+/i
module.exports = {
@ -9,19 +10,26 @@ module.exports = {
connect: (server, sessionMiddleware) => {
const io = require('socket.io')(server);
const redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({ ...configs.redis }));
const sessionRefresh = require(__dirname+'/helpers/sessionrefresh.js');
io.adapter(redisAdapter({ ...redisConfig }));
io.use((socket, next) => {
//make session available in socket.request.session
sessionMiddleware(socket.request, socket.request, next);
});
io.use((socket, next) => {
sessionRefresh(socket.request, socket.request, next);
});
module.exports.io = io;
module.exports.startRooms();
},
startRooms: () => {
module.exports.io.on('connection', socket => {
//TODO: if we need authed socket endpoints (e.g. modview pages), we can use socket.request.session
socket.on('room', room => {
if ((!roomRegex.test(room) && room !== 'globalmanage-recent') //if not a valid room name
|| (room === 'globalmanage-recent' && (!socket.request.locals.user
|| socket.request.locals.user.authLevel > 1))) { //or no perms
return socket.disconnect(true); //then disconnect them
}
socket.join(room);
socket.send('joined');
});

@ -1,2 +1,2 @@
include ../mixins/post.pug
+post(post)
+post(post, false, false, globalmanage)

@ -8,7 +8,12 @@ block head
block content
h1.board-title Global Management
br
+globalmanagenav('recent')
.wrapbar
+globalmanagenav('recent')
.jsonly#livetext
.dot#livecolor
| Connecting...
input.postform-style.ml-5.di#updatepostsbutton(type='button' value='Update')
form(action=`/forms/global/actions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
if posts.length === 0

@ -68,7 +68,7 @@ block content
.jsonly#livetext
.dot#livecolor
| Connecting...
input.postform-style.ml-5.di(type='button' value='Update')
input.postform-style.ml-5.di#updatepostsbutton(type='button' value='Update')
if modview
include ../includes/actionfooter_manage.pug
else

Loading…
Cancel
Save