diff --git a/src/controllers/api/channel/banController.js b/src/controllers/api/channel/banController.js
new file mode 100644
index 0000000..106c58e
--- /dev/null
+++ b/src/controllers/api/channel/banController.js
@@ -0,0 +1,99 @@
+/*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 .*/
+
+//NPM Imports
+const {validationResult, matchedData} = require('express-validator');
+
+//local imports
+const {exceptionHandler} = require('../../../utils/loggerUtils.js');
+const {userModel} = require('../../../schemas/userSchema.js');
+const channelModel = require('../../../schemas/channel/channelSchema');
+
+//api account functions
+module.exports.get = async function(req, res){
+ try{
+ //Get validation result
+ const validResult = validationResult(req);
+
+ //If we data is valid
+ if(validResult.isEmpty()){
+ //Set channel object from sanatized/validated data, and get user document from session data
+ const {chanName} = matchedData(req);
+ const chanDB = await channelModel.findOne({name: chanName});
+
+ res.status(200);
+ return res.send(await chanDB.getChanBans());
+ }else{
+ res.status(400);
+ res.send({errors: validResult.array()})
+ }
+ }catch(err){
+ exceptionHandler(res, err);
+ }
+
+}
+
+module.exports.post = async function(req, res){
+ try{
+ //Get validation result
+ const validResult = validationResult(req);
+
+ //If we data is valid
+ if(validResult.isEmpty()){
+ //Set channel object from sanatized/validated data, and get user document from session data
+ const {chanName, user, expirationDays, banAlts} = matchedData(req);
+ const userDB = await userModel.findOne({user});
+ const chanDB = await channelModel.findOne({name: chanName});
+
+ await chanDB.banByUserDoc(userDB, expirationDays, banAlts);
+
+ res.status(200);
+ return res.send(await chanDB.getChanBans());
+ }else{
+ res.status(400);
+ res.send({errors: validResult.array()})
+ }
+ }catch(err){
+ exceptionHandler(res, err);
+ }
+
+}
+
+module.exports.delete = async function(req, res){
+ try{
+ //Get validation result
+ const validResult = validationResult(req);
+
+ //If we data is valid
+ if(validResult.isEmpty()){
+ //Set channel object from sanatized/validated data, and get user document from session data
+ const {chanName, user} = matchedData(req);
+ const userDB = await userModel.findOne({user});
+ const chanDB = await channelModel.findOne({name: chanName});
+
+ await chanDB.unbanByUserDoc(userDB);
+
+ res.status(200);
+ return res.send(await chanDB.getChanBans());
+ }else{
+ res.status(400);
+ res.send({errors: validResult.array()})
+ }
+ }catch(err){
+ exceptionHandler(res, err);
+ }
+
+}
\ No newline at end of file
diff --git a/src/routers/api/channelRouter.js b/src/routers/api/channelRouter.js
index 6c70bca..edb10cb 100644
--- a/src/routers/api/channelRouter.js
+++ b/src/routers/api/channelRouter.js
@@ -15,6 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .*/
//npm imports
+const { body } = require('express-validator');
const { Router } = require('express');
//local imports
@@ -29,6 +30,7 @@ const settingsController = require("../../controllers/api/channel/settingsContro
const permissionsController = require("../../controllers/api/channel/permissionsController")
const rankController = require("../../controllers/api/channel/rankController");
const deleteController = require("../../controllers/api/channel/deleteController");
+const banController = require("../../controllers/api/channel/banController");
//globals
const router = Router();
@@ -38,16 +40,28 @@ router.use("/register",permissionSchema.reqPermCheck("registerChannel"));
router.use("/settings", channelValidator.name('chanName'), channelModel.reqPermCheck("manageChannel"));
router.use("/permissions", channelValidator.name('chanName'), channelModel.reqPermCheck("manageChannel"));
router.use("/rank", channelValidator.name('chanName'), channelModel.reqPermCheck("manageChannel"));
+router.use("/delete", channelValidator.name('chanName'), channelModel.reqPermCheck("deleteChannel"));
+router.use("/ban", channelValidator.name('chanName'), channelModel.reqPermCheck("manageChannel"));
//routing functions
+//register
router.post('/register', channelValidator.name(), channelValidator.description(), channelValidator.thumbnail(), registerController.post);
+//list
router.get('/list', listController.get);
+//settings
router.get('/settings', settingsController.get);
router.post('/settings', channelValidator.settingsMap(), settingsController.post);
+//permissions
router.get('/permissions', permissionsController.get);
router.post('/permissions', channelPermissionValidator.channelPermissionsMap(), permissionsController.post);
+//rank
router.get('/rank', rankController.get);
router.post('/rank', accountValidator.user(), channelValidator.rank(), rankController.post);
-router.post('/delete', channelValidator.name('chanName'), channelValidator.name('confirm'), channelModel.reqPermCheck("deleteChannel"), deleteController.post);
+//delete
+router.post('/delete', channelValidator.name('confirm'), deleteController.post);
+//ban
+router.get('/ban', banController.get);
+router.post('/ban', accountValidator.user(), body("banAlts").isBoolean(), body("expirationDays").isInt(), banController.post);
+router.delete('/ban', accountValidator.user(), banController.delete);
module.exports = router;
\ No newline at end of file
diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js
index 089b441..2d35ac0 100644
--- a/src/schemas/channel/channelSchema.js
+++ b/src/schemas/channel/channelSchema.js
@@ -67,6 +67,29 @@ const channelSchema = new mongoose.Schema({
required: true,
enum: permissionModel.rankEnum
}
+ }],
+ //Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a little easier :P
+ banList: [{
+ user: {
+ type: mongoose.SchemaTypes.ObjectID,
+ required: true,
+ ref: "user"
+ },
+ banDate: {
+ type: mongoose.SchemaTypes.Date,
+ required: true,
+ default: new Date()
+ },
+ expirationDays: {
+ type: mongoose.SchemaTypes.Number,
+ required: true,
+ default: 14
+ },
+ banAlts: {
+ type: mongoose.SchemaTypes.Boolean,
+ required: true,
+ default: false
+ }
}]
});
@@ -323,6 +346,113 @@ channelSchema.methods.permCheck = async function (user, perm){
return await this.permCheckByUserDoc(userDB, perm)
}
+channelSchema.methods.checkBanByUserDoc = async function(userDB){
+ var foundBan = null;
+
+ this.banList.forEach((ban) => {
+ if(ban.user != null){
+ if(ban.user.toString() == userDB._id.toString()){
+ foundBan = ban;
+ }
+ }
+ });
+
+ return foundBan;
+}
+
+channelSchema.methods.getChanBans = async function(){
+ //Create an empty list to hold our found bans
+ var banList = [];
+ //Populate the users in the banList
+ await this.populate('banList.user');
+
+ //Crawl through known bans
+ this.banList.forEach((ban) => {
+
+ var banObj = {
+ banDate: ban.banDate,
+ expirationDays: ban.expirationDays,
+ }
+
+ //Check if the ban was permanent (expiration set before ban date)
+ if(ban.expirationDays > 0){
+ //if not calculate expiration date
+ var expirationDate = new Date(ban.banDate);
+ expirationDate.setDate(expirationDate.getDate() + ban.expirationDays);
+
+ //Set calculated expiration date
+ banObj.expirationDate = expirationDate;
+ }
+
+ //Setup user object (Do this last to keep it at bottom for human-readibility of json :P)
+ banObj.user = {
+ id: ban.user.id,
+ user: ban.user.user,
+ img: ban.user.img,
+ date: ban.user.date
+ }
+
+ banList.push(banObj);
+ });
+
+ return banList;
+}
+
+channelSchema.methods.banByUserDoc = async function(userDB, expirationDays, banAlts){
+ //Throw a shitfit if the user doesn't exist
+ if(userDB == null){
+ throw new Error("Cannot ban non-existant user!");
+ }
+
+ const foundBan = await this.checkBanByUserDoc(userDB);
+
+ if(foundBan != null){
+ throw new Error("User already banned!");
+ }
+
+ //Create a new ban document based on input
+ const banDoc = {
+ user: userDB._id,
+ expirationDays,
+ banAlts
+ }
+
+ //Push the ban to the list
+ this.banList.push(banDoc);
+
+ await this.save();
+}
+
+channelSchema.methods.ban = async function(user, expirationDays, banAlts){
+ const userDB = await userModel.find({user});
+ return await this.banByUserDoc(userDB, expirationDays, banAlts);
+}
+
+channelSchema.methods.unbanByUserDoc = async function(userDB){
+ //Throw a shitfit if the user doesn't exist
+ if(userDB == null){
+ throw new Error("Cannot ban non-existant user!");
+ }
+
+ const foundBan = await this.checkBanByUserDoc(userDB);
+
+ if(foundBan == null){
+ throw new Error("User already unbanned!");
+ }
+
+ //You know I can't help but feel like an asshole for looking for the index of something I just pulled out of an array using forEach...
+ //Then again this is such an un-used function that the issue of code re-use overshadows performance
+ //I mean how often are we REALLY going to be un-banning users from channels?
+ const banIndex = this.banList.indexOf(foundBan);
+ this.banList.splice(banIndex,1);
+ return await this.save();
+}
+
+channelSchema.methods.unban = async function(user){
+ const userDB = await userModel.find({user});
+ return await this.unbanByUserDoc(userDB);
+}
+
channelSchema.methods.nuke = async function(confirm){
if(confirm == "" || confirm == null){
throw new Error("Empty Confirmation String!");
diff --git a/www/js/utils.js b/www/js/utils.js
index 9b44e2d..eece7af 100644
--- a/www/js/utils.js
+++ b/www/js/utils.js
@@ -324,8 +324,8 @@ class canopyAjaxUtils{
}
}
- async getChannelRank(channel){
- var response = await fetch(`/api/channel/rank?chanName=${channel}`,{
+ async getChannelRank(chanName){
+ var response = await fetch(`/api/channel/rank?chanName=${chanName}`,{
method: "GET"
});
@@ -380,6 +380,53 @@ class canopyAjaxUtils{
utils.ux.displayResponseError(await response.json());
}
}
+
+ async getChanBans(chanName){
+ var response = await fetch(`/api/channel/ban?chanName=${chanName}`,{
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+
+ if(response.status == 200){
+ return await response.json();
+ }else{
+ utils.ux.displayResponseError(await response.json());
+ }
+ }
+
+ async chanBan(chanName, user, expirationDays, banAlts){
+ var response = await fetch(`/api/channel/ban`,{
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({chanName, user, expirationDays, banAlts})
+ });
+
+ if(response.status == 200){
+ return await response.json();
+ }else{
+ utils.ux.displayResponseError(await response.json());
+ }
+ }
+
+ async chanUnban(chanName, user){
+ var response = await fetch(`/api/channel/ban`,{
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({chanName, user})
+ });
+
+ if(response.status == 200){
+ return await response.json();
+ }else{
+ utils.ux.displayResponseError(await response.json());
+ }
+ }
}
const utils = new canopyUtils()
\ No newline at end of file