Worked ban frontend and api.
This commit is contained in:
parent
5c936462a6
commit
c848994c1d
|
|
@ -39,16 +39,43 @@ module.exports.post = async function(req, res){
|
||||||
try{
|
try{
|
||||||
const validResult = validationResult(req);
|
const validResult = validationResult(req);
|
||||||
if(validResult.isEmpty()){
|
if(validResult.isEmpty()){
|
||||||
const {user} = matchedData(req);
|
const {user, permanent, expirationDays} = matchedData(req);
|
||||||
const userDB = await userModel.findOne({user});
|
const userDB = await userModel.findOne({user});
|
||||||
|
|
||||||
if(userDB == null){
|
if(userDB == null){
|
||||||
res.status(400);
|
res.status(400);
|
||||||
res.send({errors:[{type: "Bad Query", msg: "User not found.", date: new Date()}]});
|
return res.send({errors:[{type: "Bad Query", msg: "User not found.", date: new Date()}]});
|
||||||
}
|
}
|
||||||
|
|
||||||
await banModel.banByUserDoc(userDB);
|
await banModel.banByUserDoc(userDB, permanent, expirationDays);
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.send(await banModel.getBans());
|
||||||
|
}else{
|
||||||
|
res.status(400);
|
||||||
|
return res.send({errors: validResult.array()})
|
||||||
|
}
|
||||||
|
}catch(err){
|
||||||
|
return exceptionHandler(res, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.delete = async function(req, res){
|
||||||
|
try{
|
||||||
|
const validResult = validationResult(req);
|
||||||
|
if(validResult.isEmpty()){
|
||||||
|
const {user} = matchedData(req);
|
||||||
|
const userDB = await userModel.findOne({user});
|
||||||
|
|
||||||
|
if(userDB == null){
|
||||||
|
res.status(400);
|
||||||
|
return res.send({errors:[{type: "Bad Query", msg: "User not found.", date: new Date()}]});
|
||||||
|
}
|
||||||
|
|
||||||
|
await banModel.unbanByUserDoc(userDB);
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.send(await banModel.getBans());
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
return res.send({errors: validResult.array()})
|
return res.send({errors: validResult.array()})
|
||||||
|
|
|
||||||
20
src/controllers/popup/placeholderController.js
Normal file
20
src/controllers/popup/placeholderController.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
//root index functions
|
||||||
|
module.exports.get = async function(req, res){
|
||||||
|
res.render('partial/popup/placeholder', {});
|
||||||
|
}
|
||||||
20
src/controllers/popup/userBanController.js
Normal file
20
src/controllers/popup/userBanController.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
//root index functions
|
||||||
|
module.exports.get = async function(req, res){
|
||||||
|
res.render('partial/popup/userBan');
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//npm imports
|
//npm imports
|
||||||
const { checkExact } = require('express-validator');
|
const { body, checkExact} = require('express-validator');
|
||||||
const { Router } = require('express');
|
const { Router } = require('express');
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,6 +42,8 @@ router.get('/permissions', permissionsController.get);
|
||||||
router.post('/permissions', checkExact([permissionsValidator.permissionsMap(), channelPermissionValidator.channelPermissionsMap()]), permissionsController.post);
|
router.post('/permissions', checkExact([permissionsValidator.permissionsMap(), channelPermissionValidator.channelPermissionsMap()]), permissionsController.post);
|
||||||
router.post('/changeRank', accountValidator.user(), accountValidator.rank(), changeRankController.post);
|
router.post('/changeRank', accountValidator.user(), accountValidator.rank(), changeRankController.post);
|
||||||
router.get('/ban', banController.get);
|
router.get('/ban', banController.get);
|
||||||
router.post('/ban', accountValidator.user(), banController.post);
|
//Sometimes they're so simple you don't need to put your validators in their own special place :P
|
||||||
|
router.post('/ban', accountValidator.user(), body("permanent").isBoolean(), body("expirationDays").isInt(), banController.post);
|
||||||
|
router.delete('/ban', accountValidator.user(), banController.delete);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
32
src/routers/popupRouter.js
Normal file
32
src/routers/popupRouter.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
//npm imports
|
||||||
|
const { Router } = require('express');
|
||||||
|
|
||||||
|
|
||||||
|
//local imports
|
||||||
|
const placeholderController = require("../controllers/popup/placeholderController");
|
||||||
|
const userBanController = require("../controllers/popup/userBanController");
|
||||||
|
|
||||||
|
//globals
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
//routing functions
|
||||||
|
router.get('/placeholder', placeholderController.get);
|
||||||
|
router.get('/userBan', userBanController.get);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -48,7 +48,7 @@ const userBanSchema = new mongoose.Schema({
|
||||||
default: 30
|
default: 30
|
||||||
},
|
},
|
||||||
//If true, then expiration date deletes associated accounts instead of deleting the ban record
|
//If true, then expiration date deletes associated accounts instead of deleting the ban record
|
||||||
deleteAccountOnExpire: {
|
permanent: {
|
||||||
type: mongoose.SchemaTypes.Boolean,
|
type: mongoose.SchemaTypes.Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -73,17 +73,58 @@ userBanSchema.statics.checkBan = async function(user){
|
||||||
return this.checkBanByUserDoc(userDB);
|
return this.checkBanByUserDoc(userDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
userBanSchema.statics.banByUserDoc = async function(userDB){
|
userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expirationDays){
|
||||||
|
//Prevent missing users
|
||||||
|
if(userDB == null){
|
||||||
|
throw new Error("User not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure the user isn't already banned
|
||||||
if(await this.checkBanByUserDoc(userDB) != null){
|
if(await this.checkBanByUserDoc(userDB) != null){
|
||||||
throw new Error("User already banned");
|
throw new Error("User already banned");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.create({user: userDB._id});
|
if(expirationDays < 0){
|
||||||
|
throw new Error("Expiration Days must be a positive integer!");
|
||||||
|
}else if(expirationDays < 30 && permanent){
|
||||||
|
throw new Error("Permanent bans must be given at least 30 days before automatic account deletion!");
|
||||||
|
}else if(expirationDays > 185){
|
||||||
|
throw new Error("Expiration/Deletion date cannot be longer than half a year out from the original ban date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log the user out
|
||||||
|
await userDB.killAllSessions();
|
||||||
|
|
||||||
|
//Add the ban to the database
|
||||||
|
return await this.create({user: userDB._id, permanent, expirationDays});
|
||||||
}
|
}
|
||||||
|
|
||||||
userBanSchema.statics.ban = async function(user){
|
userBanSchema.statics.ban = async function(user, permanent, expirationDays){
|
||||||
const userDB = await userModel.findOne({user: user.user});
|
const userDB = await userModel.findOne({user: user.user});
|
||||||
return this.banByUserDoc(userDB);
|
return this.banByUserDoc(userDB, permanent, expirationDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
userBanSchema.statics.unbanByUserDoc = async function(userDB){
|
||||||
|
|
||||||
|
//Prevent missing users
|
||||||
|
if(userDB == null){
|
||||||
|
throw new Error("User not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const ban = await this.checkBanByUserDoc(userDB);
|
||||||
|
|
||||||
|
if(!ban){
|
||||||
|
throw new Error("User already un-banned");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Use _id in-case mongoose wants to be a cunt
|
||||||
|
var oldBan = await this.deleteOne({_id: ban._id});
|
||||||
|
return oldBan;
|
||||||
|
}
|
||||||
|
|
||||||
|
userBanSchema.statics.unban = async function(user){
|
||||||
|
const userDB = await userModel.findOne({user: user.user});
|
||||||
|
return this.unbanByUserDoc(userDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
userBanSchema.statics.getBans = async function(){
|
userBanSchema.statics.getBans = async function(){
|
||||||
|
|
@ -109,7 +150,7 @@ userBanSchema.statics.getBans = async function(){
|
||||||
user: userObj,
|
user: userObj,
|
||||||
ips: ban.ips,
|
ips: ban.ips,
|
||||||
alts: ban.alts,
|
alts: ban.alts,
|
||||||
deleteAccountOnExpire: ban.deleteAccountOnExpire
|
permanent: ban.permanent
|
||||||
}
|
}
|
||||||
|
|
||||||
bans.push(banObj);
|
bans.push(banObj);
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ userSchema.statics.getUserList = async function(fullList = false){
|
||||||
//create a user object with limited properties (safe for public consumption)
|
//create a user object with limited properties (safe for public consumption)
|
||||||
var userObj = {
|
var userObj = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.user,
|
user: user.user,
|
||||||
img: user.img,
|
img: user.img,
|
||||||
date: user.date
|
date: user.date
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +270,7 @@ userSchema.methods.nuke = async function(pass){
|
||||||
|
|
||||||
if(this.checkPass(pass)){
|
if(this.checkPass(pass)){
|
||||||
//Annoyingly there isnt a good way to do this from 'this'
|
//Annoyingly there isnt a good way to do this from 'this'
|
||||||
var oldUser = await module.exports.deleteOne(this);
|
var oldUser = await module.exports.userModel.deleteOne(this);
|
||||||
|
|
||||||
if(oldUser){
|
if(oldUser){
|
||||||
await this.killAllSessions();
|
await this.killAllSessions();
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const adminPanelRouter = require('./routers/adminPanelRouter');
|
||||||
const channelRouter = require('./routers/channelRouter');
|
const channelRouter = require('./routers/channelRouter');
|
||||||
const newChannelRouter = require('./routers/newChannelRouter');
|
const newChannelRouter = require('./routers/newChannelRouter');
|
||||||
const panelRouter = require('./routers/panelRouter');
|
const panelRouter = require('./routers/panelRouter');
|
||||||
|
const popupRouter = require('./routers/popupRouter');
|
||||||
const apiRouter = require('./routers/apiRouter');
|
const apiRouter = require('./routers/apiRouter');
|
||||||
|
|
||||||
//Define Config
|
//Define Config
|
||||||
|
|
@ -92,6 +93,8 @@ app.use('/c', channelRouter);
|
||||||
app.use('/newChannel', newChannelRouter);
|
app.use('/newChannel', newChannelRouter);
|
||||||
//Panel
|
//Panel
|
||||||
app.use('/panel', panelRouter);
|
app.use('/panel', panelRouter);
|
||||||
|
//Popup
|
||||||
|
app.use('/popup', popupRouter);
|
||||||
//Bot-Ready
|
//Bot-Ready
|
||||||
app.use('/api', apiRouter);
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<td id="admin-ban-list-entry-expiration-type-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-ban-list-entry-expiration-type-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<h3>Expiration Action</h3>
|
<h3>Expiration Action</h3>
|
||||||
</td>
|
</td>
|
||||||
|
<td id="admin-ban-list-entry-actions-type-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
|
<h3>Actions</h3>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -29,33 +29,36 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<td id="admin-user-list-entry-rank-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-rank-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<h3>Rank</h3>
|
<h3>Rank</h3>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-rank-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-mail-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<h3>E-Mail</h3>
|
<h3>E-Mail</h3>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-rank-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-date-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<h3>Sign-Up Date</h3>
|
<h3>Sign-Up Date</h3>
|
||||||
</td>
|
</td>
|
||||||
|
<td id="admin-user-list-entry-control-title" class="admin-list-entry admin-list-entry-title admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
|
<h3>Actions</h3>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% userList.forEach((curUser) => { %>
|
<% userList.forEach((curUser) => { %>
|
||||||
<tr id="admin-user-list-entry-<%- curUser.name %>" class="admin-list-entry">
|
<tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry">
|
||||||
<td id="admin-user-list-entry-img-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
<td id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.user %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<img id="admin-user-list-entry-img-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item" src="<%- curUser.img %>">
|
<img id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item" src="<%- curUser.img %>">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-id-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-id-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.user %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<%- curUser.id %>
|
<%- curUser.id %>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-name-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-name-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.user %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<%- curUser.name %>
|
<%- curUser.user %>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-rank-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-rank-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%>
|
<% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%>
|
||||||
<select id="admin-user-list-rank-select-<%- curUser.name %>" class="admin-user-list-rank-select">
|
<select id="admin-user-list-rank-select-<%- curUser.user %>" class="admin-user-list-rank-select">
|
||||||
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
|
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
|
||||||
<option <%if(curUser.rank == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
|
<option <%if(curUser.rank == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
@ -64,12 +67,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<%- curUser.rank %>
|
<%- curUser.rank %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-email-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-email-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<%- curUser.email ? curUser.email : "N/A" %>
|
<%- curUser.email ? curUser.email : "N/A" %>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-date-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
<td id="admin-user-list-entry-date-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
<%- curUser.date.toUTCString() %>
|
<%- curUser.date.toUTCString() %>
|
||||||
</td>
|
</td>
|
||||||
|
<td id="admin-user-list-entry-action-<%- curUser.user %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-col">
|
||||||
|
<!-- It's either this or add whitespce >:( -->
|
||||||
|
<i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" id="admin-user-list-nuke-icon-<%- curUser.user %>" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" id="admin-user-list-ban-icon-<%- curUser.user %>" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" id="admin-user-list-pw-reset-icon-<%- curUser.user %>" title="Generate Password Reset Link for <%- curUser.user %>"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
16
src/views/partial/popup/placeholder.ejs
Normal file
16
src/views/partial/popup/placeholder.ejs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!--Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
|
<h1>This is a test popup!</h1>
|
||||||
33
src/views/partial/popup/userBan.ejs
Normal file
33
src/views/partial/popup/userBan.ejs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!--Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/popup/userBan.css">
|
||||||
|
<h3 class="popup-title">Ban NULL</h3>
|
||||||
|
<form class="ban-popup-content" action="javascript:">
|
||||||
|
<span>
|
||||||
|
<label for="ban-popup-perm">Perma-Ban:</label>
|
||||||
|
<input type="checkbox" name="ban-popup-perm" id="ban-popup-perm">
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<label id="ban-popup-expiration-prefix" for="ban-popup-expiration">Ban Expires In: </label>
|
||||||
|
<input type="number" name="ban-popup-expiration" id="ban-popup-expiration" value="14">
|
||||||
|
<label fid="ban-popup-expiration-postfix" or="ban-popup-expiration"> Days</label>
|
||||||
|
</span>
|
||||||
|
<span id="ban-popup-button-span">
|
||||||
|
<button id="ban-popup-cancel-button">Cancel</button>
|
||||||
|
<span id="ban-popup-button-span-spacer"></span>
|
||||||
|
<button id="ban-popup-ban-button">Ban</button>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
|
@ -109,3 +109,8 @@ img.admin-list-entry-item{
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 1.3em;
|
height: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-user-list-icon{
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 0.2em;
|
||||||
|
}
|
||||||
|
|
@ -49,3 +49,42 @@ p.navbar-item, input.navbar-item{
|
||||||
.navbar-item input{
|
.navbar-item input{
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-backer{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-div{
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: auto;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: fit-content;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-close-icon{
|
||||||
|
text-align: right;
|
||||||
|
position:absolute;
|
||||||
|
right: 0;
|
||||||
|
margin: 0.5em;
|
||||||
|
margin-bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content-div{
|
||||||
|
margin: 1em;
|
||||||
|
padding-top: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title{
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
13
www/css/popup/userBan.css
Normal file
13
www/css/popup/userBan.css
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
.ban-popup-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ban-popup-button-span{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ban-popup-button-span-spacer{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
@ -282,3 +282,21 @@ select.panel-head-element{
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.popup-backer{
|
||||||
|
background-color: var(--bg0-alpha1);
|
||||||
|
backdrop-filter: var(--background-panel-effect-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-div{
|
||||||
|
background-color: var(--bg1);
|
||||||
|
color: var(--accent1);
|
||||||
|
box-shadow: 3px 3px 1px var(--bg1-alt0) inset;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ban-popup-ban-button{
|
||||||
|
background-color: var(--accent0-warning);
|
||||||
|
color: var(--accent1);
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,59 @@ class canopyAdminUtils{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Statics
|
||||||
|
static banUserPopup = class{
|
||||||
|
constructor(target){
|
||||||
|
this.target = target;
|
||||||
|
this.popup = new canopyUXUtils.popup("userBan", true, this.asyncConstruction.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncConstruction(){
|
||||||
|
this.title = document.querySelector(".popup-title");
|
||||||
|
//Setup title text real quick-like :P
|
||||||
|
this.title.innerHTML = `Ban ${this.target}`;
|
||||||
|
|
||||||
|
this.permBan = document.querySelector("#ban-popup-perm");
|
||||||
|
this.expiration = document.querySelector("#ban-popup-expiration");
|
||||||
|
this.expirationPrefix = document.querySelector("#ban-popup-expiration-prefix");
|
||||||
|
this.banButton = document.querySelector("#ban-popup-ban-button");
|
||||||
|
this.cancelButton = document.querySelector("#ban-popup-cancel-button");
|
||||||
|
|
||||||
|
this.setupInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInput(){
|
||||||
|
this.permBan.addEventListener("change", this.permaBanLabel.bind(this));
|
||||||
|
this.cancelButton.addEventListener("click", this.popup.closePopup.bind(this.popup));
|
||||||
|
this.banButton.addEventListener("click",this.ban.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
permaBanLabel(event){
|
||||||
|
if(event.target.checked){
|
||||||
|
this.expirationPrefix.innerHTML = "Account Deletion In: "
|
||||||
|
this.expiration.value = 30;
|
||||||
|
}else{
|
||||||
|
this.expirationPrefix.innerHTML = "Ban Expires In: "
|
||||||
|
this.expiration.value = 14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async ban(event){
|
||||||
|
//Close out the popup
|
||||||
|
this.popup.closePopup();
|
||||||
|
|
||||||
|
//Submit the user ban based on input
|
||||||
|
const bans = await adminUtil.banUser(this.target, this.permBan.checked, this.expiration.value);
|
||||||
|
|
||||||
|
//For some reason comparing this against undefined or null wasnt working in and of itself...
|
||||||
|
if(typeof userBanList != "undefined" && bans != null){
|
||||||
|
//Why add an extra get request when we already have the data? :P
|
||||||
|
await userBanList.renderBanList(bans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Methods
|
||||||
async setUserRank(user, rank){
|
async setUserRank(user, rank){
|
||||||
var response = await fetch(`/api/admin/changeRank`,{
|
var response = await fetch(`/api/admin/changeRank`,{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -82,13 +135,30 @@ class canopyAdminUtils{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async banUser(user){
|
async banUser(user, permanent, expirationDays){
|
||||||
var response = await fetch(`/api/admin/ban`,{
|
var response = await fetch(`/api/admin/ban`,{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
||||||
|
body: JSON.stringify({user, permanent, expirationDays})
|
||||||
|
});
|
||||||
|
|
||||||
|
if(response.status == 200){
|
||||||
|
return await response.json();
|
||||||
|
}else{
|
||||||
|
utils.ux.displayResponseError(await response.json());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unbanUser(user){
|
||||||
|
var response = await fetch(`/api/admin/ban`,{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
||||||
body: JSON.stringify({user})
|
body: JSON.stringify({user})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -103,6 +173,7 @@ class canopyAdminUtils{
|
||||||
class adminUserList{
|
class adminUserList{
|
||||||
constructor(){
|
constructor(){
|
||||||
this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
|
this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
|
||||||
|
this.banIcons = document.querySelectorAll(".admin-user-list-ban-icon");
|
||||||
|
|
||||||
this.setupInput();
|
this.setupInput();
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +182,10 @@ class adminUserList{
|
||||||
this.rankSelectors.forEach((rankSelector)=>{
|
this.rankSelectors.forEach((rankSelector)=>{
|
||||||
rankSelector.addEventListener("change", this.setRank.bind(this))
|
rankSelector.addEventListener("change", this.setRank.bind(this))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.banIcons.forEach((banIcon) => {
|
||||||
|
banIcon.addEventListener("click", this.banPopup.bind(this));
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async setRank(event){
|
async setRank(event){
|
||||||
|
|
@ -120,6 +195,12 @@ class adminUserList{
|
||||||
this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
|
this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
banPopup(event){
|
||||||
|
const user = event.target.id.replace("admin-user-list-ban-icon-","");
|
||||||
|
|
||||||
|
new canopyAdminUtils.banUserPopup(user);
|
||||||
|
}
|
||||||
|
|
||||||
updateSelect(update, select){
|
updateSelect(update, select){
|
||||||
if(update != null){
|
if(update != null){
|
||||||
select.value = update.rank;
|
select.value = update.rank;
|
||||||
|
|
@ -186,7 +267,22 @@ class adminUserBanList{
|
||||||
this.renderBanList(await adminUtil.getBans());
|
this.renderBanList(await adminUtil.getBans());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearBanList(){
|
||||||
|
const oldRows = this.table.querySelectorAll('tr.admin-list-entry');
|
||||||
|
oldRows.forEach((row) => {
|
||||||
|
row.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async unban(event){
|
||||||
|
//Get username from target id
|
||||||
|
const user = event.target.id.replace("admin-user-list-unban-icon-","");
|
||||||
|
//Send unban command to server and display the resulting banlist
|
||||||
|
this.renderBanList(await adminUtil.unbanUser(user));
|
||||||
|
}
|
||||||
|
|
||||||
renderBanList(banList){
|
renderBanList(banList){
|
||||||
|
this.clearBanList();
|
||||||
banList.forEach((ban) => {
|
banList.forEach((ban) => {
|
||||||
//Create entry row
|
//Create entry row
|
||||||
const entryRow = document.createElement('tr');
|
const entryRow = document.createElement('tr');
|
||||||
|
|
@ -197,26 +293,40 @@ class adminUserBanList{
|
||||||
imgNode.classList.add("admin-list-entry","admin-list-entry-item");
|
imgNode.classList.add("admin-list-entry","admin-list-entry-item");
|
||||||
imgNode.src = ban.user.img;
|
imgNode.src = ban.user.img;
|
||||||
|
|
||||||
console.log(new Date(ban.user.date).toDateString());
|
//Calculate expiration date and expiration days
|
||||||
|
|
||||||
const expirationDate = new Date(ban.expirationDate);
|
const expirationDate = new Date(ban.expirationDate);
|
||||||
|
const expirationDays = ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1);
|
||||||
|
|
||||||
const expirationDays = Math.floor((expirationDate - new Date()) / (1000 * 60 * 60 * 24));
|
//Create unban icon
|
||||||
|
const unbanIcon = document.createElement('i');
|
||||||
|
unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon");
|
||||||
|
unbanIcon.id = `admin-user-list-unban-icon-${ban.user.user}`;
|
||||||
|
unbanIcon.title = `Unban ${ban.user.user}`;
|
||||||
|
unbanIcon.addEventListener("click", this.unban.bind(this));
|
||||||
|
|
||||||
|
//Create nuke account icon
|
||||||
|
const nukeAccount = document.createElement('i');
|
||||||
|
nukeAccount.classList.add("bi-radioactive","admin-user-list-icon","admin-user-list-unban-icon");
|
||||||
|
nukeAccount.id = `admin-user-list-unban-icon-${ban.user.user}`;
|
||||||
|
nukeAccount.title = `Nuke accounts`;
|
||||||
|
nukeAccount.addEventListener("click",console.log);
|
||||||
|
|
||||||
//Append cells to row
|
//Append cells to row
|
||||||
entryRow.appendChild(newCell(imgNode, true, true));
|
entryRow.appendChild(newCell(imgNode, true));
|
||||||
entryRow.appendChild(newCell(ban.user.id));
|
entryRow.appendChild(newCell(ban.user.id));
|
||||||
entryRow.appendChild(newCell(ban.user.user));
|
entryRow.appendChild(newCell(ban.user.user));
|
||||||
entryRow.appendChild(newCell(new Date(ban.user.date).toDateString()));
|
entryRow.appendChild(newCell(new Date(ban.user.date).toDateString()));
|
||||||
entryRow.appendChild(newCell(new Date(ban.banDate).toDateString()));
|
entryRow.appendChild(newCell(new Date(ban.banDate).toDateString()));
|
||||||
entryRow.appendChild(newCell(`${expirationDate.toDateString()} (${expirationDays} days left)`));
|
entryRow.appendChild(newCell(`${expirationDate.toDateString()} (${expirationDays} days left)`));
|
||||||
entryRow.appendChild(newCell(ban.deleteAccountOnExpire ? "Delete" : "Un-Ban"));
|
entryRow.appendChild(newCell(ban.permanent ? "Account Deletion" : "Un-Ban"));
|
||||||
|
entryRow.appendChild(newCell([unbanIcon, nukeAccount]));
|
||||||
|
|
||||||
//Append row to table
|
//Append row to table
|
||||||
this.table.appendChild(entryRow);
|
this.table.appendChild(entryRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
function newCell(content, addAsNode = false, firstCol = false){
|
//We should really move this over to uxutils along with newrow & newtable functions
|
||||||
|
function newCell(content, firstCol = false){
|
||||||
//Create a new 'td' element
|
//Create a new 'td' element
|
||||||
const cell = document.createElement('td');
|
const cell = document.createElement('td');
|
||||||
cell.classList.add("admin-list-entry","admin-list-entry-item");
|
cell.classList.add("admin-list-entry","admin-list-entry-item");
|
||||||
|
|
@ -226,17 +336,33 @@ class adminUserBanList{
|
||||||
cell.classList.add("admin-list-entry-not-first-col");
|
cell.classList.add("admin-list-entry-not-first-col");
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we're adding as node
|
//check for arrays
|
||||||
if(addAsNode){
|
if(content.forEach == null){
|
||||||
//append it like it's a node
|
//add single items
|
||||||
cell.appendChild(content);
|
addContent(content);
|
||||||
}else{
|
}else{
|
||||||
//otherwise use it as innerHTML
|
//Crawl through content array
|
||||||
cell.innerHTML = content;
|
content.forEach((item)=>{
|
||||||
|
//add each item
|
||||||
|
addContent(item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//return the resulting cell
|
//return the resulting cell
|
||||||
return cell;
|
return cell;
|
||||||
|
|
||||||
|
|
||||||
|
function addContent(ct){
|
||||||
|
//If we're adding as node
|
||||||
|
if(ct.cloneNode != null){
|
||||||
|
//append it like it's a node
|
||||||
|
cell.appendChild(ct);
|
||||||
|
}else{
|
||||||
|
//otherwise use it as innerHTML
|
||||||
|
cell.innerHTML = ct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,68 @@ class canopyUXUtils{
|
||||||
constructor(){
|
constructor(){
|
||||||
}
|
}
|
||||||
|
|
||||||
async displayResponseError(body){
|
displayResponseError(body){
|
||||||
const errors = body.errors;
|
const errors = body.errors;
|
||||||
errors.forEach((err)=>{
|
errors.forEach((err)=>{
|
||||||
window.alert(`ERROR: ${err.msg} \nTYPE: ${err.type} \nDATE: ${err.date}`);
|
window.alert(`ERROR: ${err.msg} \nTYPE: ${err.type} \nDATE: ${err.date}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static popup = class{
|
||||||
|
constructor(content, ajaxPopup = false, cb){
|
||||||
|
//Define non-popup node values
|
||||||
|
this.content = content;
|
||||||
|
this.ajaxPopup = ajaxPopup;
|
||||||
|
this.cb = cb;
|
||||||
|
//define popup nodes
|
||||||
|
this.createPopup();
|
||||||
|
|
||||||
|
//fill popup nodes
|
||||||
|
this.fillPopupContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
createPopup(){
|
||||||
|
this.popupBacker = document.createElement('div');
|
||||||
|
this.popupBacker.classList.add('popup-backer');
|
||||||
|
|
||||||
|
this.popupDiv = document.createElement('div');
|
||||||
|
this.popupDiv.classList.add('popup-div');
|
||||||
|
|
||||||
|
this.closeIcon = document.createElement('i');
|
||||||
|
this.closeIcon.classList.add('bi-x','popup-close-icon');
|
||||||
|
this.closeIcon.addEventListener("click", this.closePopup.bind(this));
|
||||||
|
|
||||||
|
this.contentDiv = document.createElement('div');
|
||||||
|
this.contentDiv.classList.add('popup-content-div');
|
||||||
|
|
||||||
|
this.popupDiv.appendChild(this.closeIcon);
|
||||||
|
this.popupDiv.appendChild(this.contentDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillPopupContent(){
|
||||||
|
if(this.ajaxPopup){
|
||||||
|
this.contentDiv.innerHTML = await utils.ajax.getPopup(this.content);
|
||||||
|
}else{
|
||||||
|
this.contentDiv.innerHTML = this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
//display popup nodes
|
||||||
|
this.displayPopup();
|
||||||
|
|
||||||
|
//Callbacks are kinda out of vogue, but there really isn't a good way to handle asynchronously constructed objects/classes
|
||||||
|
this.cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
displayPopup(){
|
||||||
|
document.body.prepend(this.popupDiv);
|
||||||
|
document.body.prepend(this.popupBacker);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup(){
|
||||||
|
this.popupDiv.remove();
|
||||||
|
this.popupBacker.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static clickDragger = class{
|
static clickDragger = class{
|
||||||
constructor(handle, element, leftHandle = true){
|
constructor(handle, element, leftHandle = true){
|
||||||
|
|
@ -309,6 +364,18 @@ class canopyAjaxUtils{
|
||||||
utils.ux.displayResponseError(await response.json());
|
utils.ux.displayResponseError(await response.json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPopup(popup){
|
||||||
|
var response = await fetch(`/popup/${popup}`,{
|
||||||
|
method: "GET"
|
||||||
|
});
|
||||||
|
|
||||||
|
if(response.status == 200){
|
||||||
|
return (await response.text())
|
||||||
|
}else{
|
||||||
|
utils.ux.displayResponseError(await response.json());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const utils = new canopyUtils()
|
const utils = new canopyUtils()
|
||||||
Loading…
Reference in a new issue