Staff override

This commit is contained in:
rxdn 2022-06-23 17:10:00 +01:00
parent ec3b721b40
commit 1a6b50d293
24 changed files with 863 additions and 58 deletions

View File

@ -0,0 +1,23 @@
package botstaff
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"strconv"
)
func AddBotStaffHandler(ctx *gin.Context) {
userId, err := strconv.ParseUint(ctx.Param("userid"), 10, 64)
if err != nil {
ctx.JSON(400, utils.ErrorJson(err))
return
}
if err := database.Client.BotStaff.Add(userId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -0,0 +1,57 @@
package botstaff
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"
)
type userData struct {
Id uint64 `json:"id,string"`
Username string `json:"username"`
Discriminator user.Discriminator `json:"discriminator"`
}
func ListBotStaffHandler(ctx *gin.Context) {
staff, err := database.Client.BotStaff.GetAll()
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
// Get usernames
group, _ := errgroup.WithContext(context.Background())
users := make([]userData, len(staff))
for i, userId := range staff {
i := i
userId := userId
group.Go(func() error {
user, ok := cache.Instance.GetUser(userId)
data := userData{
Id: userId,
}
if ok {
data.Username = user.Username
data.Discriminator = user.Discriminator
} else {
data.Username = "Unknown User"
}
users[i] = data
return nil
})
}
_ = group.Wait() // error not possible
ctx.JSON(200, users)
}

View File

@ -0,0 +1,23 @@
package botstaff
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"strconv"
)
func RemoveBotStaffHandler(ctx *gin.Context) {
userId, err := strconv.ParseUint(ctx.Param("userid"), 10, 64)
if err != nil {
ctx.JSON(400, utils.ErrorJson(err))
return
}
if err := database.Client.BotStaff.Delete(userId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -33,17 +33,12 @@ func SessionHandler(ctx *gin.Context) {
return
}
var whitelabelOverride bool
for _, id := range config.Conf.ForceWhitelabel {
if id == userId {
whitelabelOverride = true
break
}
}
whitelabelOverride := utils.Contains(config.Conf.ForceWhitelabel, userId)
ctx.JSON(200, gin.H{
"username": store.Name,
"avatar": store.Avatar,
"whitelabel": tier >= premium.Whitelabel || whitelabelOverride,
"admin": utils.Contains(config.Conf.Admins, userId),
})
}

View File

@ -0,0 +1,32 @@
package api
import (
"fmt"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"time"
)
type createOverrideBody struct {
TimePeriod int `json:"time_period"`
}
func CreateOverrideHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
var body createOverrideBody
if err := ctx.BindJSON(&body); err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid request body"))
fmt.Println(err.Error())
return
}
expires := time.Now().Add(time.Hour * time.Duration(body.TimePeriod))
if err := database.Client.StaffOverride.Set(guildId, expires); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -0,0 +1,18 @@
package api
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
)
func DeleteOverrideHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
if err := database.Client.StaffOverride.Delete(guildId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.Status(204)
}

View File

@ -0,0 +1,21 @@
package api
import (
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
)
func GetOverrideHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
hasOverride, err := database.Client.StaffOverride.HasActiveOverride(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.JSON(200, gin.H{
"has_override": hasOverride,
})
}

View File

@ -0,0 +1,17 @@
package middleware
import (
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
)
func AdminOnly(ctx *gin.Context) {
userId := ctx.Keys["userid"].(uint64)
if !utils.Contains(config.Conf.Admins, userId) {
ctx.JSON(401, utils.ErrorStr("Unauthorized"))
ctx.Abort()
return
}
}

View File

@ -42,10 +42,12 @@ func AuthenticateGuild(requiredPermissionLevel permission.PermissionLevel) gin.H
if permLevel < requiredPermissionLevel {
ctx.JSON(403, utils.ErrorStr("Unauthorized"))
ctx.Abort()
return
}
} else {
ctx.JSON(400, utils.ErrorStr("Invalid guild ID"))
ctx.Abort()
return
}
}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"io/ioutil"
"runtime/debug"
"strconv"
)
@ -46,6 +47,7 @@ func Logging(minLevel sentry.Level) gin.HandlerFunc {
"user_id": ctx.Keys["userid"],
"request_body": string(requestBody),
"response": string(responseBody),
"stacktrace": string(debug.Stack()),
},
Level: level,
Message: fmt.Sprintf("HTTP %d on %s %s", statusCode, ctx.Request.Method, ctx.FullPath()),

