Initial import into Gitea
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using GoogleApi.Configuration;
|
||||
using GoogleApi.Configuration;
|
||||
using GoogleApi.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -23,15 +23,24 @@ public sealed class GoogleAdsService
|
||||
{
|
||||
private readonly GoogleAdsConfig _config;
|
||||
private readonly GoogleAdsClientFactory _clientFactory;
|
||||
private readonly AudienceService _audienceService;
|
||||
private readonly ReportingService _reportingService;
|
||||
private readonly KeywordForecastService _forecastService;
|
||||
private readonly ILogger<GoogleAdsService> _logger;
|
||||
|
||||
public GoogleAdsService(
|
||||
IOptions<GoogleAdsConfig> config,
|
||||
GoogleAdsClientFactory clientFactory,
|
||||
AudienceService audienceService,
|
||||
ReportingService reportingService,
|
||||
KeywordForecastService forecastService,
|
||||
ILogger<GoogleAdsService> logger)
|
||||
{
|
||||
_config = config.Value;
|
||||
_clientFactory = clientFactory;
|
||||
_audienceService = audienceService;
|
||||
_reportingService = reportingService;
|
||||
_forecastService = forecastService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -65,7 +74,20 @@ public sealed class GoogleAdsService
|
||||
"GetCampaignStats" => GetCampaignStats(request, requestId),
|
||||
"GetAccountStats" => GetAccountStats(request, requestId),
|
||||
|
||||
// Reporting (Campaign Intelligence)
|
||||
"GetCampaignReport" => await _reportingService.GetCampaignReportAsync(request, context, requestId, ct),
|
||||
"GetAccountReport" => await _reportingService.GetAccountReportAsync(request, context, requestId, ct),
|
||||
|
||||
"ListAccessibleCustomers" => await ListAccessibleCustomersAsync(context, requestId, ct),
|
||||
"CreateCustomerClient" => await CreateCustomerClientAsync(request, context, requestId, ct),
|
||||
|
||||
// Audience Operations
|
||||
"GetAudienceSegments" => await _audienceService.GetAudienceSegmentsAsync(context, requestId, ct),
|
||||
"SearchGeoTargets" => await _audienceService.SearchGeoTargetsAsync(
|
||||
request.GetPayload<GeoTargetSearchPayload>(), context, requestId, ct),
|
||||
|
||||
// Forecast
|
||||
"KeywordForecast" => await ExecuteForecastAsync(request, context, requestId, ct),
|
||||
|
||||
"" => ProviderResponse.Fail(requestId, "VALIDATION", "Operation is required"),
|
||||
_ => ProviderResponse.Fail(requestId, "UNKNOWN_OPERATION", $"Unknown operation: {operation}")
|
||||
@@ -94,6 +116,14 @@ public sealed class GoogleAdsService
|
||||
timestamp = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
private async Task<ProviderResponse> ExecuteForecastAsync(
|
||||
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
||||
{
|
||||
var payload = request.GetPayload<KeywordForecastPayload>();
|
||||
var result = await _forecastService.ForecastAsync(payload, context, ct);
|
||||
return ProviderResponse.Success(requestId, result);
|
||||
}
|
||||
|
||||
private async Task<ProviderResponse> CreateCampaignAsync(
|
||||
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
||||
{
|
||||
@@ -495,6 +525,100 @@ ORDER BY campaign.name";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ProviderResponse> CreateCustomerClientAsync(
|
||||
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
||||
{
|
||||
var payload = request.GetPayload<CreateCustomerClientPayload>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(payload.AccountName))
|
||||
return ProviderResponse.Fail(requestId, "VALIDATION", "AccountName is required");
|
||||
|
||||
// For emulated mode
|
||||
if (!_clientFactory.IsRealApiEnabled)
|
||||
{
|
||||
var fakeId = new Random().NextInt64(1000000000, 9999999999).ToString();
|
||||
_logger.LogInformation("[GoogleAds] EMULATED: Created customer client {AccountName} => {CustomerId}",
|
||||
payload.AccountName, fakeId);
|
||||
|
||||
return ProviderResponse.Success(requestId, new
|
||||
{
|
||||
customerId = fakeId,
|
||||
accountName = payload.AccountName,
|
||||
currencyCode = payload.CurrencyCode,
|
||||
timeZone = payload.TimeZone,
|
||||
emulated = true
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// CreateCustomerClient runs against the MCC (manager account).
|
||||
// The context.CustomerId should be the MCC ID.
|
||||
var mccId = GoogleAdsClientFactory.NormalizeCustomerId(
|
||||
context.CustomerId ?? request.TenantId ?? string.Empty);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mccId))
|
||||
return ProviderResponse.Fail(requestId, "VALIDATION",
|
||||
"TenantId (MCC customer ID) is required to create a sub-account");
|
||||
|
||||
// Force LoginCustomerId = MCC for this call
|
||||
var mccContext = new GoogleAdsContext
|
||||
{
|
||||
CustomerId = mccId,
|
||||
LoginCustomerId = mccId
|
||||
};
|
||||
|
||||
GoogleAdsClient client = _clientFactory.CreateClient(mccContext);
|
||||
|
||||
CustomerServiceClient customerService =
|
||||
client.GetService(GAdsServices.V22.CustomerService);
|
||||
|
||||
var customerClient = new Customer
|
||||
{
|
||||
DescriptiveName = payload.DescriptiveName ?? payload.AccountName,
|
||||
CurrencyCode = payload.CurrencyCode,
|
||||
TimeZone = payload.TimeZone
|
||||
};
|
||||
|
||||
var response = await customerService.CreateCustomerClientAsync(
|
||||
new CreateCustomerClientRequest
|
||||
{
|
||||
CustomerId = mccId,
|
||||
CustomerClient = customerClient
|
||||
},
|
||||
cancellationToken: ct);
|
||||
|
||||
// Response contains the resource name like "customers/9336988646/customerClients/1234567890"
|
||||
var newCustomerId = response.ResourceName?.Split('/').LastOrDefault() ?? response.ResourceName;
|
||||
|
||||
_logger.LogInformation(
|
||||
"[GoogleAds] Created customer client {AccountName} => {NewCustomerId} under MCC {MccId}",
|
||||
payload.AccountName, newCustomerId, mccId);
|
||||
|
||||
return ProviderResponse.Success(requestId, new
|
||||
{
|
||||
customerId = newCustomerId,
|
||||
resourceName = response.ResourceName,
|
||||
accountName = payload.AccountName,
|
||||
descriptiveName = payload.DescriptiveName ?? payload.AccountName,
|
||||
currencyCode = payload.CurrencyCode,
|
||||
timeZone = payload.TimeZone,
|
||||
mccId = mccId,
|
||||
emulated = false
|
||||
});
|
||||
}
|
||||
catch (GoogleAdsException gex)
|
||||
{
|
||||
_logger.LogError(gex, "Google Ads API error creating customer client | RequestId={RequestId}", requestId);
|
||||
return HandleGoogleAdsException(gex, requestId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create customer client via real API");
|
||||
return ProviderResponse.Fail(requestId, "API_ERROR", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateId() => Guid.NewGuid().ToString("N")[..12];
|
||||
|
||||
private static AdvertisingChannelTypeEnum.Types.AdvertisingChannelType MapChannelType(CampaignType type)
|
||||
|
||||
Reference in New Issue
Block a user