Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/lfx-v2-query-service/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ apiVersion: v2
name: lfx-v2-query-service
description: LFX Platform V2 Query Service chart
type: application
version: 0.4.0
version: 0.4.1
appVersion: "latest"
4 changes: 2 additions & 2 deletions charts/lfx-v2-query-service/templates/httproute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
rules:
- matches:
- path:
type: Exact
type: PathPrefix
value: /query/orgs
{{- if .Values.heimdall.enabled }}
filters:
Expand All @@ -25,7 +25,7 @@ spec:
kind: Middleware
name: heimdall
{{- if .Values.orgSearch.rateLimit.enabled }}
# Organization search endpoint (with rate limiting + authentication)
# Organization endpoints (with rate limiting + authentication)
- type: ExtensionRef
extensionRef:
group: traefik.io
Expand Down
16 changes: 16 additions & 0 deletions charts/lfx-v2-query-service/templates/ruleset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,19 @@ spec:
config:
values:
aud: lfx-v2-query-service
- id: "rule:lfx:lfx-v2-query-service:org-suggest"
match:
methods:
- GET
routes:
- path: /query/orgs/suggest
execute:
- authenticator: oidc
{{- if .Values.app.use_oidc_contextualizer }}
- contextualizer: oidc_contextualizer
{{- end }}
- authorizer: allow_all
- finalizer: create_jwt
config:
values:
aud: lfx-v2-query-service
28 changes: 28 additions & 0 deletions cmd/service/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,31 @@ func (s *querySvcsrvc) domainOrganizationToResponse(org *model.Organization) *qu
Employees: &org.Employees,
}
}

// payloadToOrganizationSuggestionCriteria converts the generated payload to domain organization suggestion criteria
func (s *querySvcsrvc) payloadToOrganizationSuggestionCriteria(ctx context.Context, p *querysvc.SuggestOrgsPayload) model.OrganizationSuggestionCriteria {
criteria := model.OrganizationSuggestionCriteria{
Query: p.Query,
}
return criteria
}

