Mirror for https://github.com/STBoyden/go-portfolio
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: stream some components of the site

stboyden.com 87b11005 ed56ff4f

verified
+110 -35
+3
internal/pkg/common/consts/consts.go
··· 1 + package consts 2 + 3 + const TokenCookieName string = "TOKEN"
+17
internal/pkg/common/utils/streaming.go
··· 1 + package utils 2 + 3 + // StreamArray turns a given array into a receive-only chan that can be used to 4 + // stream data to client. 5 + func StreamArray[T any](array []T) <-chan T { 6 + ch := make(chan T, len(array)) 7 + 8 + go func() { 9 + defer close(ch) 10 + 11 + for _, element := range array { 12 + ch <- element 13 + } 14 + }() 15 + 16 + return ch 17 + }
+4 -4
internal/pkg/routes/api/v1/api_router.go
··· 3 3 import "net/http" 4 4 5 5 func Router() *http.ServeMux { 6 - r := http.NewServeMux() 6 + mux := http.NewServeMux() 7 7 8 - r.Handle("/github/", http.StripPrefix("/github", GithubAPI())) 9 - r.Handle("/blog/", http.StripPrefix("/blog", BlogAPI())) 8 + mux.Handle("/github/", http.StripPrefix("/github", GithubAPI())) 9 + mux.Handle("/blog/", http.StripPrefix("/blog", BlogAPI())) 10 10 11 - return r 11 + return mux 12 12 }
+5 -4
internal/pkg/routes/api/v1/github_api.go
··· 6 6 "net/http" 7 7 "os" 8 8 9 + "github.com/a-h/templ" 9 10 gh "github.com/shurcooL/githubv4" 10 11 "golang.org/x/oauth2" 11 12 ··· 54 55 } 55 56 56 57 func GithubAPI() *http.ServeMux { 57 - r := http.NewServeMux() 58 + mux := http.NewServeMux() 58 59 59 60 token, ok := os.LookupEnv("GITHUB_TOKEN") 60 61 if !ok { ··· 65 66 c := oauth2.NewClient(context.Background(), src) 66 67 client := gh.NewClient(c) 67 68 68 - r.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { 69 + mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { 69 70 input := gh.LanguageOrder{ 70 71 Field: gh.LanguageOrderFieldSize, 71 72 Direction: gh.OrderDirectionDesc, ··· 108 109 repositories[i] = repository 109 110 } 110 111 111 - _ = components.Repositories(repositories).Render(r.Context(), w) 112 + templ.Handler(components.Repositories(repositories), templ.WithStreaming()).ServeHTTP(w, r) 112 113 }) 113 114 114 - return r 115 + return mux 115 116 }
+6 -5
internal/pkg/routes/root_router.go
··· 10 10 ) 11 11 12 12 func Router(static embed.FS) *http.ServeMux { 13 - r := http.NewServeMux() 14 - r.Handle("/", middleware.Handlers.Logger(site.Router())) 15 - r.Handle("/api/v1/", middleware.Handlers.Logger(http.StripPrefix("/api/v1", v1.Router()))) 16 - r.Handle("/static/", http.FileServer(http.FS(static))) 13 + mux := http.NewServeMux() 14 + 15 + mux.Handle("/", middleware.Handlers.Logger(site.Router())) 16 + mux.Handle("/api/v1/", middleware.Handlers.Logger(http.StripPrefix("/api/v1", v1.Router()))) 17 + mux.Handle("/static/", middleware.Handlers.Logger(http.FileServer(http.FS(static)))) 17 18 18 - return r 19 + return mux 19 20 }
+18 -3
internal/pkg/routes/site/views/components/post_list_item.templ
··· 1 1 package components 2 2 3 3 import "github.com/STBoyden/go-portfolio/internal/pkg/persistence" 4 + import "github.com/STBoyden/go-portfolio/internal/pkg/common/utils" 4 5 5 - templ PostList(posts []persistence.Post) { 6 - if len(posts) == 0 { 6 + templ PostList(posts []persistence.Post, showEdit bool) { 7 + if !showEdit && len(posts) == 0 { 7 8 <div> 8 9 No blog posts (yet...) 9 10 </div> 10 11 } else { 11 - <div></div> 12 + <div class="flex flex-row gap-2"> 13 + for post := range utils.StreamArray(posts) { 14 + @templ.Flush() { 15 + <span>{ post.Content.Title }</span> 16 + } 17 + } 18 + <button 19 + class="btn btn-md btn-info text-info-content" 20 + hx-target="#body" 21 + hx-get="/blog/admin/new-post" 22 + hx-swap="innerHTML" 23 + > 24 + New post 25 + </button> 26 + </div> 12 27 } 13 28 }
+18 -15
internal/pkg/routes/site/views/components/repositories.templ
··· 1 1 package components 2 2 3 3 import "github.com/STBoyden/go-portfolio/internal/pkg/common/types" 4 + import "github.com/STBoyden/go-portfolio/internal/pkg/common/utils" 4 5 5 6 templ Repositories(repos []types.Repository) { 6 7 <script> ··· 9 10 } 10 11 </script> 11 12 <div class="flex flex-col lg:grid lg:grid-cols-3 gap-4 justify-center"> 12 - for _, repo := range repos { 13 - <div 14 - class="card w-full bg-base-200 hover:bg-base-300 cursor-pointer card-sm shadow-sm" 15 - onclick={ templ.JSFuncCall("goToRepo", repo.URL) } 16 - title={ repo.URL } 17 - > 18 - <div class="card-body"> 19 - <h2 class="card-title">{ repo.Owner + "/"+ repo.Name }</h2> 20 - if repo.Description != "" { 21 - <p>{ repo.Description }</p> 22 - } 23 - <div class="justify-left card-actions"> 24 - for _, language := range repo.Languages { 25 - @LanguageTag(language) 13 + for repo := range utils.StreamArray(repos) { 14 + @templ.Flush() { 15 + <div 16 + class="card w-full bg-base-200 hover:bg-base-300 cursor-pointer card-sm shadow-sm" 17 + onclick={ templ.JSFuncCall("goToRepo", repo.URL) } 18 + title={ repo.URL } 19 + > 20 + <div class="card-body"> 21 + <h2 class="card-title">{ repo.Owner + "/"+ repo.Name }</h2> 22 + if repo.Description != "" { 23 + <p>{ repo.Description }</p> 26 24 } 25 + <div class="justify-left card-actions"> 26 + for _, language := range repo.Languages { 27 + @LanguageTag(language) 28 + } 29 + </div> 27 30 </div> 28 31 </div> 29 - </div> 32 + } 30 33 } 31 34 </div> 32 35 }
+1 -1
internal/pkg/routes/site/views/root.templ
··· 8 8 <head> 9 9 <title>Samuel Boyden</title> 10 10 <link href="/static/css/styles.css" rel="stylesheet"/> 11 - <script src="/static/js/htmx.min.js"></script> 11 + <script src="/static/js/htmx.min.js" defer></script> 12 12 <script src="/static/js/alpinejs.min.js" defer></script> 13 13 <meta name="viewport" content="width=device-width, initial-scale=1.0"/> 14 14 </head>
+3
migrations/0004_add_default_value_for_auth_id.down.sql
··· 1 + ALTER TABLE "authorisations" 2 + ALTER COLUMN "id" 3 + SET DEFAULT uuid_generate_v4 ();
migrations/0004_add_default_value_for_auth_id.up.sql

This is a binary file and will not be displayed.

+1
migrations/0005_install_uuid_extension.down.sql
··· 1 + DROP EXTENSION IF NOT EXISTS "uuid-ossp";
+1
migrations/0005_install_uuid_extension.up.sql
··· 1 + CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+2
migrations/0006_add_published_field.down.sql
··· 1 + ALTER TABLE "posts" 2 + DROP COLUMN "published";
+19
migrations/0006_add_published_field.up.sql
··· 1 + -- alter posts table to have published field. 2 + ALTER TABLE "posts" 3 + ADD COLUMN "published" BOOLEAN; 4 + 5 + -- set all published column values to false 6 + UPDATE "posts" 7 + SET 8 + "published" = 'f'; 9 + 10 + -- require all published fields to be non-null (should be ensured by above) 11 + ALTER TABLE "posts" 12 + ALTER COLUMN "published" 13 + SET 14 + NOT NULL; 15 + 16 + -- set default value for future posts to be FALSE 17 + ALTER TABLE "posts" 18 + ALTER COLUMN "published" 19 + SET DEFAULT FALSE;
+3
queries/auth.sql
··· 4 4 -- name: GetExpiredAuths :many 5 5 SELECT * FROM "authorisations" WHERE "expiry" <= current_timestamp; 6 6 7 + -- name: GetAuthByToken :one 8 + SELECT * FROM "authorisations" WHERE "id" = @id::uuid LIMIT 1; 9 + 7 10 -- name: CheckAuthExists :one 8 11 SELECT EXISTS (SELECT * FROM "authorisations" WHERE "id" = @id::uuid); 9 12
+9 -3
queries/posts.sql
··· 1 1 -- name: CreatePost :one 2 - INSERT INTO "posts" (slug, content) 2 + INSERT INTO "posts" (slug, content) 3 3 VALUES (@slug::text, @content::json) 4 4 RETURNING *; 5 5 6 - -- name: GetPosts :many 7 - SELECT * FROM "posts"; 6 + -- name: GetPublishedPosts :many 7 + SELECT * FROM "posts" WHERE published = TRUE; 8 + 9 + -- name: GetPostByID :one 10 + SELECT * FROM "posts" WHERE id = @id::uuid LIMIT 1; 11 + 12 + -- name: GetAllPosts :many 13 + SELECT * FROM "posts";