diff --git a/src/discord/rest.go b/src/discord/rest.go index 6b7e55b7..e11dae5c 100644 --- a/src/discord/rest.go +++ b/src/discord/rest.go @@ -273,7 +273,7 @@ func GetCurrentUserAsOAuth(ctx context.Context, accessToken string) (*User, erro } func AddGuildMemberRole(ctx context.Context, userID, roleID string) error { - const name = "Delete Message" + const name = "Add Guild Member Role" path := fmt.Sprintf("/guilds/%s/members/%s/roles/%s", config.Config.Discord.GuildID, userID, roleID) res, err := doWithRateLimiting(ctx, name, func(ctx context.Context) *http.Request { @@ -292,6 +292,27 @@ func AddGuildMemberRole(ctx context.Context, userID, roleID string) error { return nil } +func RemoveGuildMemberRole(ctx context.Context, userID, roleID string) error { + const name = "Remove Guild Member Role" + + path := fmt.Sprintf("/guilds/%s/members/%s/roles/%s", config.Config.Discord.GuildID, userID, roleID) + logging.ExtractLogger(ctx).Warn().Str("path", path).Msg("I dunno") + res, err := doWithRateLimiting(ctx, name, func(ctx context.Context) *http.Request { + return makeRequest(ctx, http.MethodDelete, path, nil) + }) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + logErrorResponse(ctx, name, res, "") + return oops.New(nil, "got unexpected status code when removing role") + } + + return nil +} + func logErrorResponse(ctx context.Context, name string, res *http.Response, msg string) { dump, err := httputil.DumpResponse(res, true) if err != nil { diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go index a07d6f37..737435fc 100644 --- a/src/hmnurl/urls.go +++ b/src/hmnurl/urls.go @@ -561,6 +561,12 @@ func BuildDiscordOAuthCallback() string { return Url("/_discord_callback", nil) } +var RegexDiscordUnlink = regexp.MustCompile("^/_discord_unlink$") + +func BuildDiscordUnlink() string { + return Url("/_discord_unlink", nil) +} + /* * Assets */ diff --git a/src/templates/src/discordtest.html b/src/templates/src/discordtest.html index c899ebff..3c8eb10f 100644 --- a/src/templates/src/discordtest.html +++ b/src/templates/src/discordtest.html @@ -5,8 +5,13 @@ Wow a discord
{{ with .DiscordUser }} - - {{ .Username }}#{{ .Discriminator }} +
+ + {{ .Username }}#{{ .Discriminator }} +
+
+ +
{{ else }} Link your account {{ end }} diff --git a/src/website/discord.go b/src/website/discord.go index 1f005d38..57611fb6 100644 --- a/src/website/discord.go +++ b/src/website/discord.go @@ -41,6 +41,7 @@ func DiscordTest(c *RequestContext) ResponseData { templates.BaseData DiscordUser *templates.DiscordUser AuthorizeURL string + UnlinkURL string } baseData := getBaseData(c) @@ -56,6 +57,7 @@ func DiscordTest(c *RequestContext) ResponseData { td := templateData{ BaseData: baseData, AuthorizeURL: fmt.Sprintf("https://discord.com/api/oauth2/authorize?%s", params.Encode()), + UnlinkURL: hmnurl.BuildDiscordUnlink(), } if userDiscord != nil { @@ -111,12 +113,13 @@ func DiscordOAuthCallback(c *RequestContext) ResponseData { return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch Discord user info")) } - // TODO: Add the role on Discord + // Add the role on Discord err = discord.AddGuildMemberRole(c.Context(), user.ID, config.Config.Discord.MemberRoleID) if err != nil { return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to add member role")) } + // Add the user to our database _, err = c.Conn.Exec(c.Context(), ` INSERT INTO handmade_discorduser (username, discriminator, access_token, refresh_token, avatar, locale, userid, expiry, hmn_user_id) @@ -138,3 +141,51 @@ func DiscordOAuthCallback(c *RequestContext) ResponseData { return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther) } + +func DiscordUnlink(c *RequestContext) ResponseData { + tx, err := c.Conn.Begin(c.Context()) + if err != nil { + panic(err) + } + defer tx.Rollback(c.Context()) + + iDiscordUser, err := db.QueryOne(c.Context(), tx, models.DiscordUser{}, + ` + SELECT $columns + FROM handmade_discorduser + WHERE hmn_user_id = $1 + `, + c.CurrentUser.ID, + ) + if err != nil { + if errors.Is(err, db.ErrNoMatchingRows) { + return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther) + } else { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to get Discord user for unlink")) + } + } + discordUser := iDiscordUser.(*models.DiscordUser) + + _, err = tx.Exec(c.Context(), + ` + DELETE FROM handmade_discorduser + WHERE id = $1 + `, + discordUser.ID, + ) + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete Discord user")) + } + + err = tx.Commit(c.Context()) + if err != nil { + return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to commit Discord user delete")) + } + + err = discord.RemoveGuildMemberRole(c.Context(), discordUser.UserID, config.Config.Discord.MemberRoleID) + if err != nil { + c.Logger.Warn().Err(err).Msg("failed to remove member role on unlink") + } + + return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther) +} diff --git a/src/website/routes.go b/src/website/routes.go index fe2f963c..89d1b876 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -199,6 +199,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt mainRoutes.GET(hmnurl.RegexDiscordTest, authMiddleware(DiscordTest)) // TODO: Delete this route mainRoutes.GET(hmnurl.RegexDiscordOAuthCallback, authMiddleware(DiscordOAuthCallback)) + mainRoutes.POST(hmnurl.RegexDiscordUnlink, authMiddleware(DiscordUnlink)) mainRoutes.GET(hmnurl.RegexProjectCSS, ProjectCSS) mainRoutes.GET(hmnurl.RegexEditorPreviewsJS, func(c *RequestContext) ResponseData {