// domainOrganizationSuggestionsToResponse converts domain organization suggestions result to generated response
func (s *querySvcsrvc) domainOrganizationSuggestionsToResponse(result *model.OrganizationSuggestionsResult) *querysvc.SuggestOrgsResult {
if result == nil || len(result.Suggestions) == 0 {
return &querysvc.SuggestOrgsResult{Suggestions: []*querysvc.OrganizationSuggestion{}}
}
suggestions := make([]*querysvc.OrganizationSuggestion, len(result.Suggestions))

for i, domainSuggestion := range result.Suggestions {
suggestions[i] = &querysvc.OrganizationSuggestion{
Name: domainSuggestion.Name,
Domain: domainSuggestion.Domain,
Logo: domainSuggestion.Logo,
}
}

return &querysvc.SuggestOrgsResult{
Suggestions: suggestions,
}
}
3 changes: 3 additions & 0 deletions cmd/service/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ func OrganizationSearcherImpl(ctx context.Context) port.OrganizationSearcher {
// Parse Clearbit environment variables
clearbitAPIKey := os.Getenv("CLEARBIT_CREDENTIAL")
clearbitBaseURL := os.Getenv("CLEARBIT_BASE_URL")
clearbitAutocompleteBaseURL := os.Getenv("CLEARBIT_AUTOCOMPLETE_BASE_URL")
clearbitTimeout := os.Getenv("CLEARBIT_TIMEOUT")

clearbitMaxRetries := os.Getenv("CLEARBIT_MAX_RETRIES")
Expand All @@ -181,6 +182,7 @@ func OrganizationSearcherImpl(ctx context.Context) port.OrganizationSearcher {

clearbitConfig, err := clearbit.NewConfig(clearbitAPIKey,
clearbitBaseURL,
clearbitAutocompleteBaseURL,
clearbitTimeout,
clearbitMaxRetriesInt,
clearbitRetryDelay,
Expand All @@ -191,6 +193,7 @@ func OrganizationSearcherImpl(ctx context.Context) port.OrganizationSearcher {

slog.InfoContext(ctx, "initializing Clearbit organization searcher",
"base_url", clearbitConfig.BaseURL,
"autocomplete_base_url", clearbitConfig.AutocompleteBaseURL,
"timeout", clearbitConfig.Timeout,
"max_retries", clearbitConfig.MaxRetries,
)
Expand Down
21 changes: 21 additions & 0 deletions cmd/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ func (s *querySvcsrvc) QueryOrgs(ctx context.Context, p *querysvc.QueryOrgsPaylo
return res, nil
}

// Get organization suggestions for typeahead search based on a query.
func (s *querySvcsrvc) SuggestOrgs(ctx context.Context, p *querysvc.SuggestOrgsPayload) (res *querysvc.SuggestOrgsResult, err error) {

slog.DebugContext(ctx, "querySvc.suggest-orgs",
"query", p.Query,
)

// Convert payload to domain criteria
criteria := s.payloadToOrganizationSuggestionCriteria(ctx, p)

// Execute search using the service layer
result, errSuggestOrgs := s.organizationService.SuggestOrganizations(ctx, criteria)
if errSuggestOrgs != nil {
return nil, wrapError(ctx, errSuggestOrgs)
}

// Convert domain result to response
res = s.domainOrganizationSuggestionsToResponse(result)
return res, nil
}

// Check if the service is able to take inbound requests.
func (s *querySvcsrvc) Readyz(ctx context.Context) (res []byte, err error) {
errIsReady := s.resourceService.IsReady(ctx)
Expand Down
38 changes: 38 additions & 0 deletions design/query-svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,44 @@ var _ = dsl.Service("query-svc", func() {
})
})

dsl.Method("suggest-orgs", func() {
dsl.Description("Get organization suggestions for typeahead search based on a query.")

dsl.Security(JWTAuth)

dsl.Payload(func() {
dsl.Token("bearer_token", dsl.String, func() {
dsl.Description("Token")
dsl.Example("eyJhbGci...")
})
dsl.Attribute("version", dsl.String, "Version of the API", func() {
dsl.Enum("1")
dsl.Example("1")
})
dsl.Attribute("query", dsl.String, "Search query for organization suggestions", func() {
dsl.Example("linux")
dsl.MinLength(1)
})
dsl.Required("bearer_token", "version", "query")
})

dsl.Result(func() {
dsl.Attribute("suggestions", dsl.ArrayOf(OrganizationSuggestion), "Organization suggestions", func() {})
dsl.Required("suggestions")
})

dsl.HTTP(func() {
dsl.GET("/query/orgs/suggest")
dsl.Param("version:v")
dsl.Param("query")
dsl.Header("bearer_token:Authorization")
dsl.Response(dsl.StatusOK)
dsl.Response("BadRequest", dsl.StatusBadRequest)
dsl.Response("InternalServerError", dsl.StatusInternalServerError)
dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable)
})
})

dsl.Method("readyz", func() {
dsl.Description("Check if the service is able to take inbound requests.")
dsl.Meta("swagger:generate", "false")
Expand Down
15 changes: 15 additions & 0 deletions design/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ var Organization = dsl.Type("Organization", func() {
})
})

var OrganizationSuggestion = dsl.Type("OrganizationSuggestion", func() {
dsl.Description("An organization suggestion for the search.")

dsl.Attribute("name", dsl.String, "Organization name", func() {
dsl.Example("Linux Foundation")
})
dsl.Attribute("domain", dsl.String, "Organization domain", func() {
dsl.Example("linuxfoundation.org")
})
dsl.Attribute("logo", dsl.String, "Organization logo URL", func() {
dsl.Example("https://example.com/logo.png")
})
dsl.Required("name", "domain")
})

// Define an example cached LFX resource for the nested "data" attribute for
// resource searches. This example happens to be a committee to match the
// example value of "committee" for the "type" attribute of Resource.
Expand Down
28 changes: 27 additions & 1 deletion gen/http/cli/lfx_v2_query_service/cli.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion gen/http/openapi.json

Large diffs are not rendered by default.

Loading