generated from rudderlabs/rudder-repo-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add get bulk upload url api support
- Loading branch information
Showing
11 changed files
with
542 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
coverage.txt | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
vendor/ | ||
|
||
token.json | ||
|
||
.env | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
install: | ||
sh install-hooks.sh | ||
default: build | ||
|
||
test: install-tools test-run | ||
|
||
test-run: | ||
gotestsum --format pkgname-and-test-fails -- -count=1 -shuffle=on -coverprofile=coverage.txt -vet=all ./... | ||
|
||
build: | ||
go build -o bin/$(NAME) ./cmd/$(NAME).go | ||
|
||
test-with-coverage: test coverage | ||
|
||
coverage: | ||
go tool cover -html=coverage.txt -o coverage.html | ||
|
||
install-tools: | ||
go install mvdan.cc/gofumpt@latest | ||
go install gotest.tools/gotestsum@v1.8.2 | ||
|
||
.PHONY: lint | ||
lint: fmt ## Run linters on all go files | ||
docker run --rm -v $(shell pwd):/app:ro -w /app golangci/golangci-lint:v1.51.1 bash -e -c \ | ||
'golangci-lint run -v --timeout 5m' | ||
|
||
.PHONY: fmt | ||
fmt: install-tools ## Formats all go files | ||
gofumpt -l -w -extra . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package bingads | ||
|
||
import ( | ||
"encoding/xml" | ||
"net/http" | ||
|
||
"golang.org/x/oauth2" | ||
) | ||
|
||
var ( | ||
EnvelopeNamespace = "http://schemas.xmlsoap.org/soap/envelope/" | ||
BingNamespace = "https://bingads.microsoft.com/CampaignManagement/v13" | ||
AuthEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" | ||
TokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token" | ||
) | ||
|
||
type RequestEnvelope struct { | ||
XMLName xml.Name `xml:"s:Envelope"` | ||
EnvNS string `xml:"xmlns:i,attr"` | ||
EnvSS string `xml:"xmlns:s,attr"` | ||
Header RequestHeader | ||
Body RequestBody | ||
} | ||
|
||
type SoapResponseBody struct { | ||
OperationResponse []byte `xml:",innerxml"` | ||
} | ||
|
||
type SoapResponseEnvelope struct { | ||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` | ||
Header TrackingId `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` | ||
Body SoapResponseBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` | ||
} | ||
|
||
type TrackingId struct { | ||
Nil bool `xml:"http://www.w3.org/2001/XMLSchema-instance nil,attr"` | ||
TrackingId string `xml:"https://adcenter.microsoft.com/v8 TrackingId"` | ||
} | ||
|
||
type RequestBody struct { | ||
XMLName xml.Name `xml:"s:Body"` | ||
Body interface{} | ||
} | ||
|
||
type RequestHeader struct { | ||
XMLName xml.Name `xml:"s:Header"` | ||
BingNS string `xml:"xmlns,attr"` | ||
Action string | ||
AuthenticationToken string `xml:"AuthenticationToken,omitempty"` | ||
CustomerAccountId string `xml:"CustomerAccountId"` | ||
CustomerId string `xml:"CustomerId,omitempty"` | ||
DeveloperToken string `xml:"DeveloperToken"` | ||
Password string `xml:"Password,omitempty"` | ||
Username string `xml:"UserName,omitempty"` | ||
} | ||
|
||
type Session struct { | ||
AccountId string | ||
CustomerId string | ||
DeveloperToken string | ||
Username string | ||
Password string | ||
HTTPClient HttpClient | ||
TokenSource oauth2.TokenSource | ||
} | ||
|
||
type HttpClient interface { | ||
Do(*http.Request) (*http.Response, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package bingads | ||
|
||
import ( | ||
"encoding/xml" | ||
"strconv" | ||
) | ||
|
||
type BulkService struct { | ||
Endpoint string | ||
Session *Session | ||
} | ||
|
||
func NewBulkService(session *Session) *BulkService { | ||
return &BulkService{ | ||
Endpoint: "https://bulk.api.bingads.microsoft.com/Api/Advertiser/CampaignManagement/v13/BulkService.svc", | ||
Session: session, | ||
} | ||
} | ||
|
||
type GetBulkUploadUrlRequest struct { | ||
XMLName xml.Name `xml:"GetBulkUploadUrlRequest"` | ||
NS string `xml:"xmlns,attr"` | ||
AccountId int64 `xml:"AccountId"` | ||
ResponseMode string `xml:"ResponseMode"` | ||
} | ||
|
||
type GetBulkUploadUrlResponse struct { | ||
UploadUrl string `xml:"UploadUrl"` | ||
RequestId string `xml:"RequestId"` | ||
} | ||
|
||
// GetBulkCampaignsByAccountId | ||
func (c *BulkService) GetBulkUploadUrl() (*GetBulkUploadUrlResponse, error) { | ||
accountId, _ := strconv.ParseInt(c.Session.AccountId, 10, 64) | ||
req := GetBulkUploadUrlRequest{ | ||
NS: BingNamespace, | ||
AccountId: accountId, | ||
ResponseMode: "ErrorsOnly", | ||
} | ||
|
||
resp, err := c.Session.SendRequest(req, c.Endpoint, "GetBulkUploadUrl") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var getBulkUploadUrlResponse GetBulkUploadUrlResponse | ||
if err = xml.Unmarshal(resp, &getBulkUploadUrlResponse); err != nil { | ||
return nil, err | ||
} | ||
return &getBulkUploadUrlResponse, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package bingads | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/xml" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
|
||
"golang.org/x/oauth2" | ||
) | ||
|
||
var debug = os.Getenv("BING_SDK_DEBUG") | ||
|
||
func (b *Session) SendRequest(body interface{}, endpoint, soapAction string) ([]byte, error) { | ||
return b.sendRequest(body, endpoint, soapAction, campaignns) | ||
} | ||
|
||
var campaignns = "https://bingads.microsoft.com/CampaignManagement/v13" | ||
|
||
// FIXME: namespace | ||
func (b *Session) sendRequest(body interface{}, endpoint, soapAction, ns string) ([]byte, error) { | ||
header := RequestHeader{ | ||
BingNS: ns, | ||
Action: soapAction, | ||
CustomerAccountId: b.AccountId, | ||
CustomerId: b.CustomerId, | ||
DeveloperToken: b.DeveloperToken, | ||
} | ||
if b.TokenSource != nil { | ||
token, err := b.TokenSource.Token() | ||
if err != nil { | ||
return nil, err | ||
} | ||
header.AuthenticationToken = token.AccessToken | ||
} else { | ||
header.Username = b.Username | ||
header.Password = b.Password | ||
} | ||
|
||
envelope := RequestEnvelope{ | ||
EnvNS: "http://www.w3.org/2001/XMLSchema-instance", | ||
EnvSS: "http://schemas.xmlsoap.org/soap/envelope/", | ||
Header: header, | ||
Body: RequestBody{ | ||
Body: body, | ||
}, | ||
} | ||
|
||
req, err := xml.MarshalIndent(envelope, "", " ") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
httpRequest, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(req)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
httpRequest.Header.Add("Content-Type", "text/xml; charset=utf-8") | ||
httpRequest.Header.Add("SOAPAction", soapAction) | ||
|
||
response, err := b.HTTPClient.Do(httpRequest) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
raw, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if debug != "" { | ||
fmt.Println(string(req)) | ||
} | ||
// fmt.Println(string(raw)) | ||
|
||
res := SoapResponseEnvelope{} | ||
|
||
err = xml.Unmarshal(raw, &res) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if debug != "" { | ||
fmt.Println(">>>") | ||
fmt.Println(string(res.Body.OperationResponse)) | ||
fmt.Println(">>>") | ||
} | ||
|
||
switch response.StatusCode { | ||
case 400, 401, 403, 405, 500: | ||
fault := Fault{} | ||
err = xml.Unmarshal(res.Body.OperationResponse, &fault) | ||
if err != nil { | ||
return res.Body.OperationResponse, err | ||
} | ||
for _, e := range fault.Detail.Errors.AdApiErrors { | ||
switch e.ErrorCode { | ||
case "AuthenticationTokenExpired", "InvalidCredentials", "InternalError", "CallRateExceeded": | ||
return res.Body.OperationResponse, &baseError{ | ||
code: e.ErrorCode, | ||
origErr: &fault.Detail.Errors, | ||
} | ||
} | ||
} | ||
return res.Body.OperationResponse, &fault.Detail.Errors | ||
} | ||
|
||
return res.Body.OperationResponse, err | ||
} | ||
|
||
type SessionConfig struct { | ||
OAuth2Config *oauth2.Config | ||
OAuth2Token *oauth2.Token | ||
AccountId string | ||
CustomerId string | ||
DeveloperToken string | ||
HTTPClient HttpClient | ||
} | ||
|
||
func NewSession(config SessionConfig) *Session { | ||
tokenSource := config.OAuth2Config.TokenSource(context.TODO(), config.OAuth2Token) | ||
|
||
return &Session{ | ||
AccountId: config.AccountId, | ||
CustomerId: config.CustomerId, | ||
DeveloperToken: config.DeveloperToken, | ||
HTTPClient: config.HTTPClient, | ||
TokenSource: tokenSource, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package bingads | ||
|
||
import ( | ||
"encoding/xml" | ||
"strings" | ||
) | ||
|
||
type baseError struct { | ||
code string | ||
origErr error | ||
} | ||
|
||
func (b baseError) Error() string { | ||
return b.origErr.Error() | ||
} | ||
|
||
func (b baseError) String() string { | ||
return b.Error() | ||
} | ||
|
||
func (b baseError) Code() string { | ||
return b.code | ||
} | ||
|
||
func (b baseError) OrigErr() error { | ||
return b.origErr | ||
} | ||
|
||
type AdApiError struct { | ||
Code int64 `xml:"AdApiError>Code"` | ||
Details string `xml:"AdApiError>Details"` | ||
ErrorCode string `xml:"AdApiError>ErrorCode"` | ||
Message string `xml:"AdApiError>Message"` | ||
} | ||
|
||
type OperationError struct { | ||
Code int64 `xml:"OperationError>Code"` | ||
Details string `xml:"OperationError>Details"` | ||
ErrorCode string `xml:"OperationError>ErrorCode"` | ||
Message string `xml:"OperationError>Message"` | ||
} | ||
|
||
type Fault struct { | ||
FaultCode string `xml:"faultcode"` | ||
FaultString string `xml:"faultstring"` | ||
Detail struct { | ||
XMLName xml.Name `xml:"detail"` | ||
Errors ErrorsType `xml:",any"` | ||
} | ||
} | ||
|
||
type ErrorsType struct { | ||
TrackingId string `xml:"TrackingId"` | ||
AdApiErrors []AdApiError `xml:"Errors"` | ||
OperationErrors []OperationError `xml:"OperationErrors"` | ||
} | ||
|
||
func (f *ErrorsType) Error() string { | ||
errors := []string{} | ||
for _, e := range f.AdApiErrors { | ||
errors = append(errors, e.Message) | ||
} | ||
|
||
for _, e := range f.OperationErrors { | ||
errors = append(errors, e.Message) | ||
} | ||
return strings.Join(errors, "\n") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env bash | ||
make fmt | ||
make test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module github.com/rudderlabs/bing-ads-go-sdk | ||
|
||
go 1.20 | ||
|
||
require golang.org/x/oauth2 v0.7.0 | ||
|
||
require ( | ||
github.com/golang/protobuf v1.5.2 // indirect | ||
github.com/joho/godotenv v1.5.1 | ||
golang.org/x/net v0.9.0 // indirect | ||
google.golang.org/appengine v1.6.7 // indirect | ||
google.golang.org/protobuf v1.28.0 // indirect | ||
) |
Oops, something went wrong.