Add backend to monitore the app in a desktop app
This commit is contained in:
186
backend/Program.cs
Normal file
186
backend/Program.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using DotNetEnv;
|
||||
using Google.Apis.AnalyticsData.v1beta;
|
||||
using Google.Apis.AnalyticsData.v1beta.Data;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
Env.Load();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// Register Google Analytics Data Client
|
||||
builder.Services.AddSingleton<AnalyticsDataService>(sp =>
|
||||
{
|
||||
GoogleCredential credential;
|
||||
try
|
||||
{
|
||||
// Tries to find credentials from GOOGLE_APPLICATION_CREDENTIALS environment variable
|
||||
// or default cloud environment credentials.
|
||||
credential = GoogleCredential.GetApplicationDefault();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Fallback for local development if env var not set (User might need to run 'gcloud auth application-default login')
|
||||
// Or we warn them. For now, we assume it's set up or will fail at runtime.
|
||||
throw new InvalidOperationException("Could not load Google Credentials. Set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'.");
|
||||
}
|
||||
|
||||
if (credential.IsCreateScopedRequired)
|
||||
{
|
||||
credential = credential.CreateScoped(AnalyticsDataService.Scope.AnalyticsReadonly);
|
||||
}
|
||||
|
||||
var service = new AnalyticsDataService(new BaseClientService.Initializer
|
||||
{
|
||||
HttpClientInitializer = credential,
|
||||
ApplicationName = "TravelMate Backend",
|
||||
});
|
||||
|
||||
return service;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
string? ga4PropertyId = Environment.GetEnvironmentVariable("GA4_PROPERTY_ID");
|
||||
if (string.IsNullOrEmpty(ga4PropertyId))
|
||||
{
|
||||
Console.WriteLine("Warning: GA4_PROPERTY_ID not set in .env");
|
||||
// Default or error? Let's just hold it and fail in the endpoint if needed, or fail startup.
|
||||
// For now, let's allow it to start but endpoint will likely fail.
|
||||
}
|
||||
|
||||
app.MapGet("/api/metrics/daily", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string from,
|
||||
[FromQuery] string to) =>
|
||||
{
|
||||
// Basic validation
|
||||
// Basic validation: Check if it's a valid date OR a valid GA4 keyword
|
||||
bool IsValidDate(string date)
|
||||
{
|
||||
return DateTime.TryParse(date, out _) ||
|
||||
date == "today" ||
|
||||
date == "yesterday" ||
|
||||
date.EndsWith("daysAgo");
|
||||
}
|
||||
|
||||
if (!IsValidDate(from) || !IsValidDate(to))
|
||||
{
|
||||
return Results.BadRequest(new { error = "Invalid date format. Use YYYY-MM-DD, 'today', 'yesterday', or 'NdaysAgo'." });
|
||||
}
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange>
|
||||
{
|
||||
new DateRange { StartDate = from, EndDate = to }
|
||||
},
|
||||
Dimensions = new List<Dimension>
|
||||
{
|
||||
new Dimension { Name = "date" }
|
||||
},
|
||||
Metrics = new List<Metric>
|
||||
{
|
||||
new Metric { Name = "activeUsers" } // equivalent to Daily Active Users in this context often
|
||||
// Or strictly "activeUsers"
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
|
||||
var result = response.Rows?.Select(row => new
|
||||
{
|
||||
date = FormatDate(row.DimensionValues[0].Value), // GA4 returns date as YYYYMMDD string usually
|
||||
dailyActiveUsers = int.Parse(row.MetricValues[0].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex}");
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetDailyMetrics")
|
||||
.WithOpenApi();
|
||||
|
||||
app.MapGet("/api/metrics/general", async (
|
||||
[FromServices] AnalyticsDataService service) =>
|
||||
{
|
||||
// "Global" means all time ideally, or a very long range.
|
||||
// GA4 retention might limit this for user-level data, but aggregated metrics usually go back further.
|
||||
var startDate = "2023-01-01"; // Arbitrary start date for the app
|
||||
var endDate = "today";
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange>
|
||||
{
|
||||
new DateRange { StartDate = startDate, EndDate = endDate }
|
||||
},
|
||||
Metrics = new List<Metric>
|
||||
{
|
||||
new Metric { Name = "totalUsers" }, // Proxy for "nb de comptes"
|
||||
new Metric { Name = "eventCount" } // Proxy for "nb de requètes"
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
|
||||
var totalUsers = 0;
|
||||
var eventCount = 0;
|
||||
|
||||
if (response.Rows != null && response.Rows.Count > 0)
|
||||
{
|
||||
var row = response.Rows[0];
|
||||
totalUsers = int.Parse(row.MetricValues[0].Value);
|
||||
eventCount = int.Parse(row.MetricValues[1].Value);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
totalAccounts = totalUsers,
|
||||
totalRequests = eventCount
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex}");
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetGeneralMetrics")
|
||||
.WithOpenApi();
|
||||
|
||||
app.Run();
|
||||
|
||||
// Helper to format GA4 YYYYMMDD to YYYY-MM-DD
|
||||
static string FormatDate(string gaDate)
|
||||
{
|
||||
if (gaDate.Length == 8)
|
||||
{
|
||||
return $"{gaDate.Substring(0, 4)}-{gaDate.Substring(4, 2)}-{gaDate.Substring(6, 2)}";
|
||||
}
|
||||
return gaDate;
|
||||
}
|
||||
Reference in New Issue
Block a user