Example #1
0
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
}
Example #2
0
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)
	}
}
Example #3
0
// 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
	}
}
Example #4
0
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
	}
}
Example #5
0
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
}
Example #6
0
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
}
Example #7
0
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
}
Example #8
0
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
}
Example #9
0
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
}
Example #10
0
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
}