diff --git a/cmd/service/parent_regex_test.go b/cmd/service/parent_regex_test.go new file mode 100644 index 0000000..0d9790c --- /dev/null +++ b/cmd/service/parent_regex_test.go @@ -0,0 +1,44 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package service + +import ( + "regexp" + "testing" +) + +// parentPattern mirrors the Goa design pattern at design/query-svc.go. +// This test exists to guard against accidental regex regression. +const parentPattern = `^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$` + +func TestParentPattern(t *testing.T) { + re := regexp.MustCompile(parentPattern) + + valid := []string{ + "project:123", + "past_meeting:98471391296-1765832400000", + "v1_meeting:abc-123", + "v1_past_meeting:foo_bar-baz", + "committee:abc", + } + for _, v := range valid { + if !re.MatchString(v) { + t.Errorf("expected %q to match parent pattern", v) + } + } + + invalid := []string{ + "past_meeting:", // empty id fragment + ":abc", // empty type fragment + "_leading:abc", // leading underscore in type + "past meeting:abc", // space in type + "project", // missing colon + "", // empty + } + for _, v := range invalid { + if re.MatchString(v) { + t.Errorf("expected %q to NOT match parent pattern", v) + } + } +} diff --git a/design/query-svc.go b/design/query-svc.go index e76c534..a27231b 100644 --- a/design/query-svc.go +++ b/design/query-svc.go @@ -45,7 +45,7 @@ var _ = dsl.Service("query-svc", func() { }) dsl.Attribute("parent", dsl.String, "Parent (for navigation; varies by object type)", func() { dsl.Example("project:123") - dsl.Pattern(`^[a-zA-Z]+:[a-zA-Z0-9_-]+$`) + dsl.Pattern(`^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$`) }) dsl.Attribute("type", dsl.String, "Resource type to search", func() { dsl.Example("committee") @@ -145,6 +145,7 @@ var _ = dsl.Service("query-svc", func() { }) dsl.Attribute("parent", dsl.String, "Parent (for navigation; varies by object type)", func() { dsl.Example("project:123") + dsl.Pattern(`^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$`) }) dsl.Attribute("type", dsl.String, "Resource type to search", func() { dsl.Example("committee") diff --git a/gen/http/openapi.json b/gen/http/openapi.json index 26a4919..6162111 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"LFX V2 - Query Service","description":"Query indexed resources","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/query/orgs":{"get":{"tags":["query-svc"],"summary":"query-orgs query-svc","description":"Locate a single organization by name or domain.","operationId":"query-svc#query-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Organization name","required":false,"type":"string","minLength":1},{"name":"domain","in":"query","description":"Organization domain or website URL","required":false,"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]*\\.[a-zA-Z]{2,}$"},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Organization"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/orgs/suggest":{"get":{"tags":["query-svc"],"summary":"suggest-orgs query-svc","description":"Get organization suggestions for typeahead search based on a query.","operationId":"query-svc#suggest-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"query","in":"query","description":"Search query for organization suggestions","required":true,"type":"string","minLength":1},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcSuggestOrgsResponseBody","required":["suggestions"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/resources":{"get":{"tags":["query-svc"],"summary":"query-resources query-svc","description":"Locate resources by their type or parent, or use typeahead search to query resources by a display name or similar alias.","operationId":"query-svc#query-resources","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","required":false,"type":"string","minLength":1},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","required":false,"type":"string","pattern":"^[a-zA-Z]+:[a-zA-Z0-9_-]+$"},{"name":"type","in":"query","description":"Resource type to search","required":false,"type":"string"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","required":false,"type":"string"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"cel_filter","in":"query","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","required":false,"type":"string","maxLength":1000},{"name":"filter_grants","in":"query","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","required":false,"type":"string","enum":["direct"]},{"name":"sort","in":"query","description":"Sort order for results","required":false,"type":"string","default":"name_asc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]},{"name":"page_token","in":"query","description":"Opaque token for pagination","required":false,"type":"string"},{"name":"page_size","in":"query","description":"Number of results per page","required":false,"type":"integer","default":50,"maximum":1000,"minimum":1},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcQueryResourcesResponseBody","required":["resources"]},"headers":{"Cache-Control":{"description":"Cache control header","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/resources/count":{"get":{"tags":["query-svc"],"summary":"query-resources-count query-svc","description":"Count matching resources by query.","operationId":"query-svc#query-resources-count","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","required":false,"type":"string","minLength":1},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","required":false,"type":"string"},{"name":"type","in":"query","description":"Resource type to search","required":false,"type":"string"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","required":false,"type":"string"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcQueryResourcesCountResponseBody","required":["count","has_more"]},"headers":{"Cache-Control":{"description":"Cache control header","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"message":"The request was invalid."},"required":["message"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The requested resource was not found."}},"description":"Not found","example":{"message":"The requested resource was not found."},"required":["message"]},"Organization":{"title":"Organization","type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"employees":{"type":"string","description":"Employee count or range","example":"100-499"},"industry":{"type":"string","description":"Organization industry classification","example":"Non-Profit"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"},"sector":{"type":"string","description":"Business sector classification","example":"Technology"}},"example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}},"OrganizationSuggestion":{"title":"OrganizationSuggestion","type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"logo":{"type":"string","description":"Organization logo URL","example":"https://example.com/logo.png"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"}},"description":"An organization suggestion for the search.","example":{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},"required":["name","domain"]},"QuerySvcQueryResourcesCountResponseBody":{"title":"QuerySvcQueryResourcesCountResponseBody","type":"object","properties":{"count":{"type":"integer","description":"Count of resources found","example":1234,"format":"int64"},"has_more":{"type":"boolean","description":"True if count is not guaranteed to be exhaustive: client should request a narrower query","example":false}},"example":{"count":1234,"has_more":false},"required":["count","has_more"]},"QuerySvcQueryResourcesResponseBody":{"title":"QuerySvcQueryResourcesResponseBody","type":"object","properties":{"page_token":{"type":"string","description":"Opaque token if more results are available","example":"****"},"resources":{"type":"array","items":{"$ref":"#/definitions/Resource"},"description":"Resources found","example":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]},"required":["resources"]},"QuerySvcSuggestOrgsResponseBody":{"title":"QuerySvcSuggestOrgsResponseBody","type":"object","properties":{"suggestions":{"type":"array","items":{"$ref":"#/definitions/OrganizationSuggestion"},"description":"Organization suggestions","example":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]},"required":["suggestions"]},"Resource":{"title":"Resource","type":"object","properties":{"data":{"description":"Resource data snapshot","example":{"id":"123","name":"My committee","description":"a committee"}},"id":{"type":"string","description":"Resource ID (within its resource collection)","example":"123"},"type":{"type":"string","description":"Resource type","example":"committee"}},"description":"A resource is a universal representation of an LFX API resource for indexing.","example":{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}},"ServiceUnavailableError":{"title":"ServiceUnavailableError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"message":"The service is unavailable."},"required":["message"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"Heimdall authorization","name":"Authorization","in":"header"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"LFX V2 - Query Service","description":"Query indexed resources","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/query/orgs":{"get":{"tags":["query-svc"],"summary":"query-orgs query-svc","description":"Locate a single organization by name or domain.","operationId":"query-svc#query-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Organization name","required":false,"type":"string","minLength":1},{"name":"domain","in":"query","description":"Organization domain or website URL","required":false,"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]*\\.[a-zA-Z]{2,}$"},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Organization"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/orgs/suggest":{"get":{"tags":["query-svc"],"summary":"suggest-orgs query-svc","description":"Get organization suggestions for typeahead search based on a query.","operationId":"query-svc#suggest-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"query","in":"query","description":"Search query for organization suggestions","required":true,"type":"string","minLength":1},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcSuggestOrgsResponseBody","required":["suggestions"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/resources":{"get":{"tags":["query-svc"],"summary":"query-resources query-svc","description":"Locate resources by their type or parent, or use typeahead search to query resources by a display name or similar alias.","operationId":"query-svc#query-resources","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","required":false,"type":"string","minLength":1},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","required":false,"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$"},{"name":"type","in":"query","description":"Resource type to search","required":false,"type":"string"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","required":false,"type":"string"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"cel_filter","in":"query","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","required":false,"type":"string","maxLength":1000},{"name":"filter_grants","in":"query","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","required":false,"type":"string","enum":["direct"]},{"name":"sort","in":"query","description":"Sort order for results","required":false,"type":"string","default":"name_asc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]},{"name":"page_token","in":"query","description":"Opaque token for pagination","required":false,"type":"string"},{"name":"page_size","in":"query","description":"Number of results per page","required":false,"type":"integer","default":50,"maximum":1000,"minimum":1},{"name":"Authorization","in":"header","description":"Token","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcQueryResourcesResponseBody","required":["resources"]},"headers":{"Cache-Control":{"description":"Cache control header","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/query/resources/count":{"get":{"tags":["query-svc"],"summary":"query-resources-count query-svc","description":"Count matching resources by query.","operationId":"query-svc#query-resources-count","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","required":false,"type":"string","minLength":1},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","required":false,"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$"},{"name":"type","in":"query","description":"Resource type to search","required":false,"type":"string"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","required":false,"type":"string"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","required":false,"type":"string"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/QuerySvcQueryResourcesCountResponseBody","required":["count","has_more"]},"headers":{"Cache-Control":{"description":"Cache control header","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"message":"The request was invalid."},"required":["message"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The requested resource was not found."}},"description":"Not found","example":{"message":"The requested resource was not found."},"required":["message"]},"Organization":{"title":"Organization","type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"employees":{"type":"string","description":"Employee count or range","example":"100-499"},"industry":{"type":"string","description":"Organization industry classification","example":"Non-Profit"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"},"sector":{"type":"string","description":"Business sector classification","example":"Technology"}},"example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}},"OrganizationSuggestion":{"title":"OrganizationSuggestion","type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"logo":{"type":"string","description":"Organization logo URL","example":"https://example.com/logo.png"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"}},"description":"An organization suggestion for the search.","example":{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},"required":["name","domain"]},"QuerySvcQueryResourcesCountResponseBody":{"title":"QuerySvcQueryResourcesCountResponseBody","type":"object","properties":{"count":{"type":"integer","description":"Count of resources found","example":1234,"format":"int64"},"has_more":{"type":"boolean","description":"True if count is not guaranteed to be exhaustive: client should request a narrower query","example":false}},"example":{"count":1234,"has_more":false},"required":["count","has_more"]},"QuerySvcQueryResourcesResponseBody":{"title":"QuerySvcQueryResourcesResponseBody","type":"object","properties":{"page_token":{"type":"string","description":"Opaque token if more results are available","example":"****"},"resources":{"type":"array","items":{"$ref":"#/definitions/Resource"},"description":"Resources found","example":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]},"required":["resources"]},"QuerySvcSuggestOrgsResponseBody":{"title":"QuerySvcSuggestOrgsResponseBody","type":"object","properties":{"suggestions":{"type":"array","items":{"$ref":"#/definitions/OrganizationSuggestion"},"description":"Organization suggestions","example":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]},"required":["suggestions"]},"Resource":{"title":"Resource","type":"object","properties":{"data":{"description":"Resource data snapshot","example":{"id":"123","name":"My committee","description":"a committee"}},"id":{"type":"string","description":"Resource ID (within its resource collection)","example":"123"},"type":{"type":"string","description":"Resource type","example":"committee"}},"description":"A resource is a universal representation of an LFX API resource for indexing.","example":{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}},"ServiceUnavailableError":{"title":"ServiceUnavailableError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"message":"The service is unavailable."},"required":["message"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"Heimdall authorization","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index 9f1087e..f4654f2 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -159,7 +159,7 @@ paths: description: Parent (for navigation; varies by object type) required: false type: string - pattern: ^[a-zA-Z]+:[a-zA-Z0-9_-]+$ + pattern: ^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$ - name: type in: query description: Resource type to search @@ -322,6 +322,7 @@ paths: description: Parent (for navigation; varies by object type) required: false type: string + pattern: ^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$ - name: type in: query description: Resource type to search diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index 011d06d..e8f5d22 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"LFX V2 - Query Service","description":"Query indexed resources","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-query-service"}],"paths":{"/query/orgs":{"get":{"tags":["query-svc"],"summary":"query-orgs query-svc","description":"Locate a single organization by name or domain.","operationId":"query-svc#query-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Organization name","allowEmptyValue":true,"schema":{"type":"string","description":"Organization name","example":"The Linux Foundation","minLength":1},"example":"The Linux Foundation"},{"name":"domain","in":"query","description":"Organization domain or website URL","allowEmptyValue":true,"schema":{"type":"string","description":"Organization domain or website URL","example":"linuxfoundation.org","pattern":"^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]*\\.[a-zA-Z]{2,}$"},"example":"linuxfoundation.org"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"},"example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The requested resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/orgs/suggest":{"get":{"tags":["query-svc"],"summary":"suggest-orgs query-svc","description":"Get organization suggestions for typeahead search based on a query.","operationId":"query-svc#suggest-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"query","in":"query","description":"Search query for organization suggestions","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Search query for organization suggestions","example":"linux","minLength":1},"example":"linux"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestOrgsResponseBody"},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/resources":{"get":{"tags":["query-svc"],"summary":"query-resources query-svc","description":"Locate resources by their type or parent, or use typeahead search to query resources by a display name or similar alias.","operationId":"query-svc#query-resources","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","allowEmptyValue":true,"schema":{"type":"string","description":"Resource name or alias; supports typeahead","example":"gov board","minLength":1},"example":"gov board"},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","allowEmptyValue":true,"schema":{"type":"string","description":"Parent (for navigation; varies by object type)","example":"project:123","pattern":"^[a-zA-Z]+:[a-zA-Z0-9_-]+$"},"example":"project:123"},{"name":"type","in":"query","description":"Resource type to search","allowEmptyValue":true,"schema":{"type":"string","description":"Resource type to search","example":"committee"},"example":"committee"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Animi aspernatur."},"description":"Tags to search with OR logic - matches resources with any of these tags","example":["active","public"]},"example":["active","public"]},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Ex itaque."},"description":"Tags to search with AND logic - matches resources that have all of these tags","example":["governance","security"]},"example":["governance","security"]},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","allowEmptyValue":true,"schema":{"type":"string","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","example":"updated_at"},"example":"updated_at"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","example":"2025-01-10"},"example":"2025-01-10"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","example":"2025-01-28"},"example":"2025-01-28"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Sint commodi."},"description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Labore aperiam libero ipsam et ullam."},"description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Doloribus voluptatem ipsa optio."},"description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","example":["mailing_list_id:abc","mailing_list_id:xyz"]},"example":["mailing_list_id:abc","mailing_list_id:xyz"]},{"name":"cel_filter","in":"query","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","allowEmptyValue":true,"schema":{"type":"string","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","example":"data.slug == \"tlf\"","maxLength":1000},"example":"data.slug == \"tlf\""},{"name":"filter_grants","in":"query","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","allowEmptyValue":true,"schema":{"type":"string","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","example":"direct","enum":["direct"]},"example":"direct"},{"name":"sort","in":"query","description":"Sort order for results","allowEmptyValue":true,"schema":{"type":"string","description":"Sort order for results","default":"name_asc","example":"updated_desc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]},"example":"updated_desc"},{"name":"page_token","in":"query","description":"Opaque token for pagination","allowEmptyValue":true,"schema":{"type":"string","description":"Opaque token for pagination","example":"****"},"example":"****"},{"name":"page_size","in":"query","description":"Number of results per page","allowEmptyValue":true,"schema":{"type":"integer","description":"Number of results per page","default":50,"example":20,"format":"int64","minimum":1,"maximum":1000},"example":20}],"responses":{"200":{"description":"OK response.","headers":{"Cache-Control":{"description":"Cache control header","schema":{"type":"string","description":"Cache control header","example":"public, max-age=300"},"example":"public, max-age=300"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResourcesResponseBody"},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/resources/count":{"get":{"tags":["query-svc"],"summary":"query-resources-count query-svc","description":"Count matching resources by query.","operationId":"query-svc#query-resources-count","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","allowEmptyValue":true,"schema":{"type":"string","description":"Resource name or alias; supports typeahead","example":"gov board","minLength":1},"example":"gov board"},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","allowEmptyValue":true,"schema":{"type":"string","description":"Parent (for navigation; varies by object type)","example":"project:123"},"example":"project:123"},{"name":"type","in":"query","description":"Resource type to search","allowEmptyValue":true,"schema":{"type":"string","description":"Resource type to search","example":"committee"},"example":"committee"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Nobis corporis aperiam consectetur temporibus voluptatem vitae."},"description":"Tags to search with OR logic - matches resources with any of these tags","example":["active","public"]},"example":["active","public"]},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Dolor culpa aliquam."},"description":"Tags to search with AND logic - matches resources that have all of these tags","example":["governance","security"]},"example":["governance","security"]},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","allowEmptyValue":true,"schema":{"type":"string","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","example":"updated_at"},"example":"updated_at"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","example":"2025-01-10"},"example":"2025-01-10"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","example":"2025-01-28"},"example":"2025-01-28"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Facere dolores numquam consequatur ut est."},"description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Necessitatibus labore minima vitae."},"description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Eum sequi dolorum adipisci numquam iusto ipsum."},"description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","example":["mailing_list_id:abc","mailing_list_id:xyz"]},"example":["mailing_list_id:abc","mailing_list_id:xyz"]}],"responses":{"200":{"description":"OK response.","headers":{"Cache-Control":{"description":"Cache control header","schema":{"type":"string","description":"Cache control header","example":"public, max-age=300"},"example":"public, max-age=300"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResourcesCountResponseBody"},"example":{"count":1234,"has_more":false}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"message":"The request was invalid."},"required":["message"]},"InternalServerError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The requested resource was not found."}},"example":{"message":"The requested resource was not found."},"required":["message"]},"Organization":{"type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"employees":{"type":"string","description":"Employee count or range","example":"100-499"},"industry":{"type":"string","description":"Organization industry classification","example":"Non-Profit"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"},"sector":{"type":"string","description":"Business sector classification","example":"Technology"}},"description":"An organization is a universal representation of an LFX API organization.","example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}},"OrganizationSuggestion":{"type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"logo":{"type":"string","description":"Organization logo URL","example":"https://example.com/logo.png"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"}},"description":"An organization suggestion for the search.","example":{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},"required":["name","domain"]},"QueryResourcesCountResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Count of resources found","example":1234,"format":"int64"},"has_more":{"type":"boolean","description":"True if count is not guaranteed to be exhaustive: client should request a narrower query","example":false}},"example":{"count":1234,"has_more":false},"required":["count","has_more"]},"QueryResourcesResponseBody":{"type":"object","properties":{"page_token":{"type":"string","description":"Opaque token if more results are available","example":"****"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/Resource"},"description":"Resources found","example":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]},"required":["resources"]},"Resource":{"type":"object","properties":{"data":{"description":"Resource data snapshot","example":{"id":"123","name":"My committee","description":"a committee"}},"id":{"type":"string","description":"Resource ID (within its resource collection)","example":"123"},"type":{"type":"string","description":"Resource type","example":"committee"}},"description":"A resource is a universal representation of an LFX API resource for indexing.","example":{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}},"ServiceUnavailableError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"message":"The service is unavailable."},"required":["message"]},"Sortable":{"type":"object","properties":{"page_size":{"type":"integer","description":"Number of results per page","default":50,"example":20,"format":"int64","minimum":1,"maximum":1000},"page_token":{"type":"string","description":"Opaque token for pagination","example":"****"},"sort":{"type":"string","description":"Sort order for results","default":"name_asc","example":"updated_desc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]}},"example":{"page_size":20,"page_token":"****","sort":"updated_desc"}},"SuggestOrgsResponseBody":{"type":"object","properties":{"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationSuggestion"},"description":"Organization suggestions","example":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]},"required":["suggestions"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"Heimdall authorization","scheme":"bearer"}}},"tags":[{"name":"query-svc","description":"The query service provides resource and user queries."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"LFX V2 - Query Service","description":"Query indexed resources","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-query-service"}],"paths":{"/query/orgs":{"get":{"tags":["query-svc"],"summary":"query-orgs query-svc","description":"Locate a single organization by name or domain.","operationId":"query-svc#query-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Organization name","allowEmptyValue":true,"schema":{"type":"string","description":"Organization name","example":"The Linux Foundation","minLength":1},"example":"The Linux Foundation"},{"name":"domain","in":"query","description":"Organization domain or website URL","allowEmptyValue":true,"schema":{"type":"string","description":"Organization domain or website URL","example":"linuxfoundation.org","pattern":"^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]*\\.[a-zA-Z]{2,}$"},"example":"linuxfoundation.org"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"},"example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The requested resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/orgs/suggest":{"get":{"tags":["query-svc"],"summary":"suggest-orgs query-svc","description":"Get organization suggestions for typeahead search based on a query.","operationId":"query-svc#suggest-orgs","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"query","in":"query","description":"Search query for organization suggestions","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Search query for organization suggestions","example":"linux","minLength":1},"example":"linux"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestOrgsResponseBody"},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/resources":{"get":{"tags":["query-svc"],"summary":"query-resources query-svc","description":"Locate resources by their type or parent, or use typeahead search to query resources by a display name or similar alias.","operationId":"query-svc#query-resources","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","allowEmptyValue":true,"schema":{"type":"string","description":"Resource name or alias; supports typeahead","example":"gov board","minLength":1},"example":"gov board"},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","allowEmptyValue":true,"schema":{"type":"string","description":"Parent (for navigation; varies by object type)","example":"project:123","pattern":"^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$"},"example":"project:123"},{"name":"type","in":"query","description":"Resource type to search","allowEmptyValue":true,"schema":{"type":"string","description":"Resource type to search","example":"committee"},"example":"committee"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Animi aspernatur."},"description":"Tags to search with OR logic - matches resources with any of these tags","example":["active","public"]},"example":["active","public"]},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Ex itaque."},"description":"Tags to search with AND logic - matches resources that have all of these tags","example":["governance","security"]},"example":["governance","security"]},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","allowEmptyValue":true,"schema":{"type":"string","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","example":"updated_at"},"example":"updated_at"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","example":"2025-01-10"},"example":"2025-01-10"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","example":"2025-01-28"},"example":"2025-01-28"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Sint commodi."},"description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Labore aperiam libero ipsam et ullam."},"description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Doloribus voluptatem ipsa optio."},"description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","example":["mailing_list_id:abc","mailing_list_id:xyz"]},"example":["mailing_list_id:abc","mailing_list_id:xyz"]},{"name":"cel_filter","in":"query","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","allowEmptyValue":true,"schema":{"type":"string","description":"CEL expression to filter results on resource data fields. Available variables: data (map), resource_type (string), id (string)","example":"data.slug == \"tlf\"","maxLength":1000},"example":"data.slug == \"tlf\""},{"name":"filter_grants","in":"query","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","allowEmptyValue":true,"schema":{"type":"string","description":"Filter results to only resources the authenticated user has direct FGA grants on. Requires type. Use 'direct' to filter by direct grants.","example":"direct","enum":["direct"]},"example":"direct"},{"name":"sort","in":"query","description":"Sort order for results","allowEmptyValue":true,"schema":{"type":"string","description":"Sort order for results","default":"name_asc","example":"updated_desc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]},"example":"updated_desc"},{"name":"page_token","in":"query","description":"Opaque token for pagination","allowEmptyValue":true,"schema":{"type":"string","description":"Opaque token for pagination","example":"****"},"example":"****"},{"name":"page_size","in":"query","description":"Number of results per page","allowEmptyValue":true,"schema":{"type":"integer","description":"Number of results per page","default":50,"example":20,"format":"int64","minimum":1,"maximum":1000},"example":20}],"responses":{"200":{"description":"OK response.","headers":{"Cache-Control":{"description":"Cache control header","schema":{"type":"string","description":"Cache control header","example":"public, max-age=300"},"example":"public, max-age=300"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResourcesResponseBody"},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/query/resources/count":{"get":{"tags":["query-svc"],"summary":"query-resources-count query-svc","description":"Count matching resources by query.","operationId":"query-svc#query-resources-count","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"name","in":"query","description":"Resource name or alias; supports typeahead","allowEmptyValue":true,"schema":{"type":"string","description":"Resource name or alias; supports typeahead","example":"gov board","minLength":1},"example":"gov board"},{"name":"parent","in":"query","description":"Parent (for navigation; varies by object type)","allowEmptyValue":true,"schema":{"type":"string","description":"Parent (for navigation; varies by object type)","example":"project:123","pattern":"^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$"},"example":"project:123"},{"name":"type","in":"query","description":"Resource type to search","allowEmptyValue":true,"schema":{"type":"string","description":"Resource type to search","example":"committee"},"example":"committee"},{"name":"tags","in":"query","description":"Tags to search with OR logic - matches resources with any of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Nobis corporis aperiam consectetur temporibus voluptatem vitae."},"description":"Tags to search with OR logic - matches resources with any of these tags","example":["active","public"]},"example":["active","public"]},{"name":"tags_all","in":"query","description":"Tags to search with AND logic - matches resources that have all of these tags","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Dolor culpa aliquam."},"description":"Tags to search with AND logic - matches resources that have all of these tags","example":["governance","security"]},"example":["governance","security"]},{"name":"date_field","in":"query","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","allowEmptyValue":true,"schema":{"type":"string","description":"Date field to filter on (within data object) - used with date_from and/or date_to. Supports ISO 8601 datetime (2006-01-02T15:04:05Z) or date-only (2006-01-02, assumes UTC)","example":"updated_at"},"example":"updated_at"},{"name":"date_from","in":"query","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"Start date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses start of day UTC. Requires date_field.","example":"2025-01-10"},"example":"2025-01-10"},{"name":"date_to","in":"query","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","allowEmptyValue":true,"schema":{"type":"string","description":"End date (inclusive). Format: ISO 8601 datetime or date-only. Date-only uses end of day UTC. Requires date_field.","example":"2025-01-28"},"example":"2025-01-28"},{"name":"filters","in":"query","description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Facere dolores numquam consequatur ut est."},"description":"Direct field filters with term clauses on data fields - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_all","in":"query","description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Necessitatibus labore minima vitae."},"description":"Direct field filters with term clauses on data fields using AND logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy all of the provided filters.","example":["status:active","priority:high"]},"example":["status:active","priority:high"]},{"name":"filters_or","in":"query","description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","allowEmptyValue":true,"schema":{"type":"array","items":{"type":"string","example":"Eum sequi dolorum adipisci numquam iusto ipsum."},"description":"Direct field filters with term clauses on data fields using OR logic - format: 'field:value' (e.g., 'status:active'). Fields are automatically prefixed with 'data.'. Matches resources that satisfy at least one of the provided filters.","example":["mailing_list_id:abc","mailing_list_id:xyz"]},"example":["mailing_list_id:abc","mailing_list_id:xyz"]}],"responses":{"200":{"description":"OK response.","headers":{"Cache-Control":{"description":"Cache control header","schema":{"type":"string","description":"Cache control header","example":"public, max-age=300"},"example":"public, max-age=300"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResourcesCountResponseBody"},"example":{"count":1234,"has_more":false}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"message":"The request was invalid."},"required":["message"]},"InternalServerError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The requested resource was not found."}},"example":{"message":"The requested resource was not found."},"required":["message"]},"Organization":{"type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"employees":{"type":"string","description":"Employee count or range","example":"100-499"},"industry":{"type":"string","description":"Organization industry classification","example":"Non-Profit"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"},"sector":{"type":"string","description":"Business sector classification","example":"Technology"}},"description":"An organization is a universal representation of an LFX API organization.","example":{"domain":"linuxfoundation.org","employees":"100-499","industry":"Non-Profit","name":"Linux Foundation","sector":"Technology"}},"OrganizationSuggestion":{"type":"object","properties":{"domain":{"type":"string","description":"Organization domain","example":"linuxfoundation.org"},"logo":{"type":"string","description":"Organization logo URL","example":"https://example.com/logo.png"},"name":{"type":"string","description":"Organization name","example":"Linux Foundation"}},"description":"An organization suggestion for the search.","example":{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},"required":["name","domain"]},"QueryResourcesCountResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Count of resources found","example":1234,"format":"int64"},"has_more":{"type":"boolean","description":"True if count is not guaranteed to be exhaustive: client should request a narrower query","example":false}},"example":{"count":1234,"has_more":false},"required":["count","has_more"]},"QueryResourcesResponseBody":{"type":"object","properties":{"page_token":{"type":"string","description":"Opaque token if more results are available","example":"****"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/Resource"},"description":"Resources found","example":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]}},"example":{"page_token":"****","resources":[{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"},{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}]},"required":["resources"]},"Resource":{"type":"object","properties":{"data":{"description":"Resource data snapshot","example":{"id":"123","name":"My committee","description":"a committee"}},"id":{"type":"string","description":"Resource ID (within its resource collection)","example":"123"},"type":{"type":"string","description":"Resource type","example":"committee"}},"description":"A resource is a universal representation of an LFX API resource for indexing.","example":{"data":{"id":"123","name":"My committee","description":"a committee"},"id":"123","type":"committee"}},"ServiceUnavailableError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"message":"The service is unavailable."},"required":["message"]},"Sortable":{"type":"object","properties":{"page_size":{"type":"integer","description":"Number of results per page","default":50,"example":20,"format":"int64","minimum":1,"maximum":1000},"page_token":{"type":"string","description":"Opaque token for pagination","example":"****"},"sort":{"type":"string","description":"Sort order for results","default":"name_asc","example":"updated_desc","enum":["name_asc","name_desc","updated_asc","updated_desc","best_match"]}},"example":{"page_size":20,"page_token":"****","sort":"updated_desc"}},"SuggestOrgsResponseBody":{"type":"object","properties":{"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationSuggestion"},"description":"Organization suggestions","example":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]}},"example":{"suggestions":[{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"},{"domain":"linuxfoundation.org","logo":"https://example.com/logo.png","name":"Linux Foundation"}]},"required":["suggestions"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"Heimdall authorization","scheme":"bearer"}}},"tags":[{"name":"query-svc","description":"The query service provides resource and user queries."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index 84d76e7..eb5836c 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -207,7 +207,7 @@ paths: type: string description: Parent (for navigation; varies by object type) example: project:123 - pattern: ^[a-zA-Z]+:[a-zA-Z0-9_-]+$ + pattern: ^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$ example: project:123 - name: type in: query @@ -484,6 +484,7 @@ paths: type: string description: Parent (for navigation; varies by object type) example: project:123 + pattern: ^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$ example: project:123 - name: type in: query diff --git a/gen/http/query_svc/client/cli.go b/gen/http/query_svc/client/cli.go index c13fcee..e34b5b4 100644 --- a/gen/http/query_svc/client/cli.go +++ b/gen/http/query_svc/client/cli.go @@ -47,7 +47,7 @@ func BuildQueryResourcesPayload(querySvcQueryResourcesVersion string, querySvcQu { if querySvcQueryResourcesParent != "" { parent = &querySvcQueryResourcesParent - err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z]+:[a-zA-Z0-9_-]+$")) + err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$")) if err != nil { return nil, err } @@ -241,6 +241,10 @@ func BuildQueryResourcesCountPayload(querySvcQueryResourcesCountVersion string, { if querySvcQueryResourcesCountParent != "" { parent = &querySvcQueryResourcesCountParent + err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$")) + if err != nil { + return nil, err + } } } var type_ *string diff --git a/gen/http/query_svc/server/encode_decode.go b/gen/http/query_svc/server/encode_decode.go index 4b7d32e..533623b 100644 --- a/gen/http/query_svc/server/encode_decode.go +++ b/gen/http/query_svc/server/encode_decode.go @@ -82,7 +82,7 @@ func DecodeQueryResourcesRequest(mux goahttp.Muxer, decoder func(*http.Request) parent = &parentRaw } if parent != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z]+:[a-zA-Z0-9_-]+$")) + err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$")) } type_Raw := qp.Get("type") if type_Raw != "" { @@ -283,6 +283,9 @@ func DecodeQueryResourcesCountRequest(mux goahttp.Muxer, decoder func(*http.Requ if parentRaw != "" { parent = &parentRaw } + if parent != nil { + err = goa.MergeErrors(err, goa.ValidatePattern("parent", *parent, "^[a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+$")) + } type_Raw := qp.Get("type") if type_Raw != "" { type_ = &type_Raw