Overhaul blacklist

This commit is contained in:
rxdn 2022-07-26 15:15:05 +01:00
parent 73352dea35
commit c155effded
8 changed files with 569 additions and 155 deletions

View File

@ -1,56 +1,104 @@
package api
import (
"context"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/user"
"golang.org/x/sync/errgroup"
"strconv"
)
type userData struct {
type (
response struct {
PageLimit int `json:"page_limit"`
Users []blacklistedUser `json:"users"`
Roles []blacklistedRole `json:"roles"`
}
blacklistedUser struct {
UserId uint64 `json:"id,string"`
Username string `json:"username"`
Discriminator user.Discriminator `json:"discriminator"`
}
}
blacklistedRole struct {
RoleId uint64 `json:"id,string"`
Name string `json:"name"`
}
)
const pageLimit = 30
// TODO: Paginate
func GetBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
blacklistedUsers, err := database.Client.Blacklist.GetBlacklistedUsers(guildId)
page, err := strconv.Atoi(ctx.Query("page"))
if err != nil || page < 1 {
page = 1
}
offset := pageLimit * (page - 1)
blacklistedUsers, err := database.Client.Blacklist.GetBlacklistedUsers(guildId, pageLimit, offset)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
data := make([]userData, len(blacklistedUsers))
userObjects, err := cache.Instance.GetUsers(blacklistedUsers)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
group, _ := errgroup.WithContext(context.Background())
// Build struct with user_id, name and discriminator
users := make([]blacklistedUser, len(blacklistedUsers))
for i, userId := range blacklistedUsers {
i := i
userId := userId
// TODO: Mass lookup
group.Go(func() error {
userData := userData{
userData := blacklistedUser{
UserId: userId,
}
user, ok := cache.Instance.GetUser(userId)
user, ok := userObjects[userId]
if ok {
userData.Username = user.Username
userData.Discriminator = user.Discriminator
}
data[i] = userData
return nil
})
users[i] = userData
}
_ = group.Wait()
blacklistedRoles, err := database.Client.RoleBlacklist.GetBlacklistedRoles(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.JSON(200, data)
roleObjects, err := cache.Instance.GetRoles(guildId, blacklistedRoles)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
// Build struct with role_id and name
roles := make([]blacklistedRole, len(blacklistedRoles))
for i, roleId := range blacklistedRoles {
roleData := blacklistedRole{
RoleId: roleId,
}
role, ok := roleObjects[roleId]
if ok {
roleData.Name = role.Name
}
roles[i] = roleData
}
ctx.JSON(200, response{
PageLimit: pageLimit,
Users: users,
Roles: roles,
})
}

View File

@ -1,22 +1,46 @@
package api
import (
"fmt"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/common/permission"
"github.com/gin-gonic/gin"
"strconv"
)
type (
blacklistAddResponse struct {
Success bool `json:"success"`
Resolved bool `json:"resolved"`
Id uint64 `json:"id,string"`
Username string `json:"username"`
Discriminator string `json:"discriminator"`
}
blacklistAddBody struct {
EntityType entityType `json:"entity_type"`
Snowflake uint64 `json:"snowflake,string"`
}
entityType int
)
const (
entityTypeUser entityType = iota
entityTypeRole
)
func AddBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
id, err := strconv.ParseUint(ctx.Param("user"), 10, 64)
if err != nil {
var body blacklistAddBody
if err := ctx.BindJSON(&body); err != nil {
ctx.JSON(400, utils.ErrorJson(err))
return
}
if body.EntityType == entityTypeUser {
// Max of 250 blacklisted users
count, err := database.Client.Blacklist.GetBlacklistedCount(guildId)
if err != nil {
@ -29,7 +53,7 @@ func AddBlacklistHandler(ctx *gin.Context) {
return
}
permLevel, err := utils.GetPermissionLevel(guildId, id)
permLevel, err := utils.GetPermissionLevel(guildId, body.Snowflake)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
@ -40,10 +64,52 @@ func AddBlacklistHandler(ctx *gin.Context) {
return
}
if err = database.Client.Blacklist.Add(guildId, id); err != nil {
if err := database.Client.Blacklist.Add(guildId, body.Snowflake); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.JSON(200, utils.SuccessResponse)
// Resolve user
user, ok := cache.Instance.GetUser(body.Snowflake)
if ok {
ctx.JSON(200, blacklistAddResponse{
Success: true,
Resolved: true,
Id: body.Snowflake,
Username: user.Username,
Discriminator: fmt.Sprintf("%04d", user.Discriminator),
})
} else {
ctx.JSON(200, blacklistAddResponse{
Success: true,
Resolved: false,
Id: body.Snowflake,
})
}
} else if body.EntityType == entityTypeRole {
// Max of 50 blacklisted roles
count, err := database.Client.RoleBlacklist.GetBlacklistedCount(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if count >= 50 {
ctx.JSON(400, utils.ErrorStr("Blacklist limit (50) reached"))
return
}
if err := database.Client.RoleBlacklist.Add(guildId, body.Snowflake); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.JSON(200, blacklistAddResponse{
Success: true,
Id: body.Snowflake,
})
} else {
ctx.JSON(400, utils.ErrorStr("Invalid entity type"))
return
}
}

View File

@ -0,0 +1,25 @@
package api
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"strconv"
)
func RemoveRoleBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
roleId, err := strconv.ParseUint(ctx.Param("role"), 10, 64)
if err != nil {
ctx.JSON(400, utils.ErrorJson(err))
return
}
if err := database.Client.RoleBlacklist.Remove(guildId, roleId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -2,30 +2,24 @@ package api
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"strconv"
)
func RemoveBlacklistHandler(ctx *gin.Context) {
func RemoveUserBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
userId, err := strconv.ParseUint(ctx.Param("user"), 10, 64)
if err != nil {
ctx.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": err.Error(),
})
ctx.JSON(400, utils.ErrorJson(err))
return
}
if err := database.Client.Blacklist.Remove(guildId, userId); err == nil {
ctx.JSON(200, gin.H{
"success": true,
})
} else {
ctx.JSON(200, gin.H{
"success": false,
"err": err.Error(),
})
if err := database.Client.Blacklist.Remove(guildId, userId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -100,8 +100,9 @@ func StartServer() {
guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler)
guildAuthApiSupport.GET("/blacklist", api_blacklist.GetBlacklistHandler)
guildAuthApiSupport.POST("/blacklist/:user", api_blacklist.AddBlacklistHandler)
guildAuthApiSupport.DELETE("/blacklist/:user", api_blacklist.RemoveBlacklistHandler)
guildAuthApiSupport.POST("/blacklist", api_blacklist.AddBlacklistHandler)
guildAuthApiSupport.DELETE("/blacklist/user/:user", api_blacklist.RemoveUserBlacklistHandler)
guildAuthApiSupport.DELETE("/blacklist/role/:role", api_blacklist.RemoveRoleBlacklistHandler)
// Must be readable to load transcripts page
guildAuthApiSupport.GET("/panels", api_panels.ListPanels)

View File

@ -34,7 +34,6 @@
</script>
<style>
.modal {
position: absolute;
top: 0;

View File

@ -1,57 +1,141 @@
<div class="parent">
{#if blacklistUserModal}
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist User</span>
<div slot="body" class="modal-inner">
<div>
<label class="form-label" style="margin-bottom: 0 !important;">Use User ID</label>
<Toggle hideLabel
toggledColor="#66bb6a"
untoggledColor="#ccc"
bind:toggled={blacklistById}/>
</div>
{#if blacklistById}
<Input label="User ID" placeholder="592348585904198711" bind:value={blacklistUserId}/>
{:else}
<div class="user-select-wrapper">
<UserSelect {guildId} label="User" bind:value={blacklistUser} />
</div>
{/if}
</div>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistUserModal = false}>Cancel</Button>
<Button on:click={addUser}>Confirm</Button>
</div>
</Card>
</div>
</div>
<div class="modal-backdrop" transition:fade>
</div>
{:else if blacklistRoleModal}
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist Role</span>
<div slot="body" class="modal-inner user-select-wrapper">
<RoleSelect {guildId} {roles} label="Role" bind:value={blacklistRole} />
</div>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistRoleModal = false}>Cancel</Button>
<Button on:click={addRole}>Confirm</Button>
</div>
</Card>
</div>
</div>
<div class="modal-backdrop" transition:fade>
</div>
{/if}
{#if data}
<div class="parent">
<div class="content">
<div class="main-col">
<Card footer={false}>
<span slot="title">Blacklisted Users</span>
<span slot="title">Blacklist</span>
<div slot="body" class="body-wrapper">
<div class="row" style="gap: 10px">
<Button icon="fas fa-ban" on:click={() => blacklistUserModal = true}>Blacklist New User</Button>
<Button icon="fas fa-ban" on:click={() => blacklistRoleModal = true}>Blacklist New Role</Button>
</div>
<hr/>
<div class="tables">
<table class="nice">
<thead>
<tr>
<th>Username</th>
<th>User ID</th>
<th class="full-width">Role</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each blacklistedUsers as user}
{#each data.roles as role}
<tr>
{#if user.username !== '' && user.discriminator !== ''}
<td>{user.username}#{user.discriminator}</td>
{#if role.name === ''}
<td class="full-width">Unknown ({role.id})</td>
{:else}
<td>Unknown</td>
<td class="full-width">{role.name}</td>
{/if}
<td>{user.id}</td>
<td>
<Button type="button" on:click={() => removeBlacklist(user)}>Remove</Button>
<Button type="button" danger icon="fas fa-trash-can" on:click={() => removeRoleBlacklist(role)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
<table class="nice">
<thead>
<tr>
<th class="full-width">User</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each data.users as user}
<tr>
{#if user.username !== '' && user.discriminator !== ''}
<td class="full-width">{user.username}#{user.discriminator} ({user.id})</td>
{:else}
<td class="full-width">Unknown ({user.id})</td>
{/if}
<td>
<Button type="button" danger icon="fas fa-trash-can" on:click={() => removeUserBlacklist(user)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</Card>
<div class="row nav">
<i class="fas fa-chevron-left pagination-chevron" class:disabled-chevron={page <= 1}
on:click={loadPrevious}></i>
<span>Page {page}</span>
<i class="fas fa-chevron-right pagination-chevron"
class:disabled-chevron={data.users.length < data.page_limit && data.roles.length < data.page_limit}
on:click={loadNext}></i>
</div>
<div class="right-col">
<Card footer={false}>
<span slot="title">Blacklist A User</span>
<div slot="body" class="body-wrapper">
<form class="body-wrapper" on:submit|preventDefault={addBlacklist}>
<div class="row" style="flex-direction: column">
<UserSelect {guildId} label="User" bind:value={addUser}/>
</div>
<div class="row" style="justify-content: center">
<div class="col-2">
<Button fullWidth={true} icon="fas fa-plus"
disabled={addUser === undefined || addUser === ''}>Blacklist</Button>
</div>
</div>
</form>
</div>
</Card>
</div>
</div>
</div>
</div>
{/if}
<script>
import Card from "../components/Card.svelte";
@ -61,59 +145,160 @@
import axios from "axios";
import {API_URL} from "../js/constants";
import {setDefaultHeaders} from '../includes/Auth.svelte'
import {fade} from "svelte/transition";
import Toggle from "svelte-toggle";
import Input from "../components/form/Input.svelte";
import RoleSelect from "../components/form/RoleSelect.svelte";
export let currentRoute;
let guildId = currentRoute.namedParams.id;
let addUser;
let blacklistedUsers = [];
let page = 1;
let data;
let roles = [];
async function addBlacklist() {
const res = await axios.post(`${API_URL}/api/${guildId}/blacklist/${addUser.id}`);
let blacklistUserModal = false;
let blacklistRoleModal = false;
let blacklistById = false;
let blacklistUserId;
let blacklistUser;
let blacklistRole;
function loadPrevious() {
if (page > 1) {
page--;
loadData();
}
}
function loadNext() {
if (data.users.length >= data.page_limit || data.roles.length >= data.page_limit) {
page++;
loadData();
}
}
async function addUser() {
let snowflake;
if (blacklistById) {
snowflake = blacklistUserId;
} else {
snowflake = blacklistUser.id;
}
const body = {
entity_type: 0,
snowflake: snowflake
};
const res = await axios.post(`${API_URL}/api/${guildId}/blacklist`, body);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
notifySuccess(`${addUser.username}#${addUser.discriminator} has been blacklisted`);
blacklistedUsers = [...blacklistedUsers, {
id: addUser.id,
username: addUser.username,
discriminator: addUser.discriminator,
if (res.data.resolved) {
notifySuccess(`${res.data.username}#${res.data.discriminator} has been blacklisted`);
data.users = [...data.users, {
id: res.data.id,
username: res.data.username,
discriminator: res.data.discriminator,
}];
} else {
notifySuccess(`User with ID ${res.data.id} has been blacklisted`);
data.users = [...data.users, {
id: res.data.id,
username: "Unknown",
discriminator: "0000",
}];
}
async function removeBlacklist(user) {
const res = await axios.delete(`${API_URL}/api/${guildId}/blacklist/${user.id}`);
blacklistById = false;
blacklistUser = undefined;
blacklistUserId = undefined;
blacklistUserModal = false;
}
async function addRole() {
const body = {
entity_type: 1,
snowflake: blacklistRole.id,
};
const res = await axios.post(`${API_URL}/api/${guildId}/blacklist`, body);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
data.roles = [...data.roles, {
id: blacklistRole.id,
name: blacklistRole.name,
}];
notifySuccess(`${blacklistRole.name} has been blacklisted`);
blacklistRole = undefined;
blacklistRoleModal = false;
}
async function removeUserBlacklist(user) {
const res = await axios.delete(`${API_URL}/api/${guildId}/blacklist/user/${user.id}`);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
notifySuccess(`${user.username}#${user.discriminator} has been removed from the blacklist`);
blacklistedUsers = blacklistedUsers.filter((u) => u.id !== user.id);
data.users = data.users.filter((u) => u.id !== user.id);
}
async function loadUsers() {
const res = await axios.get(`${API_URL}/api/${guildId}/blacklist`);
async function removeRoleBlacklist(role) {
const res = await axios.delete(`${API_URL}/api/${guildId}/blacklist/role/${role.id}`);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
notifySuccess(`${role.name} has been removed from the blacklist`);
data.roles = data.roles.filter((r) => r.id !== role.id);
}
async function loadRoles() {
const res = await axios.get(`${API_URL}/api/${guildId}/roles`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
blacklistedUsers = res.data;
roles = res.data.roles;
}
async function loadData() {
const res = await axios.get(`${API_URL}/api/${guildId}/blacklist?page=${page}`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
data = res.data;
}
withLoadingScreen(async () => {
setDefaultHeaders();
await loadUsers();
await Promise.all([
loadData(),
loadRoles()
]);
});
</script>
<style>
.parent {
display: flex;
justify-content: center;
justify-content: flex-start;
padding-left: 2%;
width: 100%;
height: 100%;
}
@ -121,7 +306,7 @@
.content {
display: flex;
justify-content: space-between;
width: 96%;
width: 60%;
height: 100%;
margin-top: 30px;
}
@ -129,14 +314,7 @@
.main-col {
display: flex;
flex-direction: column;
width: 64%;
height: 100%;
}
.right-col {
display: flex;
flex-direction: column;
width: 34%;
width: 100%;
height: 100%;
}
@ -152,7 +330,43 @@
flex-direction: row;
width: 100%;
height: 100%;
margin-bottom: 2%;
}
hr {
border-top: 1px solid #777;
border-bottom: 0;
border-left: 0;
border-right: 0;
width: 100%;
flex: 1;
}
.tables {
display: flex;
flex-direction: column;
row-gap: 4vh;
}
.full-width {
width: 100%;
}
.nav {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 2px;
}
.pagination-chevron {
cursor: pointer;
color: #3472f7;
}
.disabled-chevron {
color: #777 !important;
cursor: default !important;
}
@media only screen and (max-width: 950px) {
@ -169,4 +383,66 @@
width: 100%;
}
}
.modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.modal-wrapper {
display: flex;
width: 60%;
margin: 10% auto auto auto;
}
.modal-inner {
display: flex;
flex-direction: row;
justify-content: flex-start;
gap: 2%;
width: 100%;
}
.user-select-wrapper {
display: flex;
flex-direction: column;
width: 33%;
}
@media only screen and (max-width: 1280px) {
.modal-wrapper {
width: 96%;
}
}
@media only screen and (max-width: 950px) {
.content {
width: 96%;
}
}
@media only screen and (max-width: 700px) {
.user-select-wrapper {
width: 100%;
}
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 500;
background-color: #000;
opacity: .5;
}
</style>

5
go.mod
View File

@ -83,3 +83,8 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
nhooyr.io/websocket v1.8.4 // indirect
)
replace (
github.com/rxdn/gdl => "../../rxdn/gdl"
github.com/TicketsBot/database => "../database"
)