mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-03-27 00:31:44 +01:00
Plausible Analytics
This commit is contained in:
parent
1d451aac82
commit
c3dbecca2a
@ -236,4 +236,7 @@ public static class Extensions
|
||||
|
||||
public static bool HasVirusScanner(this VoidSettings settings)
|
||||
=> settings.VirusScanner?.ClamAV != default || settings.VirusScanner?.VirusTotal != default;
|
||||
|
||||
public static bool HasPlausible(this VoidSettings settings)
|
||||
=> settings.PlausibleAnalytics?.Endpoint != null;
|
||||
}
|
@ -90,6 +90,11 @@ namespace VoidCat.Model
|
||||
/// Select which store to use for files storage, if not set "local-disk" will be used
|
||||
/// </summary>
|
||||
public string DefaultFileStore { get; init; } = "local-disk";
|
||||
|
||||
/// <summary>
|
||||
/// Plausible Analytics endpoint url
|
||||
/// </summary>
|
||||
public PlausibleSettings? PlausibleAnalytics { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TorSettings
|
||||
@ -158,4 +163,10 @@ namespace VoidCat.Model
|
||||
public Uri? Url { get; init; }
|
||||
public string? EgressQuery { get; init; }
|
||||
}
|
||||
|
||||
public sealed class PlausibleSettings
|
||||
{
|
||||
public Uri? Endpoint { get; init; }
|
||||
public string? Domain { get; init; }
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using Newtonsoft.Json;
|
||||
using Prometheus;
|
||||
using VoidCat;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Analytics;
|
||||
using VoidCat.Services.Migrations;
|
||||
|
||||
JsonConvert.DefaultSettings = () => VoidStartup.ConfigJsonSettings(new());
|
||||
@ -90,6 +91,7 @@ if (mode.HasFlag(RunModes.Webserver))
|
||||
|
||||
app.UseHealthChecks("/healthz");
|
||||
|
||||
app.UseMiddleware<AnalyticsMiddleware>();
|
||||
app.UseEndpoints(ep =>
|
||||
{
|
||||
ep.MapControllers();
|
||||
|
6
VoidCat/Services/Abstractions/IWebAnalyticsCollector.cs
Normal file
6
VoidCat/Services/Abstractions/IWebAnalyticsCollector.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IWebAnalyticsCollector
|
||||
{
|
||||
Task TrackPageView(HttpContext context);
|
||||
}
|
32
VoidCat/Services/Analytics/AnalyticsMiddleware.cs
Normal file
32
VoidCat/Services/Analytics/AnalyticsMiddleware.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public class AnalyticsMiddleware : IMiddleware
|
||||
{
|
||||
private readonly ILogger<AnalyticsMiddleware> _logger;
|
||||
private readonly IEnumerable<IWebAnalyticsCollector> _collectors;
|
||||
|
||||
public AnalyticsMiddleware(IEnumerable<IWebAnalyticsCollector> collectors, ILogger<AnalyticsMiddleware> logger)
|
||||
{
|
||||
_collectors = collectors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
foreach (var collector in _collectors)
|
||||
{
|
||||
try
|
||||
{
|
||||
await collector.TrackPageView(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to track page view");
|
||||
}
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
21
VoidCat/Services/Analytics/AnalyticsStartup.cs
Normal file
21
VoidCat/Services/Analytics/AnalyticsStartup.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public static class AnalyticsStartup
|
||||
{
|
||||
/// <summary>
|
||||
/// Add services needed to collect analytics
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="settings"></param>
|
||||
public static void AddAnalytics(this IServiceCollection services, VoidSettings settings)
|
||||
{
|
||||
services.AddTransient<AnalyticsMiddleware>();
|
||||
if (settings.HasPlausible())
|
||||
{
|
||||
services.AddTransient<IWebAnalyticsCollector, PlausibleAnalytics>();
|
||||
}
|
||||
}
|
||||
}
|
71
VoidCat/Services/Analytics/PlausibleAnalytics.cs
Normal file
71
VoidCat/Services/Analytics/PlausibleAnalytics.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public class PlausibleAnalytics : IWebAnalyticsCollector
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly string _domain;
|
||||
|
||||
public PlausibleAnalytics(HttpClient client, VoidSettings settings)
|
||||
{
|
||||
_client = client;
|
||||
_client.BaseAddress = settings.PlausibleAnalytics!.Endpoint!;
|
||||
_domain = settings.PlausibleAnalytics!.Domain!;
|
||||
}
|
||||
|
||||
public async Task TrackPageView(HttpContext context)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "/api/event");
|
||||
request.Headers.Add("user-agent", context.Request.Headers.UserAgent.First());
|
||||
request.Headers.Add("x-forwarded-for",
|
||||
context.Request.Headers.TryGetValue("x-forwarded-for", out var xff) ? xff.First() : null);
|
||||
|
||||
var ub = new UriBuilder("http:", context.Request.Host.Host, context.Request.Host.Port ?? 80,
|
||||
context.Request.Path)
|
||||
{
|
||||
Query = context.Request.QueryString.Value
|
||||
};
|
||||
|
||||
var ev = new EventObj(_domain, ub.Uri)
|
||||
{
|
||||
Referrer =
|
||||
context.Request.Headers.Referer.Any()
|
||||
? new Uri(context.Request.Headers.Referer.FirstOrDefault()!)
|
||||
: null
|
||||
};
|
||||
request.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(ev)));
|
||||
request.Content.Headers.ContentType = new("application/json");
|
||||
|
||||
var rsp = await _client.SendAsync(request);
|
||||
if (!rsp.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Invalid plausible analytics response {rsp.StatusCode} {await rsp.Content.ReadAsStringAsync()}");
|
||||
}
|
||||
}
|
||||
|
||||
internal class EventObj
|
||||
{
|
||||
public EventObj(string domain, Uri url)
|
||||
{
|
||||
Domain = domain;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
[JsonProperty("name")] public string Name { get; init; } = "pageview";
|
||||
|
||||
[JsonProperty("domain")] public string Domain { get; init; }
|
||||
|
||||
[JsonProperty("url")] public Uri Url { get; init; }
|
||||
|
||||
[JsonProperty("screen_width")] public int? ScreenWidth { get; init; }
|
||||
|
||||
[JsonProperty("referrer")] public Uri? Referrer { get; init; }
|
||||
|
||||
[JsonProperty("props")] public object? Props { get; init; }
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ using StackExchange.Redis;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Analytics;
|
||||
using VoidCat.Services.Background;
|
||||
using VoidCat.Services.Captcha;
|
||||
using VoidCat.Services.Files;
|
||||
@ -150,6 +151,7 @@ public static class VoidStartup
|
||||
});
|
||||
|
||||
services.AddTransient<RazorPartialToStringRenderer>();
|
||||
services.AddAnalytics(voidSettings);
|
||||
}
|
||||
|
||||
public static void AddBackgroundServices(this IServiceCollection services, VoidSettings voidSettings)
|
||||
|
Loading…
x
Reference in New Issue
Block a user