From a34467a67d1adb7e9e3b59e8ac606e518a1802ac Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:34:15 -0600 Subject: [PATCH] feat: define links in separate json file --- cmd/web/main.go | 1 + internal/assets/images/favicon.ico | Bin 0 -> 15086 bytes internal/assets/json/links.json | 22 ++++++++++ internal/assets/main.go | 4 ++ internal/assets/templates/index.gohtml | 30 ++++---------- internal/handlers/main.go | 53 +++++++++++++++++++++---- 6 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 internal/assets/images/favicon.ico create mode 100644 internal/assets/json/links.json diff --git a/cmd/web/main.go b/cmd/web/main.go index 7dcae99..bff4884 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -20,6 +20,7 @@ func main() { mux.HandleFunc("/", handlers.Index) mux.HandleFunc("/css/", handlers.CSS) mux.HandleFunc("/fonts/", handlers.Fonts) + mux.HandleFunc("/images/", handlers.Images) loggingMux := middleware.Logging(mux) diff --git a/internal/assets/images/favicon.ico b/internal/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e1cc98aa38ba76f9142036aad57504921b06eb5d GIT binary patch literal 15086 zcmeI3U2IfE6vwAlEGke0^uef$QG`S!7!^O0CE>xK@k!JN6T_PdzDNS#ekm) z5fa{vHt`G5L?h5920{cQwn$QhZYdN?OIbi*P1at2=W-7@JKo*iWtV_*H<_F>=i`6Q z%$=D#ckW&18eF5BJJ&&{yH92~H_tgYeR@=%?A!rllcA!2>1yZR!eACQxVXg>k*B}3 z2htu$dm!zBv*Bx|JQwrT-l{a%5hWxrgnb&2uhw_CXL_MeZkK^54?e*Y_CuakTGx zxjvNNjon#vjW+**fr0zbFF$Wn>v^Q*WL)%S}!?J{FgYgY8q~_sZVA zdnc+5vSpMrF*u4hK--^G`+8DvkJ`A@HzdVpzE8%t$NG=Ke=FBJDgIb?J9q9J=f@iv z8EGENPxQmX!;@%h-uEA?j!$>J<~_0Q^5UIIlDpX8x7+j8v9cL?8)Xe;Pn=D?+Q7V= z?ct9k$seERJimaPw)OP%O!EDiU-w`;5@TQUbpUg!kL$;etCMTa`cdyvJNShZSph?hAx)y5WH?#Z5Q$()yK=jnsN!NCa@r(7HG`^DNN<)d-? zs~?y8G3LV*izBgpJN5`SS{wAoJ)1|YUib*-h-W8j@JGIFlDgbexURA<)^$9j$cE|j z8!QI0A!|o{qt&A;!9l;!>Xk3o23@z*E-8)X-@mSdjj5AX9>;{?K}4ZnDJvQ{6JSPFsu{Iy3nieH2180&ZVzUZ3NFOK5B$odFg zoLy2G@vfqef3}#ygFC4ASa?~t4pZd0^8EOK@ACY5lf?CWob z{%0))n4GlN{-X`DC)i=J6Y|IHe5sU4^c-Q^P1vBW&(@5Y&l2V>dGz>eZl(O`>kSs0 zddWSzl5aIS=JexW%bYFgD^r&-uuL(#ySpb)Uy4uJXGe4|7=OiGt{4}C$}>4JmvBwq zrA;4FR?y$~+gLL0$nAv5Z6EcgvAs^7`=F1alV=^xIi%Q}#J6tGm^PCSM&py^JS$4S8Aq7!wbWb31K0 zWpY15{VVDVF3Y6Tq|zQpdm!zBvKQ`Sa1vgiC=b4W*BQ%H*uEx4ve ziBrhD&6y}Pn|E1gKAUqHp)EN)g?7B&6w=N&8=-~ls%sjAW?CDa(B{_Gl`YN{n_5?O z2yI-|naesi+R)jV&(IFn(OD3>a`CF7(2m<%M|pb@LLLM;myKvkNb?dOc~SC$@~fT725CBFJ|+E5i%v*?X%AG<1MJmjvS&YkZYXYr2ralxj=7PVNFY^`0yA0TGQw*exr_T9I| zEf=3S_MCUK*x5LK9#&TlQ|0*X)g0v9NadEQUZ}{U^QmyWst>x7_l8Mx;2e3r;*-lM z8&`SNL)S&|<#}J{%YL=P|AKM=%bpFFysr~%6YD(_>u;5-OsJm&V={d2VQqXK8#xEp z_6R*@ZH%s{a%AtTTxCj;+cos8$}3TsJBlZib_}UK^#iJtvlfw~U#Bv)vlQKk>hZ~1 zT{*tJz1`0tJSQf%af(IUTf|@MLgVl}tl#2n;$;4{Al{(ZA#b3BXYRz@qVdu1io+3^ z+LX(&oL@^zi$5O1cT?c(x0qw$THA85%E_g@w5@0{e4ckoCGUkT7QXWSOvm<E+}* z-CtpF6mP9!5a(0I*9xr*r9Q>_*mtYeu2!Dduh}@%?fIkT`dzJY%K68AOJMVX-%-xD zo^sm#u@_g?pPBWHkz%Di@PFw6*4Efxk>i#ECo6eMZlEdXD>MZC1sC+^zijC{F+SUO zVnQZ=Vs&%=)iuq9xog^rlh?G3UfaAqaGCAF%1m33dy#{h4p2HWpk!l%Ae3_o{$duM zRa`0O6yEw&)wlKPOV^EKt^1vNxTk_Hr@UNgT;l5MrmeP0OovZ>?JjhJ0&L(r^AZ6r$%>@!JS&mdL(Bu*pH*q3_e{B>pz$SiHPL z6(22Q+5eaH=gvfB@5^_Ml+LVk>p5p#&(~VJ_#*blubs<1%sDTm*os}-Ia~Gn2TDk$ AJ^%m! literal 0 HcmV?d00001 diff --git a/internal/assets/json/links.json b/internal/assets/json/links.json new file mode 100644 index 0000000..5b17358 --- /dev/null +++ b/internal/assets/json/links.json @@ -0,0 +1,22 @@ +[ + { + "href": "https://youtube.stevanfreeborn.com", + "icon": "fa-brands fa-square-youtube fa-lg", + "text": "YouTube" + }, + { + "href": "https://github.stevanfreeborn.com", + "icon": "fa-brands fa-square-github fa-lg", + "text": "GitHub" + }, + { + "href": "https://blog.stevanfreeborn.com", + "icon": "fa-solid fa-square-rss fa-lg", + "text": "Blog" + }, + { + "href": "https://linkedin.stevanfreeborn.com", + "icon": "fa-brands fa-square-linkedin fa-lg", + "text": "LinkedIn" + } +] diff --git a/internal/assets/main.go b/internal/assets/main.go index ce695cb..6867e41 100644 --- a/internal/assets/main.go +++ b/internal/assets/main.go @@ -12,4 +12,8 @@ var ( CSS embed.FS //go:embed "fonts/*" Fonts embed.FS + //go:embed "json/*" + JSON embed.FS + //go:embed "images/*" + Images embed.FS ) diff --git a/internal/assets/templates/index.gohtml b/internal/assets/templates/index.gohtml index e12222b..21a211a 100644 --- a/internal/assets/templates/index.gohtml +++ b/internal/assets/templates/index.gohtml @@ -9,6 +9,7 @@ integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> + @@ -18,35 +19,20 @@ alt="A man with short brown hair, a grey hoodie, and a white t-shirt smiles at the camera. He has a white earbud in his left ear. The background is a blurred office setting with warm lighting visible through glass partitions." />

Stevan Freeborn

-

I'm a {{ .Age }}-year old dad of 2 who enjoys drinking coffee, lifting weights, and solving problems with code.

+

I'm a {{ .Age }}-year old dad of 2 who enjoys drinking coffee, lifting weights, and solving problems with code. +

diff --git a/internal/handlers/main.go b/internal/handlers/main.go index d55c241..6fd7e0a 100644 --- a/internal/handlers/main.go +++ b/internal/handlers/main.go @@ -2,6 +2,8 @@ package handlers import ( + "encoding/json" + "io/fs" "math" "net/http" "text/template" @@ -10,21 +12,24 @@ import ( "github.com/StevanFreeborn/links.stevanfreeborn.com/internal/assets" ) +const LINKS_JSON_PATH = "json/links.json" const DAYS_IN_YEAR = 365 const HOURS_IN_DAY = 24 var birthday time.Time = time.Date(1993, time.April, 21, 0, 0, 0, 0, time.UTC) +type Link struct { + Href string `json:"href"` + Icon string `json:"icon"` + Text string `json:"text"` +} + type IndexViewModel struct { - Age float64 + Age float64 + Links []Link } func Index(w http.ResponseWriter, r *http.Request) { - // TODO: Links coming in external JSON data - // this could be static file in the repo - // or it could be fetched - // { link: "link", text: "text", "icon": "icon" } - t, err := template.ParseFS(assets.Templates, "templates/index.gohtml") if err != nil { @@ -32,10 +37,25 @@ func Index(w http.ResponseWriter, r *http.Request) { return } + linksFile, err := assets.JSON.Open(LINKS_JSON_PATH) + + if err != nil { + http.Error(w, "Unable to load links", http.StatusInternalServerError) + return + } + + links, err := readLinksFromJSON(linksFile) + + if err != nil { + http.Error(w, "Unable to parse links", http.StatusInternalServerError) + return + } + age := time.Since(birthday).Hours() / HOURS_IN_DAY / DAYS_IN_YEAR viewModel := IndexViewModel{ - Age: math.Floor(age), + Age: math.Floor(age), + Links: links, } t.Execute(w, viewModel) @@ -48,3 +68,22 @@ func CSS(w http.ResponseWriter, r *http.Request) { func Fonts(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, assets.Fonts, r.URL.Path) } + +func Images(w http.ResponseWriter, r *http.Request) { + http.ServeFileFS(w, r, assets.Images, r.URL.Path) +} + +func readLinksFromJSON(file fs.File) ([]Link, error) { + defer file.Close() + var links []Link + + decoder := json.NewDecoder(file) + + err := decoder.Decode(&links) + + if err != nil { + return nil, err + } + + return links, nil +}