diff --git a/internals/router.go b/internals/router.go new file mode 100644 index 0000000..34eee18 --- /dev/null +++ b/internals/router.go @@ -0,0 +1,22 @@ +package internals + +import ( + "net/http" + + "github.com/a-h/templ" +) + +type RouteHandler = func(http.ResponseWriter, *http.Request) + +type Route struct { + Pattern string + Static bool + Handler RouteHandler + Page templ.Component +} + +func RegisterAllRoutes(routes []Route, s *http.ServeMux) { + for _, r := range routes { + s.HandleFunc(r.Pattern, r.Handler) + } +} diff --git a/main.go b/main.go index e9ba8a2..463d899 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func main() { mux := http.NewServeMux() - routes.RegisterAllRoutes(routes.ROUTES, mux) + internals.RegisterAllRoutes(routes.ROUTES, mux) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { logger.Printf("Handling file server request. path=%s", r.URL.Path) diff --git a/routes/mastodon.templ b/routes/mastodon.templ deleted file mode 100644 index 7a08027..0000000 --- a/routes/mastodon.templ +++ /dev/null @@ -1,278 +0,0 @@ -package routes - -import ( - "net/http" - "fmt" - "encoding/json" - "errors" - "net/url" - - "extrovert/layouts" - "extrovert/internals" - "slices" - "strings" - "log" -) - -const MASTODON_APP_NAME = "project-extrovert-v1" -const MASTODON_COOKIE_NAME = "mastodon-cookie" -const MASTODON_REDIRECT_URI = "http://localhost:7331/api/mastodon/oauth" -const MASTODON_SCOPES = "read write" - -type MastodonApp struct { - Id string `json:"id"` - Name string `json:"name"` - Website *string `json:"website"` - RedirectUri string `json:"redirect_uri"` - ClientId string `json:"client_id"` - ClientSecret string `json:"client_secret"` - VapidKey string `json:"vapid_key"` -} - -type MastodonTokenResponse struct { - Type string `json:"token_type"` - Token string `json:"access_token"` - CreateAt int `json:"created_at"` - Scope string `json:"scope"` -} - -type MastodonCookie struct { - App *MastodonApp `json:"app"` - Token *MastodonTokenResponse `json:"token"` - Instance *string `json:"instance_url,string"` -} - -func (a MastodonCookie) Validate() error { - u, err := url.Parse(*a.Instance) - if err != nil { - return err - } - u.Path = "/api/v1/apps/verify_credentials" - - return nil -} - -func NewMastodonApp(u url.URL) (MastodonApp, error) { - u.Path = "/api/v1/apps" - q := u.Query() - q.Add("client_name", MASTODON_APP_NAME) - q.Add("redirect_uris", MASTODON_REDIRECT_URI) - q.Add("scopes", MASTODON_SCOPES) - u.RawQuery = q.Encode() - - res, err := http.Post(u.String(), "application/x-www-form-urlencoded", bytes.NewReader([]byte(""))) - if err != nil { - return MastodonApp{}, err - } - - body, err := io.ReadAll(res.Body) - if err != nil { - return MastodonApp{}, err - } - - if res.StatusCode != 200 { - return MastodonApp{}, errors.New(string(body)) - } - - var app MastodonApp - err = json.Unmarshal(body, &app) - - return app, err -} - -func MastodonAppHandler(w http.ResponseWriter, r *http.Request) { - error := internals.HttpErrorHelper(w) - - if r.Method != http.MethodPost { - error("Method not allowed", errors.New("method "+r.Method+" is not allowed"), http.StatusMethodNotAllowed) - return - } - - i := r.URL.Query().Get("instance-url") - if i == "" { - i = r.FormValue("instance-url") - if i == "" { - error( - "Bad request", - errors.New("Missing \"instance-url\" parameter"), - http.StatusBadRequest, - ) - return - } - } - - u, err := url.Parse(i) - if error("Bad request\n\"instance-url\" is not a valid url", err, http.StatusBadRequest) { - return - } - if u.Scheme == "" { - u, err = url.Parse("https://" + u.String()) - if error("Bad request\n\"instance-url\" is not a valid url", err, http.StatusBadRequest) { - return - } - } - - rc, err := r.Cookie(MASTODON_COOKIE_NAME + "-" + u.Hostname()) - if err != nil { - rc = &http.Cookie{ - Name: MASTODON_COOKIE_NAME + u.Hostname(), - Value: fmt.Sprintf("{\"instance_url\": \"%s\"}", u.String()), - Path: "/", - Secure: true, - } - } - - var c MastodonCookie - err = json.Unmarshal([]byte(rc.Value), &c) - if err != nil { - _ = json.Unmarshal([]byte("{\"instance_url\": \"%s\"}"), c) - } - - if c.Instance == nil { - str := u.String() - c.Instance = &str - } - if c.App == nil { - a, err := NewMastodonApp(*u) - c.App = &a - if error("Internal Error\nerror trying to create new Mastodon application", err, http.StatusInternalServerError) { - return - } - } - - v, err := json.Marshal(c) - rc.Value = string(v) - http.SetCookie(w, rc) - - log.Print(rc) - - q := u.Query() - q.Add("response_type", "code") - q.Add("client_id", c.App.ClientId) - q.Add("redirect_uri", MASTODON_REDIRECT_URI) - q.Add("scope", MASTODON_SCOPES) - u.RawQuery = q.Encode() - u.Path = "/oauth/authorize" - - MastodonAppPage(*u).Render(context.Background(), w) -} - -templ MastodonAppPage(u url.URL) { - @layouts.Page("Project Extrovert") { - -
-
-

Mastodon app created!

-
-

- An app for your interactions was created on { u.Hostname() }, - Click "Ok" to authorize your account. -

- -
-
- @IndexPage() - } -} - -func MastodonOAuthHandler(w http.ResponseWriter, r *http.Request) { - var err error - error := internals.HttpErrorHelper(w) - - var rc *http.Cookie - - o := r.Header.Get("Origin") - u, err := url.Parse(o) - - if err == nil && o != "" { - rc, err = r.Cookie(MASTODON_COOKIE_NAME + "-" + u.Hostname()) - } - if err != nil || o == "" { - i := slices.IndexFunc(r.Cookies(), func(c *http.Cookie) bool { - return strings.HasPrefix(c.Name, MASTODON_COOKIE_NAME+"-") - }) - rc = r.Cookies()[i] - } - - var c MastodonCookie - err = json.Unmarshal([]byte(rc.Value), &c) - if error("Bad Request", err, http.StatusBadRequest) { - return - } - - code := r.URL.Query().Get("code") - if code == "" { - error( - "Bad request", - errors.New("Missing \"code\" parameter"), - http.StatusBadRequest, - ) - return - } - - u.Path = "/oauth/token" - q := u.Query() - q.Add("grant_type", "authorization_code") - q.Add("client_id", c.App.ClientId) - q.Add("client_secret", c.App.ClientSecret) - q.Add("redirect_uri", MASTODON_REDIRECT_URI) - q.Add("scope", MASTODON_SCOPES) - q.Add("code", code) - u.RawQuery = q.Encode() - - t, err := http.Post(u.String(), "application/x-www-form-urlencoded", bytes.NewReader([]byte(""))) - if error("Error trying to request token from twitter", err, http.StatusInternalServerError) { - return - } - - b, err := io.ReadAll(t.Body) - if error("Error trying to read response body from twitter", err, http.StatusInternalServerError) { - return - } else if t.StatusCode < 200 || t.StatusCode > 299 { - error( - "Error trying to request token from twitter, returned non-200 code", - errors.New(fmt.Sprintf("Code: %v, Return value: %s", t.StatusCode, string(b))), - http.StatusInternalServerError, - ) - return - } - - var res MastodonTokenResponse - err = json.Unmarshal(b, &res) - if error("Error trying to parse response body from twitter", err, http.StatusInternalServerError) { - return - } - c.Token = &res - - v, err := json.Marshal(c) - rc.Value = string(v) - http.SetCookie(w, rc) - - MastodonOAuth().Render(context.Background(), w) -} - -templ MastodonOAuth() { - @layouts.Page("Project Extrovert") { - -
-
-

Logged into Mastodon!

-
-

- Your account was succefully connected with project-extrovert! - Click "Ok" to return to the index page. -

- -
-
- @IndexPage() - } -} diff --git a/routes/router.go b/routes/router.go deleted file mode 100644 index 14acc99..0000000 --- a/routes/router.go +++ /dev/null @@ -1,60 +0,0 @@ -package routes - -import ( - "net/http" - - "github.com/a-h/templ" -) - -var ROUTES = []Route{ - { - Pattern: "/index.html", - Static: true, - Page: IndexPage(), - Handler: IndexHandler, - }, - { - Pattern: "/api/twitter/oauth", - Static: false, - Page: TwitterOAuth(), - Handler: TwitterOAuthHandler, - }, - { - Pattern: "/api/mastodon/oauth", - Static: false, - Page: MastodonOAuth(), - Handler: MastodonOAuthHandler, - }, - { - Pattern: "/api/mastodon/apps", - Static: false, - Handler: MastodonAppHandler, - }, - { - Pattern: "/robots.txt", - Static: true, - Page: RobotsTxt(), - Handler: RobotsTxtHandler, - }, - { - Pattern: "/ai.txt", - Static: true, - Page: AiTxt(), - Handler: AiTxtHandler, - }, -} - -type RouteHandler = func(http.ResponseWriter, *http.Request) - -type Route struct { - Pattern string - Static bool - Handler RouteHandler - Page templ.Component -} - -func RegisterAllRoutes(routes []Route, s *http.ServeMux) { - for _, r := range routes { - s.HandleFunc(r.Pattern, r.Handler) - } -} diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..e768692 --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,44 @@ +package routes + +import ( + "context" + "extrovert/internals" + "extrovert/templates/pages" + "log" + "net/http" + + "github.com/a-h/templ" +) + +func NewStaticPageHandler(c templ.Component) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + err := c.Render(context.Background(), w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Fatalf("TODO-ERR trying to render static page:\n%s", err) + return + } + w.WriteHeader(http.StatusOK) + } +} + +var ROUTES = []internals.Route{ + { + Pattern: "/index.html", + Static: true, + Page: pages.Homepage(), + Handler: NewStaticPageHandler(pages.Homepage()), + }, + { + Pattern: "/robots.txt", + Static: true, + Page: RobotsTxt(), + Handler: RobotsTxtHandler, + }, + { + Pattern: "/ai.txt", + Static: true, + Page: AiTxt(), + Handler: AiTxtHandler, + }, +} diff --git a/routes/twitter.templ b/routes/twitter.templ deleted file mode 100644 index 1e973b6..0000000 --- a/routes/twitter.templ +++ /dev/null @@ -1,95 +0,0 @@ -package routes - -import ( - "net/http" - "fmt" - "encoding/json" - "errors" - "os" - - "extrovert/layouts" - "extrovert/internals" -) - -type TwitterTokenResponse struct { - Type string `json:"token_type"` - Token string `json:"access_token"` - Expires int `json:"expires_in"` - Scope string `json:"scope"` -} - -func TwitterOAuthHandler(w http.ResponseWriter, r *http.Request) { - error := internals.HttpErrorHelper(w) - - code := r.URL.Query().Get("code") - if code == "" { - error( - "Bad request", - errors.New("Missing \"code\" parameter"), - http.StatusBadRequest, - ) - return - } - - tReq := fmt.Sprintf("https://api.twitter.com/2/oauth2/token"+ - "?grant_type=authorization_code"+ - "&client_id=%s"+ - "&code_verifier=challenge"+ - "&code=%s"+ - "&challenge_method=plain"+ - "&redirect_uri=http://localhost:7331/api/oauth/twitter", - os.Getenv("TWITTER_CLIENT_ID"), - code, - ) - - t, err := http.Post(tReq, "application/x-www-form-urlencoded", bytes.NewReader([]byte(""))) - if error("Error trying to request token from twitter", err, http.StatusInternalServerError) { - return - } - - b, err := io.ReadAll(t.Body) - if error("Error trying to read response body from twitter", err, http.StatusInternalServerError) { - return - } else if t.StatusCode < 200 || t.StatusCode > 299 { - error( - "Error trying to request token from twitter, returned non-200 code", - errors.New(fmt.Sprintf("Code: %v, Return value: %s", t.StatusCode, string(b))), - http.StatusInternalServerError, - ) - return - } - - var res TwitterTokenResponse - err = json.Unmarshal(b, &res) - if error("Error trying to parse response body from twitter", err, http.StatusInternalServerError) { - return - } - - c := internals.GetCookie("twitter-data", w, r) - c.Value = res.Token - http.SetCookie(w, c) - - TwitterOAuth().Render(context.Background(), w) -} - -templ TwitterOAuth() { - @layouts.Page("Project Extrovert") { - -
-
-

Logged into Twitter!

-
-

- Your account was succefully connected with project-extrovert! - Click "Ok" to return to the index page. -

- -
-
- @IndexPage() - } -} diff --git a/layouts/page.templ b/templates/layouts/page.templ similarity index 100% rename from layouts/page.templ rename to templates/layouts/page.templ diff --git a/routes/index.templ b/templates/pages/index.templ similarity index 81% rename from routes/index.templ rename to templates/pages/index.templ index a7e69a1..7c77103 100644 --- a/routes/index.templ +++ b/templates/pages/index.templ @@ -1,20 +1,11 @@ -package routes +package pages import ( - "net/http" - - "extrovert/layouts" + "extrovert/templates/layouts" "extrovert/components" - "extrovert/internals" ) -func IndexHandler(w http.ResponseWriter, r *http.Request) { - _ = internals.GetCookie("twitter-data", w, r) - - IndexPage().Render(context.TODO(), w) -} - -templ IndexPage() { +templ Homepage() { @layouts.Page("Project Extrovert") {
diff --git a/templates/pages/redirect.templ b/templates/pages/redirect.templ new file mode 100644 index 0000000..dc8b7af --- /dev/null +++ b/templates/pages/redirect.templ @@ -0,0 +1,26 @@ +package pages + +import ( + "extrovert/templates/layouts" +) + +templ RedirectPopUp(title string, msg string, url templ.SafeURL) { + @layouts.Page("Project Extrovert") { + +
+
+

{ title }

+
+

+ { msg } +

+ +
+
+ @Homepage() + } +}