refactor: tidy up app structure and entrypoint file

This commit is contained in:
Marc 2024-09-13 00:38:46 -04:00
parent 8dafeb7e17
commit 82f1c4bbb2
Signed by: marc
GPG key ID: 048E042F22B5DC79
3 changed files with 114 additions and 93 deletions

View file

@ -12,9 +12,15 @@ type Route struct {
Handler http.HandlerFunc
}
type API struct {
Routes []Route
StaticRoot string
type BackgroundTask struct {
Handler func()
Interval time.Duration
}
type App struct {
Routes []Route
BackgroundTasks []BackgroundTask
StaticRoot string
}
func LoggingMiddleware(f http.HandlerFunc) http.HandlerFunc {
@ -25,7 +31,7 @@ func LoggingMiddleware(f http.HandlerFunc) http.HandlerFunc {
}
}
func (a API) Start(addr string) {
func (a App) Start(addr string) {
for _, route := range a.Routes {
http.HandleFunc(route.Path, LoggingMiddleware(route.Handler))
}
@ -34,9 +40,28 @@ func (a API) Start(addr string) {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(a.StaticRoot))))
}
for _, task := range a.BackgroundTasks {
go func() {
for {
task.Handler()
time.Sleep(task.Interval)
}
}()
}
http.ListenAndServe(addr, nil)
}
func (a *API) AddRoute(path string, handler http.HandlerFunc) {
func (a *App) AddRoute(path string, handler http.HandlerFunc) {
a.Routes = append(a.Routes, Route{Path: path, Handler: handler})
}
func NewApp(routes map[string]http.HandlerFunc, backgroundTasks []BackgroundTask, staticRoot string) *App {
app := App{StaticRoot: staticRoot, BackgroundTasks: backgroundTasks}
for route, handler := range routes {
app.AddRoute(route, handler)
}
return &app
}

93
main.go
View file

@ -1,86 +1,12 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"text/template"
"time"
)
var SharedCache *Datastore
type Link struct {
Url string `json:"url"`
PublishedDate string `json:"publishedDate"`
Title string `json:"title"`
}
// Healthcheck route
//
// Confirms that the app is alive without guarantees
// about it being fully-functional.
func healthcheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// About page
//
// Static content for the about page.
func about(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.New("about.html.tmpl").ParseFiles("templates/about.html.tmpl")
tmpl.Execute(w, nil)
}
// Feeds list
//
// Lists all feed elements in store.
func listContent(w http.ResponseWriter, r *http.Request) {
links := []Link{}
for _, feed := range SharedCache.List("feeds") {
var formattedItems []Link
json.Unmarshal([]byte(feed), &formattedItems)
links = append(links, formattedItems...)
}
tmpl, _ := template.New("index.html.tmpl").ParseFiles("templates/index.html.tmpl")
tmpl.Execute(w, links)
}
// Manage content
//
// Interface to add new feed subscriptions and get a list
// of current subs.
func manageContent(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
r.ParseForm()
urlValue := r.PostFormValue("url")
if _, err := url.Parse(urlValue); err != nil {
w.WriteHeader(400)
return
}
cacheKey := fmt.Sprintf("feedurl:%s", urlValue)
SharedCache.Set(cacheKey, urlValue)
go fetchFeed(urlValue)
w.WriteHeader(201)
}
allFeeds := SharedCache.List("feedurl")
type ManageTmplData struct {
Feeds map[string]string
}
tmpl, _ := template.New("manage.html.tmpl").ParseFiles("templates/manage.html.tmpl")
tmpl.Execute(w, ManageTmplData{Feeds: allFeeds})
}
var routeMap = map[string]http.HandlerFunc{
"/": listContent,
"/about": about,
@ -88,6 +14,10 @@ var routeMap = map[string]http.HandlerFunc{
"/ping": healthcheck,
}
var bgTasks = []BackgroundTask{
{refreshFeeds, 10 * time.Minute},
}
func main() {
SharedCache = &Datastore{
Data: map[string]string{},
@ -97,18 +27,5 @@ func main() {
SharedCache = existingStore
}
api := API{StaticRoot: "./static"}
for route, handler := range routeMap {
api.AddRoute(route, handler)
}
go func() {
for {
refreshFeeds()
time.Sleep(10 * time.Minute)
}
}()
api.Start(":9000")
NewApp(routeMap, bgTasks, "./static").Start(":9000")
}

79
routes.go Normal file
View file

@ -0,0 +1,79 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"text/template"
)
type Link struct {
Url string `json:"url"`
PublishedDate string `json:"publishedDate"`
Title string `json:"title"`
}
// Healthcheck route
//
// Confirms that the app is alive without guarantees
// about it being fully-functional.
func healthcheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// About page
//
// Static content for the about page.
func about(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.New("about.html.tmpl").ParseFiles("templates/about.html.tmpl")
tmpl.Execute(w, nil)
}
// Feeds list
//
// Lists all feed elements in store.
func listContent(w http.ResponseWriter, r *http.Request) {
links := []Link{}
for _, feed := range SharedCache.List("feeds") {
var formattedItems []Link
json.Unmarshal([]byte(feed), &formattedItems)
links = append(links, formattedItems...)
}
tmpl, _ := template.New("index.html.tmpl").ParseFiles("templates/index.html.tmpl")
tmpl.Execute(w, links)
}
// Manage content
//
// Interface to add new feed subscriptions and get a list
// of current subs.
func manageContent(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
r.ParseForm()
urlValue := r.PostFormValue("url")
if _, err := url.Parse(urlValue); err != nil {
w.WriteHeader(400)
return
}
cacheKey := fmt.Sprintf("feedurl:%s", urlValue)
SharedCache.Set(cacheKey, urlValue)
go fetchFeed(urlValue)
w.WriteHeader(201)
}
allFeeds := SharedCache.List("feedurl")
type ManageTmplData struct {
Feeds map[string]string
}
tmpl, _ := template.New("manage.html.tmpl").ParseFiles("templates/manage.html.tmpl")
tmpl.Execute(w, ManageTmplData{Feeds: allFeeds})
}