fix: switch location lookup to /oauth/installedLocations (oauth.readonly scope)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
/locations/search requires locations.readonly which GHL never includes in company-level OAuth tokens. /oauth/installedLocations uses oauth.readonly, which is always present in company tokens, and returns only locations where this app is actually installed. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
3863e8f0cd
commit
59b0a8c93f
@ -17,10 +17,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ghlTokenURL = "https://services.leadconnectorhq.com/oauth/token"
|
||||
ghlLocationTokenURL = "https://services.leadconnectorhq.com/oauth/locationToken"
|
||||
ghlLocationsURL = "https://services.leadconnectorhq.com/locations/search"
|
||||
ghlLocationAPIVersion = "2021-07-28"
|
||||
ghlTokenURL = "https://services.leadconnectorhq.com/oauth/token"
|
||||
ghlLocationTokenURL = "https://services.leadconnectorhq.com/oauth/locationToken"
|
||||
ghlInstalledLocationsURL = "https://services.leadconnectorhq.com/oauth/installedLocations"
|
||||
ghlLocationAPIVersion = "2021-07-28"
|
||||
)
|
||||
|
||||
// TokenStore is the interface OAuthHandler uses for token persistence.
|
||||
@ -286,14 +286,16 @@ func (h *OAuthHandler) installAllLocations(ctx context.Context, companyToken *To
|
||||
return installed, nil
|
||||
}
|
||||
|
||||
// getCompanyLocations lists all locations for a company using the company-scoped token.
|
||||
// getCompanyLocations lists installed locations for a company using /oauth/installedLocations.
|
||||
// This endpoint requires the oauth.readonly scope, which is present on all Company-level tokens.
|
||||
func (h *OAuthHandler) getCompanyLocations(ctx context.Context, companyAccessToken, companyID string) ([]LocationInfo, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ghlLocationsURL, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ghlInstalledLocationsURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := req.URL.Query()
|
||||
q.Set("companyId", companyID)
|
||||
q.Set("isInstalled", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req.Header.Set("Authorization", "Bearer "+companyAccessToken)
|
||||
req.Header.Set("Version", ghlLocationAPIVersion)
|
||||
@ -308,14 +310,14 @@ func (h *OAuthHandler) getCompanyLocations(ctx context.Context, companyAccessTok
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("locations endpoint returned %d: %s", resp.StatusCode, string(body))
|
||||
return nil, fmt.Errorf("installedLocations endpoint returned %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var locResp LocationsResponse
|
||||
var locResp InstalledLocationsResponse
|
||||
if err := json.Unmarshal(body, &locResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse locations response: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse installedLocations response: %w", err)
|
||||
}
|
||||
slog.Info("ghl company locations fetched", "company_id", companyID, "count", len(locResp.Locations))
|
||||
slog.Info("ghl installed locations fetched", "company_id", companyID, "count", len(locResp.Locations))
|
||||
return locResp.Locations, nil
|
||||
}
|
||||
|
||||
|
||||
@ -11,13 +11,15 @@ type TokenResponse struct {
|
||||
InstalledLocations []string `json:"installedLocations"` // populated on bulk company installs
|
||||
}
|
||||
|
||||
// LocationInfo represents a GHL location entry from the /oauth/installedLocations response.
|
||||
type LocationInfo struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"_id"` // /oauth/installedLocations uses _id
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LocationsResponse struct {
|
||||
type InstalledLocationsResponse struct {
|
||||
Locations []LocationInfo `json:"locations"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type OutboundMessageWebhook struct {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user