diff --git a/.env.example b/.env.example index 9cd8ec5..48cc759 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,4 @@ DATABASE_URL="file:dev.db?cache=shared&mode=rwc" +DISCORD_CLIENT_ID="" +DISCORD_CLIENT_SECRET="" +DISCORD_REDIRECT_URI="" diff --git a/go.mod b/go.mod index 5d3a939..370aa7b 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -41,6 +42,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/realTristan/disgoauth v1.0.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 0870e55..69c2d63 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -78,6 +80,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/realTristan/disgoauth v1.0.2 h1:dfto2Kf1gFlZsf8XuwRNoemLgk+hGn/TJpSdtMrEh8E= +github.com/realTristan/disgoauth v1.0.2/go.mod h1:t72aRaWMq2gknUZcKONReJlEYFod5sHC86WCJ0X9GxA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= diff --git a/internal/api/handlers/discordoauth.go b/internal/api/handlers/discordoauth.go new file mode 100644 index 0000000..866495f --- /dev/null +++ b/internal/api/handlers/discordoauth.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + "github.com/gin-gonic/gin" +) + +type DiscordOauthHandler struct { + discordOauthService services.DiscordOauthServicer +} + +func NewDiscordOauthHandler(discordOauthService services.DiscordOauthServicer) *DiscordOauthHandler { + return &DiscordOauthHandler{discordOauthService: discordOauthService} +} + +func (h *DiscordOauthHandler) GoRedirect(c *gin.Context) { + w := c.Writer + r := c.Request + h.discordOauthService.Redirect(w, r) + +} + +func (h *DiscordOauthHandler) HandleRedirect(c *gin.Context) { + code := c.Request.URL.Query()["code"][0] + h.discordOauthService.HandleRedirect(code) + +} diff --git a/internal/api/routes/v1.go b/internal/api/routes/v1.go index 2fc4a94..d7d1d6d 100644 --- a/internal/api/routes/v1.go +++ b/internal/api/routes/v1.go @@ -10,7 +10,8 @@ import ( ) func SetupV1(router *gin.Engine, eventService services.EventsServicer, - announcementService services.AnnouncementServicer) { + announcementService services.AnnouncementServicer, + discordOauthService services.DiscordOauthServicer) { // Version 1 routes v1 := router.Group("/v1") @@ -34,5 +35,11 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, announcements.PUT(":id", h.UpdateAnnouncement) announcements.DELETE(":id", h.DeleteAnnouncement) } + discordOauth := v1.Group("/discord") + { + h := handlers.NewDiscordOauthHandler(discordOauthService) + discordOauth.GET("", h.GoRedirect) + discordOauth.GET("handle", h.HandleRedirect) + } } } diff --git a/internal/api/server.go b/internal/api/server.go index 8c7f120..d722d04 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -29,13 +29,14 @@ func Run(ctx context.Context) { queries := models.New(db) eventsService := services.NewEventsService(queries) announcementService := services.NewAnnouncementService(queries) + discordOauthService := services.NewDiscordOauthService() router := gin.Default() router.SetTrustedProxies([]string{ "127.0.0.1/32", }) routes.SetupRoot(router) - routes.SetupV1(router, eventsService, announcementService) + routes.SetupV1(router, eventsService, announcementService, discordOauthService) port := os.Getenv("PORT") if port == "" { diff --git a/internal/api/services/discordoauth.go b/internal/api/services/discordoauth.go new file mode 100644 index 0000000..e66f15e --- /dev/null +++ b/internal/api/services/discordoauth.go @@ -0,0 +1,89 @@ +package services + +import ( + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + disgoauth "github.com/realTristan/disgoauth" + "io" + "net/http" + "os" +) + +var RequestClient *http.Client = &http.Client{} + +type DiscordOauthServicer interface { + Redirect(w gin.ResponseWriter, r *http.Request) + HandleRedirect(code string) +} + +type DiscordOauthService struct { + dc *disgoauth.Client +} + +func NewDiscordOauthService() *DiscordOauthService { + return &DiscordOauthService{dc: disgoauth.Init(&disgoauth.Client{ + ClientID: os.Getenv("DISCORD_CLIENT_ID"), + ClientSecret: os.Getenv("DISCORD_CLIENT_SECRET"), + RedirectURI: os.Getenv("DISCORD_REDIRECT_URI"), + Scopes: []string{disgoauth.ScopeIdentify, "guilds.members.read"}, + })} +} + +func (s *DiscordOauthService) Redirect(w gin.ResponseWriter, r *http.Request) { + s.dc.RedirectHandler(w, r, "") +} + +func (s *DiscordOauthService) HandleRedirect(code string) { + var ( + accessToken, _ = s.dc.GetOnlyAccessToken(code) + guildData, _ = GetUserGuildData(accessToken) + ) + jguildData, _ := json.MarshalIndent(guildData, "", "\t") + fmt.Println(string(jguildData)) +} + +// Reused disgoauth.userData function and remapped to work +// in getting User's Guild Data, includes user info too. +func GetUserGuildData(token string) (map[string]any, error) { + // Establish a new request object + req, err := http.NewRequest("GET", "https://discord.com/api/users/@me/guilds/710225099923521558/member", nil) + + // Handle the error + if err != nil { + return map[string]any{}, err + } + // Set the request object's headers + req.Header = http.Header{ + "Content-Type": []string{"application/json"}, + "Authorization": []string{token}, + } + // Send the http request + resp, err := RequestClient.Do(req) + + // Handle the error + // If the response status isn't a success + if resp.StatusCode != 200 || err != nil { + // Read the http body + body, _err := io.ReadAll(resp.Body) + + // Handle the read body error + if _err != nil { + return map[string]any{}, _err + } + // Handle http response error + return map[string]any{}, + fmt.Errorf("status: %d, code: %v, body: %s", + resp.StatusCode, err, string(body)) + } + + // Readable golang map used for storing + // the response body + var data map[string]any + + // Handle the error + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return map[string]any{}, err + } + return data, nil +} diff --git a/internal/discord_oauth.yaml b/internal/discord_oauth.yaml new file mode 100644 index 0000000..f96aa1c --- /dev/null +++ b/internal/discord_oauth.yaml @@ -0,0 +1,21 @@ +GUILD_ID: "710225099923521558" + +ROLES: + GENERAL_BOARD: "935357601628192789" + ALGO_BOARD: "808120863609323560" + DEV_BOARD: "808121046028779602" + AI_BOARD: "978120489086111774" + GAMEDEV_BOARD: "1067580814340145192" + ICPC_BOARD: "1416119894264643655" + OPEN_SOURCE_BOARD: "1199257547996135515" + +PERMISSIONS: + GENERAL_BOARD: + ALGO_BOARD: + DEV_BOARD: + AI_BOARD: + GAMEDEV_BOARD: + ICPC_BOARD: + OPEN_SOURCE_BOARD: + add_meetings: true + add_members: true