View File

@ -2,12 +2,14 @@ package http
import (
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
"github.com/TicketsBot/GoPanel/app/http/endpoints/api/admin/botstaff"
api_autoclose "github.com/TicketsBot/GoPanel/app/http/endpoints/api/autoclose"
api_blacklist "github.com/TicketsBot/GoPanel/app/http/endpoints/api/blacklist"
api_customisation "github.com/TicketsBot/GoPanel/app/http/endpoints/api/customisation"
api_forms "github.com/TicketsBot/GoPanel/app/http/endpoints/api/forms"
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings"
api_override "github.com/TicketsBot/GoPanel/app/http/endpoints/api/staffoverride"
api_tags "github.com/TicketsBot/GoPanel/app/http/endpoints/api/tags"
api_team "github.com/TicketsBot/GoPanel/app/http/endpoints/api/team"
api_ticket "github.com/TicketsBot/GoPanel/app/http/endpoints/api/ticket"
@ -67,8 +69,8 @@ func StartServer() {
apiGroup.GET("/session", api.SessionHandler)
}
guildAuthApiAdmin := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Admin))
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Support))
guildAuthApiAdmin := apiGroup.Group("/:id", middleware.AuthenticateGuild(permission.Admin))
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(permission.Support))
guildApiNoAuth := apiGroup.Group("/:id", middleware.ParseGuildId)
{
guildAuthApiSupport.GET("/channels", api.ChannelsHandler)
@ -150,6 +152,10 @@ func StartServer() {
guildAuthApiAdmin.PUT("/team/:teamid/:snowflake", rl(middleware.RateLimitTypeGuild, 5, time.Second*10), api_team.AddMember)
guildAuthApiAdmin.DELETE("/team/:teamid", api_team.DeleteTeam)
guildAuthApiAdmin.DELETE("/team/:teamid/:snowflake", rl(middleware.RateLimitTypeGuild, 30, time.Minute), api_team.RemoveMember)
guildAuthApiAdmin.GET("/staff-override", api_override.GetOverrideHandler)
guildAuthApiAdmin.POST("/staff-override", api_override.CreateOverrideHandler)
guildAuthApiAdmin.DELETE("/staff-override", api_override.DeleteOverrideHandler)
}
userGroup := router.Group("/user", middleware.AuthenticateToken)
@ -173,6 +179,13 @@ func StartServer() {
}
}
adminGroup := apiGroup.Group("/admin", middleware.AdminOnly)
{
adminGroup.GET("/bot-staff", botstaff.ListBotStaffHandler)
adminGroup.POST("/bot-staff/:userid", botstaff.AddBotStaffHandler)
adminGroup.DELETE("/bot-staff/:userid", botstaff.RemoveBotStaffHandler)
}
if err := router.Run(config.Conf.Server.Host); err != nil {
panic(err)
}

View File

@ -1,6 +1,6 @@
`{#if label !== undefined}
{#if label !== undefined}
<label class="form-label">{label}</label>
{/if}`
{/if}
<div class="multiselect-super">
<Select placeholder="Search..." optionIdentifier="id" items={roles}

View File

@ -0,0 +1,107 @@
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer="{true}" footerRight="{true}" fill="{false}">
<span slot="title">Grant Permissions To Tickets Team</span>
<div slot="body" class="body-wrapper">
Grant permission for
<Dropdown bind:value={timePeriod}>
<option value="1">1 hour</option>
<option value="6">6 hours</option>
<option value="24">1 day</option>
<option value="72">3 days</option>
</Dropdown>
</div>
<div slot="footer" class="footer-wrapper">
<Button danger={true} on:click={dispatchClose}>Cancel</Button>
<div style="">
<Button on:click={dispatchConfirm}>Confirm</Button>
</div>
</div>
</Card>
</div>
</div>
<div class="modal-backdrop" transition:fade>
</div>
<svelte:window on:keydown={handleKeydown}/>
<script>
import {createEventDispatcher} from 'svelte';
import {fade} from 'svelte/transition'
import Card from "../Card.svelte";
import Button from "../Button.svelte";
import Dropdown from "../form/Dropdown.svelte";
export let guildId;
const dispatch = createEventDispatcher();
let timePeriod = "1";
function dispatchClose() {
dispatch('close', {});
}
// Dispatch with data
function dispatchConfirm() {
dispatch('confirm', {timePeriod: parseInt(timePeriod)});
}
function handleKeydown(e) {
if (e.key === "Escape") {
dispatchClose();
}
}
</script>
<style>
.modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 501;
display: flex;
justify-content: center;
align-items: center;
}
.modal-wrapper {
display: flex;
width: 40%;
}
@media only screen and (max-width: 1280px) {
.modal-wrapper {
width: 96%;
}
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 500;
background-color: #000;
opacity: .5;
}
.body-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
.footer-wrapper {
display: flex;
flex-direction: row;
gap: 12px;
}
</style>

