feat(wetter): Wetter auf dem Schreibtisch und in der Leiste
Zwei Schalter, unabhaengig voneinander: Schreibtisch, Leiste, beides oder keins. Die Stadt wird einmal gesucht und mit ihren Koordinaten gespeichert, sonst suchte jede Abfrage den Ort erneut. Geholt wird serverseitig und zehn Minuten zwischengespeichert. Der Grund ist nicht Sparsamkeit: Die Leiste steht auf jeder Seite jeder App, und fragte sie selbst an, ginge bei jedem Seitenaufruf eine Anfrage samt IP an einen Fremden — und die CSP jeder App muesste ihn erlauben. Kein Ort eingestellt oder Dienst nicht erreichbar: 204, und das Symbol bleibt weg. Eine Zahl von gestern waere schlechter als keine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
52b11efe06
commit
4e38ccdec1
10 files changed
+823
-6
No files matched your search
@@ -14,6 +14,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -27,7 +28,7 @@ const localSub = "local"
|
||||
// Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus
|
||||
// und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public.
|
||||
// Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander.
|
||||
const Version = "0.7"
|
||||
const Version = "0.8"
|
||||
|
||||
type Config struct {
|
||||
Addr string
|
||||
@@ -68,9 +69,10 @@ func (c Config) RedirectURL() string {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
cfg Config
|
||||
auth *Auth
|
||||
store *Store
|
||||
cfg Config
|
||||
auth *Auth
|
||||
store *Store
|
||||
wetter *wetterDienst
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -128,7 +130,7 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := &Server{cfg: cfg, auth: NewAuth(cfg), store: store}
|
||||
s := &Server{cfg: cfg, auth: NewAuth(cfg), store: store, wetter: neuerWetterdienst()}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
@@ -161,6 +163,13 @@ func (s *Server) routes() http.Handler {
|
||||
mux.HandleFunc("PUT /api/settings", s.requireAuth(s.handleSetSettings))
|
||||
mux.HandleFunc("GET /api/apps", s.appsReadable(s.handleApps))
|
||||
mux.HandleFunc("PUT /api/apps", s.requireAuth(s.handleSetApps))
|
||||
// Dieselbe Tür wie die App-Liste: Die Leiste steht in einer fremden App und
|
||||
// kommt dort nur mit dem Schlüssel an Daten.
|
||||
mux.HandleFunc("GET /api/weather", s.appsReadable(s.handleWeather))
|
||||
// Die Ortssuche dagegen nur für Angemeldete. Sie reicht einen Suchbegriff an
|
||||
// einen fremden Dienst weiter — offen stünde damit ein Weg, über HoneyOS
|
||||
// beliebig dort anzufragen.
|
||||
mux.HandleFunc("GET /api/weather/search", s.requireAuth(s.handleWeatherSearch))
|
||||
|
||||
web, err := fs.Sub(webFS, "web")
|
||||
if err != nil {
|
||||
@@ -339,6 +348,39 @@ func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string)
|
||||
})
|
||||
}
|
||||
|
||||
// handleWeather liefert das Wetter für den eingestellten Ort — oder nichts.
|
||||
//
|
||||
// „Nichts" ist hier ein 204 und kein Fehler: Kein Ort eingestellt ist der
|
||||
// Normalfall, solange niemand einen gewählt hat, und die Leiste soll dafür
|
||||
// nicht rot blinken, sondern das Symbol einfach weglassen. Dasselbe gilt, wenn
|
||||
// der Dienst nicht erreichbar ist: lieber kein Wetter als eine Zahl von gestern.
|
||||
func (s *Server) handleWeather(w http.ResponseWriter, r *http.Request, sub string) {
|
||||
set := s.store.SettingsFor(sub)
|
||||
lat, err1 := strconv.ParseFloat(set["weatherLat"], 64)
|
||||
lon, err2 := strconv.ParseFloat(set["weatherLon"], 64)
|
||||
if set["weatherCity"] == "" || err1 != nil || err2 != nil {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
wetter, err := s.wetter.Jetzt(r.Context(), set["weatherCity"], lat, lon)
|
||||
if err != nil {
|
||||
log.Printf("wetter: %v", err)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, wetter)
|
||||
}
|
||||
|
||||
func (s *Server) handleWeatherSearch(w http.ResponseWriter, r *http.Request, sub string) {
|
||||
orte, err := s.wetter.Suche(r.Context(), r.URL.Query().Get("q"))
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": "Ortssuche nicht erreichbar"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, orte)
|
||||
}
|
||||
|
||||
func (s *Server) handleSetApps(w http.ResponseWriter, r *http.Request, sub string) {
|
||||
var in []App
|
||||
if err := decode(r, &in); err != nil {
|
||||
|
||||
Reference in new issue
Block a user