Initial import into Gitea

This commit is contained in:
Grae Jones
2026-03-14 13:50:09 -07:00
parent 8e7e03702e
commit 34c1f09e01
154 changed files with 17666 additions and 1548 deletions

View File

@@ -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)