mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 14:35:36 +08:00
1. Database Enhancements (db/repositories.go): - Added ConfigRepository interface and implementations for PostgreSQL and SQLite - Updated Repositories struct to include ConfigRepository - Modified NewRepositories to initialize both UserRepo and ConfigRepo 2. FSD Server Improvements: - Removed hardcoded jwtSecret, now retrieved from ConfigRepository (fsd/conn.go, web/auth.go) - Added dynamic welcome message retrieval from ConfigRepository (fsd/conn.go) - Optimized METAR buffer size from 4096 to 512 bytes (fsd/metar.go) - Reduced minimum fields for DeleteATC and DeletePilot packets (fsd/packet.go) - Improved Haversine distance calculation with constants (fsd/postoffice.go) - Added thread-safety documentation for sendError (fsd/client.go) 3. Server Configuration (fsd/server.go): - Added NewDefaultServer to initialize server with environment-based config - Implemented automatic database migration and default admin user creation - Added configurable METAR worker count - Improved logging with slog and environment-based debug level 4. Web Interface Enhancements: - Added user and config editor frontend routes (web/frontend.go, web/routes.go) - Improved JWT handling by retrieving secret from ConfigRepository (web/auth.go) - Enhanced user management API endpoints (web/user.go) - Updated dashboard to display CID and conditional admin links (web/templates/dashboard.html) - Embedded templates using go:embed (web/templates.go) 5. Frontend JavaScript Improvements: - Added networkRatingFromInt helper for readable ratings (web/static/js/openfsd/dashboard.js) - Improved API request handling with auth/no-auth variants (web/static/js/openfsd/api.js) 6. Miscellaneous: - Added sethvargo/go-envconfig dependency for environment variable parsing - Fixed parseVisRange to use 64-bit float parsing (fsd/util.go) - Added strPtr utility function (fsd/util.go, web/main.go) - Improved SVG logo rendering in layout (web/templates/layout.html)
123 lines
2.7 KiB
Go
123 lines
2.7 KiB
Go
package fsd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type metarService struct {
|
|
numWorkers int
|
|
httpClient *http.Client
|
|
metarRequests chan metarRequest
|
|
}
|
|
|
|
type metarRequest struct {
|
|
client *Client
|
|
icaoCode string
|
|
}
|
|
|
|
func newMetarService(numWorkers int) *metarService {
|
|
return &metarService{
|
|
numWorkers: numWorkers,
|
|
httpClient: &http.Client{},
|
|
metarRequests: make(chan metarRequest, 128),
|
|
}
|
|
}
|
|
|
|
func (s *metarService) run(ctx context.Context) {
|
|
for range s.numWorkers {
|
|
go s.worker(ctx)
|
|
}
|
|
}
|
|
|
|
func (s *metarService) worker(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case req := <-s.metarRequests:
|
|
s.handleMetarRequest(&req)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *metarService) handleMetarRequest(req *metarRequest) {
|
|
url := buildMetarRequestURL(req.icaoCode)
|
|
res, err := s.httpClient.Get(url)
|
|
if err != nil {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
if res.StatusCode != http.StatusOK {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
bufBytes := make([]byte, 512)
|
|
buf := bytes.NewBuffer(bufBytes)
|
|
if _, err = io.Copy(buf, res.Body); err != nil {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
resBody := buf.Bytes()
|
|
|
|
if bytes.Count(resBody, []byte("\n")) != 2 {
|
|
fmt.Println("NOAA METAR response was invalid")
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
// First line is timestamp
|
|
resBody = resBody[bytes.IndexByte(resBody, '\n')+1:]
|
|
|
|
// Second line is METAR and ends with \n
|
|
resBody = resBody[:bytes.IndexByte(resBody, '\n')+1]
|
|
|
|
packet := buildMetarResponsePacket(req.client.callsign, resBody)
|
|
req.client.send(packet)
|
|
}
|
|
|
|
func buildMetarResponsePacket(callsign string, metar []byte) string {
|
|
packet := strings.Builder{}
|
|
packet.WriteString("$ARSERVER:")
|
|
packet.WriteString(callsign)
|
|
packet.WriteString(":METAR:")
|
|
packet.Write(metar)
|
|
packet.WriteString("\r\n")
|
|
return packet.String()
|
|
}
|
|
|
|
func buildMetarRequestURL(icaoCode string) string {
|
|
url := strings.Builder{}
|
|
url.WriteString("https://tgftp.nws.noaa.gov/data/observations/metar/stations/")
|
|
url.WriteString(icaoCode)
|
|
url.WriteString(".TXT")
|
|
return url.String()
|
|
}
|
|
|
|
func sendMetarServiceError(req *metarRequest) {
|
|
req.client.sendError(NoWeatherProfileError, metarServiceErrString(req.icaoCode))
|
|
}
|
|
|
|
func metarServiceErrString(icaoCode string) string {
|
|
msg := strings.Builder{}
|
|
msg.WriteString("Error fetching METAR for ")
|
|
msg.WriteString(icaoCode)
|
|
|
|
return msg.String()
|
|
}
|
|
|
|
// fetchAndSendMetar fetches a METAR observation for a given ICAO code and sends it to the client once received.
|
|
// This function returns immediately once the request has been queued.
|
|
func (s *metarService) fetchAndSendMetar(ctx context.Context, client *Client, icaoCode string) {
|
|
select {
|
|
case <-ctx.Done():
|
|
case s.metarRequests <- metarRequest{client: client, icaoCode: icaoCode}:
|
|
}
|
|
}
|