jschan - Anonymous imageboard software. Classic look, modern features and feel. Works without JavaScript and supports Tor, I2P, Lokinet, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

391 lines
7.1 KiB

5 years ago
'use strict';
const Mongo = require(__dirname+'/db.js')
, Boards = require(__dirname+'/boards.js')
, db = Mongo.client.db('jschan').collection('posts')
5 years ago
module.exports = {
5 years ago
db,
getThreadPage: async (board, thread) => {
const threadsBefore = await db.countDocuments({
'board': board,
'thread': null,
'bumped': {
'$gte': thread.bumped
}
});
return Math.ceil(threadsBefore/10) || 1; //1 because 0 threads before is page 1
},
getRecent: async (board, page, limit=10) => {
5 years ago
// get all thread posts (posts with null thread id)
5 years ago
const threads = await db.find({
'thread': null,
'board': board
5 years ago
},{
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
5 years ago
}).sort({
'sticky': -1,
'bumped': -1,
}).skip(10*(page-1)).limit(limit).toArray();
5 years ago
// add last 5 posts in reverse order to preview
5 years ago
await Promise.all(threads.map(async thread => {
5 years ago
const replies = await db.find({
'thread': thread.postId,
'board': board
5 years ago
},{
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
5 years ago
}).sort({
'postId': -1
}).limit(5).toArray();
//reverse order for board page
thread.replies = replies.reverse();
5 years ago
//if enough replies, show omitted count
if (thread.replyposts > 5) {
//cout omitted image and posts
const numPreviewImages = replies.reduce((acc, post) => { return acc + post.files.length }, 0);
thread.omittedimages = thread.replyfiles - numPreviewImages;
thread.omittedposts = thread.replyposts - replies.length;
}
}));
5 years ago
return threads;
},
5 years ago
getReplyCounts: (board, thread) => {
return db.aggregate([
{
'$match': {
'thread': thread,
'board': board,
}
}, {
'$group': {
'_id': null,
'replyposts': {
'$sum': 1
},
'replyfiles': {
'$sum': {
'$size': '$files'
}
}
}
}
]).toArray();
},
setReplyCounts: (board, thread, replyposts, replyfiles) => {
return db.updateOne({
'postId': thread,
'board': board
}, {
'$set': {
'replyposts': replyposts,
'replyfiles': replyfiles,
}
})
},
getPages: (board) => {
5 years ago
return db.countDocuments({
'board': board,
'thread': null
5 years ago
});
},
getThread: async (board, id) => {
5 years ago
// get thread post and potential replies concurrently
const data = await Promise.all([
5 years ago
db.findOne({
'postId': id,
'board': board,
'thread': null,
}, {
5 years ago
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
5 years ago
}),
module.exports.getThreadPosts(board, id)
5 years ago
])
// attach the replies to the thread post
const thread = data[0];
if (thread) {
thread.replies = data[1];
}
return thread;
},
5 years ago
getThreadPosts: (board, id) => {
// all posts within a thread
5 years ago
return db.find({
'thread': id,
'board': board
}, {
5 years ago
'projection': {
'salt': 0 ,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
}).sort({
'postId': 1
}).toArray();
},
getMultipleThreadPosts: (board, ids) => {
//all posts from multiple threads in a single board
return db.find({
'board': board,
'thread': {
'$in': ids
}
}, {
'projection': {
'salt': 0 ,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
}
}).toArray();
},
getCatalog: (board) => {
5 years ago
// get all threads for catalog
5 years ago
return db.find({
'thread': null,
'board': board
}, {
5 years ago
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
}).sort({
'sticky': -1,
'bumped': -1,
5 years ago
}).toArray();
},
5 years ago
getPost: (board, id, admin) => {
5 years ago
// get a post
if (admin) {
5 years ago
return db.findOne({
'postId': id,
'board': board
});
}
5 years ago
return db.findOne({
'postId': id,
'board': board
}, {
5 years ago
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
5 years ago
});
},
5 years ago
//takes array "ids" of post ids
getPosts: (board, ids, admin) => {
if (admin) {
5 years ago
return db.find({
'postId': {
'$in': ids
5 years ago
},
'board': board
}).toArray();
}
5 years ago
return db.find({
'postId': {
'$in': ids
5 years ago
},
'board': board
}, {
5 years ago
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
5 years ago
}
}).toArray();
},
// get only thread and post id for use in quotes
getPostsForQuotes: (queryOrs) => {
return db.find({
'$or': queryOrs
}, {
'projection': {
'postId': 1,
'board': 1,
'thread': 1,
}
}).limit(15).toArray(); //limit 15 quotes for now.
},
//takes array "ids" of mongo ids to get posts from any board
globalGetPosts: (ids) => {
return db.find({
'_id': {
'$in': ids
},
}).toArray();
},
insertOne: async (board, data, thread) => {
if (data.thread !== null) {
const filter = {
5 years ago
'postId': data.thread,
'board': board
}
//update thread reply and reply file count
const query = {
'$inc': {
'replyposts': 1,
'replyfiles': data.files.length
5 years ago
}
}
//if post email is not sage, and thread not saged, set bump date
if (data.email !== 'sage' && !thread.saged) {
query['$set'] = {
'bumped': Date.now()
}
}
//update the thread
await db.updateOne(filter, query);
} else {
//this is a new thread so just set the bump date
data.bumped = Date.now();
5 years ago
}
//get the postId and add it to the post
5 years ago
const postId = await Boards.getNextId(board);
data.postId = postId;
//insert the post itself
const postMongoId = await db.insertOne(data).then(result => result.insertedId); //_id of post
//add backlinks to the posts this post quotes
if (data.thread && data.quotes.length > 0) {
await db.updateMany({
'_id': {
'$in': data.quotes.map(q => q._id)
}
}, {
'$push': {
'backlinks': { _id: postMongoId, postId: postId }
}
});
}
5 years ago
return postId;
},
5 years ago
getReports: (board) => {
return db.find({
'reports.0': {
'$exists': true
},
'board': board
}, {
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'globalreports': 0,
}
}).toArray();
},
getGlobalReports: () => {
5 years ago
return db.find({
'globalreports.0': {
'$exists': true
}
}, {
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'reports': 0,
}
}).toArray();
},
deleteOne: (board, options) => {
5 years ago
return db.deleteOne(options);
},
5 years ago
pruneOldThreads: async (board, threadLimit) => {
//get lowest bumped threads
const threads = await db.find({
'thread': null,
'board': board
}).sort({
'sticky': -1,
'bumped': -1
}).skip(threadLimit).toArray();
return threads;
},
deleteMany: (ids) => {
5 years ago
return db.deleteMany({
'_id': {
'$in': ids
}
});
},
5 years ago
5 years ago
deleteAll: (board) => {
return db.deleteMany();
},
5 years ago
exists: async (req, res, next) => {
const thread = await module.exports.getThread(req.params.board, req.params.id);
if (!thread) {
return res.status(404).render('404');
}
res.locals.thread = thread; // can acces this in views or next route handlers
next();
}
}