func form2Complaint(r *http.Request) types.Complaint { c := types.Complaint{ Description: r.FormValue("content"), Timestamp: time.Now(), // No point setting a timezone, it gets reset to UTC HeardSpeedbreaks: FormValueCheckbox(r, "speedbrakes"), Loudness: int(FormValueInt64(r, "loudness")), Activity: r.FormValue("activity"), Browser: types.Browser{ UUID: r.FormValue("browser_uuid"), Name: r.FormValue("browser_name"), Version: r.FormValue("browser_version"), Vendor: r.FormValue("browser_vendor"), Platform: r.FormValue("browser_platform"), }, } // This field is set during updates (it identifies a complaint to update) if r.FormValue("datastorekey") != "" { c.DatastoreKey = r.FormValue("datastorekey") } // These fields are set directly in CGI args, for historical population if r.FormValue("timestamp_epoch") != "" { c.Timestamp = time.Unix(FormValueInt64(r, "timestamp_epoch"), 0) } if r.FormValue("flight") != "" { c.AircraftOverhead.FlightNumber = r.FormValue("flight") } return c }
func FixupComplaint(c *types.Complaint, key *datastore.Key) { // 0. Snag the key, so we can refer to this object later c.DatastoreKey = key.Encode() // 1. GAE datastore helpfully converts timezones to UTC upon storage; fix that c.Timestamp = date.InPdt(c.Timestamp) // 2. Compute the flight details URL, if within 24 days age := date.NowInPdt().Sub(c.Timestamp) if age < time.Hour*24 { // c.AircraftOverhead.Fr24Url = c.AircraftOverhead.PlaybackUrl() c.AircraftOverhead.Fr24Url = "http://flightaware.com/live/flight/" + c.AircraftOverhead.FlightNumber // Or: http://flightaware.com/live/flight/UAL337/history/20151215/ [0655Z/KLAX/KSFO] // date is UTC of departure time; might be tricky to guess :/ } // 3. Compute distances, if we have an aircraft if c.AircraftOverhead.FlightNumber != "" { a := c.AircraftOverhead aircraftPos := geo.Latlong{a.Lat, a.Long} observerPos := geo.Latlong{c.Profile.Lat, c.Profile.Long} c.Dist2KM = observerPos.Dist(aircraftPos) c.Dist3KM = observerPos.Dist3(aircraftPos, a.Altitude) } }
// Overwrite user-entered data (and timestamp) into the base complaint. func Overwrite(this, from *types.Complaint) { orig := *this // Keep a temp copy *this = *from // Overwrite everything // Restore a few key fields from the original this.DatastoreKey = orig.DatastoreKey // If the orig had a description but new doesn't, don't lose it if this.Description == "" && orig.Description != "" { this.Description = orig.Description } }
func (cdb ComplaintDB) UpdateAnyComplaint(complaint types.Complaint) error { if k, err := datastore.DecodeKey(complaint.DatastoreKey); err != nil { return err } else { complaint.Version = kComplaintVersion _, err := datastore.Put(cdb.Ctx(), k, &complaint) return err } }
func (cdb ComplaintDB) complainByProfile(cp types.ComplainerProfile, c *types.Complaint) error { client := urlfetch.Client(cdb.C) fr := fr24.Fr24{Client: client} overhead := fr24.Aircraft{} //cdb.C.Infof("adding complaint for [%s] %s", cp.CallerCode, overhead.FlightNumber) // abw hack hack grabAnything := (cp.CallerCode == "QWERTY") c.Debug, _ = fr.FindOverhead(geo.Latlong{cp.Lat, cp.Long}, &overhead, grabAnything) if overhead.Id != "" { c.AircraftOverhead = overhead } c.Version = kComplaintVersion c.Profile = cp // Copy the profile fields into every complaint // Too much like the last complaint by this user ? Just update that one. if prev, err := cdb.GetNewestComplaintByEmailAddress(cp.EmailAddress); err != nil { cdb.C.Errorf("complainByProfile/GetNewest: %v", err) } else if prev != nil && ComplaintsAreEquivalent(*prev, *c) { // The two complaints are in fact one complaint. Overwrite the old one with data from new one. Overwrite(prev, c) return cdb.UpdateComplaint(*prev, cp.EmailAddress) } key := datastore.NewIncompleteKey(cdb.C, kComplaintKind, cdb.emailToRootKey(cp.EmailAddress)) _, err := datastore.Put(cdb.C, key, c) // TEMP /* if debug,err := bksv.PostComplaint(client, cp, *c); err != nil { cdb.C.Infof("BKSV Debug\n------\n%s\n------\n", debug) cdb.C.Infof("BKSV posting error: %v", err) } else { cdb.C.Infof("BKSV Debug\n------\n%s\n------\n", debug) } */ return err }
func (cdb ComplaintDB) AddHistoricalComplaintByEmailAddress(ea string, c *types.Complaint) error { var cp *types.ComplainerProfile var err error cp, err = cdb.GetProfileByEmailAddress(ea) if err != nil { return err } c.Profile = *cp key := datastore.NewIncompleteKey(cdb.Ctx(), kComplaintKind, cdb.emailToRootKey(cp.EmailAddress)) _, err = datastore.Put(cdb.Ctx(), key, c) return err }
func (cdb ComplaintDB) UpdateComplaint(complaint types.Complaint, ownerEmail string) error { k, err := datastore.DecodeKey(complaint.DatastoreKey) if err != nil { return err } if k.Parent() == nil { return fmt.Errorf("Update: key <%v> had no parent", k) } if k.Parent().StringID() != ownerEmail { return fmt.Errorf("Update: key <%v> owned by %s, not %s", k, k.Parent().StringID(), ownerEmail) } complaint.Version = kComplaintVersion if _, err2 := datastore.Put(cdb.Ctx(), k, &complaint); err2 != nil { return err2 } return nil }
func PostComplaint(client *http.Client, p types.ComplainerProfile, c types.Complaint) (string, error) { first, last := p.SplitName() addr := p.GetStructuredAddress() if c.Activity == "" { c.Activity = "Loud noise" } debug, submitkey, err := GetSubmitkey(client) if err != nil { return debug, err } debug += fmt.Sprintf("We got submitkey=%s\n", submitkey) // {{{ Populate form vals := url.Values{ "response": {"json"}, "contactmethod": {"App"}, "app_key": {"TUC8uDJMooVMvf7hew93nhUGcWgw"}, "caller_code": {p.CallerCode}, "name": {first}, "surname": {last}, "address1": {addr.Street}, "address2": {""}, "zipcode": {addr.Zip}, "city": {addr.City}, "state": {addr.State}, "email": {p.EmailAddress}, "airports": {"KSFO"}, // KOAK, KSJC, KSAN "month": {date.InPdt(c.Timestamp).Format("1")}, "day": {date.InPdt(c.Timestamp).Format("2")}, "year": {date.InPdt(c.Timestamp).Format("2006")}, "hour": {date.InPdt(c.Timestamp).Format("15")}, "min": {date.InPdt(c.Timestamp).Format("4")}, "aircraftcategory": {"J"}, "eventtype": {"Loud noise"}, // perhaps map c.Activity to something ? "comments": {c.Description}, "responserequired": {"N"}, "enquirytype": {"C"}, "submit": {"Submit complaint"}, "submitkey": {submitkey}, "nowebtrak": {"1"}, "defaulttime": {"0"}, "webtraklinkback": {""}, "title": {""}, "homephone": {""}, "workphone": {""}, "cellphone": {""}, } if c.AircraftOverhead.FlightNumber != "" { vals.Add("acid", c.AircraftOverhead.Callsign) vals.Add("aacode", c.AircraftOverhead.Id2) vals.Add("tailnumber", c.AircraftOverhead.Registration) vals.Add("aircrafttype", c.AircraftOverhead.EquipType) //vals.Add("adflag", "??") // Operation type (A, D or O for Arr, Dept or Overflight) //vals.Add("beacon", "??") // SSR code (eg 210) } // }}} debug += "Submitting these vals:-\n" for k, v := range vals { debug += fmt.Sprintf(" * %-20.20s: %v\n", k, v) } if resp, err := client.PostForm("https://"+bksvHost+bksvPath, vals); err != nil { return debug, err } else { defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) if resp.StatusCode >= 400 { debug += fmt.Sprintf("ComplaintPOST: HTTP err '%s'\nBody:-\n%s\n--\n", resp.Status, body) return debug, fmt.Errorf("ComplaintPOST: HTTP err %s", resp.Status) } var jsonMap map[string]interface{} if err := json.Unmarshal([]byte(body), &jsonMap); err != nil { debug += fmt.Sprintf("ComplaintPOST: JSON unmarshal '%v'\nBody:-\n%s\n--\n", err, body) return debug, fmt.Errorf("ComplaintPOST: JSON unmarshal %v", err) /* Fall back ? if !regexp.MustCompile(`(?i:received your complaint)`).MatchString(string(body)) { debug += fmt.Sprintf("BKSV body ...\n%s\n------\n", string(body)) return debug,fmt.Errorf("Returned response did not say 'received your complaint'") } else { debug += "Success !\n"+string(body) } */ } else if v := jsonMap["result"]; v == nil { return debug, fmt.Errorf("ComplaintPOST: jsonmap had no 'result'.\nBody:-\n%s\n--\n", body) } else { result := v.(string) if result == "1" { debug += "Json Success !\n" } else { debug += fmt.Sprintf("Json result not '1':-\n%#v\n--\n", jsonMap) return debug, fmt.Errorf("ComplaintPOST: result='%s'", result) } } } return debug, nil }
func (cdb ComplaintDB) complainByProfile(cp types.ComplainerProfile, c *types.Complaint) error { client := cdb.HTTPClient() overhead := flightid.Aircraft{} // Check we're not over a daily cap for this user cdb.Debugf("cbe_010", "doing rate limit check") s, e := date.WindowForToday() if prevKeys, err := cdb.GetComplaintKeysInSpanByEmailAddress(s, e, cp.EmailAddress); err != nil { return err } else if len(prevKeys) >= KMaxComplaintsPerDay { return fmt.Errorf("Too many complaints filed today") } else { cdb.Debugf("cbe_011", "rate limit check passed (%d); calling FindOverhead", len(prevKeys)) } elev := 0.0 pos := geo.Latlong{cp.Lat, cp.Long} algo := flightid.AlgoConservativeNoCongestion if c.Description == "ANYANY" { algo = flightid.AlgoGrabClosest } if as, err := fr24.FetchAirspace(client, pos.Box(64, 64)); err != nil { cdb.Errorf("FindOverhead failed for %s: %v", cp.EmailAddress, err) } else { oh, deb := flightid.IdentifyOverhead(as, pos, elev, algo) c.Debug = deb if oh != nil { overhead = *oh c.AircraftOverhead = overhead } } cdb.Debugf("cbe_020", "FindOverhead returned") // Contrast with the skypi pathway if cp.CallerCode == "WOR004" || cp.CallerCode == "WOR005" { asFdb, _ := airspace.Fetch(client, "", pos.Box(60, 60)) oh3, deb3 := flightid.IdentifyOverhead(asFdb, pos, elev, algo) if oh3 == nil { oh3 = &flightid.Aircraft{} } newdebug := c.Debug + "\n*** v2 / fdb testing\n" + deb3 + "\n" headline := "" if overhead.FlightNumber != oh3.FlightNumber { headline = fmt.Sprintf("** * * DIFFERS * * **\n") } else { // Agree ! Copy over the Fdb IdSpec, and pretend we're living in the future headline = fmt.Sprintf("**---- Agrees ! ----**\n") c.AircraftOverhead.Id = oh3.Id } headline += fmt.Sprintf(" * skypi: %s\n * orig : %s\n", oh3, overhead) c.Debug = headline + newdebug } c.Version = kComplaintVersion // Kill this off ? c.Profile = cp // Copy the profile fields into every complaint // Too much like the last complaint by this user ? Just update that one. cdb.Debugf("cbe_030", "retrieving prev complaint") if prev, err := cdb.GetNewestComplaintByEmailAddress(cp.EmailAddress); err != nil { cdb.Errorf("complainByProfile/GetNewest: %v", err) } else if prev != nil && ComplaintsAreEquivalent(*prev, *c) { cdb.Debugf("cbe_031", "returned, equiv; about to UpdateComlaint()") // The two complaints are in fact one complaint. Overwrite the old one with data from new one. Overwrite(prev, c) err := cdb.UpdateComplaint(*prev, cp.EmailAddress) cdb.Debugf("cbe_032", "updated in place (all done)") return err } cdb.Debugf("cbe_033", "returned, distinct/first; about to put()") key := datastore.NewIncompleteKey(cdb.Ctx(), kComplaintKind, cdb.emailToRootKey(cp.EmailAddress)) _, err := datastore.Put(cdb.Ctx(), key, c) cdb.Debugf("cbe_034", "new entity added (all done)") // TEMP /* if debug,err := bksv.PostComplaint(client, cp, *c); err != nil { cdb.Infof("BKSV Debug\n------\n%s\n------\n", debug) cdb.Infof("BKSV posting error: %v", err) } else { cdb.Infof("BKSV Debug\n------\n%s\n------\n", debug) } */ return err }
func PopulateForm(c types.Complaint, submitkey string) url.Values { first, last := c.Profile.SplitName() addr := c.Profile.GetStructuredAddress() if c.Activity == "" { c.Activity = "Loud noise" } vals := url.Values{ "response": {"json"}, "contactmethod": {"App"}, "apiKey": {"399734e01c8cd5c21205599689cc77f2a50467f28e6f5d58a69f2b097d71b839c20e0051175107e74130ae9a3bbaccbe51ec5742e6ca3e51ff40cc1a8f401009"}, "caller_code": {c.Profile.CallerCode}, "name": {first}, "surname": {last}, "address1": {addr.Street}, "address2": {""}, "zipcode": {addr.Zip}, "city": {addr.City}, "state": {addr.State}, "email": {c.Profile.EmailAddress}, "airports": {"KSFO"}, // KOAK, KSJC, KSAN "month": {date.InPdt(c.Timestamp).Format("1")}, "day": {date.InPdt(c.Timestamp).Format("2")}, "year": {date.InPdt(c.Timestamp).Format("2006")}, "hour": {date.InPdt(c.Timestamp).Format("15")}, "min": {date.InPdt(c.Timestamp).Format("4")}, "aircraftcategory": {"J"}, "eventtype": {"Loud noise"}, // perhaps map c.Activity to something ? "comments": {c.Description}, "responserequired": {"N"}, "enquirytype": {"C"}, "submit": {"Submit complaint"}, //"submitkey": {submitkey}, "nowebtrak": {"1"}, "defaulttime": {"0"}, "webtraklinkback": {""}, "title": {""}, "homephone": {""}, "workphone": {""}, "cellphone": {""}, "browser_name": {c.Browser.Name}, "browser_version": {c.Browser.Version}, "browser_vendor": {c.Browser.Vendor}, "browser_uuid": {c.Browser.UUID}, "browser_platform": {c.Browser.Platform}, } if c.AircraftOverhead.FlightNumber != "" { vals.Add("acid", c.AircraftOverhead.Callsign) vals.Add("aacode", c.AircraftOverhead.Id2) vals.Add("tailnumber", c.AircraftOverhead.Registration) vals.Add("aircrafttype", c.AircraftOverhead.EquipType) //vals.Add("adflag", "??") // Operation type (A, D or O for Arr, Dept or Overflight) //vals.Add("beacon", "??") // Squawk SSR code (eg 2100) } return vals }