diff --git a/doc/openapi/rest.json b/doc/openapi/rest.json index b2978dce..14c069fa 100644 --- a/doc/openapi/rest.json +++ b/doc/openapi/rest.json @@ -5661,6 +5661,20 @@ } ] }, + { + "description": "Search for routes serving a stop with this OnestopID", + "in": "query", + "name": "serves_stop_onestop_id", + "schema": { + "type": "string" + }, + "x-example-requests": [ + { + "description": "serves_stop_onestop_id=s-9q8yyzcny3-embarcadero", + "url": "serves_stop_onestop_id=s-9q8yyzcny3-embarcadero" + } + ] + }, { "$ref": "#/components/parameters/includeAlertsParam" }, diff --git a/internal/generated/gqlout/generated.go b/internal/generated/gqlout/generated.go index 1214a210..0d48f2d3 100644 --- a/internal/generated/gqlout/generated.go +++ b/internal/generated/gqlout/generated.go @@ -10759,6 +10759,8 @@ input RouteFilter { search: String "Search for routes operated by operators with this OnestopID" operator_onestop_id: String + "Search for routes that serve a stop with this OnestopID" + serves_stop_onestop_id: String "Search for routes with these license details" license: LicenseFilter "Search for routes with these agency integer IDs. Deprecated." @@ -61452,7 +61454,7 @@ func (ec *executionContext) unmarshalInputRouteFilter(ctx context.Context, obj a asMap[k] = v } - fieldsInOrder := [...]string{"onestop_id", "onestop_ids", "allow_previous_onestop_ids", "feed_version_sha1", "feed_onestop_id", "route_id", "route_type", "route_types", "serviced", "search", "operator_onestop_id", "license", "agency_ids", "location", "bbox", "within", "near"} + fieldsInOrder := [...]string{"onestop_id", "onestop_ids", "allow_previous_onestop_ids", "feed_version_sha1", "feed_onestop_id", "route_id", "route_type", "route_types", "serviced", "search", "operator_onestop_id", "serves_stop_onestop_id", "license", "agency_ids", "location", "bbox", "within", "near"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -61536,6 +61538,13 @@ func (ec *executionContext) unmarshalInputRouteFilter(ctx context.Context, obj a return it, err } it.OperatorOnestopID = data + case "serves_stop_onestop_id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("serves_stop_onestop_id")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.ServesStopOnestopID = data case "license": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("license")) data, err := ec.unmarshalOLicenseFilter2ᚖgithubᚗcomᚋinterlineᚑioᚋtransitlandᚑlibᚋserverᚋmodelᚐLicenseFilter(ctx, v) diff --git a/schema/graphql/schema.graphqls b/schema/graphql/schema.graphqls index 0dc2e6b5..766ed90e 100644 --- a/schema/graphql/schema.graphqls +++ b/schema/graphql/schema.graphqls @@ -1977,6 +1977,8 @@ input RouteFilter { search: String "Search for routes operated by operators with this OnestopID" operator_onestop_id: String + "Search for routes that serve a stop with this OnestopID" + serves_stop_onestop_id: String "Search for routes with these license details" license: LicenseFilter "Search for routes with these agency integer IDs. Deprecated." diff --git a/server/finders/dbfinder/route.go b/server/finders/dbfinder/route.go index ee921eb1..5d28e68a 100644 --- a/server/finders/dbfinder/route.go +++ b/server/finders/dbfinder/route.go @@ -281,6 +281,15 @@ func routeSelect(limit *int, after *model.Cursor, ids []int, useActive *UseActiv JoinClause("LEFT JOIN current_operators_in_feed coif ON coif.feed_id = feed_versions.feed_id AND coif.resolved_gtfs_agency_id = gtfs_agencies.agency_id"). Where(sq.Eq{"coif.resolved_onestop_id": *where.OperatorOnestopID}) } + if where.ServesStopOnestopID != nil { + q = q.JoinClause(`JOIN ( + SELECT tlrs.route_id + FROM feed_version_stop_onestop_ids fvsoid + JOIN gtfs_stops gs ON gs.stop_id = fvsoid.entity_id AND gs.feed_version_id = fvsoid.feed_version_id + JOIN tl_route_stops tlrs ON tlrs.stop_id = gs.id + WHERE fvsoid.onestop_id = ? + ) tlrs_stop ON tlrs_stop.route_id = gtfs_routes.id`, *where.ServesStopOnestopID) + } // Handle license filtering q = licenseFilter(where.License, q) diff --git a/server/model/models_gen.go b/server/model/models_gen.go index 8f1438b4..fa812545 100644 --- a/server/model/models_gen.go +++ b/server/model/models_gen.go @@ -860,6 +860,8 @@ type RouteFilter struct { Search *string `json:"search,omitempty"` // Search for routes operated by operators with this OnestopID OperatorOnestopID *string `json:"operator_onestop_id,omitempty"` + // Search for routes that serve a stop with this OnestopID + ServesStopOnestopID *string `json:"serves_stop_onestop_id,omitempty"` // Search for routes with these license details License *LicenseFilter `json:"license,omitempty"` // Search for routes with these agency integer IDs. Deprecated. diff --git a/server/rest/openapi.go b/server/rest/openapi.go index c89e15e4..2ff76bd7 100644 --- a/server/rest/openapi.go +++ b/server/rest/openapi.go @@ -287,9 +287,9 @@ var ParameterComponents = oa.ParametersMap{ Value: ¶m{ Name: "include_routes", In: "query", - Description: `Include routes that serve this stop (requires tl_user_pro role)`, + Description: `Include routes that serve this stop`, Schema: newSRVal("string", "", []any{"true", "false"}), - Extensions: newExtWithRole("", "include_routes=true", "include_routes=true", "tl_user_pro"), + Extensions: newExt("", "include_routes=true", "include_routes=true"), }, }, } diff --git a/server/rest/route_request.go b/server/rest/route_request.go index ce5c0558..c483ba19 100644 --- a/server/rest/route_request.go +++ b/server/rest/route_request.go @@ -14,26 +14,27 @@ var routeQuery string // RouteRequest holds options for a Route request type RouteRequest struct { - ID int `json:"id,string"` - RouteKey string `json:"route_key"` - AgencyKey string `json:"agency_key"` - RouteID string `json:"route_id"` - RouteType string `json:"route_type"` - RouteTypes string `json:"route_types"` - OnestopID string `json:"onestop_id"` - OperatorOnestopID string `json:"operator_onestop_id"` - Format string `json:"format"` - Search string `json:"search"` - AgencyID int `json:"agency_id,string"` - FeedVersionSHA1 string `json:"feed_version_sha1"` - FeedOnestopID string `json:"feed_onestop_id"` - Lon float64 `json:"lon,string"` - Lat float64 `json:"lat,string"` - Radius float64 `json:"radius,string"` - Bbox *restBbox `json:"bbox"` - IncludeGeometry bool `json:"include_geometry,string"` - IncludeAlerts bool `json:"include_alerts,string"` - IncludeStops bool `json:"include_stops,string"` + ID int `json:"id,string"` + RouteKey string `json:"route_key"` + AgencyKey string `json:"agency_key"` + RouteID string `json:"route_id"` + RouteType string `json:"route_type"` + RouteTypes string `json:"route_types"` + OnestopID string `json:"onestop_id"` + OperatorOnestopID string `json:"operator_onestop_id"` + ServesStopOnestopID string `json:"serves_stop_onestop_id"` + Format string `json:"format"` + Search string `json:"search"` + AgencyID int `json:"agency_id,string"` + FeedVersionSHA1 string `json:"feed_version_sha1"` + FeedOnestopID string `json:"feed_onestop_id"` + Lon float64 `json:"lon,string"` + Lat float64 `json:"lat,string"` + Radius float64 `json:"radius,string"` + Bbox *restBbox `json:"bbox"` + IncludeGeometry bool `json:"include_geometry,string"` + IncludeAlerts bool `json:"include_alerts,string"` + IncludeStops bool `json:"include_stops,string"` LicenseFilter WithCursor } @@ -93,6 +94,13 @@ func (r RouteRequest) RequestInfo() RequestInfo { Schema: newSRVal("string", "", nil), Extensions: newExt("", "operator_onestop_id=...", "operator_onestop_id=o-9q9-caltrain"), }}, + &pref{Value: ¶m{ + Name: "serves_stop_onestop_id", + In: "query", + Description: `Search for routes serving a stop with this OnestopID`, + Schema: newSRVal("string", "", nil), + Extensions: newExt("", "serves_stop_onestop_id=s-9q8yyzcny3-embarcadero", "serves_stop_onestop_id=s-9q8yyzcny3-embarcadero"), + }}, newPRef("includeAlertsParam"), &pref{Value: ¶m{ Name: "include_geometry", @@ -189,6 +197,9 @@ func (r RouteRequest) Query(ctx context.Context) (string, map[string]interface{} if r.OperatorOnestopID != "" { where["operator_onestop_id"] = r.OperatorOnestopID } + if r.ServesStopOnestopID != "" { + where["serves_stop_onestop_id"] = r.ServesStopOnestopID + } if r.AgencyID > 0 { where["agency_ids"] = []int{r.AgencyID} } diff --git a/server/rest/route_request_test.go b/server/rest/route_request_test.go index 4195a308..912ba4f2 100644 --- a/server/rest/route_request_test.go +++ b/server/rest/route_request_test.go @@ -74,6 +74,18 @@ func TestRouteRequest(t *testing.T) { selector: "routes.#.route_id", expectSelect: []string{"01", "03", "05", "07", "11", "19"}, }, + { + name: "serves_stop_onestop_id", + h: RouteRequest{ServesStopOnestopID: "s-9q8yyufxmv-sanfranciscocaltrain"}, + selector: "routes.#.route_id", + expectSelect: []string{"Bu-130", "Gi-130", "Li-130", "Lo-130", "Sp-130"}, + }, + { + name: "serves_stop_onestop_id:none", + h: RouteRequest{ServesStopOnestopID: "s-invalid-stop"}, + selector: "routes.#.route_id", + expectLength: 0, + }, { name: "lat,lon,radius 100m", h: RouteRequest{Lon: -122.407974, Lat: 37.784471, Radius: 100}, diff --git a/server/rest/stop_request.go b/server/rest/stop_request.go index f0ed1189..87e6d7bd 100644 --- a/server/rest/stop_request.go +++ b/server/rest/stop_request.go @@ -7,8 +7,6 @@ import ( "strings" oa "github.com/getkin/kin-openapi/openapi3" - "github.com/interline-io/log" - "github.com/interline-io/transitland-lib/server/auth/authn" ) //go:embed stop_request.gql @@ -130,12 +128,6 @@ func (r StopRequest) Query(ctx context.Context) (string, map[string]any) { r.IncludeRoutes = true } - user := authn.ForContext(ctx) - if user == nil || (!user.HasRole("tl_user_pro") && r.IncludeRoutes) { - log.For(ctx).Trace().Msg("setting include_routes = false") - r.IncludeRoutes = false - } - where := hw{} if r.FeedVersionSHA1 != "" { where["feed_version_sha1"] = r.FeedVersionSHA1 diff --git a/server/rest/stop_request_test.go b/server/rest/stop_request_test.go index b1092e58..4618a6fb 100644 --- a/server/rest/stop_request_test.go +++ b/server/rest/stop_request_test.go @@ -186,18 +186,10 @@ func TestStopRequest(t *testing.T) { func TestStopRequest_IncludeRoutes(t *testing.T) { testcases := []testCase{ { - name: "no auth", - h: StopRequest{StopID: "70011", IncludeRoutes: true}, - selector: "stops.0.route_stops", - expectLength: 0, - }, - { - name: "with tl_user_pro", + name: "include_routes", h: StopRequest{StopID: "70011", IncludeRoutes: true}, selector: "stops.0.route_stops", expectLength: 5, - user: "test", - userRoles: []string{"tl_user_pro"}, }, } for _, tc := range testcases {