first pass at assessment
This commit is contained in:
		
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
								
							@@ -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
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -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
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
										Normal 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
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								main.go
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user