package httpserver import ( "context" "fmt" "log" "net/http" "path/filepath" "text/template" "git.netflux.io/rob/elon-eats-my-tweets/config" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "golang.org/x/oauth2" ) type handler struct { templates *template.Template oauth2Config *oauth2.Config } func newHandler(cfg config.Config, templates *template.Template) *handler { return &handler{ templates: templates, oauth2Config: &oauth2.Config{ ClientID: cfg.Twitter.ClientID, ClientSecret: cfg.Twitter.ClientSecret, RedirectURL: cfg.Twitter.CallbackURL, Scopes: []string{"tweet.read", "tweet.write", "users.read", "offline.access"}, Endpoint: oauth2.Endpoint{ AuthURL: "https://twitter.com/i/oauth2/authorize", TokenURL: "https://api.twitter.com/2/oauth2/token", }, }, } } func (h *handler) getIndex(w http.ResponseWriter, r *http.Request) { if err := h.templates.ExecuteTemplate(w, "index", nil); err != nil { log.Printf("error rendering template: %v", err) http.Error(w, "error rendering template", http.StatusInternalServerError) return } } func (h *handler) getLogin(w http.ResponseWriter, r *http.Request) { url := h.oauth2Config.AuthCodeURL( // TODO: implement state and code_challenge tokens "state", oauth2.SetAuthURLParam("code_challenge", "challenge"), oauth2.SetAuthURLParam("code_challenge_method", "plain"), ) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func (h *handler) getCallback(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { http.Error(w, "empty code", http.StatusBadRequest) return } _, err := h.oauth2Config.Exchange(context.Background(), code, oauth2.SetAuthURLParam("code_verifier", "challenge")) if err != nil { log.Printf("error exchanging code: %v", err) http.Error(w, "error exchanging code", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Header().Set("content-type", "text/html") if _, err = w.Write([]byte("ok")); err != nil { log.Printf("error writing response: %v", err) } } func Start(cfg config.Config) error { r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) templates, err := template.ParseGlob(filepath.Join(cfg.PublicPath, "views", "*.html")) if err != nil { return fmt.Errorf("error loading templates: %v", err) } h := newHandler(cfg, templates) r.Get("/", h.getIndex) r.Get("/login", h.getLogin) r.Get("/callback", h.getCallback) return http.ListenAndServe(":8000", r) }