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
@ -19,7 +19,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
ghlTokenURL = "https://services.leadconnectorhq.com/oauth/token"
|
ghlTokenURL = "https://services.leadconnectorhq.com/oauth/token"
|
||||||
ghlLocationTokenURL = "https://services.leadconnectorhq.com/oauth/locationToken"
|
ghlLocationTokenURL = "https://services.leadconnectorhq.com/oauth/locationToken"
|
||||||
ghlLocationsURL = "https://services.leadconnectorhq.com/locations/search"
|
ghlInstalledLocationsURL = "https://services.leadconnectorhq.com/oauth/installedLocations"
|
||||||
ghlLocationAPIVersion = "2021-07-28"
|
ghlLocationAPIVersion = "2021-07-28"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -286,14 +286,16 @@ func (h *OAuthHandler) installAllLocations(ctx context.Context, companyToken *To
|
|||||||
return installed, nil
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
q.Set("companyId", companyID)
|
q.Set("companyId", companyID)
|
||||||
|
q.Set("isInstalled", "true")
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
req.Header.Set("Authorization", "Bearer "+companyAccessToken)
|
req.Header.Set("Authorization", "Bearer "+companyAccessToken)
|
||||||
req.Header.Set("Version", ghlLocationAPIVersion)
|
req.Header.Set("Version", ghlLocationAPIVersion)
|
||||||
@ -308,14 +310,14 @@ func (h *OAuthHandler) getCompanyLocations(ctx context.Context, companyAccessTok
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
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 {
|
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
|
return locResp.Locations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,15 @@ type TokenResponse struct {
|
|||||||
InstalledLocations []string `json:"installedLocations"` // populated on bulk company installs
|
InstalledLocations []string `json:"installedLocations"` // populated on bulk company installs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocationInfo represents a GHL location entry from the /oauth/installedLocations response.
|
||||||
type LocationInfo struct {
|
type LocationInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"_id"` // /oauth/installedLocations uses _id
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationsResponse struct {
|
type InstalledLocationsResponse struct {
|
||||||
Locations []LocationInfo `json:"locations"`
|
Locations []LocationInfo `json:"locations"`
|
||||||
|
Count int `json:"count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundMessageWebhook struct {
|
type OutboundMessageWebhook struct {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user