From 406383e3c1b2f325d94404cad211344efb65eab0 Mon Sep 17 00:00:00 2001 From: Lewis Dale Date: Sun, 12 May 2024 21:48:27 +0100 Subject: [PATCH] Finish tomorrow's WeblogPoMo post --- src/blog/posts/2024/5/learning-go-day-11.md | 64 ++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/blog/posts/2024/5/learning-go-day-11.md b/src/blog/posts/2024/5/learning-go-day-11.md index b0f4e94..c30a268 100644 --- a/src/blog/posts/2024/5/learning-go-day-11.md +++ b/src/blog/posts/2024/5/learning-go-day-11.md @@ -96,5 +96,65 @@ And if I then force a failure, it should also work: ## Adding some output -Okay, now I have a database with some data in it, I'd like to expose that data -[^1]: Yes, despite what I said in another post I've not written any _actual_ tests. I'm human, alright? \ No newline at end of file +Right, now to actually output the values in the database. First of all, I've defined the Struct for the response: + +```go +// ping/ping.go + +type PingResponse struct { + Site sites.Site + Timestamp string + Status string +} +``` + +And now I've added a `List` function that reads the data I need from the database, and places it into an array slice of `PingResponse` values: + +```go +func List(db *sql.DB) []PingResponse { + rows, err := db.Query(`SELECT sites.url as url, sites.name, ping.timestamp as timestamp, statuses.name as status FROM ping + JOIN sites ON ping.site = sites.url + JOIN statuses ON ping.status = statuses.id + ORDER BY timestamp DESC`) + + if err != nil { + panic(err) + } + defer rows.Close() + + pings := make([]PingResponse, 0) + + for rows.Next() { + p := PingResponse{} + rows.Scan(&p.Site.Url, &p.Site.Name, &p.Timestamp, &p.Status) + pings = append(pings, p) + } + + return pings +} +``` + +The interesting parts here are the `defer` statement, and `rows.Scan`. Defer queues that call up until after the function has executed, it's just a way of saying "I will be doing this at the end regardless" as a cleanup operation[^2]. Then `rows.Scan` will automagically insert the values to the variables I pass it, in the order the columns are read from the database[^3]. + +Then finally, I can update my handler function so that it uses `json.Marshal` to convert the pings to JSON, and output them to the browser: + +```go +http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { + pings := ping.List(db) + + if output, err := json.Marshal(pings); err != nil { + w.Write([]byte(err.Error())) + w.WriteHeader(http.StatusInternalServerError) + return + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(output) + } +}) +``` + +And that works! You can see it at https://oopsie.lewisdale.dev, with (hopefully) some actual output. + +[^1]: Yes, despite what I said in another post I've not written any _actual_ tests. I'm human, alright? +[^2]: I think +[^3]: This is where it helps to be explicit with what is selected and avoid `SELECT *`. \ No newline at end of file