From 1061a38c6af63ea1dd5d7be3982a4fe52fbffe1b Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Fri, 1 Nov 2024 04:00:55 -0500 Subject: [PATCH] Complete transformation to htmx --- .gitignore | 1 + blog.go | 56 ++++---------------- files.go | 8 ++- internal/backend/darkstorm.go | 2 +- internal/blog/author.go | 11 ++++ internal/blog/blog.go | 38 +++++++++++++- internal/blog/portfolio.go | 99 +++++++++++++++++++++++++++++++++-- main.go | 70 ++++++++++++++++++------- portfolio.go | 57 ++------------------ 9 files changed, 215 insertions(+), 127 deletions(-) diff --git a/.gitignore b/.gitignore index e0fe3a4..e373643 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ darkstorm-server +test.sh diff --git a/blog.go b/blog.go index fdc8ddc..91fbcf2 100644 --- a/blog.go +++ b/blog.go @@ -1,28 +1,10 @@ package main import ( - "context" - "fmt" "log" "net/http" - "time" "github.com/CalebQ42/darkstorm-server/internal/backend" - "github.com/CalebQ42/darkstorm-server/internal/blog" -) - -const ( - blogTitle = "

%v

" - blogAuthor = "

By %v

" - blogCreate = "
Written on: %v
" - blogMain = "
%v
" - - authorInfo = ` -

About the author:

- - - -
%v

%v

%v
` ) func latestBlogsHandle(w http.ResponseWriter, r *http.Request) { @@ -40,9 +22,13 @@ func latestBlogsHandle(w http.ResponseWriter, r *http.Request) { } var out string for _, b := range latest { - out += blogElement(r.Context(), b) + out += b.HTMX(blogApp, r.Context()) + } + if r.Header.Get("Hx-Request") == "true" { + w.Write([]byte(out)) + } else { + sendContent(w, r, out, "", "") } - sendContent(w, r, out, "", "") } func blogHandle(w http.ResponseWriter, r *http.Request, blog string) { @@ -58,33 +44,9 @@ func blogHandle(w http.ResponseWriter, r *http.Request, blog string) { sendContent(w, r, "Error getting page", "", "") return } - sendContent(w, r, blogElement(r.Context(), bl), bl.Title, bl.Favicon) -} - -func blogElement(ctx context.Context, b *blog.Blog) (out string) { - if b.StaticPage { - return b.Blog - } - out = fmt.Sprintf(blogTitle, b.ID, b.ID, b.Title) - auth, err := blogApp.GetAuthor(ctx, b) - if err == nil { - out += fmt.Sprintf(blogAuthor, auth.Name) + if r.Header.Get("Hx-Request") == "true" { + w.Write([]byte(bl.HTMX(blogApp, r.Context()))) } else { - out += fmt.Sprintf(blogAuthor, "unknown") + sendContent(w, r, bl.HTMX(blogApp, r.Context()), bl.Title, bl.Favicon) } - cTime := time.Unix(b.CreateTime, 0).Format(time.DateOnly) - if b.UpdateTime > b.CreateTime { - out += fmt.Sprintf(blogCreate, cTime+"; Last updated on: "+time.Unix(b.UpdateTime, 0).Format(time.DateOnly)) - } else { - out += fmt.Sprintf(blogCreate, cTime) - } - out += fmt.Sprintf(blogMain, b.Blog) - if err == nil { - out += authorSection(auth) - } - return -} - -func authorSection(a *blog.Author) string { - return fmt.Sprintf(authorInfo, a.PicURL, a.Name+"'s profile picture", a.Name, a.About) } diff --git a/files.go b/files.go index 7f7f033..6488b8f 100644 --- a/files.go +++ b/files.go @@ -21,7 +21,7 @@ func filesRequest(w http.ResponseWriter, r *http.Request) { if err != nil { if os.IsNotExist(err) { pageContent = "

404 Not Found

" - w.WriteHeader(http.StatusNotFound) + // w.WriteHeader(http.StatusNotFound) } else { pageContent = "

Server error!

" w.WriteHeader(http.StatusInternalServerError) @@ -52,5 +52,9 @@ func filesRequest(w http.ResponseWriter, r *http.Request) { return } } - sendContent(w, r, pageContent, "Files", "") + if r.Header.Get("Hx-Request") == "true" { + w.Write([]byte(pageContent)) + } else { + sendContent(w, r, pageContent, "Files", "") + } } diff --git a/internal/backend/darkstorm.go b/internal/backend/darkstorm.go index 0c3cc5a..bfb22a5 100644 --- a/internal/backend/darkstorm.go +++ b/internal/backend/darkstorm.go @@ -104,7 +104,7 @@ func (b *Backend) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { w.Header().Set("Access-Control-Allow-Methods", "*") w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Authorization, X-API-Key, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers") + w.Header().Set("Access-Control-Allow-Headers", "*") } } b.m.ServeHTTP(w, r) diff --git a/internal/blog/author.go b/internal/blog/author.go index da47f5f..b553190 100644 --- a/internal/blog/author.go +++ b/internal/blog/author.go @@ -3,6 +3,7 @@ package blog import ( "context" "encoding/json" + "fmt" "log" "net/http" "strconv" @@ -13,6 +14,12 @@ import ( "go.mongodb.org/mongo-driver/mongo" ) +const authorInfo = ` + + + +
%v

%v

%v
` + type Author struct { ID string `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` @@ -20,6 +27,10 @@ type Author struct { PicURL string `json:"picurl" bson:"picurl"` } +func (a Author) HTML() string { + return fmt.Sprintf(authorInfo, a.PicURL, a.Name+"'s profile picture", a.Name, a.About) +} + func (b *BlogApp) AboutMe(ctx context.Context) (*Author, error) { res := b.authCol.FindOne(ctx, bson.M{"_id": "caleb_gardner"}) if res.Err() != nil { diff --git a/internal/blog/blog.go b/internal/blog/blog.go index 8cddc1a..8057193 100644 --- a/internal/blog/blog.go +++ b/internal/blog/blog.go @@ -3,6 +3,7 @@ package blog import ( "context" "encoding/json" + "fmt" "log" "net/http" "strconv" @@ -15,6 +16,13 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) +const ( + blogTitle = "

%v

" + blogAuthor = "

By %v

" + blogCreate = "
Written on: %v
" + blogMain = "
%v
" +) + type Blog struct { ID string `json:"id" bson:"_id"` Author string `json:"author" bson:"author"` @@ -27,6 +35,30 @@ type Blog struct { UpdateTime int64 `json:"updateTime" bson:"updateTime"` } +func (b *Blog) HTMX(blogApp *BlogApp, ctx context.Context) string { + if b.StaticPage { + return b.Blog + } + out := fmt.Sprintf(blogTitle, b.ID, b.ID, b.Title) + auth, err := blogApp.GetAuthor(ctx, b) + if err == nil { + out += fmt.Sprintf(blogAuthor, auth.Name) + } else { + out += fmt.Sprintf(blogAuthor, "unknown") + } + cTime := time.Unix(b.CreateTime, 0).Format(time.DateOnly) + if b.UpdateTime > b.CreateTime { + out += fmt.Sprintf(blogCreate, cTime+"; Last updated on: "+time.Unix(b.UpdateTime, 0).Format(time.DateOnly)) + } else { + out += fmt.Sprintf(blogCreate, cTime) + } + out += fmt.Sprintf(blogMain, b.Blog) + if err == nil { + out += "

About the author:

" + auth.HTML() + } + return out +} + func (b *BlogApp) ConvertBlog(blog *Blog) { if !blog.StaticPage { blog.Blog = b.conv.HTMLConvert(blog.Blog) @@ -95,7 +127,11 @@ func (b *BlogApp) reqBlog(w http.ResponseWriter, r *http.Request) { backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error") return } - json.NewEncoder(w).Encode(blog) + if r.Header.Get("Hx-Request") == "true" { + w.Write([]byte(blog.HTMX(b, r.Context()))) + } else { + json.NewEncoder(w).Encode(blog) + } } func (b *BlogApp) createBlog(w http.ResponseWriter, r *http.Request) { diff --git a/internal/blog/portfolio.go b/internal/blog/portfolio.go index b9aca36..2fba534 100644 --- a/internal/blog/portfolio.go +++ b/internal/blog/portfolio.go @@ -3,13 +3,24 @@ package blog import ( "context" "encoding/json" + "fmt" "net/http" + "slices" + "strings" "github.com/CalebQ42/darkstorm-server/internal/backend" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) +const ( + portfolioTitle = "

%v

" + portfolioLink = "

%v: %v

" + portfolioTech = "

Tech: %v

" + portfolioDesc = "

%v

" +) + type PortfolioProject struct { Title string `json:"_id" bson:"_id"` Order int `json:"order" bson:"order"` @@ -22,10 +33,80 @@ type PortfolioProject struct { } `json:"language" bson:"language"` } -func (b *BlogApp) Projects(ctx context.Context, languageFilter string) ([]PortfolioProject, error) { +func (p PortfolioProject) HTMX() string { + out := fmt.Sprintf(portfolioTitle, p.Title) + out += fmt.Sprintf(portfolioLink, p.Repository, p.Repository) + for _, l := range p.Languages { + out += fmt.Sprintf(portfolioLanguage, l.Language, l.Dates) + } + out += fmt.Sprintf(portfolioTech, strings.Join(p.Technologies, ", ")) + out += fmt.Sprintf(portfolioDesc, p.Description) + return out +} + +type Portfolio []PortfolioProject + +const ( + portfolioSelector = ` +

Tech Filter: + +

` + portfolioSelectorOption = "" +) + +func (p Portfolio) FullHTMX(ctx context.Context, blogApp *BlogApp, selectedTech string) string { + aboutMe := "

About Me

" + if me, err := blogApp.AboutMe(ctx); err != nil { + aboutMe += "Error getting info about me :(" + } else { + aboutMe += me.HTML() + } + aboutMe += "

My Projects

" + tech := make(map[string]struct{}) + for i := range p { + for _, t := range p[i].Technologies { + tech[t] = struct{}{} + } + } + techKeys := make([]string, 0, len(tech)) + for k := range tech { + techKeys = append(techKeys, k) + } + slices.Sort(techKeys) + var out string + if selectedTech == "" { + out = fmt.Sprintf(portfolioSelectorOption, "", " selected=true", "All") + } else { + out = fmt.Sprintf(portfolioSelectorOption, "", "", "All") + } + for _, k := range techKeys { + if selectedTech == strings.ToLower(k) { + out += fmt.Sprintf(portfolioSelectorOption, k, " selected=true", k) + } else { + out += fmt.Sprintf(portfolioSelectorOption, k, "", k) + } + } + return aboutMe + fmt.Sprintf(portfolioSelector, out) + "
" + p.HTMX() + "
" +} + +func (p Portfolio) HTMX() string { + out := "" + for _, proj := range p { + out += proj.HTMX() + } + return out +} + +func (b *BlogApp) Projects(ctx context.Context, techFilter string) (Portfolio, error) { filter := bson.M{} - if languageFilter != "" { - filter["language.language"] = languageFilter + if techFilter != "" { + filter = bson.M{"technologies": techFilter} } res, err := b.portfolioCol.Find(ctx, filter, options.Find().SetSort(bson.M{"order": 1})) if err != nil { @@ -37,10 +118,18 @@ func (b *BlogApp) Projects(ctx context.Context, languageFilter string) ([]Portfo } func (b *BlogApp) reqPortfolio(w http.ResponseWriter, r *http.Request) { - folio, err := b.Projects(r.Context(), r.URL.Query().Get("lang")) + folio, err := b.Projects(r.Context(), r.URL.Query().Get("tech")) if err != nil { backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server Error") return } - json.NewEncoder(w).Encode(folio) + if r.Header.Get("Hx-Request") == "true" { + if r.URL.Query().Has("tech") { + w.Write([]byte(folio.HTMX())) + } else { + w.Write([]byte(folio.FullHTMX(r.Context(), b, r.URL.Query().Get("tech")))) + } + } else { + json.NewEncoder(w).Encode(folio) + } } diff --git a/main.go b/main.go index 367fcdd..4a55683 100644 --- a/main.go +++ b/main.go @@ -27,23 +27,30 @@ var ( back *backend.Backend blogApp *blog.BlogApp webRoot *string + testing *bool ) func main() { mongoURL := flag.String("mongo", "", "Enables MongoDB usage for Darkstorm backend.") webRoot = flag.String("web-root", "", "Sets root directory of web server.") addr := flag.String("addr", ":443", "Set listen address. Defaults to \":443\"") + testing = flag.Bool("testing", false, "Start in testing mode. If you don't know what this is, don't use it.") flag.Parse() - if flag.NArg() != 1 { + if *testing { + *addr = ":4242" + } + if !*testing && flag.NArg() != 1 { log.Fatal("You must specify key directory. ex: darkstorm-server /etc/web-keys") } if *mongoURL == "" || *webRoot == "" { log.Fatal("SPECIFY MONGO AND WEB-ROOT OR I WILL DIE, OH NO, THEY'RE COMING FOR ME.... **DEATH NOISES**") } - go func() { - log.Println("error redirecting http traffice:", - http.ListenAndServe(":80", http.RedirectHandler("https://darkstorm.tech", http.StatusPermanentRedirect))) - }() + if !*testing { + go func() { + log.Println("error redirecting http traffice:", + http.ListenAndServe(":80", http.RedirectHandler("https://darkstorm.tech", http.StatusPermanentRedirect))) + }() + } mux := http.NewServeMux() setupMongo(*mongoURL) setupBackend(mux) @@ -52,20 +59,33 @@ func main() { Addr: *addr, Handler: mux, } - err := serv.ListenAndServeTLS(filepath.Join(flag.Arg(0), "fullchain.pem"), filepath.Join(flag.Arg(0), "key.pem")) + var err error + if *testing { + err = serv.ListenAndServe() + } else { + err = serv.ListenAndServeTLS(filepath.Join(flag.Arg(0), "fullchain.pem"), filepath.Join(flag.Arg(0), "key.pem")) + } log.Println("webserver closed:", err) } func setupMongo(uri string) { - mongoCert, err := tls.LoadX509KeyPair(filepath.Join(flag.Arg(0), "mongo.pem"), filepath.Join(flag.Arg(0), "key.pem")) - if err != nil { - log.Fatal("error loading mongo keys:", err) - } - mongoClient, err = mongo.Connect(context.Background(), options.Client().ApplyURI(uri).SetTLSConfig(&tls.Config{ - Certificates: []tls.Certificate{mongoCert}, - })) - if err != nil { - log.Fatal("error connecting to mongo:", err) + if !*testing { + mongoCert, err := tls.LoadX509KeyPair(filepath.Join(flag.Arg(0), "mongo.pem"), filepath.Join(flag.Arg(0), "key.pem")) + if err != nil { + log.Fatal("error loading mongo keys:", err) + } + mongoClient, err = mongo.Connect(context.Background(), options.Client().ApplyURI(uri).SetTLSConfig(&tls.Config{ + Certificates: []tls.Certificate{mongoCert}, + })) + if err != nil { + log.Fatal("error connecting to mongo:", err) + } + } else { + var err error + mongoClient, err = mongo.Connect(context.Background(), options.Client().ApplyURI(uri)) + if err != nil { + log.Fatal("error connecting to mongo:", err) + } } } @@ -78,16 +98,28 @@ func setupBackend(mux *http.ServeMux) { swassistant.NewSWBackend(mongoClient.Database("swassistant")), cdr.NewBackend(mongoClient.Database("cdr")), ) - back.AddCorsAddress("https://darkstorm.tech") + if !*testing { + back.AddCorsAddress("https://darkstorm.tech") + } else { + back.AddCorsAddress("*") + } if err != nil { log.Fatal("error setting up backend:", err) } - mux.Handle("api.darkstorm.tech/", back) + if !*testing { + mux.Handle("api.darkstorm.tech/", back) + } else { + go func() { + http.ListenAndServe(":2323", back) + }() + } } func setupWebsite(mux *http.ServeMux) { - url, _ := url.Parse("https://localhost:30000") - mux.Handle("rpg.darkstorm.tech/", httputil.NewSingleHostReverseProxy(url)) + if !*testing { + url, _ := url.Parse("https://localhost:30000") + mux.Handle("rpg.darkstorm.tech/", httputil.NewSingleHostReverseProxy(url)) + } mux.HandleFunc("GET /files/{w...}", filesRequest) mux.HandleFunc("GET /portfolio", portfolioRequest) mux.HandleFunc("/", mainHandle) diff --git a/portfolio.go b/portfolio.go index b46c83e..765221b 100644 --- a/portfolio.go +++ b/portfolio.go @@ -1,69 +1,22 @@ package main import ( - "fmt" "log" "net/http" - "slices" - "strings" -) - -const ( - portfolioSelector = "

Language Filter:

" - portfolioSelectorOption = "" - portfolioTitle = "

%v

" - portfolioLink = "

%v: %v

" - portfolioTech = "

Tech: %v

" - portfolioDesc = "

%v

" ) func portfolioRequest(w http.ResponseWriter, r *http.Request) { - selectedLang := r.URL.Query().Get("lang") - proj, err := blogApp.Projects(r.Context(), selectedLang) + selectedTech := r.URL.Query().Get("tech") + proj, err := blogApp.Projects(r.Context(), selectedTech) if err != nil { log.Println("error getting portfolio projects:", err) w.WriteHeader(http.StatusInternalServerError) sendContent(w, r, "Error getting portfolio", "", "") return } - aboutMe := "

About Me

" - if me, err := blogApp.AboutMe(r.Context()); err != nil { - aboutMe += "Error getting info about me :(" + if r.Header.Get("Hx-Request") == "true" { + w.Write([]byte(proj.FullHTMX(r.Context(), blogApp, selectedTech))) } else { - aboutMe += authorSection(me) + sendContent(w, r, proj.FullHTMX(r.Context(), blogApp, selectedTech), "Portfolio", "") } - aboutMe += "

My Projects

" - langs := make(map[string]struct{}) - out := "" - for _, p := range proj { - out += fmt.Sprintf(portfolioTitle, p.Title) - out += fmt.Sprintf(portfolioLink, p.Repository, p.Repository) - for _, l := range p.Languages { - langs[l.Language] = struct{}{} - out += fmt.Sprintf(portfolioLanguage, l.Language, l.Dates) - } - out += fmt.Sprintf(portfolioTech, strings.Join(p.Technologies, ", ")) - out += fmt.Sprintf(portfolioDesc, p.Description) - } - langKeys := make([]string, 0, len(langs)) - for k := range langs { - langKeys = append(langKeys, k) - } - slices.Sort(langKeys) - var tmp string - if selectedLang == "" { - tmp = fmt.Sprintf(portfolioSelectorOption, "", " selected=true", "All") - } else { - tmp = fmt.Sprintf(portfolioSelectorOption, "", "", "All") - } - for _, k := range langKeys { - if selectedLang == strings.ToLower(k) { - tmp += fmt.Sprintf(portfolioSelectorOption, strings.ToLower(k), " selected=true", k) - } else { - tmp += fmt.Sprintf(portfolioSelectorOption, strings.ToLower(k), "", k) - } - } - out = aboutMe + fmt.Sprintf(portfolioSelector, tmp) + out - sendContent(w, r, out, "Portfolio", "") }