// Serves API documentation. Route requests for /api-docs* to this handler. func (d *Docs) Serve(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", web.HtmlContent) w.Header().Set("Surrogate-Control", web.MaxAge300) switch { case r.URL.Path == "/api-docs" || r.URL.Path == "/api-docs/" || r.URL.Path == "/api-docs/index.html": b, err := d.indexPage() if err != nil { web.ServiceUnavailablePage(w, r, err) return } web.OkBuf(w, r, b) // /api-docs/endpoints/ case strings.HasPrefix(r.URL.Path, endpointPath): if _, ok := d.endpoints[r.URL.Path[endpointsLen:]]; !ok { web.NotFoundPage(w, r) return } b, err := d.endpointPage(r.URL.Path[endpointsLen:]) if err != nil { web.ServiceUnavailablePage(w, r, err) return } web.OkBuf(w, r, b) default: web.NotFoundPage(w, r) } }
// returns a simple state of health page. If heartbeat times in the DB are old then it also returns an http status of 500. func soh(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", web.HtmlContent) var b bytes.Buffer b.Write([]byte(head)) b.Write([]byte(`<p>Current time is: ` + time.Now().UTC().String() + `</p>`)) b.Write([]byte(`<h3>Messaging</h3>`)) var bad bool var s string var t time.Time b.Write([]byte(`<table><tr><th>Service</th><th>Time Received</th></tr>`)) rows, err := db.Query("select serverid, timereceived from qrt.soh") if err == nil { defer rows.Close() for rows.Next() { err := rows.Scan(&s, &t) if err == nil { if t.Before(time.Now().UTC().Add(old)) { bad = true b.Write([]byte(`<tr class="tr error">`)) } else { b.Write([]byte(`<tr>`)) } b.Write([]byte(`<td>` + s + `</td><td>` + t.String() + `</td></tr>`)) } else { bad = true b.Write([]byte(`<tr class="tr error"><td>DB error</td><td>` + err.Error() + `</td></tr>`)) } } rows.Close() } else { bad = true b.Write([]byte(`<tr class="tr error"><td>DB error</td><td>` + err.Error() + `</td></tr>`)) } b.Write([]byte(`</table>`)) b.Write([]byte(foot)) if bad { web.ServiceInternalServerErrorBuf(w, r, &b) return } web.OkBuf(w, r, &b) }
// returns a simple state of health page. If the count of measured intensities falls below 50 this it also returns an http status of 500. func impactSOH(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", web.HtmlContent) var b bytes.Buffer b.Write([]byte(head)) b.Write([]byte(`<p>Current time is: ` + time.Now().UTC().String() + `</p>`)) b.Write([]byte(`<h3>Impact</h3>`)) var bad bool b.Write([]byte(`<table><tr><th>Impact</th><th>Count</th></tr>`)) var meas int err := db.QueryRow("select count(*) from impact.intensity_measured").Scan(&meas) if err == nil { if meas < 50 { bad = true b.Write([]byte(`<tr class="tr error"><td>shaking measured</td><td>` + strconv.Itoa(meas) + ` < 50</td></tr>`)) } else { b.Write([]byte(`<tr><td>shaking measured</td><td>` + strconv.Itoa(meas) + ` >= 50</td></tr>`)) } } else { bad = true b.Write([]byte(`<tr class="tr error"><td>DB error</td><td>` + err.Error() + `</td></tr>`)) } b.Write([]byte(`</table>`)) b.Write([]byte(foot)) if bad { web.ServiceInternalServerErrorBuf(w, r, &b) return } web.OkBuf(w, r, &b) }
func spark(w http.ResponseWriter, r *http.Request) { if err := sparkD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } var plotType string var s siteQ var t typeQ var days int var ymin, ymax float64 var stddev string var label string var ok bool if plotType, ok = getPlotType(w, r); !ok { return } if stddev, ok = getStddev(w, r); !ok { return } if label, ok = getSparkLabel(w, r); !ok { return } if days, ok = getDays(w, r); !ok { return } if ymin, ymax, ok = getYRange(w, r); !ok { return } if t, ok = getType(w, r); !ok { return } if s, ok = getSite(w, r); !ok { return } var p plt var tmin time.Time if days > 0 { n := time.Now().UTC() tmin = n.Add(time.Duration(days*-1) * time.Hour * 24) p.SetXAxis(tmin, n) days = 0 // add all data > than tmin } switch { case ymin == 0 && ymax == 0: case ymin == ymax: p.SetYRange(ymin) default: p.SetYAxis(ymin, ymax) } p.SetUnit(t.unit) var err error if stddev == `pop` { err = p.setStddevPop(s, t, tmin, days) } if err != nil { web.ServiceUnavailable(w, r, err) return } err = p.addSeries(t, tmin, days, s) if err != nil { web.ServiceUnavailable(w, r, err) return } b := new(bytes.Buffer) switch plotType { case ``, `line`: switch label { case ``, `all`: err = ts.SparkLineAll.Draw(p.Plot, b) case `latest`: err = ts.SparkLineLatest.Draw(p.Plot, b) case `none`: err = ts.SparkLineNone.Draw(p.Plot, b) } case `scatter`: switch label { case ``, `all`: err = ts.SparkScatterAll.Draw(p.Plot, b) case `latest`: err = ts.SparkScatterLatest.Draw(p.Plot, b) case `none`: err = ts.SparkScatterNone.Draw(p.Plot, b) } } if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, b) }
func plotSite(w http.ResponseWriter, r *http.Request) { if err := plotSiteD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } var plotType string var s siteQ var t typeQ var start time.Time var days int var ymin, ymax float64 var showMethod bool var stddev string var ok bool if plotType, ok = getPlotType(w, r); !ok { return } if showMethod, ok = getShowMethod(w, r); !ok { return } if stddev, ok = getStddev(w, r); !ok { return } if start, ok = getStart(w, r); !ok { return } if days, ok = getDays(w, r); !ok { return } if ymin, ymax, ok = getYRange(w, r); !ok { return } if t, ok = getType(w, r); !ok { return } if s, ok = getSite(w, r); !ok { return } var p plt switch { case start.IsZero() && days == 0: // do nothing - autorange on the data. case start.IsZero() && days > 0: n := time.Now().UTC() start = n.Add(time.Duration(days*-1) * time.Hour * 24) p.SetXAxis(start, n) days = 0 // add all data > than start by setting 0. Allows for adding start end to URL. case !start.IsZero() && days > 0: p.SetXAxis(start, start.Add(time.Duration(days*1)*time.Hour*24)) case !start.IsZero() && days == 0: web.BadRequest(w, r, "Invalid start specified without days") return } switch { case ymin == 0 && ymax == 0: case ymin == ymax: p.SetYRange(ymin) default: p.SetYAxis(ymin, ymax) } p.SetTitle(fmt.Sprintf("%s (%s) - %s", s.siteID, s.name, t.description)) p.SetUnit(t.unit) p.SetYLabel(fmt.Sprintf("%s (%s)", t.name, t.unit)) var err error switch showMethod { case false: err = p.addSeries(t, start, days, s) case true: err = p.addSeriesLabelMethod(t, start, days, s) } if err != nil { web.ServiceUnavailable(w, r, err) return } if stddev == `pop` { err = p.setStddevPop(s, t, start, days) } if err != nil { web.ServiceUnavailable(w, r, err) return } b := new(bytes.Buffer) switch plotType { case ``, `line`: err = ts.Line.Draw(p.Plot, b) case `scatter`: err = ts.Scatter.Draw(p.Plot, b) } if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, b) }
func observation(w http.ResponseWriter, r *http.Request) { if err := observationD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() typeID := v.Get("typeID") networkID := v.Get("networkID") siteID := v.Get("siteID") if !validType(w, r, typeID) { return } var days int if v.Get("days") != "" { var err error days, err = strconv.Atoi(v.Get("days")) if err != nil || days > 365000 { web.BadRequest(w, r, "Invalid days query param.") return } } var methodID string if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } // Find the unit for the CSV header var unit string err := db.QueryRow("select symbol FROM fits.type join fits.unit using (unitPK) where typeID = $1", typeID).Scan(&unit) if err == sql.ErrNoRows { web.NotFound(w, r, "unit not found for typeID: "+typeID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } var d string var rows *sql.Rows switch { case days == 0 && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) ORDER BY time ASC;`, networkID, siteID, typeID) case days != 0 && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND time > (now() - interval '`+strconv.Itoa(days)+` days') ORDER BY time ASC;`, networkID, siteID, typeID) case days == 0 && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND methodpk = ( SELECT methodpk FROM fits.method WHERE methodid = $4 ) ORDER BY time ASC;`, networkID, siteID, typeID, methodID) case days != 0 && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND methodpk = ( SELECT methodpk FROM fits.method WHERE methodid = $4 ) AND time > (now() - interval '`+strconv.Itoa(days)+` days') ORDER BY time ASC;`, networkID, siteID, typeID, methodID) } if err != nil { web.ServiceUnavailable(w, r, err) return } defer rows.Close() // Use a buffer for reading the data from the DB. Then if a there // is an error we can let the client know without sending // a partial data response. var b bytes.Buffer b.Write([]byte("date-time, " + typeID + " (" + unit + "), error (" + unit + ")")) b.Write(eol) for rows.Next() { err := rows.Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b.Write([]byte(d)) b.Write(eol) } rows.Close() if methodID != "" { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+networkID+`-`+siteID+`-`+typeID+`-`+methodID+`.csv"`) } else { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+networkID+`-`+siteID+`-`+typeID+`.csv"`) } w.Header().Set("Content-Type", web.V1CSV) web.OkBuf(w, r, &b) }
func siteMap(w http.ResponseWriter, r *http.Request) { if err := siteMapD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() bbox := v.Get("bbox") var insetBbox string if v.Get("insetBbox") != "" { insetBbox = v.Get("insetBbox") err := map180.ValidBbox(insetBbox) if err != nil { web.BadRequest(w, r, err.Error()) return } } if v.Get("sites") == "" && (v.Get("siteID") == "" && v.Get("networkID") == "") { web.BadRequest(w, r, "please specify sites or networkID and siteID") return } if v.Get("sites") != "" && (v.Get("siteID") != "" || v.Get("networkID") != "") { web.BadRequest(w, r, "please specify either sites or networkID and siteID") return } if v.Get("sites") == "" && (v.Get("siteID") == "" || v.Get("networkID") == "") { web.BadRequest(w, r, "please specify networkID and siteID") return } err := map180.ValidBbox(bbox) if err != nil { web.BadRequest(w, r, err.Error()) return } width := 130 if v.Get("width") != "" { width, err = strconv.Atoi(v.Get("width")) if err != nil { web.BadRequest(w, r, "invalid width.") return } } var s []st if v.Get("sites") != "" { for _, ns := range strings.Split(v.Get("sites"), ",") { nss := strings.Split(ns, ".") if len(nss) != 2 { web.BadRequest(w, r, "invalid sites query.") return } s = append(s, st{networkID: nss[0], siteID: nss[1]}) } } else { s = append(s, st{networkID: v.Get("networkID"), siteID: v.Get("siteID")}) } markers := make([]map180.Marker, 0) for _, site := range s { if !validSite(w, r, site.networkID, site.siteID) { return } g, err := geoJSONSite(site.networkID, site.siteID) if err != nil { web.ServiceUnavailable(w, r, err) return } m, err := geoJSONToMarkers(g) if err != nil { web.ServiceUnavailable(w, r, err) return } markers = append(markers, m...) } b, err := wm.SVG(bbox, width, markers, insetBbox) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, &b) }
func siteTypeMap(w http.ResponseWriter, r *http.Request) { if err := siteTypeMapD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() bbox := v.Get("bbox") err := map180.ValidBbox(bbox) if err != nil { web.BadRequest(w, r, err.Error()) return } var insetBbox, typeID, methodID, within string width := 130 if v.Get("insetBbox") != "" { insetBbox = v.Get("insetBbox") err := map180.ValidBbox(insetBbox) if err != nil { web.BadRequest(w, r, err.Error()) return } } if v.Get("width") != "" { width, err = strconv.Atoi(v.Get("width")) if err != nil { web.BadRequest(w, r, "invalid width.") return } } if v.Get("methodID") != "" && v.Get("typeID") == "" { web.BadRequest(w, r, "typeID must be specified when methodID is specified.") return } if v.Get("typeID") != "" { typeID = v.Get("typeID") if !validType(w, r, typeID) { return } if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } } if v.Get("within") != "" { within = strings.Replace(v.Get("within"), "+", "", -1) if !validPoly(w, r, within) { return } } else if bbox != "" { within, err = map180.BboxToWKTPolygon(bbox) if err != nil { web.ServiceUnavailable(w, r, err) return } } g, err := geoJSONSites(typeID, methodID, within) if err != nil { web.ServiceUnavailable(w, r, err) return } m, err := geoJSONToMarkers(g) if err != nil { web.ServiceUnavailable(w, r, err) return } b, err := wm.SVG(bbox, width, m, insetBbox) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, &b) }
func spatialObs(w http.ResponseWriter, r *http.Request) { if err := spatialObsD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() var err error var days int days, err = strconv.Atoi(v.Get("days")) if err != nil || days > 7 || days <= 0 { web.BadRequest(w, r, "Invalid days query param.") return } start, err := time.Parse(time.RFC3339, v.Get("start")) if err != nil { web.BadRequest(w, r, "Invalid start query param.") return } end := start.Add(time.Duration(days) * time.Hour * 24) var srsName, authName string var srid int if v.Get("srsName") != "" { srsName = v.Get("srsName") srs := strings.Split(srsName, ":") if len(srs) != 2 { web.BadRequest(w, r, "Invalid srsName.") return } authName = srs[0] var err error srid, err = strconv.Atoi(srs[1]) if err != nil { web.BadRequest(w, r, "Invalid srsName.") return } if !validSrs(w, r, authName, srid) { return } } else { srid = 4326 authName = "EPSG" srsName = "EPSG:4326" } typeID := v.Get("typeID") var methodID string if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } var within string if v.Get("within") != "" { within = strings.Replace(v.Get("within"), "+", "", -1) if !validPoly(w, r, within) { return } } var unit string err = db.QueryRow("select symbol FROM fits.type join fits.unit using (unitPK) where typeID = $1", typeID).Scan(&unit) if err == sql.ErrNoRows { web.NotFound(w, r, "unit not found for typeID: "+typeID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } var d string var rows *sql.Rows switch { case within == "" && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid) case within != "" && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND ST_Within(location::geometry, ST_GeomFromText($5, 4326)) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, within) case within == "" && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND methodpk = (SELECT methodpk FROM fits.method WHERE methodid = $5) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, methodID) case within != "" && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND methodpk = (SELECT methodpk FROM fits.method WHERE methodid = $6) AND ST_Within(location::geometry, ST_GeomFromText($5, 4326)) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, within, methodID) } if err != nil { // not sure what a transformation error would look like. // Return any errors as a 404. Could improve this by inspecting // the error type to check for net dial errors that shoud 503. web.NotFound(w, r, err.Error()) return } defer rows.Close() var b bytes.Buffer b.Write([]byte("networkID, siteID, X (" + srsName + "), Y (" + srsName + "), height, groundRelationship, date-time, " + typeID + " (" + unit + "), error (" + unit + ")")) b.Write(eol) for rows.Next() { err := rows.Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b.Write([]byte(d)) b.Write(eol) } rows.Close() w.Header().Set("Content-Type", web.V1CSV) if methodID != "" { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+typeID+`-`+methodID+`.csv"`) } else { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+typeID+`.csv"`) } web.OkBuf(w, r, &b) }