View File

@ -0,0 +1,100 @@
<script>
import {Navigate} from 'svelte-router-spa'
</script>
<div class="sidebar">
<div class="sidebar-container" id="sidebar-nav">
<div class="inner">
<Navigate to="/admin/bot-staff" styles="sidebar-link">
<div class="sidebar-element">
<i class="fas fa-user-group sidebar-icon"></i>
<span class="sidebar-text">Bot Staff</span>
</div>
</Navigate>
</div>
</div>
<div class="sidebar-container">
<div class="sidebar-element">
<Navigate to="/" styles="sidebar-link">
<i class="sidebar-icon fas fa-home sidebar-icon"></i>
<span class="sidebar-text">Home</span>
</Navigate>
</div>
</div>
</div>
<style>
.sidebar {
display: flex;
flex-direction: column;
height: 100%;
width: 16.6%;
background-color: #272727;
float: left;
background-size: cover;
overflow-x: hidden !important;
min-width: 250px;
}
.sidebar-container {
margin-bottom: 2%;
}
.inner {
width: 100%;
}
.sidebar-element {
display: flex;
align-items: center;
width: 100%;
cursor: pointer;
padding: 5px 0 5px 0;
}
.sidebar-element:hover {
background-color: #121212;
transition: background-color 0.5s ease;
}
#custom-image {
max-height: 70px;
max-width: 90%;
}
.sidebar-text {
margin-left: 4%;
display: flex;
align-items: center;
}
#sidebar-nav {
display: flex;
flex: 1;
}
@media (max-width: 950px) {
.sidebar {
flex-direction: row;
width: 100%;
height: unset;
min-width: unset;
overflow: visible !important;
}
.sidebar-container {
margin-bottom: unset;
}
.inner {
display: flex;
}
.sidebar-element {
width: unset;
padding: 20px 15px;
}
}
</style>

View File

