first pass at assessment

This commit is contained in:
jeff
2025-06-04 15:28:56 -07:00
parent ce57f022d4
commit 9a3610cc7c
4 changed files with 121 additions and 1 deletions

View File

@ -19,3 +19,39 @@ From recruiter:
> * We do not expect a production-ready service, but you might want to comment on your shortcuts.
> * The submitted project should build and have brief instructions so we can verify that it works.
> * You may write in whatever language or stack you're most comfortable in
This HTTP service gets a short forecast (results in JSON) for a given latitude and longitude.
### Build requirements
A local installation of Go is needed. Instructions to install Go can be found here: https://go.dev/learn/. I've used Ubuntu, but any OS should work.
### Building and Running
In the top-level-directory, run the following command:
`go build main.go`
This will build a binary in that folder, which can be run without arguments.
`./main`
To call the endpoint, use an HTTP client to send a GET request to `localhost:8080/forecast`. The payload should look like this:
```json
{
"latitude": 48.29944,
"longitude": -116.56
}
```
The result should look like this:
```json
{
"shortForecast": "Mostly Sunny",
"temperature": "hot"
}
```
### Shortcuts
* This should be containerized in something like Docker
* The code is all in main.go, but if this project was to grow, it should be broken down.
* There are no tests, but `httptest` should be used to test this. With rules, the temperature could be tested as well.
* Logging is sparse, but should be enough to test for this assessment.

2
go.mod
View File

@ -1,3 +1,5 @@
module git.simplesystems.tech/jeff/current-weather
go 1.24.3
require github.com/icodealot/noaa v0.0.2

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/icodealot/noaa v0.0.2 h1:nL21mFSxJUBog7/0/vakIfA109T2A8/JpowzEzL9O+w=
github.com/icodealot/noaa v0.0.2/go.mod h1:vPMSrP4zBvlbWC34qtUR5w64dCp0xSRjkLy9/Ky81go=

82
main.go
View File

@ -1,5 +1,85 @@
package main
func main() {
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"github.com/icodealot/noaa"
)
type ForecastReq struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
type ForecastResp struct {
ShortForecast string `json:"shortForecast"`
Temperature string `json:"temperature"`
}
func main() {
// Using the default HTTP server
// SHORTCUT: no TLS
// SHORTCUT: The default HTTP server is good enough for small projects, but would need to be better configured for production
// SHORTCUT: no rate limiting
http.HandleFunc("GET /forecast", forecast)
// SHORTCUT: This server is only stopped when the process stops. This should have a graceful shutdown.
if err := http.ListenAndServe(":8080", nil); err != nil {
slog.Error("stopping server", err)
}
}
func forecast(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// SHORTCUT: not checking the body length to see if it is unreasonably large
// Decode request
var foreReq ForecastReq
if err := json.NewDecoder(r.Body).Decode(&foreReq); err != nil {
slog.Error("decode forecast request error", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Check that request is in range
if foreReq.Longitude > 180 || foreReq.Longitude < -180 {
slog.Error("invalid request", "latitude", foreReq.Latitude, "longitude", foreReq.Longitude)
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
// Call out to NOAA for forecast
resp, err := noaa.Forecast(fmt.Sprintf("%f", foreReq.Latitude), fmt.Sprintf("%f", foreReq.Longitude))
if err != nil {
slog.Error("noaa forecast request error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Find today's forecast
var result ForecastResp
// SHORTCUT: assuming [0] is the closest to "today", and Periods has elements
p := resp.Periods[0]
result.ShortForecast = p.Summary
switch {
case p.Temperature < 32:
result.Temperature = "cold"
case p.Temperature < 70:
result.Temperature = "moderate"
default:
result.Temperature = "hot"
}
// Return result
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(result); err != nil {
slog.Error("encode forecast response error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}