Archived
0
0
Fork 0
This repository has been archived on 2024-05-09. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nextrip/graphqlhandler.go
2018-05-27 22:53:53 -05:00

323 lines
8.4 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/GregoryDosh/metrotransit"
log "github.com/Sirupsen/logrus"
"github.com/graphql-go/graphql"
"github.com/valyala/fasthttp"
)
var departuresType = graphql.NewObject(graphql.ObjectConfig{
Name: "Departure",
Fields: graphql.Fields{
"actual": &graphql.Field{
Type: graphql.Boolean,
},
"block_number": &graphql.Field{
Type: graphql.Int,
},
"departure_text": &graphql.Field{
Type: graphql.String,
},
"departure_time": &graphql.Field{
Type: graphql.String,
},
"description": &graphql.Field{
Type: graphql.String,
},
"gate": &graphql.Field{
Type: graphql.String,
},
"map": &graphql.Field{
Type: graphql.String,
},
"route": &graphql.Field{
Type: graphql.String,
},
"route_direction": &graphql.Field{
Type: graphql.String,
},
"terminal": &graphql.Field{
Type: graphql.String,
},
"vehicle_heading": &graphql.Field{
Type: graphql.Int,
},
"vehicle_latitude": &graphql.Field{
Type: graphql.Float,
},
"vehicle_longitude": &graphql.Field{
Type: graphql.Float,
},
},
})
var detailsType = graphql.NewObject(graphql.ObjectConfig{
Name: "Details",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.Int,
},
"code": &graphql.Field{
Type: graphql.Int,
},
"name": &graphql.Field{
Type: graphql.String,
},
"description": &graphql.Field{
Type: graphql.String,
},
"latitude": &graphql.Field{
Type: graphql.Float,
},
"longitude": &graphql.Field{
Type: graphql.Float,
},
"zone_id": &graphql.Field{
Type: graphql.String,
},
"url": &graphql.Field{
Type: graphql.String,
},
"location_type": &graphql.Field{
Type: graphql.Int,
},
"wheelchair_boarding": &graphql.Field{
Type: graphql.Int,
},
},
})
var stopType = graphql.NewObject(graphql.ObjectConfig{
Name: "Stop",
Fields: graphql.Fields{
"departures": &graphql.Field{
Type: graphql.NewList(departuresType),
},
"stop_details": &graphql.Field{
Type: detailsType,
},
"stop_id": &graphql.Field{
Type: graphql.Int,
},
"update_time": &graphql.Field{
Type: graphql.String,
},
"full_map": &graphql.Field{
Type: graphql.String,
},
},
})
var rootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"departures": &graphql.Field{
Type: stopType,
Description: "Gets NexTrip departures for a given stop_id.",
Args: graphql.FieldConfigArgument{
"stop_id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.Int),
},
"map_scale": &graphql.ArgumentConfig{
Type: graphql.Int,
},
"map_width": &graphql.ArgumentConfig{
Type: graphql.Int,
},
"map_height": &graphql.ArgumentConfig{
Type: graphql.Int,
},
},
Resolve: resolveDepartures,
},
},
})
type newStopType struct {
StopID int `json:"stop_id"`
Departures []newDepartureType `json:"departures"`
Details newDetailsType `json:"stop_details"`
FullMap string `json:"full_map"`
UpdateTime time.Time `json:"update_time"`
}
type newDepartureType struct {
DepartureTime string `json:"departure_time"`
Route string `json:"route"`
Actual bool `json:"actual"`
BlockNumber int `json:"block_number"`
DepartureText string `json:"departure_text"`
Description string `json:"description"`
Gate string `json:"gate"`
Map string `json:"map,omitempty"`
RouteDirection string `json:"route_direction"`
Terminal string `json:"terminal"`
VehicleHeading int `json:"vehicle_heading"`
VehicleLatitude float64 `json:"vehicle_latitude"`
VehicleLongitude float64 `json:"vehicle_longitude"`
}
type newDetailsType struct {
ID int64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
ZoneID string `json:"zone_id"`
URL string `json:"url"`
LocationType int64 `json:"location_type"`
WheelchairBoarding int64 `json:"wheelchair_boarding"`
}
func mapHelper(stopLat float64, stopLong float64, busLatLong [][]float64, scale int, size string) string {
if googleMapsAPIKey != "" {
baseURL := fmt.Sprintf("maps.googleapis.com/maps/api/staticmap?markers=color:blue%%7Clabel:S%%7C%[1]f,%[2]f&scale=%[3]d", stopLat, stopLong, scale)
busMarker := ""
sizeParam := ""
keyParm := fmt.Sprintf("&key=%s", googleMapsAPIKey)
for _, bus := range busLatLong {
busLat, busLong := bus[0], bus[1]
if busLat != 0 && busLong != 0 {
busMarker += fmt.Sprintf("&markers=%%7Ccolor:green%%7Clabel:B%%7C%[1]f,%[2]f", busLat, busLong)
}
}
if size != "" {
sizeParam = fmt.Sprintf("&size=%s", size)
}
return baseURL + busMarker + keyParm + sizeParam
}
return ""
}
func translateDepartureType(o *metrotransit.Stop, scale int, size string) *newStopType {
newDepartureList := []newDepartureType{}
allBusLatLong := [][]float64{}
for _, oldDepart := range o.Departures {
busLatLong := [][]float64{
{oldDepart.VehicleLatitude, oldDepart.VehicleLongitude},
}
allBusLatLong = append(allBusLatLong, []float64{oldDepart.VehicleLatitude, oldDepart.VehicleLongitude})
newDepartureList = append(newDepartureList, newDepartureType{
Actual: oldDepart.Actual,
BlockNumber: oldDepart.BlockNumber,
DepartureText: oldDepart.DepartureText,
DepartureTime: oldDepart.DepartureTime.String(),
Description: oldDepart.Description,
Gate: oldDepart.Gate,
Map: mapHelper(o.Details.Latitude, o.Details.Longitude, busLatLong, scale, size),
Route: oldDepart.Route,
RouteDirection: oldDepart.RouteDirection,
Terminal: oldDepart.Terminal,
VehicleHeading: oldDepart.VehicleHeading,
VehicleLatitude: oldDepart.VehicleLatitude,
VehicleLongitude: oldDepart.VehicleLongitude,
})
}
return &newStopType{
StopID: o.StopID,
UpdateTime: o.UpdateTime,
Departures: newDepartureList,
FullMap: mapHelper(o.Details.Latitude, o.Details.Longitude, allBusLatLong, scale, size),
Details: newDetailsType{
ID: o.Details.ID,
Code: o.Details.Code,
Name: o.Details.Name,
Description: o.Details.Description,
Latitude: o.Details.Latitude,
Longitude: o.Details.Longitude,
ZoneID: o.Details.ZoneID,
URL: o.Details.URL,
LocationType: o.Details.LocationType,
WheelchairBoarding: o.Details.WheelchairBoarding,
},
}
}
func resolveDepartures(params graphql.ResolveParams) (interface{}, error) {
scale, isOK := params.Args["map_scale"].(int)
if !isOK {
scale = 1
}
width, isOK := params.Args["map_width"].(int)
if !isOK {
width = 0
}
height, isOK := params.Args["map_height"].(int)
if !isOK {
height = 0
}
size := ""
if width != 0 && height != 0 {
size = fmt.Sprintf("%dx%d", width, height)
}
idQuery, isOK := params.Args["stop_id"].(int)
if isOK {
s, err := env.GetDepartures(idQuery)
if err != nil {
return nil, err
}
return translateDepartureType(s, scale, size), nil
}
return nil, errors.New("stop_id not found")
}
// define schema, with our rootQuery and rootMutation
var schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: rootQuery,
})
func executeQuery(query string, schema graphql.Schema) *graphql.Result {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
log.Errorf("wrong result, unexpected errors: %v", result.Errors)
}
return result
}
type jsonQueryBody struct {
Query string `json:"query"`
}
func graphqlHandler(ctx *fasthttp.RequestCtx) {
var query string
switch string(ctx.Method()) {
case "GET":
query = string(ctx.QueryArgs().Peek("query"))
case "POST":
switch string(ctx.Request.Header.Peek("Content-Type")) {
case "application/json":
queryStruct := &jsonQueryBody{}
if err := json.Unmarshal(ctx.PostBody(), queryStruct); err != nil {
log.Error(err)
return
}
query = queryStruct.Query
default:
query = string(ctx.PostBody())
}
}
result := executeQuery(query, schema)
pretty := string(ctx.QueryArgs().Peek("pretty"))
enc := json.NewEncoder(ctx)
enc.SetEscapeHTML(false)
if pretty != "" {
enc.SetIndent("", " ")
}
if err := enc.Encode(result); err != nil {
log.Error(err)
}
}