Added user rank changes to admin page.
This commit is contained in:
parent
064109556c
commit
8a4a21cff0
|
|
@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
//Config
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
const userModel = require('../schemas/userSchema');
|
const userModel = require('../schemas/userSchema');
|
||||||
|
const permissionModel = require('../schemas/permissionSchema');
|
||||||
const channelModel = require('../schemas/channelSchema');
|
const channelModel = require('../schemas/channelSchema');
|
||||||
const {exceptionHandler} = require("../utils/loggerUtils");
|
const {exceptionHandler} = require("../utils/loggerUtils");
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ module.exports.get = async function(req, res){
|
||||||
try{
|
try{
|
||||||
const chanGuide = await channelModel.getChannelList(true);
|
const chanGuide = await channelModel.getChannelList(true);
|
||||||
const userList = await userModel.getUserList(true);
|
const userList = await userModel.getUserList(true);
|
||||||
return res.render('adminPanel', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, userList: userList});
|
return res.render('adminPanel', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, userList: userList, rankEnum: permissionModel.rankEnum});
|
||||||
}catch(err){
|
}catch(err){
|
||||||
return exceptionHandler(res,err);
|
return exceptionHandler(res,err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
src/controllers/api/admin/changeRankController.js
Normal file
54
src/controllers/api/admin/changeRankController.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*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 {validationResult, matchedData} = require('express-validator');
|
||||||
|
|
||||||
|
//local imports
|
||||||
|
const {exceptionHandler} = require('../../../utils/loggerUtils');
|
||||||
|
const permissionModel = require('../../../schemas/permissionSchema');
|
||||||
|
const userModel = require('../../../schemas/userSchema');
|
||||||
|
|
||||||
|
//api account functions
|
||||||
|
module.exports.post = async function(req, res){
|
||||||
|
try{
|
||||||
|
const validResult = validationResult(req);
|
||||||
|
|
||||||
|
if(validResult.isEmpty()){
|
||||||
|
const data = matchedData(req);
|
||||||
|
const user = await userModel.findOne({user: data.user});
|
||||||
|
|
||||||
|
if(user == null){
|
||||||
|
res.status(400);
|
||||||
|
res.send({errors:[{type: "Bad Query", msg: "User not found.", date: new Date()}]});
|
||||||
|
}else if(permissionModel.rankToNum(data.rank) >= permissionModel.rankToNum(req.session.user.rank)){
|
||||||
|
res.status(401);
|
||||||
|
return res.send({errors:[{type: "Unauthorized", msg: "New rank must be below that of the user changing it.", date: new Date()}]});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.rank = data.rank;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.send({user: user.user, id: user.id, rank: user.rank});
|
||||||
|
}else{
|
||||||
|
res.status(400);
|
||||||
|
res.send({errors: validResult.array()})
|
||||||
|
}
|
||||||
|
}catch(err){
|
||||||
|
return exceptionHandler(res, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/controllers/api/admin/listChannelsController.js
Normal file
31
src/controllers/api/admin/listChannelsController.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*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/>.*/
|
||||||
|
|
||||||
|
//local imports
|
||||||
|
const channelModel = require('../../../schemas/channelSchema');
|
||||||
|
|
||||||
|
//api account functions
|
||||||
|
module.exports.get = async function(req, res){
|
||||||
|
try{
|
||||||
|
const chanGuide = await channelModel.getChannelList(true);
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.send(chanGuide);
|
||||||
|
}catch(err){
|
||||||
|
res.status(400);
|
||||||
|
return res.send(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/controllers/api/admin/listUsersController.js
Normal file
31
src/controllers/api/admin/listUsersController.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*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/>.*/
|
||||||
|
|
||||||
|
//local imports
|
||||||
|
const userModel = require('../../../schemas/userSchema');
|
||||||
|
|
||||||
|
//api account functions
|
||||||
|
module.exports.get = async function(req, res){
|
||||||
|
try{
|
||||||
|
const userList = await userModel.getUserList(true);
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.send(userList);
|
||||||
|
}catch(err){
|
||||||
|
res.status(400);
|
||||||
|
return res.send(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,8 +20,7 @@ const channelModel = require('../../../schemas/channelSchema');
|
||||||
//api account functions
|
//api account functions
|
||||||
module.exports.get = async function(req, res){
|
module.exports.get = async function(req, res){
|
||||||
try{
|
try{
|
||||||
//I'm not sanatizing this, it never gets processed...
|
const chanGuide = await channelModel.getChannelList();
|
||||||
const chanGuide = await channelModel.getChannelList(req.query.showHidden || req.query.showHidden === '');
|
|
||||||
|
|
||||||
res.status(200);
|
res.status(200);
|
||||||
return res.send(chanGuide);
|
return res.send(chanGuide);
|
||||||
|
|
|
||||||
39
src/routers/api/adminRouter.js
Normal file
39
src/routers/api/adminRouter.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*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 {accountValidator} = require("../../utils/validators");
|
||||||
|
const permissionSchema = require("../../schemas/permissionSchema");
|
||||||
|
const listUsersController = require("../../controllers/api/admin/listUsersController");
|
||||||
|
const listChannelsController = require("../../controllers/api/admin/listChannelsController");
|
||||||
|
const changeRankController = require("../../controllers/api/admin/changeRankController");
|
||||||
|
|
||||||
|
//globals
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
//Use authentication middleware
|
||||||
|
router.use(permissionSchema.reqPermCheck("adminPanel"))
|
||||||
|
|
||||||
|
//routing functions
|
||||||
|
router.get('/listUsers', listUsersController.get);
|
||||||
|
router.get('/listChannels', listChannelsController.get);
|
||||||
|
router.post('/changeRank', accountValidator.user(), accountValidator.rank(), changeRankController.post);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -20,6 +20,7 @@ const { Router } = require('express');
|
||||||
//local imports
|
//local imports
|
||||||
const accountRouter = require("./api/accountRouter");
|
const accountRouter = require("./api/accountRouter");
|
||||||
const channelRouter = require("./api/channelRouter");
|
const channelRouter = require("./api/channelRouter");
|
||||||
|
const adminRouter = require("./api/adminRouter");
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
@ -27,5 +28,6 @@ const router = Router();
|
||||||
//routing functions
|
//routing functions
|
||||||
router.use('/account', accountRouter);
|
router.use('/account', accountRouter);
|
||||||
router.use('/channel', channelRouter);
|
router.use('/channel', channelRouter);
|
||||||
|
router.use('/admin', adminRouter);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
//NPM Imports
|
//NPM Imports
|
||||||
const { check, body, checkSchema, checkExact} = require('express-validator');
|
const { check, body, checkSchema, checkExact} = require('express-validator');
|
||||||
|
|
||||||
|
//local imports
|
||||||
|
const permissionSchema = require("../schemas/permissionSchema");
|
||||||
|
|
||||||
|
function isRank(value){
|
||||||
|
rankVal = permissionSchema.rankToNum(value);
|
||||||
|
|
||||||
|
return rankVal != -1;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.accountValidator = {
|
module.exports.accountValidator = {
|
||||||
user: (field = 'user') => body(field).escape().trim().isLength({min: 1, max: 22}),
|
user: (field = 'user') => body(field).escape().trim().isLength({min: 1, max: 22}),
|
||||||
|
|
||||||
|
|
@ -32,6 +41,8 @@ module.exports.accountValidator = {
|
||||||
signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 150}),
|
signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 150}),
|
||||||
|
|
||||||
bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}),
|
bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}),
|
||||||
|
|
||||||
|
rank: (field = 'rank') => body(field).escape().trim().custom(isRank)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.channelValidator = {
|
module.exports.channelValidator = {
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
|
||||||
<body>
|
<body>
|
||||||
<%- include('partial/navbar', {user}); %>
|
<%- include('partial/navbar', {user}); %>
|
||||||
<%- include('partial/adminPanel/channelList', {chanGuide}) %>
|
<%- include('partial/adminPanel/channelList', {chanGuide}) %>
|
||||||
<%- include('partial/adminPanel/userList', {userList}) %>
|
<%- include('partial/adminPanel/userList', {user, userList, rankEnum}) %>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
<%- include('partial/scripts', {user}); %>
|
<%- include('partial/scripts', {user}); %>
|
||||||
<script src="js/adminPanel.js"></script>
|
<script src="/js/adminPanel.js"></script>
|
||||||
</footer>
|
</footer>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -21,31 +21,39 @@
|
||||||
<h3>Sign-Up Date</h3>
|
<h3>Sign-Up Date</h3>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% userList.forEach((user) => { %>
|
<% userList.forEach((curUser) => { %>
|
||||||
<tr id="admin-user-list-entry-<%- user.name %>" class="admin-list-entry">
|
<tr id="admin-user-list-entry-<%- curUser.name %>" class="admin-list-entry">
|
||||||
<td id="admin-user-list-entry-img-<%- user.name %>" class="admin-list-entry admin-list-entry-item">
|
<td id="admin-user-list-entry-img-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<a href="/profile/<%- user.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<img id="admin-user-list-entry-img-<%- user.name %>" class="admin-list-entry admin-list-entry-item" src="<%- user.img %>">
|
<img id="admin-user-list-entry-img-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item" src="<%- curUser.img %>">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-id-<%- user.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
<td id="admin-user-list-entry-id-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
||||||
<a href="/profile/<%- user.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<%- user.id %>
|
<%- curUser.id %>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-name-<%- user.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
<td id="admin-user-list-entry-name-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
||||||
<a href="/profile/<%- user.name %>" class="admin-list-entry admin-list-entry-item">
|
<a href="/profile/<%- curUser.name %>" class="admin-list-entry admin-list-entry-item">
|
||||||
<%- user.name %>
|
<%- curUser.name %>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-rank-<%- user.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
<td id="admin-user-list-entry-rank-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
||||||
<%- 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">
|
||||||
|
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
|
||||||
|
<option <%if(curUser.rank == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
|
||||||
|
<% }); %>
|
||||||
|
</select>
|
||||||
|
<% }else{ %>
|
||||||
|
<%- curUser.rank %>
|
||||||
|
<% } %>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-email-<%- user.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
<td id="admin-user-list-entry-email-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
||||||
<%- user.email ? user.email : "N/A" %>
|
<%- curUser.email ? curUser.email : "N/A" %>
|
||||||
</td>
|
</td>
|
||||||
<td id="admin-user-list-entry-date-<%- user.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
<td id="admin-user-list-entry-date-<%- curUser.name %>" class="admin-list-entry admin-list-entry-item admin-list-entry-not-first-row">
|
||||||
<%- user.date %>
|
<%- curUser.date %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
|
||||||
69
www/js/adminPanel.js
Normal file
69
www/js/adminPanel.js
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*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/>.*/
|
||||||
|
|
||||||
|
class canopyAdminUtils{
|
||||||
|
constructor(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserRank(user, rank){
|
||||||
|
var response = await fetch(`/api/admin/changeRank`,{
|
||||||
|
method: "POST",
|
||||||
|
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, rank})
|
||||||
|
});
|
||||||
|
|
||||||
|
if(response.status == 200){
|
||||||
|
return await response.json();
|
||||||
|
}else{
|
||||||
|
utils.ux.displayResponseError(await response.json());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class adminUserList{
|
||||||
|
constructor(){
|
||||||
|
this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
|
||||||
|
|
||||||
|
this.setupInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInput(){
|
||||||
|
this.rankSelectors.forEach((rankSelector)=>{
|
||||||
|
rankSelector.addEventListener("change", this.setRank.bind(this))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRank(event){
|
||||||
|
const user = event.target.id.replace("admin-user-list-rank-select-","");
|
||||||
|
const rank = event.target.value;
|
||||||
|
|
||||||
|
this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelect(update, select){
|
||||||
|
if(update != null){
|
||||||
|
select.value = update.rank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminUtil = new canopyAdminUtils();
|
||||||
|
const userList = new adminUserList();
|
||||||
|
|
@ -172,7 +172,7 @@ class deleteAccountPrompt{
|
||||||
if(response.status == 200){
|
if(response.status == 200){
|
||||||
window.location.pathname = '/';
|
window.location.pathname = '/';
|
||||||
}else{
|
}else{
|
||||||
displayResponseError(await response.json());
|
utils.ux.displayResponseError(await response.json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,13 @@ class canopyUXUtils{
|
||||||
constructor(){
|
constructor(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async displayResponseError(body){
|
||||||
|
const errors = body.errors;
|
||||||
|
errors.forEach((err)=>{
|
||||||
|
window.alert(`ERROR: ${err.msg} \nTYPE: ${err.type} \nDATE: ${err.date}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static clickDragger = class{
|
static clickDragger = class{
|
||||||
constructor(handle, element, leftHandle = true){
|
constructor(handle, element, leftHandle = true){
|
||||||
|
|
@ -119,12 +126,6 @@ class canopyAjaxUtils{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Profile
|
|
||||||
async displayResponseError(body){
|
|
||||||
const err = body.msg;
|
|
||||||
window.alert(`ERROR:\n${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async register(user, pass, passConfirm, email){
|
async register(user, pass, passConfirm, email){
|
||||||
var response = await fetch(`/api/account/register`,{
|
var response = await fetch(`/api/account/register`,{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue