Server locations: no user threshold
The public projection of a server's place showed only the country for servers reporting fewer than 10 users. The owner never decided that threshold: every located server now shows its city, coordinates (0.1°) and network, and a CDN-fronted one still shows only its CDN, since the address reached is the CDN's edge. Statistics:PublicCityMinUsers is gone; the ROADMAP decision on server locations, CLAUDE.md and the /stargazing explainer are corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
31d1d21d1e
commit
eb7552e8f3
9 files changed
+56
-57
No files matched your search
@@ -10,7 +10,6 @@ using PrivaPub.Domain.Statistics;
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Objects;
|
||||
using PrivaPub.Infrastructure;
|
||||
using PrivaPub.Infrastructure.Statistics;
|
||||
using PrivaPub.Models.Federation;
|
||||
using PrivaPub.Models.Jobs;
|
||||
using PrivaPub.Models.Post;
|
||||
@@ -29,16 +28,14 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
readonly DbEntities _dbEntities;
|
||||
readonly ISelfLocation _self;
|
||||
readonly ILocalActorService _localActors;
|
||||
readonly IOptionsMonitor<StatisticsOptions> _statistics;
|
||||
readonly IOptionsMonitor<RegistrationOptions> _registrations;
|
||||
|
||||
public ProvenanceController(DbEntities dbEntities, ISelfLocation self, ILocalActorService localActors,
|
||||
IOptionsMonitor<StatisticsOptions> statistics, IOptionsMonitor<RegistrationOptions> registrations)
|
||||
IOptionsMonitor<RegistrationOptions> registrations)
|
||||
{
|
||||
_dbEntities = dbEntities;
|
||||
_self = self;
|
||||
_localActors = localActors;
|
||||
_statistics = statistics;
|
||||
_registrations = registrations;
|
||||
}
|
||||
|
||||
@@ -83,15 +80,14 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
if (host == selfHost)
|
||||
described.Add(await DescribeSelf(host, token));
|
||||
else if (remote.FirstOrDefault(i => i.Host == host) is { } instance)
|
||||
described.Add(Describe(instance, _statistics.CurrentValue.PublicCityMinUsers));
|
||||
described.Add(Describe(instance));
|
||||
}
|
||||
return described;
|
||||
}
|
||||
|
||||
async Task<InstanceDescription> DescribeSelf(string host, CancellationToken token)
|
||||
{
|
||||
var (geo, configured) = await _self.Get(token);
|
||||
var users = await Counted.Users(token);
|
||||
var geo = await _self.Get(token);
|
||||
return new InstanceDescription
|
||||
{
|
||||
Host = host,
|
||||
@@ -101,13 +97,11 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
Protocols = new() { "activitypub" },
|
||||
OpenRegistrations = _registrations.CurrentValue.IsOpen,
|
||||
DescribedAt = MastodonJson.Time(DateTime.UtcNow),
|
||||
Geo = configured
|
||||
? Located(new PublicLocation(geo.Country, geo.City, geo.Latitude, geo.Longitude, default, default), geo)
|
||||
: Located(PublicGeo.Project(new RemoteInstance { Geo = geo, UsersTotal = users }, _statistics.CurrentValue.PublicCityMinUsers), geo)
|
||||
Geo = Located(PublicGeo.Project(geo), geo)
|
||||
};
|
||||
}
|
||||
|
||||
//the public projection of a server's place (owner decision on server locations, ROADMAP 2026-10-03)
|
||||
//the public projection of a server's place (PublicGeo)
|
||||
static InstanceLocation Located(PublicLocation location, InstanceGeo geo) => location == default ? default : new InstanceLocation
|
||||
{
|
||||
Precision = location.Cdn != default ? "cdn" : location.Latitude != default ? "city" : "country",
|
||||
@@ -158,11 +152,11 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
RawHash = r.RawHash,
|
||||
Raw = r.Raw == default ? default : JsonNode.Parse(r.Raw)
|
||||
}).ToList() ?? new(),
|
||||
Instance = instance == default ? default : Describe(instance, _statistics.CurrentValue.PublicCityMinUsers)
|
||||
Instance = instance == default ? default : Describe(instance)
|
||||
};
|
||||
}
|
||||
|
||||
static InstanceDescription Describe(RemoteInstance instance, int minUsers) => new()
|
||||
static InstanceDescription Describe(RemoteInstance instance) => new()
|
||||
{
|
||||
Host = instance.Host,
|
||||
Software = instance.Software,
|
||||
@@ -180,7 +174,7 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
LastSuccessAt = instance.LastSuccessAt is { } success ? MastodonJson.Time(success) : default,
|
||||
LastFailureAt = instance.LastFailureAt is { } failure ? MastodonJson.Time(failure) : default
|
||||
},
|
||||
Geo = Located(PublicGeo.Project(instance, minUsers), instance.Geo)
|
||||
Geo = Located(PublicGeo.Project(instance.Geo), instance.Geo)
|
||||
};
|
||||
|
||||
public class Provenance
|
||||
@@ -246,7 +240,8 @@ namespace PrivaPub.Api.Mastodon.Controllers
|
||||
public InstanceLocation Geo { get; set; }
|
||||
}
|
||||
|
||||
//precision: "city" (coordinates to 0.1°, city and network), "country" (the country only) or "cdn" (the CDN's name only)
|
||||
//precision: "city" (coordinates to 0.1°, city and network), "country" (located only to a country) or "cdn" (the
|
||||
//CDN's name only)
|
||||
public class InstanceLocation
|
||||
{
|
||||
public string Precision { get; set; }
|
||||
|
||||
@@ -6,18 +6,17 @@ namespace PrivaPub.Domain.Statistics
|
||||
|
||||
public static class PublicGeo
|
||||
{
|
||||
//owner decision: a CDN-fronted server shows only its CDN (the address is an edge), a server reporting at least
|
||||
//minUsers users its city, coordinates and network, any other only its country
|
||||
public static PublicLocation Project(RemoteInstance instance, int minUsers)
|
||||
//a server's place as it is shown publicly: where it was located (country, city, coordinates to 0.1°, network),
|
||||
//except that a CDN-fronted server shows only its CDN, because the address we reached is the CDN's edge
|
||||
public static PublicLocation Project(InstanceGeo geo)
|
||||
{
|
||||
var geo = instance?.Geo;
|
||||
if (geo == default)
|
||||
return default;
|
||||
if (geo.Cdn != default)
|
||||
return new PublicLocation(default, default, default, default, default, geo.Cdn);
|
||||
if (instance.UsersTotal >= minUsers)
|
||||
return new PublicLocation(geo.Country, geo.City, geo.Latitude, geo.Longitude, geo.AsnOrg, default);
|
||||
return geo.Country == default ? default : new PublicLocation(geo.Country, default, default, default, default, default);
|
||||
if (geo.Country == default && geo.Latitude == default)
|
||||
return default;
|
||||
return new PublicLocation(geo.Country, geo.City, geo.Latitude, geo.Longitude, geo.AsnOrg, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using PrivaPub.Infrastructure.Geo;
|
||||
using PrivaPub.Infrastructure.Statistics;
|
||||
@@ -12,8 +12,8 @@ namespace PrivaPub.Domain.Statistics
|
||||
{
|
||||
public interface ISelfLocation
|
||||
{
|
||||
//where this server is, or default when it cannot tell; Configured when the owner set it (Statistics:Geo:Self)
|
||||
Task<(InstanceGeo Geo, bool Configured)> Get(CancellationToken token);
|
||||
//where this server is, or default when it cannot tell; Source is "configured" when the owner set it (Statistics:Geo:Self)
|
||||
Task<InstanceGeo> Get(CancellationToken token);
|
||||
}
|
||||
|
||||
// This server's own place, for the instance API (the client's globe draws every server from it). The owner may set it,
|
||||
@@ -40,20 +40,20 @@ namespace PrivaPub.Domain.Statistics
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<(InstanceGeo Geo, bool Configured)> Get(CancellationToken token)
|
||||
public async Task<InstanceGeo> Get(CancellationToken token)
|
||||
{
|
||||
if (_options.CurrentValue.Geo.Self is { Latitude: { } latitude, Longitude: { } longitude } self)
|
||||
return (new InstanceGeo
|
||||
return new InstanceGeo
|
||||
{
|
||||
Country = self.Country,
|
||||
City = self.City,
|
||||
Latitude = Math.Round(latitude, 1),
|
||||
Longitude = Math.Round(longitude, 1),
|
||||
Source = "configured"
|
||||
}, true);
|
||||
};
|
||||
|
||||
if (DateTime.UtcNow - _locatedAt < Lifetime)
|
||||
return (_located, false);
|
||||
return _located;
|
||||
await _gate.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace PrivaPub.Domain.Statistics
|
||||
_located = await Locate(token);
|
||||
_locatedAt = DateTime.UtcNow;
|
||||
}
|
||||
return (_located, false);
|
||||
return _located;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string GeoDirectory { get; set; } = "/var/lib/privapub/geo";
|
||||
public int PublicCityMinUsers { get; set; } = 10;
|
||||
public CrawlerOptions Crawler { get; set; } = new();
|
||||
public GeoOptions Geo { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@page "/stargazing"
|
||||
@page "/stargazing"
|
||||
@model PrivaPub.Web.Pages.StargazingModel
|
||||
@{
|
||||
ViewData["Title"] = "Stargazing";
|
||||
@@ -13,8 +13,8 @@
|
||||
never accounts: what kind of software a server runs, what it exchanges with us, and how reliably. Distinct accounts are
|
||||
only counted, through a key that is destroyed at the end of each day.</p>
|
||||
<p>A server we exchange activities with is described once a week, from its public NodeInfo and, when it has one, its
|
||||
Mastodon instance API. Its location comes from the address we reached, looked up in an offline database: only the
|
||||
country is shown publicly for small servers, and only the CDN for servers behind one.</p>
|
||||
Mastodon instance API. Its location comes from the address we reached, looked up in an offline database, and is shown
|
||||
to the city; for a server behind a CDN, only the CDN is shown, since the address is the CDN's.</p>
|
||||
@if (Model.GeoSource is { } geo)
|
||||
{
|
||||
<p>Locations come from <a href="https://db-ip.com" rel="nofollow noopener">@geo</a>, licensed under
|
||||
|
||||
Reference in new issue
Block a user