refactor: change project and templates layout
This commit is contained in:
22
internals/router.go
Normal file
22
internals/router.go
Normal 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)
|
||||
}
|
||||
}
|
||||
2
main.go
2
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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
44
routes/routes.go
Normal 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,
|
||||
},
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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;">
|
||||
26
templates/pages/redirect.templ
Normal file
26
templates/pages/redirect.templ
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user