From d745f26dd81bfb1cc14991dd9af4995829170412 Mon Sep 17 00:00:00 2001 From: rxdn <29165304+rxdn@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:42:23 +0100 Subject: [PATCH] Threads --- app/http/endpoints/api/settings/settings.go | 2 +- .../endpoints/api/settings/updatesettings.go | 29 +++- app/http/endpoints/api/team/removemember.go | 143 +++++++++++++++++- botcontext/botcontext.go | 15 ++ .../src/components/ChannelDropdown.svelte | 3 +- frontend/src/components/Collapsible.svelte | 10 +- frontend/src/components/IconBadge.svelte | 27 ++++ frontend/src/components/form/Checkbox.svelte | 6 +- frontend/src/components/form/Dropdown.svelte | 3 +- .../src/components/manage/SettingsCard.svelte | 21 ++- go.mod | 7 +- go.sum | 20 ++- utils/stringutils.go | 12 +- 13 files changed, 271 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/IconBadge.svelte diff --git a/app/http/endpoints/api/settings/settings.go b/app/http/endpoints/api/settings/settings.go index 664cfab..ca95def 100644 --- a/app/http/endpoints/api/settings/settings.go +++ b/app/http/endpoints/api/settings/settings.go @@ -182,7 +182,7 @@ func GetSettingsHandler(ctx *gin.Context) { LanguageNames map[i18n.Language]string `json:"language_names"` }{ Settings: settings, - Languages: i18n.LanguagesAlphabetical, + Languages: i18n.LanguagesAlphabetical[:], LanguageNames: i18n.FullNames, }) } diff --git a/app/http/endpoints/api/settings/updatesettings.go b/app/http/endpoints/api/settings/updatesettings.go index eec6239..5e283b2 100644 --- a/app/http/endpoints/api/settings/updatesettings.go +++ b/app/http/endpoints/api/settings/updatesettings.go @@ -114,13 +114,17 @@ func (s *Settings) Validate(guildId uint64, premiumTier premium.PremiumTier) err return errors.New("Must be able to view channel to type") } - if s.Settings.UseThreads { - return fmt.Errorf("threads are disabled") + if s.Settings.UseThreads && s.TicketNotificationChannel == nil { + return errors.New("You must select a ticket notification channel") + } + + if !s.Settings.UseThreads { + s.TicketNotificationChannel = nil } if s.Language != nil { if _, ok := i18n.FullNames[*s.Language]; !ok { - return fmt.Errorf("invalid language") + return errors.New("Invalid language") } } @@ -222,6 +226,25 @@ func (s *Settings) Validate(guildId uint64, premiumTier premium.PremiumTier) err return nil }) + group.Go(func() error { + if s.Settings.TicketNotificationChannel != nil { + ch, ok := cache.Instance.GetChannel(*s.Settings.TicketNotificationChannel) + if !ok { + return fmt.Errorf("Invalid ticket notification channel") + } + + if ch.GuildId != guildId { + return fmt.Errorf("Ticket notification channel guild ID does not match") + } + + if ch.Type != channel.ChannelTypeGuildText { + return fmt.Errorf("Ticket notification channel is not a text channel") + } + } + + return nil + }) + return group.Wait() } diff --git a/app/http/endpoints/api/team/removemember.go b/app/http/endpoints/api/team/removemember.go index 741da56..f55c41f 100644 --- a/app/http/endpoints/api/team/removemember.go +++ b/app/http/endpoints/api/team/removemember.go @@ -1,10 +1,14 @@ package api import ( + "fmt" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/database" "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" "strconv" ) @@ -92,11 +96,61 @@ func removeDefaultMember(ctx *gin.Context, guildId, selfId, snowflake uint64, en return } + // Remove on-call role + metadata, err := dbclient.Client.GuildMetadata.Get(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if metadata.OnCallRole != nil { + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if entityType == entityTypeUser { + // If the member is not in the guild we do not have to worry + member, err := botContext.GetGuildMember(guildId, snowflake) + if err == nil { + if member.HasRole(*metadata.OnCallRole) { + // Attempt to remove role but ignore failure + _ = botContext.RemoveGuildMemberRole(guildId, snowflake, *metadata.OnCallRole) + } + } else { + if err, ok := err.(request.RestError); !ok || err.StatusCode != 404 { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } + } else if entityType == entityTypeRole { + // Recreate role + if err := dbclient.Client.GuildMetadata.SetOnCallRole(guildId, nil); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if err := botContext.DeleteGuildRole(guildId, *metadata.OnCallRole); err != nil && !isUnknownRoleError(err) { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if _, err := createOnCallRole(botContext, guildId, nil); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } else { + ctx.JSON(500, utils.ErrorStr("Infallible")) + return + } + } + ctx.JSON(200, utils.SuccessResponse) } func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, entityType entityType) { - exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId) + team, exists, err := dbclient.Client.SupportTeam.GetById(guildId, teamId) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return @@ -107,6 +161,7 @@ func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, e return } + // Remove from DB switch entityType { case entityTypeUser: err = dbclient.Client.SupportTeamMembers.Delete(teamId, snowflake) @@ -119,5 +174,89 @@ func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, e return } + // Remove on-call role + if team.OnCallRole != nil { + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if entityType == entityTypeUser { + // If the member is not in the guild we do not have to worry + member, err := botContext.GetGuildMember(guildId, snowflake) + if err == nil { + if member.HasRole(*team.OnCallRole) { + // Attempt to remove role but ignore failure + _ = botContext.RemoveGuildMemberRole(guildId, snowflake, *team.OnCallRole) + } + } else { + if err, ok := err.(request.RestError); !ok || err.StatusCode != 404 { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } + _ = botContext.RemoveGuildMemberRole(guildId, snowflake, *team.OnCallRole) + } else if entityType == entityTypeRole { + // Recreate role + if err := dbclient.Client.SupportTeam.SetOnCallRole(teamId, nil); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if err := botContext.DeleteGuildRole(guildId, *team.OnCallRole); err != nil && !isUnknownRoleError(err) { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + if _, err := createOnCallRole(botContext, guildId, &team); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } else { + ctx.JSON(500, utils.ErrorStr("Infallible")) + } + } + ctx.JSON(200, utils.SuccessResponse) -} \ No newline at end of file +} + +func createOnCallRole(botContext botcontext.BotContext, guildId uint64, team *database.SupportTeam) (uint64, error) { + var roleName string + if team == nil { + roleName = "On Call" // TODO: Translate + } else { + roleName = utils.StringMax(fmt.Sprintf("On Call - %s", team.Name), 100) + } + + data := rest.GuildRoleData{ + Name: roleName, + Hoist: utils.Ptr(false), + Mentionable: utils.Ptr(false), + } + + role, err := botContext.CreateGuildRole(guildId, data) + if err != nil { + return 0, err + } + + if team == nil { + if err := dbclient.Client.GuildMetadata.SetOnCallRole(guildId, &role.Id); err != nil { + return 0, err + } + } else { + if err := dbclient.Client.SupportTeam.SetOnCallRole(team.Id, &role.Id); err != nil { + return 0, err + } + } + + return role.Id, nil +} + +func isUnknownRoleError(err error) bool { + if err, ok := err.(request.RestError); ok && err.ApiError.Message == "Unknown Role" { + return true + } + + return false +} diff --git a/botcontext/botcontext.go b/botcontext/botcontext.go index 01fe32f..4a8237a 100644 --- a/botcontext/botcontext.go +++ b/botcontext/botcontext.go @@ -95,6 +95,21 @@ func (ctx BotContext) GetGuildMember(guildId, userId uint64) (m member.Member, e return } +func (ctx BotContext) RemoveGuildMemberRole(guildId, userId, roleId uint64) (err error) { + err = rest.RemoveGuildMemberRole(ctx.Token, ctx.RateLimiter, guildId, userId, roleId) + return +} + +func (ctx BotContext) CreateGuildRole(guildId uint64, data rest.GuildRoleData) (role guild.Role, err error) { + role, err = rest.CreateGuildRole(ctx.Token, ctx.RateLimiter, guildId, data) + return +} + +func (ctx BotContext) DeleteGuildRole(guildId, roleId uint64) (err error) { + err = rest.DeleteGuildRole(ctx.Token, ctx.RateLimiter, guildId, roleId) + return +} + func (ctx BotContext) GetUser(userId uint64) (u user.User, err error) { u, err = rest.GetUser(ctx.Token, ctx.RateLimiter, userId) if err == nil { diff --git a/frontend/src/components/ChannelDropdown.svelte b/frontend/src/components/ChannelDropdown.svelte index 0bd276b..eefc1f7 100644 --- a/frontend/src/components/ChannelDropdown.svelte +++ b/frontend/src/components/ChannelDropdown.svelte @@ -1,4 +1,4 @@ - + {#if withNull}