mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Split remaining service routes
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@ tmp/
|
|||||||
## OS
|
## OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
.backups
|
||||||
## Reverse-engineered artifacts (Il2CppDumper / Cpp2IL output, not for commit)
|
## Reverse-engineered artifacts (Il2CppDumper / Cpp2IL output, not for commit)
|
||||||
dump.cs
|
dump.cs
|
||||||
il2cpp.h
|
il2cpp.h
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ ARG PROJECT_PATH
|
|||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN dotnet publish "${PROJECT_PATH}" -c Release -o /app/publish
|
RUN dotnet publish "${PROJECT_PATH}" -c Release -p:SkipAdminUIBuild=true -o /app/publish
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION} AS runtime
|
FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION} AS runtime
|
||||||
ARG APP_DLL
|
ARG APP_DLL
|
||||||
|
|||||||
@@ -0,0 +1,298 @@
|
|||||||
|
namespace DorkNet.Contracts;
|
||||||
|
|
||||||
|
public static class DorkNetRouteOwnership
|
||||||
|
{
|
||||||
|
private const string AdminHost = "admin";
|
||||||
|
|
||||||
|
private static readonly DorkNetRouteGroup[] ServiceRouteGroups =
|
||||||
|
[
|
||||||
|
new(
|
||||||
|
ServiceNames.Identity,
|
||||||
|
["auth", "accounts"],
|
||||||
|
[
|
||||||
|
"/.well-known",
|
||||||
|
"/account",
|
||||||
|
"/api/account",
|
||||||
|
"/api/auth",
|
||||||
|
"/api/createaccount",
|
||||||
|
"/api/platformlogin",
|
||||||
|
"/api/playerreputation",
|
||||||
|
"/api/players",
|
||||||
|
"/api/relationships",
|
||||||
|
"/api/settings",
|
||||||
|
"/cachedlogin",
|
||||||
|
"/connect",
|
||||||
|
"/eac",
|
||||||
|
"/parentalcontrol",
|
||||||
|
"/photon",
|
||||||
|
"/profileimage",
|
||||||
|
"/settings",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Rooms,
|
||||||
|
["rooms", "match", "discovery", "lists", "roomcomments", "roomieintegrations", "studio"],
|
||||||
|
[
|
||||||
|
"/api/activities",
|
||||||
|
"/api/curatedroomplaylists",
|
||||||
|
"/api/gameconfigs",
|
||||||
|
"/api/gameconfigurations",
|
||||||
|
"/api/gamesessions",
|
||||||
|
"/api/leaderboard",
|
||||||
|
"/api/playerelo",
|
||||||
|
"/api/playlists",
|
||||||
|
"/api/roomkeys",
|
||||||
|
"/api/rooms",
|
||||||
|
"/api/royale",
|
||||||
|
"/discover",
|
||||||
|
"/featuredrooms",
|
||||||
|
"/goto",
|
||||||
|
"/hot_rooms",
|
||||||
|
"/hot_roomsandplaylists",
|
||||||
|
"/invite",
|
||||||
|
"/playlists",
|
||||||
|
"/player",
|
||||||
|
"/room",
|
||||||
|
"/roominstance",
|
||||||
|
"/rooms",
|
||||||
|
"/roomsandplaylists",
|
||||||
|
"/roomserver",
|
||||||
|
"/search_rooms",
|
||||||
|
"/search_roomsandplaylists",
|
||||||
|
"/v1/find",
|
||||||
|
"/v1/join",
|
||||||
|
"/v1/leave",
|
||||||
|
"/v1/session",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Notify,
|
||||||
|
["notify", "chat", "platformnotifications"],
|
||||||
|
[
|
||||||
|
"/api/messages",
|
||||||
|
"/api/notification",
|
||||||
|
"/api/offlineinvite",
|
||||||
|
"/hub",
|
||||||
|
"/notify",
|
||||||
|
"/thread",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Content,
|
||||||
|
["cdn", "data", "img", "storage", "strings-cdn", "videos"],
|
||||||
|
[
|
||||||
|
"/api/images",
|
||||||
|
"/api/photos",
|
||||||
|
"/config",
|
||||||
|
"/data",
|
||||||
|
"/img",
|
||||||
|
"/invention",
|
||||||
|
"/upload",
|
||||||
|
"/video",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Social,
|
||||||
|
["clubs"],
|
||||||
|
[
|
||||||
|
"/announcements",
|
||||||
|
"/api/announcement",
|
||||||
|
"/api/clubreporting",
|
||||||
|
"/api/communityboard",
|
||||||
|
"/api/groups",
|
||||||
|
"/api/playercheer",
|
||||||
|
"/api/playerevents",
|
||||||
|
"/api/playersubscriptions",
|
||||||
|
"/club",
|
||||||
|
"/members",
|
||||||
|
"/subscription",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Commerce,
|
||||||
|
["commerce", "econ"],
|
||||||
|
[
|
||||||
|
"/api/avatar",
|
||||||
|
"/api/catalog",
|
||||||
|
"/api/compatibility",
|
||||||
|
"/api/consumables",
|
||||||
|
"/api/equipment",
|
||||||
|
"/api/gamerewards",
|
||||||
|
"/api/inventions",
|
||||||
|
"/api/purchase",
|
||||||
|
"/api/storefronts",
|
||||||
|
"/commerce",
|
||||||
|
"/econ",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Platform,
|
||||||
|
["cards", "cms", "datacollection", "gamelogs", "geo", "leaderboard", "link", "ns", "playersettings", "strings", "thorn"],
|
||||||
|
[
|
||||||
|
"/api/cards",
|
||||||
|
"/api/config",
|
||||||
|
"/api/events",
|
||||||
|
"/api/quickplay",
|
||||||
|
"/api/role",
|
||||||
|
"/api/version",
|
||||||
|
"/api/versioncheck",
|
||||||
|
"/motd",
|
||||||
|
"/pageview",
|
||||||
|
"/ping",
|
||||||
|
"/role",
|
||||||
|
"/strings",
|
||||||
|
"/v1/ping",
|
||||||
|
"/v1/player",
|
||||||
|
"/v1/regions",
|
||||||
|
"/v1/services",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Moderation,
|
||||||
|
["bugreporting", "moderation"],
|
||||||
|
[
|
||||||
|
"/api/admin",
|
||||||
|
"/api/banappeal",
|
||||||
|
"/api/bugreporting",
|
||||||
|
"/api/moderation",
|
||||||
|
"/api/playerreporting",
|
||||||
|
"/api/playersbanned",
|
||||||
|
"/api/sanitize",
|
||||||
|
"/api/testcasemanagement",
|
||||||
|
]),
|
||||||
|
new(
|
||||||
|
ServiceNames.Web,
|
||||||
|
["www", AdminHost, "feed"],
|
||||||
|
[
|
||||||
|
"/api/site",
|
||||||
|
],
|
||||||
|
IncludesApexHost: true),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static IReadOnlyList<DorkNetRouteGroup> RouteGroups => ServiceRouteGroups;
|
||||||
|
|
||||||
|
public static bool IsInfrastructurePath(string path)
|
||||||
|
{
|
||||||
|
return path.Equals("/healthz", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| path.StartsWith("/internal/", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ResolvePublicService(string? host, string path, string apexDomain)
|
||||||
|
{
|
||||||
|
var normalizedPath = NormalizePath(path);
|
||||||
|
var subdomain = Subdomain(host, apexDomain);
|
||||||
|
|
||||||
|
if (string.Equals(subdomain, AdminHost, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ServiceNames.Web;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in ServiceRouteGroups.Where(group => group.ServiceName != ServiceNames.Web))
|
||||||
|
{
|
||||||
|
if (MatchesHost(subdomain, group.HostSubdomains))
|
||||||
|
{
|
||||||
|
return group.ServiceName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in ServiceRouteGroups)
|
||||||
|
{
|
||||||
|
if (StartsWithAny(normalizedPath, group.PathPrefixes))
|
||||||
|
{
|
||||||
|
return group.ServiceName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var web = ServiceRouteGroups.First(group => group.ServiceName == ServiceNames.Web);
|
||||||
|
if ((subdomain is null && web.IncludesApexHost) || MatchesHost(subdomain, web.HostSubdomains))
|
||||||
|
{
|
||||||
|
return ServiceNames.Web;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceNames.Monolith;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsOwnedBy(string serviceName, string? host, string path, string apexDomain)
|
||||||
|
{
|
||||||
|
if (IsInfrastructurePath(path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(
|
||||||
|
ResolvePublicService(host, path, apexDomain),
|
||||||
|
serviceName,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? Subdomain(string? host, string apexDomain)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(host))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedHost = host.Split(':', 2)[0].Trim().ToLowerInvariant();
|
||||||
|
var normalizedApex = apexDomain.Split(':', 2)[0].Trim().ToLowerInvariant();
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedHost)
|
||||||
|
|| string.IsNullOrWhiteSpace(normalizedApex)
|
||||||
|
|| string.Equals(normalizedHost, normalizedApex, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var suffix = "." + normalizedApex;
|
||||||
|
if (normalizedHost.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return normalizedHost[..^suffix.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
var dot = normalizedHost.IndexOf('.');
|
||||||
|
return dot > 0 ? normalizedHost[..dot] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool StartsWithAny(string path, IEnumerable<string> prefixes)
|
||||||
|
{
|
||||||
|
foreach (var prefix in prefixes)
|
||||||
|
{
|
||||||
|
if (StartsWithPathSegment(path, prefix))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool StartsWithPathSegment(string path, string prefix)
|
||||||
|
{
|
||||||
|
var normalizedPath = NormalizePath(path);
|
||||||
|
var normalizedPrefix = NormalizePath(prefix).TrimEnd('/');
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedPrefix))
|
||||||
|
{
|
||||||
|
normalizedPrefix = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedPath.Equals(normalizedPrefix, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| normalizedPath.StartsWith(normalizedPrefix + "/", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesHost(string? subdomain, IEnumerable<string> candidates)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(subdomain))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates.Contains(subdomain, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return path[0] == '/' ? path : "/" + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record DorkNetRouteGroup(
|
||||||
|
string ServiceName,
|
||||||
|
string[] HostSubdomains,
|
||||||
|
string[] PathPrefixes,
|
||||||
|
bool IncludesApexHost = false);
|
||||||
@@ -5,7 +5,13 @@ public sealed class DorkNetServiceMapOptions
|
|||||||
public string Identity { get; set; } = "http://localhost:8081";
|
public string Identity { get; set; } = "http://localhost:8081";
|
||||||
public string Rooms { get; set; } = "http://localhost:8082";
|
public string Rooms { get; set; } = "http://localhost:8082";
|
||||||
public string Notify { get; set; } = "http://localhost:8083";
|
public string Notify { get; set; } = "http://localhost:8083";
|
||||||
public string Monolith { get; set; } = "http://localhost:8084";
|
public string Content { get; set; } = "http://localhost:8084";
|
||||||
|
public string Social { get; set; } = "http://localhost:8085";
|
||||||
|
public string Commerce { get; set; } = "http://localhost:8086";
|
||||||
|
public string Platform { get; set; } = "http://localhost:8087";
|
||||||
|
public string Moderation { get; set; } = "http://localhost:8088";
|
||||||
|
public string Web { get; set; } = "http://localhost:8089";
|
||||||
|
public string Monolith { get; set; } = "http://localhost:8090";
|
||||||
|
|
||||||
public IReadOnlyList<DorkNetServiceEndpoint> Endpoints()
|
public IReadOnlyList<DorkNetServiceEndpoint> Endpoints()
|
||||||
{
|
{
|
||||||
@@ -14,6 +20,12 @@ public sealed class DorkNetServiceMapOptions
|
|||||||
new(ServiceNames.Identity, Identity),
|
new(ServiceNames.Identity, Identity),
|
||||||
new(ServiceNames.Rooms, Rooms),
|
new(ServiceNames.Rooms, Rooms),
|
||||||
new(ServiceNames.Notify, Notify),
|
new(ServiceNames.Notify, Notify),
|
||||||
|
new(ServiceNames.Content, Content),
|
||||||
|
new(ServiceNames.Social, Social),
|
||||||
|
new(ServiceNames.Commerce, Commerce),
|
||||||
|
new(ServiceNames.Platform, Platform),
|
||||||
|
new(ServiceNames.Moderation, Moderation),
|
||||||
|
new(ServiceNames.Web, Web),
|
||||||
new(ServiceNames.Monolith, Monolith),
|
new(ServiceNames.Monolith, Monolith),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,11 @@ public static class ServiceNames
|
|||||||
public const string Identity = "identity";
|
public const string Identity = "identity";
|
||||||
public const string Rooms = "rooms";
|
public const string Rooms = "rooms";
|
||||||
public const string Notify = "notify";
|
public const string Notify = "notify";
|
||||||
|
public const string Content = "content";
|
||||||
|
public const string Social = "social";
|
||||||
|
public const string Commerce = "commerce";
|
||||||
|
public const string Platform = "platform";
|
||||||
|
public const string Moderation = "moderation";
|
||||||
|
public const string Web = "web";
|
||||||
public const string Monolith = "monolith";
|
public const string Monolith = "monolith";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using DorkNet.Contracts;
|
using DorkNet.Contracts;
|
||||||
using DorkNet.ServiceDefaults;
|
using DorkNet.ServiceDefaults;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yarp.ReverseProxy.Configuration;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
@@ -9,6 +10,17 @@ builder.Configuration.AddJsonFile("appsettings.Local.json",
|
|||||||
builder.AddDorkNetServiceDefaults(ServiceNames.Gateway);
|
builder.AddDorkNetServiceDefaults(ServiceNames.Gateway);
|
||||||
builder.Services.Configure<DorkNetServiceMapOptions>(
|
builder.Services.Configure<DorkNetServiceMapOptions>(
|
||||||
builder.Configuration.GetSection("DorkNet:Services"));
|
builder.Configuration.GetSection("DorkNet:Services"));
|
||||||
|
var serviceMap = builder.Configuration
|
||||||
|
.GetSection("DorkNet:Services")
|
||||||
|
.Get<DorkNetServiceMapOptions>() ?? new DorkNetServiceMapOptions();
|
||||||
|
var apexDomain = builder.Configuration["Domain:Apex"]
|
||||||
|
?? Environment.GetEnvironmentVariable("DORKNET_DOMAIN")
|
||||||
|
?? "localhost";
|
||||||
|
builder.Services
|
||||||
|
.AddReverseProxy()
|
||||||
|
.LoadFromMemory(
|
||||||
|
BuildRoutes(apexDomain, serviceMap),
|
||||||
|
BuildClusters(serviceMap));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
@@ -17,6 +29,19 @@ app.MapDorkNetServiceDefaults();
|
|||||||
app.MapGet("/", () => Results.Redirect("/healthz"));
|
app.MapGet("/", () => Results.Redirect("/healthz"));
|
||||||
app.MapGet("/internal/services", (IOptions<DorkNetServiceMapOptions> options) =>
|
app.MapGet("/internal/services", (IOptions<DorkNetServiceMapOptions> options) =>
|
||||||
Results.Ok(options.Value.Endpoints()));
|
Results.Ok(options.Value.Endpoints()));
|
||||||
|
app.MapGet("/internal/routes", () =>
|
||||||
|
Results.Ok(new
|
||||||
|
{
|
||||||
|
ApexDomain = apexDomain,
|
||||||
|
Routes = BuildRoutes(apexDomain, serviceMap).Select(r => new
|
||||||
|
{
|
||||||
|
r.RouteId,
|
||||||
|
r.ClusterId,
|
||||||
|
r.Match.Hosts,
|
||||||
|
r.Match.Path,
|
||||||
|
r.Order,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
app.MapGet("/internal/services/health", async (
|
app.MapGet("/internal/services/health", async (
|
||||||
IOptions<DorkNetServiceMapOptions> options,
|
IOptions<DorkNetServiceMapOptions> options,
|
||||||
@@ -34,6 +59,8 @@ app.MapGet("/internal/services/health", async (
|
|||||||
return Results.Ok(probes);
|
return Results.Ok(probes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.MapReverseProxy();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
static async Task<ServiceProbeResponse> ProbeServiceAsync(
|
static async Task<ServiceProbeResponse> ProbeServiceAsync(
|
||||||
@@ -66,3 +93,206 @@ static async Task<ServiceProbeResponse> ProbeServiceAsync(
|
|||||||
checkedAt);
|
checkedAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IReadOnlyList<RouteConfig> BuildRoutes(
|
||||||
|
string apexDomain,
|
||||||
|
DorkNetServiceMapOptions services)
|
||||||
|
{
|
||||||
|
var routes = new List<RouteConfig>();
|
||||||
|
var order = 0;
|
||||||
|
var webGroup = DorkNetRouteOwnership.RouteGroups
|
||||||
|
.First(group => group.ServiceName == ServiceNames.Web);
|
||||||
|
|
||||||
|
AddHostRoute(
|
||||||
|
routes,
|
||||||
|
ref order,
|
||||||
|
webGroup.ServiceName,
|
||||||
|
BaseUrlFor(services, webGroup.ServiceName),
|
||||||
|
"web-admin-host",
|
||||||
|
[SubdomainHost("admin", apexDomain)]);
|
||||||
|
|
||||||
|
foreach (var group in DorkNetRouteOwnership.RouteGroups.Where(group => group.ServiceName != ServiceNames.Web))
|
||||||
|
{
|
||||||
|
AddHostRoute(
|
||||||
|
routes,
|
||||||
|
ref order,
|
||||||
|
group.ServiceName,
|
||||||
|
BaseUrlFor(services, group.ServiceName),
|
||||||
|
$"{group.ServiceName}-hosts",
|
||||||
|
group.HostSubdomains.Select(subdomain => SubdomainHost(subdomain, apexDomain)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in DorkNetRouteOwnership.RouteGroups)
|
||||||
|
{
|
||||||
|
AddPathRoutes(
|
||||||
|
routes,
|
||||||
|
ref order,
|
||||||
|
group.ServiceName,
|
||||||
|
BaseUrlFor(services, group.ServiceName),
|
||||||
|
group.PathPrefixes);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddHostRoute(
|
||||||
|
routes,
|
||||||
|
ref order,
|
||||||
|
webGroup.ServiceName,
|
||||||
|
BaseUrlFor(services, webGroup.ServiceName),
|
||||||
|
"web-hosts",
|
||||||
|
BuildWebHosts(webGroup, apexDomain));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(services.Monolith))
|
||||||
|
{
|
||||||
|
routes.Add(Route(ServiceNames.Monolith, "monolith-fallback", "/{**catch-all}", order++));
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IReadOnlyList<ClusterConfig> BuildClusters(DorkNetServiceMapOptions services)
|
||||||
|
{
|
||||||
|
var clusters = new List<ClusterConfig>();
|
||||||
|
foreach (var endpoint in services.Endpoints())
|
||||||
|
{
|
||||||
|
AddCluster(clusters, endpoint.Name, endpoint.BaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddHostRoute(
|
||||||
|
List<RouteConfig> routes,
|
||||||
|
ref int order,
|
||||||
|
string serviceName,
|
||||||
|
string baseUrl,
|
||||||
|
string routeId,
|
||||||
|
string[] hosts)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl) || hosts.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.Add(Route(
|
||||||
|
serviceName,
|
||||||
|
routeId,
|
||||||
|
"/{**catch-all}",
|
||||||
|
order++,
|
||||||
|
hosts));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddPathRoutes(
|
||||||
|
List<RouteConfig> routes,
|
||||||
|
ref int order,
|
||||||
|
string serviceName,
|
||||||
|
string baseUrl,
|
||||||
|
IEnumerable<string> paths)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
var routeId = $"{serviceName}-{path.Trim('/').Replace('/', '-').Replace("{**catch-all}", "all")}";
|
||||||
|
var exactPath = NormalizeRoutePrefix(path);
|
||||||
|
routes.Add(Route(serviceName, $"{routeId}-exact", exactPath, order++));
|
||||||
|
routes.Add(Route(serviceName, routeId, $"{exactPath}/{{**catch-all}}", order++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string BaseUrlFor(DorkNetServiceMapOptions services, string serviceName)
|
||||||
|
{
|
||||||
|
return serviceName switch
|
||||||
|
{
|
||||||
|
ServiceNames.Identity => services.Identity,
|
||||||
|
ServiceNames.Rooms => services.Rooms,
|
||||||
|
ServiceNames.Notify => services.Notify,
|
||||||
|
ServiceNames.Content => services.Content,
|
||||||
|
ServiceNames.Social => services.Social,
|
||||||
|
ServiceNames.Commerce => services.Commerce,
|
||||||
|
ServiceNames.Platform => services.Platform,
|
||||||
|
ServiceNames.Moderation => services.Moderation,
|
||||||
|
ServiceNames.Web => services.Web,
|
||||||
|
ServiceNames.Monolith => services.Monolith,
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static string[] BuildWebHosts(DorkNetRouteGroup group, string apexDomain)
|
||||||
|
{
|
||||||
|
var hosts = new List<string>();
|
||||||
|
if (group.IncludesApexHost)
|
||||||
|
{
|
||||||
|
hosts.Add(apexDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts.AddRange(group.HostSubdomains
|
||||||
|
.Where(subdomain => !string.Equals(subdomain, "admin", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(subdomain => SubdomainHost(subdomain, apexDomain)));
|
||||||
|
|
||||||
|
return hosts.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string SubdomainHost(string subdomain, string apexDomain)
|
||||||
|
{
|
||||||
|
return $"{subdomain}.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static string NormalizeRoutePrefix(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path) || path == "/")
|
||||||
|
{
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (path[0] == '/' ? path : "/" + path).TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
static RouteConfig Route(
|
||||||
|
string clusterId,
|
||||||
|
string routeId,
|
||||||
|
string path,
|
||||||
|
int order,
|
||||||
|
string[]? hosts = null)
|
||||||
|
{
|
||||||
|
return new RouteConfig
|
||||||
|
{
|
||||||
|
RouteId = routeId,
|
||||||
|
ClusterId = clusterId,
|
||||||
|
Order = order,
|
||||||
|
Match = new RouteMatch
|
||||||
|
{
|
||||||
|
Hosts = hosts,
|
||||||
|
Path = path,
|
||||||
|
},
|
||||||
|
Transforms =
|
||||||
|
[
|
||||||
|
new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["RequestHeaderOriginalHost"] = "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddCluster(
|
||||||
|
List<ClusterConfig> clusters,
|
||||||
|
string serviceName,
|
||||||
|
string baseUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = baseUrl.EndsWith('/') ? baseUrl : baseUrl + "/";
|
||||||
|
clusters.Add(new ClusterConfig
|
||||||
|
{
|
||||||
|
ClusterId = serviceName,
|
||||||
|
Destinations = new Dictionary<string, DestinationConfig>
|
||||||
|
{
|
||||||
|
["primary"] = new() { Address = address },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,13 @@
|
|||||||
"Identity": "http://localhost:8081",
|
"Identity": "http://localhost:8081",
|
||||||
"Rooms": "http://localhost:8082",
|
"Rooms": "http://localhost:8082",
|
||||||
"Notify": "http://localhost:8083",
|
"Notify": "http://localhost:8083",
|
||||||
"Monolith": "http://localhost:8084"
|
"Content": "http://localhost:8084",
|
||||||
|
"Social": "http://localhost:8085",
|
||||||
|
"Commerce": "http://localhost:8086",
|
||||||
|
"Platform": "http://localhost:8087",
|
||||||
|
"Moderation": "http://localhost:8088",
|
||||||
|
"Web": "http://localhost:8089",
|
||||||
|
"Monolith": "http://localhost:8090"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.HostFiltering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed class DorkNetServerFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
private const string JwtSecret = "endpoint-contract-secret-000000000000000000000000000000000000000000";
|
||||||
|
private readonly string _dataRoot;
|
||||||
|
|
||||||
|
public DorkNetServerFactory()
|
||||||
|
{
|
||||||
|
_dataRoot = Path.Combine(Path.GetTempPath(), "dorknet-server-tests", Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(_dataRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ApexDomain => "dork.test";
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
builder.UseEnvironment("Development");
|
||||||
|
builder.UseSetting("Domain:Apex", ApexDomain);
|
||||||
|
builder.ConfigureAppConfiguration((_, config) =>
|
||||||
|
{
|
||||||
|
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["AllowedHosts"] = "*",
|
||||||
|
["Database:Provider"] = "sqlite",
|
||||||
|
["Database:SqlitePath"] = Path.Combine(_dataRoot, "dorknet-test.db"),
|
||||||
|
["Domain:Apex"] = ApexDomain,
|
||||||
|
["Jwt:Secret"] = JwtSecret,
|
||||||
|
["S3:Endpoint"] = "",
|
||||||
|
["S3:AccessKey"] = "",
|
||||||
|
["S3:SecretKey"] = "",
|
||||||
|
["DorkNet:DefaultClientVersion"] = "december_2020_12_18",
|
||||||
|
["DorkNet:SupportedVersions:0"] = "december_2020_12_18",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.PostConfigure<HostFilteringOptions>(options =>
|
||||||
|
{
|
||||||
|
options.AllowedHosts =
|
||||||
|
[
|
||||||
|
ApexDomain,
|
||||||
|
$"*.{ApexDomain}",
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
];
|
||||||
|
options.AllowEmptyHosts = true;
|
||||||
|
options.IncludeFailureMessage = true;
|
||||||
|
});
|
||||||
|
services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSecret)),
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidIssuer = "https://api.rec.net/",
|
||||||
|
ValidateAudience = false,
|
||||||
|
ClockSkew = TimeSpan.Zero,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_dataRoot))
|
||||||
|
{
|
||||||
|
Directory.Delete(_dataRoot, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Test cleanup must not hide the original test failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public static partial class EndpointContractDiscovery
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<EndpointContract> Discover()
|
||||||
|
{
|
||||||
|
var serverAssembly = typeof(Program).Assembly;
|
||||||
|
var controllerBaseType = typeof(ControllerBase);
|
||||||
|
return serverAssembly.GetTypes()
|
||||||
|
.Where(t => !t.IsAbstract && controllerBaseType.IsAssignableFrom(t))
|
||||||
|
.SelectMany(DiscoverControllerRoutes)
|
||||||
|
.OrderBy(route => route.Method, StringComparer.Ordinal)
|
||||||
|
.ThenBy(route => route.SamplePath, StringComparer.Ordinal)
|
||||||
|
.ThenBy(route => route.Source, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<EndpointContract> DiscoverControllerRoutes(Type controller)
|
||||||
|
{
|
||||||
|
var controllerName = ControllerName(controller);
|
||||||
|
var controllerRouteTemplates = controller.GetCustomAttributes(inherit: true)
|
||||||
|
.OfType<IRouteTemplateProvider>()
|
||||||
|
.Select(a => a.Template)
|
||||||
|
.Where(t => !string.IsNullOrWhiteSpace(t))
|
||||||
|
.Cast<string>()
|
||||||
|
.DefaultIfEmpty("")
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var method in controller.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||||
|
{
|
||||||
|
var httpAttrs = method.GetCustomAttributes(inherit: true)
|
||||||
|
.OfType<HttpMethodAttribute>()
|
||||||
|
.ToArray();
|
||||||
|
if (httpAttrs.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attr in httpAttrs)
|
||||||
|
{
|
||||||
|
var attrMethods = attr.HttpMethods.Any()
|
||||||
|
? attr.HttpMethods
|
||||||
|
: new[] { "ANY" };
|
||||||
|
|
||||||
|
foreach (var httpMethod in attrMethods)
|
||||||
|
{
|
||||||
|
foreach (var controllerTemplate in controllerRouteTemplates)
|
||||||
|
{
|
||||||
|
var template = CombineTemplates(controllerTemplate, attr.Template);
|
||||||
|
template = ReplaceRouteTokens(template, controllerName, method.Name);
|
||||||
|
yield return new EndpointContract(
|
||||||
|
httpMethod.ToUpperInvariant(),
|
||||||
|
template,
|
||||||
|
ToSamplePath(template),
|
||||||
|
$"{controller.FullName}.{method.Name}",
|
||||||
|
RequiresAuthorization(controller, method),
|
||||||
|
IsAdminEndpoint(controller, method),
|
||||||
|
ConsumesContentTypes(controller, method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequiresAuthorization(Type controller, MethodInfo method)
|
||||||
|
{
|
||||||
|
var attributes = controller.GetCustomAttributes(inherit: true)
|
||||||
|
.Concat(method.GetCustomAttributes(inherit: true))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (attributes.OfType<IAllowAnonymous>().Any())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes.OfType<IAuthorizeData>().Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAdminEndpoint(Type controller, MethodInfo method)
|
||||||
|
{
|
||||||
|
return controller.FullName?.Contains(".Controllers.Admin.", StringComparison.Ordinal) == true
|
||||||
|
|| method.Name.Contains("Admin", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] ConsumesContentTypes(Type controller, MethodInfo method)
|
||||||
|
{
|
||||||
|
var consumes = method.GetCustomAttributes(inherit: true)
|
||||||
|
.Concat(controller.GetCustomAttributes(inherit: true))
|
||||||
|
.OfType<ConsumesAttribute>()
|
||||||
|
.SelectMany(a => a.ContentTypes)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return consumes.Length == 0
|
||||||
|
? ["application/json"]
|
||||||
|
: consumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ControllerName(Type controller)
|
||||||
|
{
|
||||||
|
const string suffix = "Controller";
|
||||||
|
return controller.Name.EndsWith(suffix, StringComparison.Ordinal)
|
||||||
|
? controller.Name[..^suffix.Length]
|
||||||
|
: controller.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CombineTemplates(string? controllerTemplate, string? actionTemplate)
|
||||||
|
{
|
||||||
|
controllerTemplate ??= "";
|
||||||
|
actionTemplate ??= "";
|
||||||
|
|
||||||
|
if (actionTemplate.StartsWith("~/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return "/" + actionTemplate[2..].TrimStart('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionTemplate.StartsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return "/" + actionTemplate.TrimStart('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = controllerTemplate.Trim('/');
|
||||||
|
var right = actionTemplate.Trim('/');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(left) && string.IsNullOrEmpty(right))
|
||||||
|
{
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(left))
|
||||||
|
{
|
||||||
|
return "/" + right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(right))
|
||||||
|
{
|
||||||
|
return "/" + left;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + left + "/" + right;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplaceRouteTokens(string template, string controllerName, string actionName)
|
||||||
|
{
|
||||||
|
return template
|
||||||
|
.Replace("[controller]", controllerName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("[action]", actionName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToSamplePath(string template)
|
||||||
|
{
|
||||||
|
var path = RouteParameterRegex().Replace(template, match =>
|
||||||
|
{
|
||||||
|
var name = match.Groups["name"].Value.ToLowerInvariant();
|
||||||
|
var constraint = match.Groups["constraint"].Value.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (name.Contains("playerid") || name.Contains("accountid") || name == "id")
|
||||||
|
{
|
||||||
|
return EndpointContract.TestPlayerToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraint.Contains("long") || constraint.Contains("int") || name.EndsWith("id"))
|
||||||
|
{
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraint.Contains("bool"))
|
||||||
|
{
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraint.Contains("guid"))
|
||||||
|
{
|
||||||
|
return "00000000-0000-0000-0000-000000000001";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Contains("roomname"))
|
||||||
|
{
|
||||||
|
return "DormRoom";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Contains("locale"))
|
||||||
|
{
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "sample";
|
||||||
|
});
|
||||||
|
|
||||||
|
path = Regex.Replace(path, "/+", "/");
|
||||||
|
return path.StartsWith('/') ? path : "/" + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\{[*]?(?<name>[^}:=\?]+)(?<constraint>:[^}=\?]+)?(?<optional>\?)?(?<default>=[^}]+)?\}")]
|
||||||
|
private static partial Regex RouteParameterRegex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record EndpointContract(
|
||||||
|
string Method,
|
||||||
|
string Template,
|
||||||
|
string SamplePath,
|
||||||
|
string Source,
|
||||||
|
bool RequiresAuthorization,
|
||||||
|
bool IsAdminEndpoint,
|
||||||
|
string[] Consumes)
|
||||||
|
{
|
||||||
|
public const string TestPlayerToken = "__test_player_id__";
|
||||||
|
|
||||||
|
public string PathFor(GameClientSession session)
|
||||||
|
{
|
||||||
|
return SamplePath.Replace(
|
||||||
|
TestPlayerToken,
|
||||||
|
session.PlayerId.ToString(),
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed class GameClientEndpointContractTests :
|
||||||
|
IClassFixture<DorkNetServerFactory>
|
||||||
|
{
|
||||||
|
private readonly DorkNetServerFactory _factory;
|
||||||
|
|
||||||
|
public GameClientEndpointContractTests(DorkNetServerFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Game_client_contract_probe_reaches_every_controller_route()
|
||||||
|
{
|
||||||
|
var routes = EndpointContractDiscovery.Discover();
|
||||||
|
Assert.NotEmpty(routes);
|
||||||
|
|
||||||
|
using var client = _factory.CreateClient(new()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
});
|
||||||
|
client.BaseAddress = new Uri($"http://api.{_factory.ApexDomain}");
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(20);
|
||||||
|
|
||||||
|
var session = await GameClientSessionFactory.CreateAsync(client, _factory.ApexDomain);
|
||||||
|
var failures = new List<string>();
|
||||||
|
|
||||||
|
foreach (var route in routes)
|
||||||
|
{
|
||||||
|
var result = await ProbeAsync(client, route, session, _factory.ApexDomain);
|
||||||
|
if (!result.Passed)
|
||||||
|
{
|
||||||
|
failures.Add(
|
||||||
|
$"{route.Method,-6} {result.Host}{result.Path} {route.Source} expected {result.Expected}, got {result.Observed}"
|
||||||
|
+ (string.IsNullOrWhiteSpace(result.ResponseExcerpt) ? "" : $" body={result.ResponseExcerpt}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
failures.Count == 0,
|
||||||
|
$"Endpoint contract failures ({failures.Count}/{routes.Count}):{Environment.NewLine}"
|
||||||
|
+ string.Join(Environment.NewLine, failures));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<EndpointProbeResult> ProbeAsync(
|
||||||
|
HttpClient client,
|
||||||
|
EndpointContract route,
|
||||||
|
GameClientSession session,
|
||||||
|
string apexDomain)
|
||||||
|
{
|
||||||
|
var path = route.PathFor(session);
|
||||||
|
path = AddClientQuery(path, route, session);
|
||||||
|
var host = HostFor(path, apexDomain);
|
||||||
|
using var request = new HttpRequestMessage(
|
||||||
|
ToHttpMethod(route.Method),
|
||||||
|
new Uri($"http://{host}{path}"));
|
||||||
|
request.Headers.Accept.ParseAdd("application/json");
|
||||||
|
request.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
||||||
|
request.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", session.AccessToken);
|
||||||
|
|
||||||
|
if (NeedsRequestBody(request.Method))
|
||||||
|
{
|
||||||
|
request.Content = CreateContent(route, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var response = await client.SendAsync(request);
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
var statusCode = (int)response.StatusCode;
|
||||||
|
var passed = ResponseMatchesContract(route, response.StatusCode);
|
||||||
|
return new EndpointProbeResult(
|
||||||
|
route.Method,
|
||||||
|
host,
|
||||||
|
path,
|
||||||
|
ExpectedContract(route),
|
||||||
|
statusCode,
|
||||||
|
null,
|
||||||
|
Excerpt(responseText),
|
||||||
|
passed);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
|
||||||
|
{
|
||||||
|
return new EndpointProbeResult(
|
||||||
|
route.Method,
|
||||||
|
host,
|
||||||
|
path,
|
||||||
|
ExpectedContract(route),
|
||||||
|
null,
|
||||||
|
ex.Message,
|
||||||
|
null,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ResponseMatchesContract(
|
||||||
|
EndpointContract route,
|
||||||
|
HttpStatusCode statusCode)
|
||||||
|
{
|
||||||
|
if ((int)statusCode >= 500)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode is HttpStatusCode.MethodNotAllowed
|
||||||
|
or HttpStatusCode.NotImplemented
|
||||||
|
or HttpStatusCode.UpgradeRequired)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.RequiresAuthorization
|
||||||
|
&& statusCode is HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExpectedContract(EndpointContract route)
|
||||||
|
{
|
||||||
|
if (route.IsAdminEndpoint)
|
||||||
|
{
|
||||||
|
return "admin endpoint may reject non-admin test player, but must not 5xx/405/426";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.RequiresAuthorization)
|
||||||
|
{
|
||||||
|
return "authenticated test player should reach the action without 401/5xx/405/426; 403 is valid for owner/admin-only resources";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "game-client request should not 5xx/405/426";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpMethod ToHttpMethod(string method)
|
||||||
|
{
|
||||||
|
return string.Equals(method, "ANY", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? HttpMethod.Get
|
||||||
|
: new HttpMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool NeedsRequestBody(HttpMethod method)
|
||||||
|
{
|
||||||
|
return method == HttpMethod.Post
|
||||||
|
|| method == HttpMethod.Put
|
||||||
|
|| method.Method.Equals("PATCH", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpContent CreateContent(
|
||||||
|
EndpointContract route,
|
||||||
|
GameClientSession session)
|
||||||
|
{
|
||||||
|
var consumes = route.Consumes.Select(c => c.ToLowerInvariant()).ToArray();
|
||||||
|
if (consumes.Any(c => c.Contains("application/x-www-form-urlencoded")))
|
||||||
|
{
|
||||||
|
return new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["account_id"] = session.PlayerId.ToString(),
|
||||||
|
["AccountId"] = session.PlayerId.ToString(),
|
||||||
|
["clubId"] = "1",
|
||||||
|
["ClubId"] = "1",
|
||||||
|
["description"] = "Endpoint contract description",
|
||||||
|
["deviceId"] = session.DeviceId,
|
||||||
|
["grant_type"] = "password",
|
||||||
|
["image"] = "endpoint-contract.png",
|
||||||
|
["imageName"] = "endpoint-contract.png",
|
||||||
|
["fileName"] = "endpoint-contract.png",
|
||||||
|
["message"] = "Endpoint contract message",
|
||||||
|
["name"] = "EndpointContract",
|
||||||
|
["newPassword"] = "endpoint-contract-password",
|
||||||
|
["oldPassword"] = "",
|
||||||
|
["password"] = "endpoint-contract-password",
|
||||||
|
["ping"] = "42",
|
||||||
|
["platformId"] = session.PlatformId,
|
||||||
|
["playerId"] = session.PlayerId.ToString(),
|
||||||
|
["playerIds"] = session.PlayerId.ToString(),
|
||||||
|
["query"] = "DormRoom",
|
||||||
|
["region"] = "us",
|
||||||
|
["targetPlayerId"] = session.PlayerId.ToString(),
|
||||||
|
["text"] = "Endpoint contract message",
|
||||||
|
["username"] = "EndpointContract",
|
||||||
|
["device_id"] = session.DeviceId,
|
||||||
|
["platform"] = "0",
|
||||||
|
["platform_id"] = session.PlatformId,
|
||||||
|
["Ids"] = session.PlayerId.ToString(),
|
||||||
|
["PlayerIds"] = session.PlayerId.ToString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumes.Any(c => c.Contains("multipart/form-data")))
|
||||||
|
{
|
||||||
|
var content = new MultipartFormDataContent();
|
||||||
|
content.Add(new StringContent("endpoint-contract"), "name");
|
||||||
|
content.Add(new StringContent(session.PlayerId.ToString()), "playerId");
|
||||||
|
content.Add(new StringContent("endpoint-contract.png"), "imageName");
|
||||||
|
content.Add(new StringContent("endpoint-contract.png"), "fileName");
|
||||||
|
content.Add(new ByteArrayContent([]), "file", "endpoint-contract.bin");
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumes.Any(c => c.Contains("text/plain")))
|
||||||
|
{
|
||||||
|
return new StringContent("", Encoding.UTF8, "text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
PlayerId = session.PlayerId,
|
||||||
|
AccountId = session.PlayerId,
|
||||||
|
ClubId = 1,
|
||||||
|
Description = "Endpoint contract description",
|
||||||
|
DeviceId = session.DeviceId,
|
||||||
|
FileName = "endpoint-contract.png",
|
||||||
|
Id = session.PlayerId,
|
||||||
|
Image = "endpoint-contract.png",
|
||||||
|
ImageName = "endpoint-contract.png",
|
||||||
|
Message = "Endpoint contract message",
|
||||||
|
Platform = 0,
|
||||||
|
PlatformId = session.PlatformId,
|
||||||
|
PlayerIds = new[] { session.PlayerId },
|
||||||
|
Name = "EndpointContract",
|
||||||
|
Password = "endpoint-contract-password",
|
||||||
|
Query = "DormRoom",
|
||||||
|
RoomName = "DormRoom",
|
||||||
|
TargetPlayerId = session.PlayerId,
|
||||||
|
Text = "Endpoint contract message",
|
||||||
|
});
|
||||||
|
return new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string AddClientQuery(
|
||||||
|
string path,
|
||||||
|
EndpointContract route,
|
||||||
|
GameClientSession session)
|
||||||
|
{
|
||||||
|
var separator = path.Contains('?', StringComparison.Ordinal) ? "&" : "?";
|
||||||
|
var lower = path.ToLowerInvariant();
|
||||||
|
var query = new List<string>();
|
||||||
|
|
||||||
|
if (lower.Contains("search", StringComparison.Ordinal)
|
||||||
|
|| lower.Contains("query", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
query.Add("query=EndpointContract");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower == "/members/bulk")
|
||||||
|
{
|
||||||
|
query.Add("clubId=1");
|
||||||
|
query.Add($"playerIds={session.PlayerId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower == "/player/photonregionpings")
|
||||||
|
{
|
||||||
|
query.Add("region=us");
|
||||||
|
query.Add("ping=42");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.Source.Contains("CachedLogins", StringComparison.Ordinal)
|
||||||
|
|| lower.Contains("platformlogin/cached", StringComparison.Ordinal)
|
||||||
|
|| lower.Contains("savedlogins", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
query.Add("platform=0");
|
||||||
|
query.Add($"platformId={Uri.EscapeDataString(session.PlatformId)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.Source.Contains("Bulk", StringComparison.Ordinal)
|
||||||
|
&& !query.Any(q => q.StartsWith("Ids=", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
query.Add($"Ids={session.PlayerId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.Count == 0)
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path + separator + string.Join("&", query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HostFor(string path, string apexDomain)
|
||||||
|
{
|
||||||
|
var lower = path.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (lower.StartsWith("/connect/")
|
||||||
|
|| lower.StartsWith("/.well-known/")
|
||||||
|
|| lower.StartsWith("/eac/")
|
||||||
|
|| lower.StartsWith("/cachedlogin/")
|
||||||
|
|| lower.StartsWith("/photon/")
|
||||||
|
|| lower.StartsWith("/api/platformlogin/")
|
||||||
|
|| lower.StartsWith("/api/version")
|
||||||
|
|| lower.StartsWith("/api/auth/"))
|
||||||
|
{
|
||||||
|
return $"auth.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/account/"))
|
||||||
|
{
|
||||||
|
return $"accounts.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/roomserver/")
|
||||||
|
|| lower.StartsWith("/rooms")
|
||||||
|
|| lower.StartsWith("/playlists")
|
||||||
|
|| lower.StartsWith("/featuredrooms")
|
||||||
|
|| lower.StartsWith("/hot_rooms")
|
||||||
|
|| lower.StartsWith("/search_rooms")
|
||||||
|
|| lower.StartsWith("/api/rooms")
|
||||||
|
|| lower.StartsWith("/api/roomkeys")
|
||||||
|
|| lower.StartsWith("/api/gamesessions")
|
||||||
|
|| lower.StartsWith("/api/curatedroomplaylists"))
|
||||||
|
{
|
||||||
|
return $"rooms.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/v1/find")
|
||||||
|
|| lower.StartsWith("/v1/join")
|
||||||
|
|| lower.StartsWith("/v1/leave")
|
||||||
|
|| lower.StartsWith("/v1/session")
|
||||||
|
|| lower.StartsWith("/roominstance/")
|
||||||
|
|| lower.StartsWith("/goto/")
|
||||||
|
|| lower.StartsWith("/player/"))
|
||||||
|
{
|
||||||
|
return $"match.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/thread"))
|
||||||
|
{
|
||||||
|
return $"chat.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/club")
|
||||||
|
|| lower.StartsWith("/announcements/")
|
||||||
|
|| lower.StartsWith("/subscription/")
|
||||||
|
|| lower.StartsWith("/members/")
|
||||||
|
|| lower.StartsWith("/api/clubreporting/"))
|
||||||
|
{
|
||||||
|
return $"clubs.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/api/storefronts/")
|
||||||
|
|| lower.StartsWith("/api/purchase/")
|
||||||
|
|| lower.StartsWith("/api/subscription/"))
|
||||||
|
{
|
||||||
|
return $"commerce.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/hub/") || lower.StartsWith("/api/notification/"))
|
||||||
|
{
|
||||||
|
return $"notify.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/v1/regions"))
|
||||||
|
{
|
||||||
|
return $"geo.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/v1/services") || lower.StartsWith("/v1/ping"))
|
||||||
|
{
|
||||||
|
return $"ns.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/upload"))
|
||||||
|
{
|
||||||
|
return $"storage.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/img/") || lower.StartsWith("/room/") || lower.StartsWith("/data/"))
|
||||||
|
{
|
||||||
|
return $"cdn.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.StartsWith("/api/admin/"))
|
||||||
|
{
|
||||||
|
return $"admin.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"api.{apexDomain}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? Excerpt(string? body)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(body))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var compact = body.ReplaceLineEndings(" ").Trim();
|
||||||
|
return compact.Length <= 240 ? compact : compact[..240] + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record EndpointProbeResult(
|
||||||
|
string Method,
|
||||||
|
string Host,
|
||||||
|
string Path,
|
||||||
|
string Expected,
|
||||||
|
int? StatusCode,
|
||||||
|
string? Error,
|
||||||
|
string? ResponseExcerpt,
|
||||||
|
bool Passed)
|
||||||
|
{
|
||||||
|
public string Observed => StatusCode is int code ? code.ToString() : Error ?? "no response";
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed record GameClientSession(
|
||||||
|
long PlayerId,
|
||||||
|
string AccessToken,
|
||||||
|
string DeviceId,
|
||||||
|
string PlatformId);
|
||||||
|
|
||||||
|
public static class GameClientSessionFactory
|
||||||
|
{
|
||||||
|
public static async Task<GameClientSession> CreateAsync(
|
||||||
|
HttpClient client,
|
||||||
|
string apexDomain)
|
||||||
|
{
|
||||||
|
var deviceId = $"endpoint-contract-device-{Guid.NewGuid():N}";
|
||||||
|
var platformId = $"endpoint-contract-platform-{Guid.NewGuid():N}";
|
||||||
|
var tokenRequest = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
new Uri($"http://auth.{apexDomain}/connect/token"));
|
||||||
|
tokenRequest.Headers.Accept.ParseAdd("application/json");
|
||||||
|
tokenRequest.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
||||||
|
tokenRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
||||||
|
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["grant_type"] = "password",
|
||||||
|
["username"] = "EndpointContract",
|
||||||
|
["device_id"] = deviceId,
|
||||||
|
["platform"] = "0",
|
||||||
|
["platform_id"] = platformId,
|
||||||
|
});
|
||||||
|
|
||||||
|
using var tokenResponse = await client.SendAsync(tokenRequest);
|
||||||
|
if (!tokenResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var body = await tokenResponse.Content.ReadAsStringAsync();
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Test player token request failed with {(int)tokenResponse.StatusCode} {tokenResponse.StatusCode}: {body}");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var tokenJson = await JsonDocument.ParseAsync(
|
||||||
|
await tokenResponse.Content.ReadAsStreamAsync());
|
||||||
|
var accessToken = tokenJson.RootElement.GetProperty("access_token").GetString()
|
||||||
|
?? throw new InvalidOperationException("Token response did not include access_token.");
|
||||||
|
|
||||||
|
var userInfoRequest = new HttpRequestMessage(
|
||||||
|
HttpMethod.Get,
|
||||||
|
new Uri($"http://auth.{apexDomain}/connect/userinfo"));
|
||||||
|
userInfoRequest.Headers.Accept.ParseAdd("application/json");
|
||||||
|
userInfoRequest.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
||||||
|
userInfoRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
||||||
|
userInfoRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||||
|
|
||||||
|
using var userInfoResponse = await client.SendAsync(userInfoRequest);
|
||||||
|
userInfoResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using var userInfoJson = await JsonDocument.ParseAsync(
|
||||||
|
await userInfoResponse.Content.ReadAsStreamAsync());
|
||||||
|
var playerId = long.Parse(userInfoJson.RootElement.GetProperty("sub").GetString()!);
|
||||||
|
|
||||||
|
return new GameClientSession(playerId, accessToken, deviceId, platformId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -201,12 +201,12 @@ public class AccountApiController(
|
|||||||
|
|
||||||
[HttpPost("api/account/v1/profileimage")]
|
[HttpPost("api/account/v1/profileimage")]
|
||||||
[HttpPost("api/account/me/profileimage")]
|
[HttpPost("api/account/me/profileimage")]
|
||||||
[HttpPost("account/me/profileimage")]
|
[HttpPost("account/me/profileimage", Order = 10)]
|
||||||
[HttpPost("profileimage")]
|
[HttpPost("profileimage", Order = 10)]
|
||||||
[HttpPut("api/account/v1/profileimage")]
|
[HttpPut("api/account/v1/profileimage")]
|
||||||
[HttpPut("api/account/me/profileimage")]
|
[HttpPut("api/account/me/profileimage")]
|
||||||
[HttpPut("account/me/profileimage")]
|
[HttpPut("account/me/profileimage", Order = 10)]
|
||||||
[HttpPut("profileimage")]
|
[HttpPut("profileimage", Order = 10)]
|
||||||
[Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")]
|
[Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")]
|
||||||
public async Task<IActionResult> SetProfileImage(
|
public async Task<IActionResult> SetProfileImage(
|
||||||
[FromForm(Name = "imageName")] string? imageName,
|
[FromForm(Name = "imageName")] string? imageName,
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ public class PageviewController(ILogger<PageviewController> logger) : Controller
|
|||||||
/// intentionally return an empty url + a 1-hour freshness — the
|
/// intentionally return an empty url + a 1-hour freshness — the
|
||||||
/// private server has no in-app deep-link system, so there's
|
/// private server has no in-app deep-link system, so there's
|
||||||
/// nothing to navigate to and no reason to be polled more often.</summary>
|
/// nothing to navigate to and no reason to be polled more often.</summary>
|
||||||
[HttpPost("pageview/consume")]
|
[HttpPost("pageview/consume", Order = 10)]
|
||||||
[HttpPost("/pageview/consume")]
|
[HttpPost("/pageview/consume")]
|
||||||
[HttpGet("pageview/consume")]
|
[HttpGet("pageview/consume", Order = 10)]
|
||||||
[HttpGet("/pageview/consume")]
|
[HttpGet("/pageview/consume")]
|
||||||
public IActionResult Consume()
|
public IActionResult Consume()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class AuthAccountController(DorkNetDbContext db) : ControllerBase
|
|||||||
/// <c>ExpectPrimitiveResponse<bool></c> so the body must be
|
/// <c>ExpectPrimitiveResponse<bool></c> so the body must be
|
||||||
/// the literal JSON <c>true</c> or <c>false</c>.</summary>
|
/// the literal JSON <c>true</c> or <c>false</c>.</summary>
|
||||||
[HttpGet("/account/me/haspassword")]
|
[HttpGet("/account/me/haspassword")]
|
||||||
[HttpGet("/api/platformlogin/haspassword")]
|
[HttpGet("/api/platformlogin/haspassword", Order = 10)]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> HasPassword()
|
public async Task<IActionResult> HasPassword()
|
||||||
{
|
{
|
||||||
@@ -40,8 +40,8 @@ public class AuthAccountController(DorkNetDbContext db) : ControllerBase
|
|||||||
/// one; first-time creation skips that check. Form-encoded body.</summary>
|
/// one; first-time creation skips that check. Form-encoded body.</summary>
|
||||||
[HttpPost("/account/me/changepassword")]
|
[HttpPost("/account/me/changepassword")]
|
||||||
[HttpPost("/account/me/createpassword")]
|
[HttpPost("/account/me/createpassword")]
|
||||||
[HttpPost("/api/platformlogin/password")]
|
[HttpPost("/api/platformlogin/password", Order = 10)]
|
||||||
[HttpPut("/api/platformlogin/password")]
|
[HttpPut("/api/platformlogin/password", Order = 10)]
|
||||||
[Consumes("application/x-www-form-urlencoded", "multipart/form-data")]
|
[Consumes("application/x-www-form-urlencoded", "multipart/form-data")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> ChangePassword(
|
public async Task<IActionResult> ChangePassword(
|
||||||
@@ -76,7 +76,7 @@ public class AuthAccountController(DorkNetDbContext db) : ControllerBase
|
|||||||
/// account from the admin tab and the user re-creates it via
|
/// account from the admin tab and the user re-creates it via
|
||||||
/// changepassword on next login. Anonymous calls 401.</summary>
|
/// changepassword on next login. Anonymous calls 401.</summary>
|
||||||
[HttpPost("/account/me/resetpassword")]
|
[HttpPost("/account/me/resetpassword")]
|
||||||
[HttpPost("/api/platformlogin/resetpassword")]
|
[HttpPost("/api/platformlogin/resetpassword", Order = 10)]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[AdminOnly]
|
[AdminOnly]
|
||||||
public async Task<IActionResult> AdminResetPassword(
|
public async Task<IActionResult> AdminResetPassword(
|
||||||
|
|||||||
@@ -668,11 +668,14 @@ public class ClubsController(ClubService clubs) : ControllerBase
|
|||||||
[HttpPost("/members/bulk")]
|
[HttpPost("/members/bulk")]
|
||||||
public async Task<IActionResult> MembersBulk()
|
public async Task<IActionResult> MembersBulk()
|
||||||
{
|
{
|
||||||
|
var form = Request.HasFormContentType ? Request.Form : null;
|
||||||
if (!long.TryParse(Request.Query["clubId"].ToString(), out var clubId)
|
if (!long.TryParse(Request.Query["clubId"].ToString(), out var clubId)
|
||||||
&& !long.TryParse(Request.Form["clubId"].ToString(), out clubId))
|
&& (form is null || !long.TryParse(form["clubId"].ToString(), out clubId)))
|
||||||
return BadRequest(new { error = "missing_club_id" });
|
return BadRequest(new { error = "missing_club_id" });
|
||||||
|
|
||||||
var idsRaw = Request.Query["playerIds"].Concat(Request.Form["playerIds"]);
|
var idsRaw = form is null
|
||||||
|
? Request.Query["playerIds"].AsEnumerable()
|
||||||
|
: Request.Query["playerIds"].Concat(form["playerIds"]);
|
||||||
var ids = idsRaw
|
var ids = idsRaw
|
||||||
.SelectMany(v => (v ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
.SelectMany(v => (v ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
.Select(s => long.TryParse(s, out var n) ? n : 0)
|
.Select(s => long.TryParse(s, out var n) ? n : 0)
|
||||||
|
|||||||
@@ -223,9 +223,12 @@ public class MatchPlayerController(
|
|||||||
var playerId = TryGetCurrentPlayerId();
|
var playerId = TryGetCurrentPlayerId();
|
||||||
if (playerId == 0) return Ok();
|
if (playerId == 0) return Ok();
|
||||||
|
|
||||||
var form = Request.Form;
|
var regions = Request.HasFormContentType
|
||||||
var regions = form["region"].ToArray();
|
? Request.Form["region"].ToArray()
|
||||||
var pings = form["ping"].ToArray();
|
: Request.Query["region"].ToArray();
|
||||||
|
var pings = Request.HasFormContentType
|
||||||
|
? Request.Form["ping"].ToArray()
|
||||||
|
: Request.Query["ping"].ToArray();
|
||||||
var pairs = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var pairs = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
for (var i = 0; i < Math.Min(regions.Length, pings.Length); i++)
|
for (var i = 0; i < Math.Min(regions.Length, pings.Length); i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ var app = builder.Build();
|
|||||||
await app.RunDatabaseBootstrapAsync();
|
await app.RunDatabaseBootstrapAsync();
|
||||||
app.UseDorkNetPipeline();
|
app.UseDorkNetPipeline();
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
public partial class Program;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace DorkNet.ServiceDefaults;
|
namespace DorkNet.ServiceDefaults;
|
||||||
|
|
||||||
@@ -21,9 +22,15 @@ public static class DorkNetServiceDefaultsExtensions
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebApplication MapDorkNetServiceDefaults(this WebApplication app)
|
public static WebApplication MapDorkNetServiceDefaults(
|
||||||
|
this WebApplication app,
|
||||||
|
bool mapPublicHealth = true)
|
||||||
{
|
{
|
||||||
app.MapHealthChecks("/healthz");
|
if (mapPublicHealth)
|
||||||
|
{
|
||||||
|
app.MapHealthChecks("/healthz");
|
||||||
|
}
|
||||||
|
|
||||||
app.MapGet("/internal/healthz", (DorkNetServiceInfo info) =>
|
app.MapGet("/internal/healthz", (DorkNetServiceInfo info) =>
|
||||||
{
|
{
|
||||||
return Results.Ok(new ServiceHealthResponse(
|
return Results.Ok(new ServiceHealthResponse(
|
||||||
@@ -34,4 +41,43 @@ public static class DorkNetServiceDefaultsExtensions
|
|||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static WebApplication UseDorkNetRouteOwnershipGuard(
|
||||||
|
this WebApplication app,
|
||||||
|
string serviceName)
|
||||||
|
{
|
||||||
|
app.Use(async (ctx, next) =>
|
||||||
|
{
|
||||||
|
var domain = Environment.GetEnvironmentVariable("DORKNET_DOMAIN")
|
||||||
|
?? ctx.RequestServices.GetService<Microsoft.Extensions.Configuration.IConfiguration>()?["Domain:Apex"]
|
||||||
|
?? "localhost";
|
||||||
|
var path = ctx.Request.Path.Value ?? "/";
|
||||||
|
if (DorkNetRouteOwnership.IsOwnedBy(serviceName, ctx.Request.Host.Host, path, domain))
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var owner = DorkNetRouteOwnership.ResolvePublicService(ctx.Request.Host.Host, path, domain);
|
||||||
|
var logger = ctx.RequestServices
|
||||||
|
.GetRequiredService<ILoggerFactory>()
|
||||||
|
.CreateLogger("DorkNet.ServiceDefaults.RouteGuard");
|
||||||
|
logger.LogDebug(
|
||||||
|
"[route-guard] service={Service} rejected {Method} {Host}{Path}; owner={Owner}",
|
||||||
|
serviceName,
|
||||||
|
ctx.Request.Method,
|
||||||
|
ctx.Request.Host,
|
||||||
|
ctx.Request.Path,
|
||||||
|
owner);
|
||||||
|
ctx.Response.StatusCode = StatusCodes.Status404NotFound;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new
|
||||||
|
{
|
||||||
|
error = "route_not_owned_by_service",
|
||||||
|
service = serviceName,
|
||||||
|
owner,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Commerce);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/commerce/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Commerce,
|
||||||
|
[
|
||||||
|
"catalog",
|
||||||
|
"storefronts",
|
||||||
|
"econ",
|
||||||
|
"inventory",
|
||||||
|
"inventions",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/api/catalog/*",
|
||||||
|
"/api/storefronts/*",
|
||||||
|
"/econ/*",
|
||||||
|
"/api/equipment/*",
|
||||||
|
"/api/inventions/*",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Commerce);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Content);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/content/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Content,
|
||||||
|
[
|
||||||
|
"cdn",
|
||||||
|
"images",
|
||||||
|
"photos",
|
||||||
|
"room-blobs",
|
||||||
|
"storage",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/api/images/*",
|
||||||
|
"/api/photos/*",
|
||||||
|
"/data/*",
|
||||||
|
"/img/*",
|
||||||
|
"/upload",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Content);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
using DorkNet.Contracts;
|
using DorkNet.Contracts;
|
||||||
using DorkNet.ServiceDefaults;
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
optional: true, reloadOnChange: true);
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
builder.AddDorkNetServiceDefaults(ServiceNames.Identity);
|
builder.AddDorkNetServiceDefaults(ServiceNames.Identity);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapDorkNetServiceDefaults();
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
app.MapGet("/internal/identity/capabilities", () =>
|
app.MapGet("/internal/identity/capabilities", () =>
|
||||||
Results.Ok(new ServiceCapabilityResponse(
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
ServiceNames.Identity,
|
ServiceNames.Identity,
|
||||||
@@ -26,4 +29,7 @@ app.MapGet("/internal/identity/capabilities", () =>
|
|||||||
"/api/auth/*",
|
"/api/auth/*",
|
||||||
])));
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Identity);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Moderation);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/moderation/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Moderation,
|
||||||
|
[
|
||||||
|
"bug-reporting",
|
||||||
|
"player-reporting",
|
||||||
|
"sanitize",
|
||||||
|
"admin-api",
|
||||||
|
"testcase-management",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/api/bugreporting/*",
|
||||||
|
"/api/playerreporting/*",
|
||||||
|
"/api/sanitize/*",
|
||||||
|
"/api/admin/*",
|
||||||
|
"/api/testcasemanagement/*",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Moderation);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
using DorkNet.Contracts;
|
using DorkNet.Contracts;
|
||||||
using DorkNet.ServiceDefaults;
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
optional: true, reloadOnChange: true);
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
builder.AddDorkNetServiceDefaults(ServiceNames.Notify);
|
builder.AddDorkNetServiceDefaults(ServiceNames.Notify);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapDorkNetServiceDefaults();
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
app.MapGet("/internal/notify/capabilities", () =>
|
app.MapGet("/internal/notify/capabilities", () =>
|
||||||
Results.Ok(new ServiceCapabilityResponse(
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
ServiceNames.Notify,
|
ServiceNames.Notify,
|
||||||
@@ -25,4 +28,7 @@ app.MapGet("/internal/notify/capabilities", () =>
|
|||||||
"/player/*presence*",
|
"/player/*presence*",
|
||||||
])));
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Notify);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Platform);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/platform/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Platform,
|
||||||
|
[
|
||||||
|
"service-directory",
|
||||||
|
"config",
|
||||||
|
"version-check",
|
||||||
|
"geo",
|
||||||
|
"strings",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/v1/services",
|
||||||
|
"/api/config/*",
|
||||||
|
"/api/versioncheck/*",
|
||||||
|
"/v1/regions",
|
||||||
|
"/strings/*",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Platform);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
using DorkNet.Contracts;
|
using DorkNet.Contracts;
|
||||||
using DorkNet.ServiceDefaults;
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
optional: true, reloadOnChange: true);
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
builder.AddDorkNetServiceDefaults(ServiceNames.Rooms);
|
builder.AddDorkNetServiceDefaults(ServiceNames.Rooms);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapDorkNetServiceDefaults();
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
app.MapGet("/internal/rooms/capabilities", () =>
|
app.MapGet("/internal/rooms/capabilities", () =>
|
||||||
Results.Ok(new ServiceCapabilityResponse(
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
ServiceNames.Rooms,
|
ServiceNames.Rooms,
|
||||||
@@ -28,4 +31,7 @@ app.MapGet("/internal/rooms/capabilities", () =>
|
|||||||
"/v1/join/*",
|
"/v1/join/*",
|
||||||
])));
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Rooms);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Social);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/social/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Social,
|
||||||
|
[
|
||||||
|
"clubs",
|
||||||
|
"groups",
|
||||||
|
"announcements",
|
||||||
|
"player-events",
|
||||||
|
"subscriptions",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/club/*",
|
||||||
|
"/api/groups/*",
|
||||||
|
"/api/playerevents/*",
|
||||||
|
"/api/playersubscriptions/*",
|
||||||
|
"/announcements/*",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Social);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DorkNet.Contracts\DorkNet.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.ServiceDefaults\DorkNet.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\DorkNet.Server\DorkNet.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using DorkNet.Contracts;
|
||||||
|
using DorkNet.ServiceDefaults;
|
||||||
|
using DorkNet.Server.Startup;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json",
|
||||||
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
builder.AddDorkNetServiceDefaults(ServiceNames.Web);
|
||||||
|
builder.AddDorkNetServices();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.RunDatabaseBootstrapAsync();
|
||||||
|
app.MapDorkNetServiceDefaults(mapPublicHealth: false);
|
||||||
|
app.MapGet("/internal/web/capabilities", () =>
|
||||||
|
Results.Ok(new ServiceCapabilityResponse(
|
||||||
|
ServiceNames.Web,
|
||||||
|
[
|
||||||
|
"public-site",
|
||||||
|
"admin-site",
|
||||||
|
"feed-site",
|
||||||
|
"site-api",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/",
|
||||||
|
"www.*",
|
||||||
|
"admin.*",
|
||||||
|
"feed.*",
|
||||||
|
"/api/site/*",
|
||||||
|
])));
|
||||||
|
|
||||||
|
app.UseDorkNetRouteOwnershipGuard(ServiceNames.Web);
|
||||||
|
app.UseDorkNetPipeline();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
-19
@@ -7,10 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Server", "DorkNet.S
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Models", "DorkNet.Models\DorkNet.Models.csproj", "{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Models", "DorkNet.Models\DorkNet.Models.csproj", "{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrateSqliteToPostgres", "Tools\MigrateSqliteToPostgres\MigrateSqliteToPostgres.csproj", "{70C60098-1B11-4C13-943B-A8A056429EAE}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.AdminMobile", "DorkNet.AdminMobile\DorkNet.AdminMobile.csproj", "{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.AdminMobile", "DorkNet.AdminMobile\DorkNet.AdminMobile.csproj", "{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Contracts", "DorkNet.Contracts\DorkNet.Contracts.csproj", "{7D9E995E-5333-43B2-848B-3522C6C3D893}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Contracts", "DorkNet.Contracts\DorkNet.Contracts.csproj", "{7D9E995E-5333-43B2-848B-3522C6C3D893}"
|
||||||
@@ -25,6 +21,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Rooms", "D
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Notify", "DorkNet.Services.Notify\DorkNet.Services.Notify.csproj", "{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Notify", "DorkNet.Services.Notify\DorkNet.Services.Notify.csproj", "{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Server.Tests", "DorkNet.Server.Tests\DorkNet.Server.Tests.csproj", "{43230177-A02F-440A-9B59-B79AB5E7BC27}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Content", "DorkNet.Services.Content\DorkNet.Services.Content.csproj", "{8958DA76-3925-4F12-8884-DE2E79FE9677}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Social", "DorkNet.Services.Social\DorkNet.Services.Social.csproj", "{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Commerce", "DorkNet.Services.Commerce\DorkNet.Services.Commerce.csproj", "{77BBD5A3-A610-4A20-A61A-B4AB56291C27}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Platform", "DorkNet.Services.Platform\DorkNet.Services.Platform.csproj", "{C68AA2CA-9E80-46E5-9EE0-1544CE070036}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Moderation", "DorkNet.Services.Moderation\DorkNet.Services.Moderation.csproj", "{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Services.Web", "DorkNet.Services.Web\DorkNet.Services.Web.csproj", "{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -59,18 +69,6 @@ Global
|
|||||||
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x64.Build.0 = Release|Any CPU
|
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.ActiveCfg = Release|Any CPU
|
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.Build.0 = Release|Any CPU
|
{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{C2265F00-DFC5-4352-A94C-B887E3C0E5CA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
@@ -155,11 +153,92 @@ Global
|
|||||||
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x64.Build.0 = Release|Any CPU
|
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x86.ActiveCfg = Release|Any CPU
|
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x86.Build.0 = Release|Any CPU
|
{5A6D7277-0974-4F12-AD9D-C705F5CFE19C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{43230177-A02F-440A-9B59-B79AB5E7BC27}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8958DA76-3925-4F12-8884-DE2E79FE9677}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C6DBC037-1E6B-4B2F-8490-08A6F9380E00}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{77BBD5A3-A610-4A20-A61A-B4AB56291C27}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C68AA2CA-9E80-46E5-9EE0-1544CE070036}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1D44C401-17B8-4314-9425-AE7A2CEDBBDE}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{F29F1B14-B61E-47CC-820A-C98BAE8F21DB}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{70C60098-1B11-4C13-943B-A8A056429EAE} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ main also auto-detects your install.
|
|||||||
| **Version key** (sent as `X-DorkNet-Version` header) | `december_2020_12_18` |
|
| **Version key** (sent as `X-DorkNet-Version` header) | `december_2020_12_18` |
|
||||||
| **Version plugin** | `DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs` |
|
| **Version plugin** | `DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs` |
|
||||||
| **Schema** | One EF `Initial` migration; SQLite + Postgres both work |
|
| **Schema** | One EF `Initial` migration; SQLite + Postgres both work |
|
||||||
| **Runtime topology** | Full API still runs from `DorkNet.Server`; microservice gateway/service hosts are scaffolded for incremental split |
|
| **Runtime topology** | Standalone `DorkNet.Server`, plus gateway-fronted service slices with monolith fallback |
|
||||||
| **Diverges from `march-2020-03-10` by** | ~170 files |
|
| **Diverges from `march-2020-03-10` by** | ~170 files |
|
||||||
|
|
||||||
Subsystems present on this branch that the March branch doesn't have:
|
Subsystems present on this branch that the March branch doesn't have:
|
||||||
@@ -84,7 +84,7 @@ categories / playlists, and starts listening. You should see
|
|||||||
`Application started.` in the log; `curl http://localhost:8080/healthz`
|
`Application started.` in the log; `curl http://localhost:8080/healthz`
|
||||||
returns 200.
|
returns 200.
|
||||||
|
|
||||||
For Docker, Dokploy, and microservices-scaffold deploy notes, see
|
For Docker, Dokploy, and microservices deploy notes, see
|
||||||
[docs/deploy.md](docs/deploy.md).
|
[docs/deploy.md](docs/deploy.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -201,10 +201,10 @@ Default `0` (off).
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `DorkNet.Server/` | ASP.NET Core backend (controllers, services, data, hubs, middleware) |
|
| `DorkNet.Server/` | ASP.NET Core backend (controllers, services, data, hubs, middleware) |
|
||||||
| `DorkNet.Models/` | DTOs shared between server and client mod |
|
| `DorkNet.Models/` | DTOs shared between server and client mod |
|
||||||
| `DorkNet.Contracts/` | Shared service names, service-map options, and health/probe contracts |
|
| `DorkNet.Contracts/` | Shared service names, route ownership, service-map options, and health/probe contracts |
|
||||||
| `DorkNet.ServiceDefaults/` | Shared microservice health, JSON, HTTP client, and service identity setup |
|
| `DorkNet.ServiceDefaults/` | Shared service health, JSON, HTTP client, service identity, and route-guard setup |
|
||||||
| `DorkNet.Gateway/` | Edge/gateway host scaffold with service-map and service-health endpoints |
|
| `DorkNet.Gateway/` | Edge reverse proxy with service-map, route-table, and service-health endpoints |
|
||||||
| `DorkNet.Services.*` | Initial identity, rooms, and notify service host scaffolds |
|
| `DorkNet.Services.*` | Gateway-fronted service hosts for identity, rooms, notify, content, social, commerce, platform, moderation, and web |
|
||||||
| `DorkNet.ClientMod/` | MelonLoader IL2CPP mod — the client patcher |
|
| `DorkNet.ClientMod/` | MelonLoader IL2CPP mod — the client patcher |
|
||||||
| `tools/` | Installers (`install-melon.ps1`, `install-legacy-client.ps1`, `remove-eac.ps1`), Cloudflare tunnel templates |
|
| `tools/` | Installers (`install-melon.ps1`, `install-legacy-client.ps1`, `remove-eac.ps1`), Cloudflare tunnel templates |
|
||||||
| `Dockerfile`, `Dockerfile.service`, `docker-compose.microservices*.yml` | Full-server, local microservices, and Dokploy microservices deployment entrypoints |
|
| `Dockerfile`, `Dockerfile.service`, `docker-compose.microservices*.yml` | Full-server, local microservices, and Dokploy microservices deployment entrypoints |
|
||||||
@@ -213,16 +213,19 @@ See [docs/architecture.md](docs/architecture.md) for the full mental
|
|||||||
model (project layout, request lifecycle, watch-mirror controller
|
model (project layout, request lifecycle, watch-mirror controller
|
||||||
pattern, where to start contributing).
|
pattern, where to start contributing).
|
||||||
|
|
||||||
### Microservices scaffold
|
### Microservices
|
||||||
|
|
||||||
`docker-compose.microservices.yml` starts the new gateway, identity,
|
`docker-compose.microservices.yml` starts the gateway, identity, rooms,
|
||||||
rooms, and notify service hosts plus Compose-managed Postgres and Redis
|
notify, content, social, commerce, platform, moderation, web, and
|
||||||
|
monolith fallback service hosts plus Compose-managed Postgres and Redis
|
||||||
for local testing. `docker-compose.microservices.dokploy.yml` is the
|
for local testing. `docker-compose.microservices.dokploy.yml` is the
|
||||||
Dokploy version; it adds a `cloudflared` sidecar so Cloudflare Tunnel
|
Dokploy version; it adds a `cloudflared` sidecar so Cloudflare Tunnel
|
||||||
can route the apex and wildcard hostnames to the gateway without
|
can route the apex and wildcard hostnames to the gateway without Dokploy
|
||||||
Dokploy domain rows. These files do **not** replace the full
|
domain rows.
|
||||||
`DorkNet.Server` runtime yet; the public API is still served by the
|
|
||||||
monolith while domains are peeled into services incrementally.
|
The gateway routes owned host/path slices to the dedicated service hosts.
|
||||||
|
The monolith fallback is still present for unknown route families, so
|
||||||
|
public client URLs remain stable while the split continues.
|
||||||
|
|
||||||
The compose file intentionally does not start object storage. Point it at
|
The compose file intentionally does not start object storage. Point it at
|
||||||
your separate S3-compatible instance:
|
your separate S3-compatible instance:
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
x-dorknet-service-env: &dorknet-service-env
|
||||||
|
DORKNET_DOMAIN: ${DORKNET_DOMAIN:?Set DORKNET_DOMAIN in Dokploy}
|
||||||
|
DORKNET_JWT_SECRET: ${DORKNET_JWT_SECRET:?Set DORKNET_JWT_SECRET in Dokploy}
|
||||||
|
Database__Provider: postgres
|
||||||
|
ConnectionStrings__Default: Host=postgres;Port=5432;Database=dorknet;Username=dorknet;Password=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy}
|
||||||
|
ConnectionStrings__Redis: redis:6379
|
||||||
|
S3__Endpoint: ${DORKNET_S3_ENDPOINT:?Set DORKNET_S3_ENDPOINT in Dokploy}
|
||||||
|
S3__AccessKey: ${DORKNET_S3_ACCESS_KEY:?Set DORKNET_S3_ACCESS_KEY in Dokploy}
|
||||||
|
S3__SecretKey: ${DORKNET_S3_SECRET_KEY:?Set DORKNET_S3_SECRET_KEY in Dokploy}
|
||||||
|
S3__Region: ${DORKNET_S3_REGION:-garage}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
gateway:
|
gateway:
|
||||||
build:
|
build:
|
||||||
@@ -11,13 +22,26 @@ services:
|
|||||||
DorkNet__Services__Identity: http://identity:8080
|
DorkNet__Services__Identity: http://identity:8080
|
||||||
DorkNet__Services__Rooms: http://rooms:8080
|
DorkNet__Services__Rooms: http://rooms:8080
|
||||||
DorkNet__Services__Notify: http://notify:8080
|
DorkNet__Services__Notify: http://notify:8080
|
||||||
DorkNet__Services__Monolith: ""
|
DorkNet__Services__Content: http://content:8080
|
||||||
|
DorkNet__Services__Social: http://social:8080
|
||||||
|
DorkNet__Services__Commerce: http://commerce:8080
|
||||||
|
DorkNet__Services__Platform: http://platform:8080
|
||||||
|
DorkNet__Services__Moderation: http://moderation:8080
|
||||||
|
DorkNet__Services__Web: http://web:8080
|
||||||
|
DorkNet__Services__Monolith: http://monolith:8080
|
||||||
expose:
|
expose:
|
||||||
- "8080"
|
- "8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- identity
|
- identity
|
||||||
- rooms
|
- rooms
|
||||||
- notify
|
- notify
|
||||||
|
- content
|
||||||
|
- social
|
||||||
|
- commerce
|
||||||
|
- platform
|
||||||
|
- moderation
|
||||||
|
- web
|
||||||
|
- monolith
|
||||||
|
|
||||||
cloudflared:
|
cloudflared:
|
||||||
image: cloudflare/cloudflared:latest
|
image: cloudflare/cloudflared:latest
|
||||||
@@ -33,6 +57,8 @@ services:
|
|||||||
args:
|
args:
|
||||||
PROJECT_PATH: DorkNet.Services.Identity/DorkNet.Services.Identity.csproj
|
PROJECT_PATH: DorkNet.Services.Identity/DorkNet.Services.Identity.csproj
|
||||||
APP_DLL: DorkNet.Services.Identity.dll
|
APP_DLL: DorkNet.Services.Identity.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
@@ -45,10 +71,7 @@ services:
|
|||||||
PROJECT_PATH: DorkNet.Services.Rooms/DorkNet.Services.Rooms.csproj
|
PROJECT_PATH: DorkNet.Services.Rooms/DorkNet.Services.Rooms.csproj
|
||||||
APP_DLL: DorkNet.Services.Rooms.dll
|
APP_DLL: DorkNet.Services.Rooms.dll
|
||||||
environment:
|
environment:
|
||||||
S3__Endpoint: ${DORKNET_S3_ENDPOINT:-}
|
<<: *dorknet-service-env
|
||||||
S3__AccessKey: ${DORKNET_S3_ACCESS_KEY:-}
|
|
||||||
S3__SecretKey: ${DORKNET_S3_SECRET_KEY:-}
|
|
||||||
S3__Region: ${DORKNET_S3_REGION:-garage}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
@@ -60,7 +83,98 @@ services:
|
|||||||
args:
|
args:
|
||||||
PROJECT_PATH: DorkNet.Services.Notify/DorkNet.Services.Notify.csproj
|
PROJECT_PATH: DorkNet.Services.Notify/DorkNet.Services.Notify.csproj
|
||||||
APP_DLL: DorkNet.Services.Notify.dll
|
APP_DLL: DorkNet.Services.Notify.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
depends_on:
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
content:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Content/DorkNet.Services.Content.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Content.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
social:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Social/DorkNet.Services.Social.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Social.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
commerce:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Commerce/DorkNet.Services.Commerce.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Commerce.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
platform:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Platform/DorkNet.Services.Platform.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Platform.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
moderation:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Moderation/DorkNet.Services.Moderation.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Moderation.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Web/DorkNet.Services.Web.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Web.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
monolith:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
@@ -68,7 +182,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: dorknet
|
POSTGRES_DB: dorknet
|
||||||
POSTGRES_USER: dorknet
|
POSTGRES_USER: dorknet
|
||||||
POSTGRES_PASSWORD: dorknet_dev
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy}
|
||||||
volumes:
|
volumes:
|
||||||
- microservices-postgres:/var/lib/postgresql/data
|
- microservices-postgres:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
x-dorknet-service-env: &dorknet-service-env
|
||||||
|
DORKNET_DOMAIN: ${DORKNET_DOMAIN:-localhost}
|
||||||
|
DORKNET_JWT_SECRET: ${DORKNET_JWT_SECRET:-dorknet-dev-jwt-secret-000000000000000000000000000000000000000000}
|
||||||
|
Database__Provider: postgres
|
||||||
|
ConnectionStrings__Default: Host=postgres;Port=5432;Database=dorknet;Username=dorknet;Password=${POSTGRES_PASSWORD:-dorknet_dev}
|
||||||
|
ConnectionStrings__Redis: redis:6379
|
||||||
|
S3__Endpoint: ${DORKNET_S3_ENDPOINT:-}
|
||||||
|
S3__AccessKey: ${DORKNET_S3_ACCESS_KEY:-}
|
||||||
|
S3__SecretKey: ${DORKNET_S3_SECRET_KEY:-}
|
||||||
|
S3__Region: ${DORKNET_S3_REGION:-garage}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
gateway:
|
gateway:
|
||||||
build:
|
build:
|
||||||
@@ -7,16 +18,30 @@ services:
|
|||||||
PROJECT_PATH: DorkNet.Gateway/DorkNet.Gateway.csproj
|
PROJECT_PATH: DorkNet.Gateway/DorkNet.Gateway.csproj
|
||||||
APP_DLL: DorkNet.Gateway.dll
|
APP_DLL: DorkNet.Gateway.dll
|
||||||
environment:
|
environment:
|
||||||
|
DORKNET_DOMAIN: ${DORKNET_DOMAIN:-localhost}
|
||||||
DorkNet__Services__Identity: http://identity:8080
|
DorkNet__Services__Identity: http://identity:8080
|
||||||
DorkNet__Services__Rooms: http://rooms:8080
|
DorkNet__Services__Rooms: http://rooms:8080
|
||||||
DorkNet__Services__Notify: http://notify:8080
|
DorkNet__Services__Notify: http://notify:8080
|
||||||
DorkNet__Services__Monolith: ""
|
DorkNet__Services__Content: http://content:8080
|
||||||
|
DorkNet__Services__Social: http://social:8080
|
||||||
|
DorkNet__Services__Commerce: http://commerce:8080
|
||||||
|
DorkNet__Services__Platform: http://platform:8080
|
||||||
|
DorkNet__Services__Moderation: http://moderation:8080
|
||||||
|
DorkNet__Services__Web: http://web:8080
|
||||||
|
DorkNet__Services__Monolith: http://monolith:8080
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- identity
|
- identity
|
||||||
- rooms
|
- rooms
|
||||||
- notify
|
- notify
|
||||||
|
- content
|
||||||
|
- social
|
||||||
|
- commerce
|
||||||
|
- platform
|
||||||
|
- moderation
|
||||||
|
- web
|
||||||
|
- monolith
|
||||||
|
|
||||||
identity:
|
identity:
|
||||||
build:
|
build:
|
||||||
@@ -25,6 +50,8 @@ services:
|
|||||||
args:
|
args:
|
||||||
PROJECT_PATH: DorkNet.Services.Identity/DorkNet.Services.Identity.csproj
|
PROJECT_PATH: DorkNet.Services.Identity/DorkNet.Services.Identity.csproj
|
||||||
APP_DLL: DorkNet.Services.Identity.dll
|
APP_DLL: DorkNet.Services.Identity.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
@@ -37,10 +64,7 @@ services:
|
|||||||
PROJECT_PATH: DorkNet.Services.Rooms/DorkNet.Services.Rooms.csproj
|
PROJECT_PATH: DorkNet.Services.Rooms/DorkNet.Services.Rooms.csproj
|
||||||
APP_DLL: DorkNet.Services.Rooms.dll
|
APP_DLL: DorkNet.Services.Rooms.dll
|
||||||
environment:
|
environment:
|
||||||
S3__Endpoint: ${DORKNET_S3_ENDPOINT:-}
|
<<: *dorknet-service-env
|
||||||
S3__AccessKey: ${DORKNET_S3_ACCESS_KEY:-}
|
|
||||||
S3__SecretKey: ${DORKNET_S3_SECRET_KEY:-}
|
|
||||||
S3__Region: ${DORKNET_S3_REGION:-garage}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
@@ -52,7 +76,98 @@ services:
|
|||||||
args:
|
args:
|
||||||
PROJECT_PATH: DorkNet.Services.Notify/DorkNet.Services.Notify.csproj
|
PROJECT_PATH: DorkNet.Services.Notify/DorkNet.Services.Notify.csproj
|
||||||
APP_DLL: DorkNet.Services.Notify.dll
|
APP_DLL: DorkNet.Services.Notify.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
depends_on:
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
content:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Content/DorkNet.Services.Content.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Content.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
social:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Social/DorkNet.Services.Social.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Social.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
commerce:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Commerce/DorkNet.Services.Commerce.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Commerce.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
platform:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Platform/DorkNet.Services.Platform.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Platform.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
moderation:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Moderation/DorkNet.Services.Moderation.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Moderation.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.service
|
||||||
|
args:
|
||||||
|
PROJECT_PATH: DorkNet.Services.Web/DorkNet.Services.Web.csproj
|
||||||
|
APP_DLL: DorkNet.Services.Web.dll
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
monolith:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *dorknet-service-env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
@@ -60,7 +175,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: dorknet
|
POSTGRES_DB: dorknet
|
||||||
POSTGRES_USER: dorknet
|
POSTGRES_USER: dorknet
|
||||||
POSTGRES_PASSWORD: dorknet_dev
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dorknet_dev}
|
||||||
volumes:
|
volumes:
|
||||||
- microservices-postgres:/var/lib/postgresql/data
|
- microservices-postgres:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
|||||||
+51
-27
@@ -18,10 +18,16 @@ DorkNet.sln
|
|||||||
├── DorkNet.Models Shared DTO + serialization types
|
├── DorkNet.Models Shared DTO + serialization types
|
||||||
├── DorkNet.Contracts Shared service contracts for the service split
|
├── DorkNet.Contracts Shared service contracts for the service split
|
||||||
├── DorkNet.ServiceDefaults Shared service-host setup
|
├── DorkNet.ServiceDefaults Shared service-host setup
|
||||||
├── DorkNet.Gateway Gateway/edge host scaffold
|
├── DorkNet.Gateway Gateway/edge reverse proxy
|
||||||
├── DorkNet.Services.Identity Identity service scaffold
|
├── DorkNet.Services.Identity Auth/accounts service host
|
||||||
├── DorkNet.Services.Rooms Rooms service scaffold
|
├── DorkNet.Services.Rooms Rooms/matchmaking service host
|
||||||
├── DorkNet.Services.Notify Notify service scaffold
|
├── DorkNet.Services.Notify Notify/chat service host
|
||||||
|
├── DorkNet.Services.Content CDN/images/photos/storage service host
|
||||||
|
├── DorkNet.Services.Social Clubs/groups/events/social service host
|
||||||
|
├── DorkNet.Services.Commerce Store/econ/inventory service host
|
||||||
|
├── DorkNet.Services.Platform Service directory/config/platform service host
|
||||||
|
├── DorkNet.Services.Moderation Bug reporting/moderation service host
|
||||||
|
├── DorkNet.Services.Web Public/admin/feed web host
|
||||||
└── DorkNet.ClientMod MelonLoader 6 IL2CPP mod — the client patcher
|
└── DorkNet.ClientMod MelonLoader 6 IL2CPP mod — the client patcher
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,12 +35,18 @@ DorkNet.sln
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 |
|
| `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 |
|
||||||
| `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 |
|
| `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 |
|
||||||
| `DorkNet.Contracts` | Service names, service-map options, health responses, probe responses | .NET 9 |
|
| `DorkNet.Contracts` | Service names, route ownership, service-map options, health responses, probe responses | .NET 9 |
|
||||||
| `DorkNet.ServiceDefaults` | Common microservice health checks, JSON defaults, HTTP clients, service identity | .NET 9 |
|
| `DorkNet.ServiceDefaults` | Common service health checks, JSON defaults, HTTP clients, service identity, route guard | .NET 9 |
|
||||||
| `DorkNet.Gateway` | Edge/gateway scaffold; exposes internal service map and service health probes | .NET 9 |
|
| `DorkNet.Gateway` | Edge reverse proxy; routes host/path slices to services and exposes service health probes | .NET 9 |
|
||||||
| `DorkNet.Services.Identity` | Future home for accounts, auth, platform login, JWT issuance | .NET 9 |
|
| `DorkNet.Services.Identity` | Accounts, auth, platform login, JWT issuance route slice | .NET 9 |
|
||||||
| `DorkNet.Services.Rooms` | Future home for rooms, room keys, matchmaking, discovery | .NET 9 |
|
| `DorkNet.Services.Rooms` | Rooms, room keys, matchmaking, discovery route slice | .NET 9 |
|
||||||
| `DorkNet.Services.Notify` | Future home for SignalR edge, notification fan-out, presence, request logs | .NET 9 |
|
| `DorkNet.Services.Notify` | SignalR edge, messages/chat, notification fan-out route slice | .NET 9 |
|
||||||
|
| `DorkNet.Services.Content` | CDN paths, image/photo APIs, uploads, S3-backed blobs | .NET 9 |
|
||||||
|
| `DorkNet.Services.Social` | Clubs, groups, announcements, player events, subscriptions | .NET 9 |
|
||||||
|
| `DorkNet.Services.Commerce` | Catalog, storefronts, econ, inventory, inventions | .NET 9 |
|
||||||
|
| `DorkNet.Services.Platform` | Service URLs, config, version checks, geo, strings, telemetry-style surfaces | .NET 9 |
|
||||||
|
| `DorkNet.Services.Moderation` | Bug reporting, player reporting, sanitize, admin API, testcase routes | .NET 9 |
|
||||||
|
| `DorkNet.Services.Web` | Public site, admin static host, feed static host, site API | .NET 9 |
|
||||||
| `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 |
|
| `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 |
|
||||||
|
|
||||||
`ClientMod` applies the client-side patches needed to point the 2020
|
`ClientMod` applies the client-side patches needed to point the 2020
|
||||||
@@ -66,31 +78,42 @@ DorkNet.Server/
|
|||||||
|
|
||||||
## Runtime topology
|
## Runtime topology
|
||||||
|
|
||||||
`DorkNet.Server` is still the only production-complete runtime. It owns
|
`DorkNet.Server` remains the compatibility fallback and full standalone
|
||||||
the full public Rec Room-compatible HTTP surface, SignalR hub, static
|
runtime. It can still run by itself, serving the full public
|
||||||
hosts, database bootstrap, Redis-backed ephemeral state, and S3-backed
|
Rec Room-compatible HTTP surface, SignalR hub, static hosts, database
|
||||||
blob storage.
|
bootstrap, Redis-backed ephemeral state, and S3-backed blob storage.
|
||||||
|
|
||||||
The microservice projects are the migration scaffold. They are wired into
|
The microservice shape is a gateway-fronted split:
|
||||||
the solution and Docker Compose, but they only expose internal health and
|
|
||||||
capability endpoints today:
|
- `DorkNet.Gateway` reverse-proxies public requests by host/path.
|
||||||
|
- `DorkNet.Services.Identity`, `DorkNet.Services.Rooms`,
|
||||||
|
`DorkNet.Services.Notify`, `DorkNet.Services.Content`,
|
||||||
|
`DorkNet.Services.Social`, `DorkNet.Services.Commerce`,
|
||||||
|
`DorkNet.Services.Platform`, `DorkNet.Services.Moderation`, and
|
||||||
|
`DorkNet.Services.Web` run the shared server stack behind a route
|
||||||
|
ownership guard. That keeps response shapes identical while moving
|
||||||
|
traffic by domain/path slice.
|
||||||
|
- `DorkNet.Server` runs as `monolith` behind the gateway for unknown
|
||||||
|
route families that are not assigned to a dedicated service yet.
|
||||||
|
|
||||||
- `DorkNet.Gateway` exposes `/internal/services` and
|
- `DorkNet.Gateway` exposes `/internal/services` and
|
||||||
`/internal/services/health`.
|
`/internal/services/health`, plus `/internal/routes` for the active
|
||||||
|
proxy table.
|
||||||
- `DorkNet.Services.Identity` exposes
|
- `DorkNet.Services.Identity` exposes
|
||||||
`/internal/identity/capabilities`.
|
`/internal/identity/capabilities`.
|
||||||
- `DorkNet.Services.Rooms` exposes `/internal/rooms/capabilities`.
|
- `DorkNet.Services.Rooms` exposes `/internal/rooms/capabilities`.
|
||||||
- `DorkNet.Services.Notify` exposes `/internal/notify/capabilities`.
|
- `DorkNet.Services.Notify` exposes `/internal/notify/capabilities`.
|
||||||
|
- The other slices expose matching `/internal/{service}/capabilities`
|
||||||
|
endpoints.
|
||||||
|
|
||||||
`docker-compose.microservices.yml` starts gateway, identity, rooms, and
|
`docker-compose.microservices.yml` starts gateway, all dedicated service
|
||||||
notify alongside Compose-managed Postgres and Redis for local testing.
|
slices, the monolith fallback, and Compose-managed Postgres and Redis for
|
||||||
|
local testing.
|
||||||
`docker-compose.microservices.dokploy.yml` is the Dokploy shape: it adds
|
`docker-compose.microservices.dokploy.yml` is the Dokploy shape: it adds
|
||||||
a `cloudflared` sidecar that joins the same Compose network and forwards
|
a `cloudflared` sidecar that joins the same Compose network and forwards
|
||||||
Cloudflare Tunnel traffic to `http://gateway:8080`. Object storage is
|
Cloudflare Tunnel traffic to `http://gateway:8080`. Object storage is
|
||||||
external S3-compatible storage configured with `DORKNET_S3_*`
|
external S3-compatible storage configured with `DORKNET_S3_*`
|
||||||
environment variables; the compose files do not run MinIO or Garage. The
|
environment variables; the compose files do not run MinIO or Garage.
|
||||||
next migration step is to move one public route group at a time behind
|
|
||||||
the gateway while preserving existing URLs and response shapes.
|
|
||||||
|
|
||||||
### Controllers/ groupings
|
### Controllers/ groupings
|
||||||
|
|
||||||
@@ -277,10 +300,11 @@ worse.
|
|||||||
intentional — the decompiled source is provenance only, not
|
intentional — the decompiled source is provenance only, not
|
||||||
shippable. Comments give a future contributor a breadcrumb to
|
shippable. Comments give a future contributor a breadcrumb to
|
||||||
re-derive the wire shape if the decompile changes.
|
re-derive the wire shape if the decompile changes.
|
||||||
- **Microservices are scaffolded, not fully cut over.** The gateway and
|
- **Microservices use shared server code during extraction.** The
|
||||||
service hosts compile and run, but the public API still lives in
|
dedicated service hosts run the proven controller/service stack behind
|
||||||
`DorkNet.Server`. Move endpoints by domain slice, not by copying random
|
route guards instead of copying controllers into new projects. This
|
||||||
controllers.
|
keeps the client contract stable while route families are peeled away
|
||||||
|
from the monolith fallback.
|
||||||
- **Two-provider EF.** SQLite and Postgres each have their own
|
- **Two-provider EF.** SQLite and Postgres each have their own
|
||||||
EnsureCreated/Migrate dance because some Postgres-only column types
|
EnsureCreated/Migrate dance because some Postgres-only column types
|
||||||
drift the model snapshot. The split is annoying but lets one
|
drift the model snapshot. The split is annoying but lets one
|
||||||
|
|||||||
+34
-25
@@ -5,13 +5,12 @@ This branch has two deployable shapes:
|
|||||||
- **Production/full API:** build the root `Dockerfile`, which runs
|
- **Production/full API:** build the root `Dockerfile`, which runs
|
||||||
`DorkNet.Server`. This is still the runtime that serves every public
|
`DorkNet.Server`. This is still the runtime that serves every public
|
||||||
Rec Room-compatible endpoint.
|
Rec Room-compatible endpoint.
|
||||||
- **Microservices scaffold:** deploy `docker-compose.microservices.yml`
|
- **Microservices:** deploy `docker-compose.microservices.yml`
|
||||||
for local testing or `docker-compose.microservices.dokploy.yml` on
|
for local testing or `docker-compose.microservices.dokploy.yml` on
|
||||||
Dokploy. Both start `DorkNet.Gateway`,
|
Dokploy. Both start `DorkNet.Gateway`, the dedicated service slices,
|
||||||
`DorkNet.Services.Identity`, `DorkNet.Services.Rooms`,
|
the `DorkNet.Server` monolith fallback, Postgres, and Redis. The
|
||||||
`DorkNet.Services.Notify`, Postgres, and Redis. These hosts currently
|
gateway routes public traffic to owned service slices and sends
|
||||||
expose internal health/capability endpoints only; public routes move
|
unknown route families to the fallback.
|
||||||
behind the gateway incrementally.
|
|
||||||
|
|
||||||
Object storage is always a **separate S3-compatible instance**. The
|
Object storage is always a **separate S3-compatible instance**. The
|
||||||
microservices compose files do not start MinIO/Garage.
|
microservices compose files do not start MinIO/Garage.
|
||||||
@@ -120,10 +119,9 @@ service URLs returned to the client by `ConfigService`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dokploy: microservices scaffold
|
## Dokploy: microservices
|
||||||
|
|
||||||
Use this to run the new gateway/service-host layout. It is not yet a
|
Use this to run the gateway/service-host layout.
|
||||||
replacement for the full public API.
|
|
||||||
|
|
||||||
Create a Dokploy **Compose** service from one of these files:
|
Create a Dokploy **Compose** service from one of these files:
|
||||||
|
|
||||||
@@ -136,10 +134,17 @@ Both compose files provide:
|
|||||||
|
|
||||||
| Service | Purpose |
|
| Service | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `gateway` | Edge host scaffold; service map and service health probes |
|
| `gateway` | Edge reverse proxy; service map, route table, and service health probes |
|
||||||
| `identity` | Future identity/auth/accounts service |
|
| `identity` | Auth, accounts, and platform-login route slice |
|
||||||
| `rooms` | Future rooms/matchmaking/discovery service |
|
| `rooms` | Rooms, room keys, playlists, matchmaking, and discovery route slice |
|
||||||
| `notify` | Future notification/presence service |
|
| `notify` | Notify, messages/chat, SignalR, and notification route slice |
|
||||||
|
| `content` | CDN paths, uploads, images, photos, room blobs, and storage route slice |
|
||||||
|
| `social` | Clubs, groups, announcements, player events, subscriptions route slice |
|
||||||
|
| `commerce` | Catalog, storefronts, econ, inventory, inventions route slice |
|
||||||
|
| `platform` | Service directory, config, version checks, geo, strings, and platform route slice |
|
||||||
|
| `moderation` | Bug reporting, player reporting, sanitize, admin API, and testcase route slice |
|
||||||
|
| `web` | Apex/www/admin/feed static hosts and site API route slice |
|
||||||
|
| `monolith` | Fallback full server for unknown route families not assigned to a service yet |
|
||||||
| `postgres` | Internal Postgres for the service network |
|
| `postgres` | Internal Postgres for the service network |
|
||||||
| `redis` | Internal Redis for ephemeral state and fan-out |
|
| `redis` | Internal Redis for ephemeral state and fan-out |
|
||||||
| `cloudflared` | Cloudflare Tunnel sidecar; Dokploy file only |
|
| `cloudflared` | Cloudflare Tunnel sidecar; Dokploy file only |
|
||||||
@@ -155,6 +160,8 @@ Set these Dokploy Compose environment variables:
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
DORKNET_DOMAIN=yourdomain.com
|
DORKNET_DOMAIN=yourdomain.com
|
||||||
|
DORKNET_JWT_SECRET=replace-with-at-least-64-random-characters
|
||||||
|
POSTGRES_PASSWORD=replace-with-a-random-db-password
|
||||||
CLOUDFLARE_TUNNEL_TOKEN=<token from Cloudflare Zero Trust tunnel>
|
CLOUDFLARE_TUNNEL_TOKEN=<token from Cloudflare Zero Trust tunnel>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -165,15 +172,16 @@ yourdomain.com -> http://gateway:8080
|
|||||||
*.yourdomain.com -> http://gateway:8080
|
*.yourdomain.com -> http://gateway:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
The wildcard covers `api`, `auth`, `rooms`, `notify`, and the other
|
The wildcard covers `api`, `auth`, `rooms`, `notify`, `cdn`, `clubs`,
|
||||||
client-facing subdomains. The apex rule is separate because wildcard DNS
|
`commerce`, and the other client-facing subdomains. The apex rule is
|
||||||
does not cover the root domain. Let Cloudflare create the tunnel DNS
|
separate because wildcard DNS does not cover the root domain. Let
|
||||||
records, or create CNAME records to the tunnel target Cloudflare gives
|
Cloudflare create the tunnel DNS records, or create CNAME records to the
|
||||||
you.
|
tunnel target Cloudflare gives you.
|
||||||
|
|
||||||
When the gateway starts carrying the public API, every public DorkNet
|
Every public DorkNet domain listed above should point to the gateway.
|
||||||
domain listed above should point to `gateway`. Until then, keep the real
|
The gateway preserves the original Host header so service code still sees
|
||||||
player-facing domains on the full `DorkNet.Server` application.
|
`auth.yourdomain.com`, `rooms.yourdomain.com`, and the other client
|
||||||
|
hosts.
|
||||||
|
|
||||||
Set these Compose environment variables in Dokploy:
|
Set these Compose environment variables in Dokploy:
|
||||||
|
|
||||||
@@ -194,11 +202,12 @@ Useful smoke checks after deploy:
|
|||||||
curl https://api.yourdomain.com/healthz
|
curl https://api.yourdomain.com/healthz
|
||||||
curl https://api.yourdomain.com/internal/services
|
curl https://api.yourdomain.com/internal/services
|
||||||
curl https://api.yourdomain.com/internal/services/health
|
curl https://api.yourdomain.com/internal/services/health
|
||||||
|
curl https://api.yourdomain.com/internal/routes
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected during scaffold-only deploys: `/internal/services/health`
|
`/internal/routes` shows which host/path patterns route to each service
|
||||||
reports gateway-visible status for identity, rooms, and notify. It does
|
slice or the monolith fallback. `/internal/services/health` reports
|
||||||
not prove the full game API has moved yet.
|
gateway-visible health for every backend service.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -235,7 +244,7 @@ S3__TimeoutSeconds=300
|
|||||||
```
|
```
|
||||||
|
|
||||||
For the microservices compose file, use the `DORKNET_S3_*` equivalents;
|
For the microservices compose file, use the `DORKNET_S3_*` equivalents;
|
||||||
Compose maps them into `S3__*` for the rooms service.
|
Compose maps them into `S3__*` for the service containers.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -87,8 +87,9 @@ visitor → https://api.localhost/api/versioncheck/v4
|
|||||||
→ Cloudflare edge (real LE cert, validated)
|
→ Cloudflare edge (real LE cert, validated)
|
||||||
→ cloudflared tunnel
|
→ cloudflared tunnel
|
||||||
→ cloudflared (running on your machine)
|
→ cloudflared (running on your machine)
|
||||||
→ https://localhost:443 (Host: api.localhost, mkcert cert)
|
→ http://gateway:8080 (Host preserved)
|
||||||
→ DorkNet.Server HostFiltering + subdomain-aware handlers
|
→ DorkNet.Gateway reverse proxy
|
||||||
|
→ dedicated service slice / monolith fallback
|
||||||
```
|
```
|
||||||
|
|
||||||
The leg from cloudflared → localhost has `noTLSVerify: true` because
|
The leg from cloudflared → localhost has `noTLSVerify: true` because
|
||||||
|
|||||||
Reference in New Issue
Block a user