refactor: change project and templates layout

This commit is contained in:
Gustavo "Guz" L. de Mello
2024-07-13 17:58:03 -03:00
parent 10eaaf4d5f
commit 7ab8527a44
9 changed files with 96 additions and 446 deletions

22
internals/router.go Normal file
View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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") {
<dialog open>
<article>
<header>
<p>Mastodon app created!</p>
</header>
<p>
An app for your interactions was created on { u.Hostname() },
Click "Ok" to authorize your account.
</p>
<footer>
<a href={ templ.SafeURL(u.String()) }>
<button>Ok</button>
</a>
</footer>
</article>
</dialog>
@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") {
<dialog open>
<article>
<header>
<p>Logged into Mastodon!</p>
</header>
<p>
Your account was succefully connected with project-extrovert!
Click "Ok" to return to the index page.
</p>
<footer>
<a href="/index.html">
<button>Ok</button>
</a>
</footer>
</article>
</dialog>
@IndexPage()
}
}

View File

@@ -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)
}
}

44
routes/routes.go Normal file
View File

@@ -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,
},
}

View File

@@ -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") {
<dialog open>
<article>
<header>
<p>Logged into Twitter!</p>
</header>
<p>
Your account was succefully connected with project-extrovert!
Click "Ok" to return to the index page.
</p>
<footer>
<a href="/index.html">
<button>Ok</button>
</a>
</footer>
</article>
</dialog>
@IndexPage()
}
}

View File

@@ -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") {
<div style="max-width:50rem;">
<div style="display:flex;flex-direction:column;gap:1rem;">

View File

@@ -0,0 +1,26 @@
package pages
import (
"extrovert/templates/layouts"
)
templ RedirectPopUp(title string, msg string, url templ.SafeURL) {
@layouts.Page("Project Extrovert") {
<dialog open>
<article>
<header>
<p>{ title }</p>
</header>
<p>
{ msg }
</p>
<footer>
<a href={ url }>
<button>Ok</button>
</a>
</footer>
</article>
</dialog>
@Homepage()
}
}