Enhance Discord role management by adding hoist functionality
This commit introduces a new `hoist` attribute for Discord roles, allowing team roles and the registered role to be displayed in separate groups within the Discord member list. The `RoleWorker` has been updated to ensure team roles are positioned above the registered role, improving visibility and organization. Database schema changes have been made to support the new `hoist` field, and corresponding updates have been implemented in the service layer and tests to validate the new behavior.
This commit is contained in:
@@ -46,7 +46,8 @@ type RoleManager struct {
|
||||
}
|
||||
|
||||
type discordRoleResponse struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Position int `json:"position"`
|
||||
}
|
||||
|
||||
type discordHTTPError struct {
|
||||
@@ -181,10 +182,14 @@ func (w *RoleWorker) reconcile(ctx context.Context, eventID string) ([]string, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registeredRole := existingRole(roles, application.DiscordRoleKindRegistered)
|
||||
roles, err = w.ensureDesiredRoles(ctx, current.Roster, roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = w.ensureTeamsAboveRegistered(ctx, roles, registeredRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
desired, warnings, err := w.desiredAssignments(ctx, current, roles)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
@@ -234,7 +239,7 @@ func (w *RoleWorker) reconcileRSVP(ctx context.Context, eventID string) ([]strin
|
||||
}
|
||||
names := rsvpRoleNames(w.locale, state.Event.Name)
|
||||
desiredRoles := []application.DiscordManagedRole{
|
||||
{Scope: application.DiscordRoleScopeEvent, EventID: eventID, Kind: application.DiscordRoleKindRegistered, RoleName: names[application.DiscordRoleKindRegistered]},
|
||||
{Scope: application.DiscordRoleScopeEvent, EventID: eventID, Kind: application.DiscordRoleKindRegistered, RoleName: names[application.DiscordRoleKindRegistered], Hoist: true},
|
||||
{Scope: application.DiscordRoleScopeEvent, EventID: eventID, Kind: application.DiscordRoleKindGoing, RoleName: names[application.DiscordRoleKindGoing]},
|
||||
{Scope: application.DiscordRoleScopeEvent, EventID: eventID, Kind: application.DiscordRoleKindMaybe, RoleName: names[application.DiscordRoleKindMaybe]},
|
||||
{Scope: application.DiscordRoleScopeEvent, EventID: eventID, Kind: application.DiscordRoleKindNotGoing, RoleName: names[application.DiscordRoleKindNotGoing]},
|
||||
@@ -281,7 +286,7 @@ func (w *RoleWorker) ensureDesiredRoles(ctx context.Context, roster domain.Roste
|
||||
for _, team := range roster.Teams {
|
||||
teamName := truncate(strings.TrimSpace(team.Name), 100)
|
||||
desired = append(desired,
|
||||
application.DiscordManagedRole{Scope: application.DiscordRoleScopeEvent, EventID: roster.EventID, TeamID: team.ID, Kind: application.DiscordRoleKindTeam, RoleName: teamName},
|
||||
application.DiscordManagedRole{Scope: application.DiscordRoleScopeEvent, EventID: roster.EventID, TeamID: team.ID, Kind: application.DiscordRoleKindTeam, RoleName: teamName, Hoist: true},
|
||||
application.DiscordManagedRole{Scope: application.DiscordRoleScopeEvent, EventID: roster.EventID, TeamID: team.ID, Kind: application.DiscordRoleKindCaptain, RoleName: truncate(teamName+" Captain", 100)},
|
||||
)
|
||||
}
|
||||
@@ -302,8 +307,8 @@ func (w *RoleWorker) ensureRoles(ctx context.Context, desired, existing []applic
|
||||
desiredKeys[key] = true
|
||||
if saved, ok := existingByKey[key]; ok {
|
||||
role.DiscordRoleID = saved.DiscordRoleID
|
||||
if saved.RoleName != role.RoleName {
|
||||
if err := w.manager.RenameRole(ctx, saved.DiscordRoleID, role.RoleName); err != nil {
|
||||
if saved.RoleName != role.RoleName || saved.Hoist != role.Hoist {
|
||||
if err := w.manager.UpdateRole(ctx, saved.DiscordRoleID, role.RoleName, role.Hoist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := w.store.UpsertDiscordManagedRole(ctx, role); err != nil {
|
||||
@@ -311,7 +316,7 @@ func (w *RoleWorker) ensureRoles(ctx context.Context, desired, existing []applic
|
||||
}
|
||||
}
|
||||
} else {
|
||||
roleID, err := w.manager.CreateRole(ctx, role.RoleName)
|
||||
roleID, err := w.manager.CreateRole(ctx, role.RoleName, role.Hoist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -486,10 +491,26 @@ func (w *RoleWorker) deleteManagedRole(ctx context.Context, role application.Dis
|
||||
return w.store.DeleteDiscordManagedRole(ctx, role)
|
||||
}
|
||||
|
||||
func (m *RoleManager) CreateRole(ctx context.Context, name string) (string, error) {
|
||||
func (w *RoleWorker) ensureTeamsAboveRegistered(ctx context.Context, roles []application.DiscordManagedRole, registered application.DiscordManagedRole) error {
|
||||
if registered.DiscordRoleID == "" {
|
||||
return nil
|
||||
}
|
||||
teamRoleIDs := make([]string, 0)
|
||||
for _, role := range roles {
|
||||
if role.Kind == application.DiscordRoleKindTeam {
|
||||
teamRoleIDs = append(teamRoleIDs, role.DiscordRoleID)
|
||||
}
|
||||
}
|
||||
if len(teamRoleIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return w.manager.MoveRolesAbove(ctx, teamRoleIDs, registered.DiscordRoleID)
|
||||
}
|
||||
|
||||
func (m *RoleManager) CreateRole(ctx context.Context, name string, hoist bool) (string, error) {
|
||||
var response discordRoleResponse
|
||||
if err := m.request(ctx, http.MethodPost, m.guildPath("/roles"), map[string]any{
|
||||
"name": name, "permissions": "0", "hoist": false, "mentionable": false,
|
||||
"name": name, "permissions": "0", "hoist": hoist, "mentionable": false,
|
||||
}, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -499,8 +520,38 @@ func (m *RoleManager) CreateRole(ctx context.Context, name string) (string, erro
|
||||
return response.ID, nil
|
||||
}
|
||||
|
||||
func (m *RoleManager) RenameRole(ctx context.Context, roleID, name string) error {
|
||||
return m.request(ctx, http.MethodPatch, m.guildPath("/roles/"+url.PathEscape(roleID)), map[string]string{"name": name}, nil)
|
||||
func (m *RoleManager) UpdateRole(ctx context.Context, roleID, name string, hoist bool) error {
|
||||
return m.request(ctx, http.MethodPatch, m.guildPath("/roles/"+url.PathEscape(roleID)), map[string]any{"name": name, "hoist": hoist}, nil)
|
||||
}
|
||||
|
||||
func (m *RoleManager) MoveRolesAbove(ctx context.Context, roleIDs []string, anchorRoleID string) error {
|
||||
var roles []discordRoleResponse
|
||||
if err := m.request(ctx, http.MethodGet, m.guildPath("/roles"), nil, &roles); err != nil {
|
||||
return err
|
||||
}
|
||||
positions := make(map[string]int, len(roles))
|
||||
for _, role := range roles {
|
||||
positions[role.ID] = role.Position
|
||||
}
|
||||
anchorPosition, ok := positions[anchorRoleID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
needsMove := false
|
||||
for _, roleID := range roleIDs {
|
||||
if position, found := positions[roleID]; !found || position <= anchorPosition {
|
||||
needsMove = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsMove {
|
||||
return nil
|
||||
}
|
||||
payload := make([]map[string]any, 0, len(roleIDs))
|
||||
for index, roleID := range roleIDs {
|
||||
payload = append(payload, map[string]any{"id": roleID, "position": anchorPosition + index + 1})
|
||||
}
|
||||
return m.request(ctx, http.MethodPatch, m.guildPath("/roles"), payload, nil)
|
||||
}
|
||||
|
||||
func (m *RoleManager) DeleteRole(ctx context.Context, roleID string) error {
|
||||
@@ -575,6 +626,15 @@ func managedRoleKey(role application.DiscordManagedRole) string {
|
||||
return strings.Join([]string{role.Scope, role.EventID, role.TeamID, role.Kind}, "\x00")
|
||||
}
|
||||
|
||||
func existingRole(roles []application.DiscordManagedRole, kind string) application.DiscordManagedRole {
|
||||
for _, role := range roles {
|
||||
if role.Kind == kind {
|
||||
return role
|
||||
}
|
||||
}
|
||||
return application.DiscordManagedRole{}
|
||||
}
|
||||
|
||||
func roleKind(role domain.Role) string {
|
||||
switch role {
|
||||
case domain.Tank:
|
||||
|
||||
Reference in New Issue
Block a user