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 {