@ -5,6 +5,7 @@
export let avatar;
export let isWhitelabel = false;
export let isAdmin = false;
</script>
<div class="sidebar">
@ -31,6 +32,14 @@
</div>
</a>
{/if}
{#if isAdmin}
<Navigate to="/admin/bot-staff" styles="sidebar-link">
<div class="sidebar-element">
<i class="fa-solid fa-user-secret sidebar-icon"></i>
<span class="sidebar-text">Admin</span>
</div>
</Navigate>
{/if}
</div>
</div>
<div class="sidebar-container">

View File

@ -0,0 +1,93 @@
<Head/>
<div class="wrapper">
<AdminSidebar />
<div class="super-container">
<LoadingScreen/>
<NotifyModal/>
<div class="content-container" class:hide={$loadingScreen}>
<Route {currentRoute} {params}/>
</div>
</div>
</div>
<script>
import {navigateTo, Route} from 'svelte-router-spa'
import Head from '../includes/Head.svelte'
import Sidebar from '../includes/Sidebar.svelte'
import LoadingScreen from '../includes/LoadingScreen.svelte'
import NotifyModal from '../includes/NotifyModal.svelte'
import axios from "axios";
import {API_URL} from '../js/constants'
import {notifyError} from '../js/util'
import {loadingScreen} from "../js/stores"
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
import AdminSidebar from "../includes/AdminSidebar.svelte";
export let currentRoute;
export let params = {};
setDefaultHeaders()
let name;
let avatar;
let isWhitelabel = false;
let isAdmin = false;
async function loadData() {
const res = await axios.get(`${API_URL}/api/session`);
if (res.status !== 200) {
if (res.data.auth === true) {
redirectLogin();
}
notifyError(res.data.error);
return;
}
isAdmin = res.data.admin;
if (!isAdmin) {
navigateTo(`/`);
}
}
loadData();
</script>
<style>
body {
padding: 0 !important;
}
.wrapper {
display: flex;
width: 100%;
height: 100%;
margin: 0 !important;
padding: 0 !important;
}
.super-container {
display: flex;
width: 100%;
height: 100%;
}
.content-container {
display: flex;
width: 100%;
height: 100%;
}
.hide {
visibility: hidden;
}
@media (max-width: 950px) {
.wrapper {
flex-direction: column;
}
}
</style>

View File

@ -1,7 +1,7 @@
<Head/>
<div class="wrapper">
<Sidebar name="{name}" avatar="{avatar}" {isWhitelabel} />
<Sidebar {name} {avatar} {isWhitelabel} {isAdmin} />
<div class="super-container">
<LoadingScreen/>
<NotifyModal/>
@ -32,6 +32,7 @@
let avatar;
let isWhitelabel = false;
let isAdmin = false;
async function loadData() {
const res = await axios.get(`${API_URL}/api/session`);
@ -47,6 +48,7 @@
name = res.data.username;
avatar = res.data.avatar;
isWhitelabel = res.data.whitelabel;
isAdmin = res.data.admin;
}
loadData();

View File

@ -2,6 +2,7 @@ import IndexLayout from './layouts/IndexLayout.svelte'
import ManageLayout from './layouts/ManageLayout.svelte'
import ErrorLayout from './layouts/ErrorPage.svelte'
import TranscriptViewLayout from './layouts/TranscriptViewLayout.svelte'
import AdminLayout from './layouts/AdminLayout.svelte';
import Index from './views/Index.svelte'
import LoginCallback from './views/LoginCallback.svelte'
@ -21,6 +22,8 @@ import Tickets from './views/Tickets.svelte'
import TicketView from './views/TicketView.svelte'
import Appearance from './views/Appearance.svelte';
import Forms from './views/Forms.svelte';
import StaffOverride from './views/StaffOverride.svelte';
import BotStaff from './views/admin/BotStaff.svelte';
export const routes = [
{name: '/', component: Index, layout: IndexLayout},
@ -30,6 +33,12 @@ export const routes = [
{name: '/logout', component: Logout},
{name: '/error', component: Error, layout: ErrorLayout},
{name: '/whitelabel', component: Whitelabel, layout: IndexLayout},
{
name: 'admin',
nestedRoutes: [
{name: 'bot-staff', component: BotStaff, layout: AdminLayout},
]
},
{
name: 'manage/:id',
nestedRoutes: [
@ -67,6 +76,7 @@ export const routes = [
{name: 'tags', component: Tags, layout: ManageLayout},
{name: 'teams', component: Teams, layout: ManageLayout},
{name: 'forms', component: Forms, layout: ManageLayout},
{name: 'staffoverride', component: StaffOverride, layout: ManageLayout},
{
name: 'tickets',
nestedRoutes: [

View File

@ -0,0 +1,134 @@
{#if modal}
<StaffOverrideModal {guildId} on:close={() => modal = false} on:confirm={handleConfirm}/>
{/if}
<div class="parent">
<div class="content">
<div class="main-col">
<Card footer footerRight>
<span slot="title">Staff Override</span>
<div slot="body" class="body-wrapper">
You can grant access to the Tickets support team to temporarily access the dashboard for your server to help
you resolve issues. You can revoke access at any time by visiting this page.
</div>
<div slot="footer" class="footer-wrapper">
{#if activeOverride}
<Button danger on:click={removeOverride}>Revoke Access</Button>
{/if}
<Button on:click={() => modal = true}>Grant Access</Button>
</div>
</Card>
</div>
</div>
</div>
<script>
import Card from "../components/Card.svelte";
import {notifyError, notifySuccess, withLoadingScreen} from '../js/util'
import axios from "axios";
import {API_URL} from "../js/constants";
import {setDefaultHeaders} from '../includes/Auth.svelte'
import Button from "../components/Button.svelte";
import StaffOverrideModal from "../components/manage/StaffOverrideModal.svelte";
export let currentRoute;
let guildId = currentRoute.namedParams.id;
let modal = false;
let activeOverride = false;
async function handleConfirm(e) {
await createOverride(e.detail.timePeriod);
}
async function loadActiveOverride() {
const res = await axios.get(`${API_URL}/api/${guildId}/staff-override`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
activeOverride = res.data.has_override;
}
async function createOverride(timePeriod) {
let data = {
time_period: timePeriod
};
const res = await axios.post(`${API_URL}/api/${guildId}/staff-override`, data);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
modal = false;
activeOverride = true;
notifySuccess('Staff access override has been granted');
}
async function removeOverride() {
const res = await axios.delete(`${API_URL}/api/${guildId}/staff-override`);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
activeOverride = false;
notifySuccess('Staff access override has been revoked');
}
withLoadingScreen(async () => {
setDefaultHeaders();
await loadActiveOverride();
});
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: center;
width: 96%;
height: 100%;
margin-top: 30px;
}
.main-col {
display: flex;
flex-direction: column;
width: 64%;
height: 100%;
}
.body-wrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.footer-wrapper {
display: flex;
flex-direction: row;
gap: 10px;
height: 100%;
}
@media only screen and (max-width: 950px) {
.content {
flex-direction: column-reverse;
}
.main-col {
width: 100%;
margin-top: 4%;
}
}
</style>

View File

@ -0,0 +1,127 @@
<div class="wrapper">
<div class="content">
<Card footer="{false}" fill="{false}">
<h4 slot="title">Bot Staff</h4>
<div slot="body" class="full-width body-wrapper">
<form class="form-wrapper" on:submit|preventDefault={addStaff}>
<Input label="User ID" placeholder="585576154958921739" bind:value={tempUserId} />
<Button type="submit">Add</Button>
</form>
<table class="nice">
<thead>
<tr>
<th>Username</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each staff as user}
<tr>
<td>{user.username}#{user.discriminator} ({user.id})</td>
<td>
<Button type="button" danger on:click={() => removeStaff(user.id)}>
Delete
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</Card>
</div>
</div>
<script>
import {notifyError, withLoadingScreen} from '../../js/util'
import axios from "axios";
import Card from '../../components/Card.svelte'
import {API_URL} from "../../js/constants";
import {setDefaultHeaders} from '../../includes/Auth.svelte'
import Button from "../../components/Button.svelte";
import Input from "../../components/form/Input.svelte";
setDefaultHeaders()
let staff = [];
let tempUserId = "";
async function addStaff() {
if (tempUserId.length === 0) {
return;
}
const res = await axios.post(`${API_URL}/api/admin/bot-staff/${tempUserId}`);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
tempUserId = "";
await loadData(); // TODO: Return user data with response
}
async function removeStaff(userId) {
const res = await axios.delete(`${API_URL}/api/admin/bot-staff/${userId}`);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
staff = staff.filter(user => user.id !== userId);
}
async function loadData() {
const res = await axios.get(`${API_URL}/api/admin/bot-staff`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
staff = res.data;
}
withLoadingScreen(async () => {
await loadData();
});
</script>
<style>
.wrapper {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
align-items: center;
}
.content {
display: flex;
justify-content: space-around;
flex-direction: row;
width: 95%;
margin-top: 2%;
}
@media only screen and (max-width: 900px) {
.content {
flex-direction: column;
}
}
.body-wrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
gap: 30px;
}
.form-wrapper {
display: flex;
flex-direction: column;
width: 100%;
}
</style>

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
github.com/TicketsBot/worker v0.0.0-20220621165800-203b0004b733
github.com/apex/log v1.1.2

2
go.sum
View File

@ -7,6 +7,8 @@ github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a h1:SwA18cDURmnXS
github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a/go.mod h1:ZAoYcDD7SQLTsZT7dbo/X0J256+pogVRAReunCGng+U=
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de h1:UsRiB3KIwqIF92huRBFKAnCoGLyT9kBYYUycsapBZk0=
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90 h1:K0t6IaZdeZzEr2BaYj/NBuWIm/hA31jkqFh2c3nyDrw=
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c h1:OqGjFH6mbE6gd+NqI2ARJdtH3UUvhiAkD0r0fhGJK2s=
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c/go.mod h1:jgi2OXQKsd5nUnTIRkwvPmeuD/i7OhN68LKMssuQY1c=
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=

View File

@ -20,6 +20,24 @@ func GetPermissionLevel(guildId, userId uint64) (permission.PermissionLevel, err
return permission.Admin, nil
}
// Check staff override
staffOverride, err := dbclient.Client.StaffOverride.HasActiveOverride(guildId)
if err != nil {
return permission.Everyone, err
}
// If staff override enabled and the user is bot staff, grant admin permissions
if staffOverride {
isBotStaff, err := dbclient.Client.BotStaff.IsStaff(userId)
if err != nil {
return permission.Everyone, err
}
if isBotStaff {
return permission.Admin, nil
}
}
// get member
member, err := botContext.GetGuildMember(guildId, userId)
if err != nil {