mirror of
https://github.com/BillyCool/MariesWonderland.git
synced 2026-03-21 22:42:19 +01:00
Add logging interceptor and implement basic user transfer
This commit is contained in:
52
src/Data/UserDataSeeder.cs
Normal file
52
src/Data/UserDataSeeder.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using MariesWonderland.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MariesWonderland.Data;
|
||||
|
||||
public class UserDataSeeder(IOptions<ServerOptions> options)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private static readonly Type DbType = typeof(DarkUserMemoryDatabase);
|
||||
|
||||
/// <summary>
|
||||
/// Reads all Entity*Table.json files from the configured UserDataPath and populates
|
||||
/// a new DarkUserMemoryDatabase. Returns an empty database if no files are found.
|
||||
/// </summary>
|
||||
public DarkUserMemoryDatabase LoadFromFiles()
|
||||
{
|
||||
DarkUserMemoryDatabase db = new();
|
||||
|
||||
string rawPath = options.Value.Data.UserDataPath;
|
||||
if (string.IsNullOrEmpty(rawPath))
|
||||
return db;
|
||||
|
||||
string dataPath = Path.IsPathRooted(rawPath)
|
||||
? rawPath
|
||||
: Path.Combine(AppContext.BaseDirectory, rawPath);
|
||||
|
||||
if (!Directory.Exists(dataPath))
|
||||
return db;
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(dataPath, "Entity*Table.json"))
|
||||
{
|
||||
// "EntityIUserCostumeTable.json" → "EntityIUserCostume"
|
||||
string fileName = Path.GetFileName(file);
|
||||
string propName = fileName[..^"Table.json".Length];
|
||||
|
||||
var prop = DbType.GetProperty(propName);
|
||||
if (prop == null) continue;
|
||||
|
||||
string json = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize(json, prop.PropertyType, JsonOptions);
|
||||
if (list != null)
|
||||
prop.SetValue(db, list);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,22 @@ public class UserDataStore
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a pre-seeded database under a UUID. If the UUID is already mapped,
|
||||
/// returns the existing userId without overwriting. Otherwise stores the seeded
|
||||
/// database and maps the UUID to the userId found inside it.
|
||||
/// </summary>
|
||||
public long SeedUserFromDatabase(string uuid, DarkUserMemoryDatabase seededDb)
|
||||
{
|
||||
if (_uuidToUserId.TryGetValue(uuid, out var existingId))
|
||||
return existingId;
|
||||
|
||||
long userId = seededDb.EntityIUser.FirstOrDefault()?.UserId ?? GenerateUserId();
|
||||
_uuidToUserId[uuid] = userId;
|
||||
_users[userId] = seededDb;
|
||||
return userId;
|
||||
}
|
||||
|
||||
public bool TryGet(long userId, out DarkUserMemoryDatabase db)
|
||||
=> _users.TryGetValue(userId, out db!);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public static class ServiceExtensions
|
||||
var masterDb = MasterDataLoader.Load(masterDataPath);
|
||||
services.AddSingleton(masterDb);
|
||||
services.AddSingleton<UserDataStore>();
|
||||
services.AddSingleton<UserDataSeeder>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
97
src/Interceptors/LoggingInterceptor.cs
Normal file
97
src/Interceptors/LoggingInterceptor.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Google.Protobuf;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using MariesWonderland.Proto.Data;
|
||||
using MariesWonderland.Proto.User;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace MariesWonderland.Interceptors;
|
||||
|
||||
public class LoggingInterceptor(ILogger<LoggingInterceptor> logger) : Interceptor
|
||||
{
|
||||
private static readonly List<string> ExcludedPropertyNames = [
|
||||
nameof(AuthUserResponse.DiffUserData),
|
||||
nameof(TableNameList.TableName),
|
||||
nameof(UserDataGetResponse.UserDataJson)
|
||||
];
|
||||
|
||||
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ServerCallContext context,
|
||||
UnaryServerMethod<TRequest, TResponse> continuation)
|
||||
{
|
||||
string methodName = context.Method;
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug("[GRPC] >> {Method} (request)", methodName);
|
||||
logger.LogDebug("{Json}", Serialize(request));
|
||||
}
|
||||
|
||||
TResponse response = await continuation(request, context);
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug("[GRPC] << {Method} (response)", methodName);
|
||||
logger.LogDebug("{Json}", Serialize(response));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static string Serialize(object obj)
|
||||
{
|
||||
string json = obj is IMessage message
|
||||
? JsonFormatter.Default.Format(message)
|
||||
: JsonSerializer.Serialize(obj);
|
||||
|
||||
return RemovePropertiesFromJson(json);
|
||||
}
|
||||
|
||||
private static string RemovePropertiesFromJson(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonNode? node = JsonNode.Parse(json);
|
||||
if (node is null) return json;
|
||||
|
||||
RemoveProperties(node);
|
||||
|
||||
// Use compact JSON (no indentation) to match prior logging style.
|
||||
return node.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If parsing fails for any reason, return the original JSON so logging still occurs.
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveProperties(JsonNode? node)
|
||||
{
|
||||
if (node is JsonObject obj)
|
||||
{
|
||||
// Iterate over a snapshot of the keys because we'll be mutating the object.
|
||||
foreach (var key in obj.Select(kvp => kvp.Key).ToList())
|
||||
{
|
||||
if (ExcludedPropertyNames.Contains(key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
obj.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveProperties(obj[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node is JsonArray arr)
|
||||
{
|
||||
foreach (var item in arr)
|
||||
{
|
||||
RemoveProperties(item);
|
||||
}
|
||||
}
|
||||
// Primitives (JsonValue) do not contain nested properties; nothing to do.
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using MariesWonderland.Configuration;
|
||||
using MariesWonderland.Data;
|
||||
using MariesWonderland.Extensions;
|
||||
using MariesWonderland.Interceptors;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -23,7 +24,8 @@ public static class Program
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddGrpc();
|
||||
builder.Services.AddSingleton<LoggingInterceptor>();
|
||||
builder.Services.AddGrpc(options => options.Interceptors.Add<LoggingInterceptor>());
|
||||
builder.Services.AddServerOptions(builder.Configuration);
|
||||
builder.Services.AddDataStores(builder.Configuration);
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ using MariesWonderland.Proto.User;
|
||||
|
||||
namespace MariesWonderland.Services;
|
||||
|
||||
public class UserService(UserDataStore store) : MariesWonderland.Proto.User.UserService.UserServiceBase
|
||||
public class UserService(UserDataStore store, UserDataSeeder seeder) : MariesWonderland.Proto.User.UserService.UserServiceBase
|
||||
{
|
||||
private readonly UserDataStore _store = store;
|
||||
private readonly UserDataSeeder _seeder = seeder;
|
||||
|
||||
public override Task<GetAndroidArgsResponse> GetAndroidArgs(GetAndroidArgsRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -183,7 +184,15 @@ public class UserService(UserDataStore store) : MariesWonderland.Proto.User.User
|
||||
|
||||
public override Task<TransferUserResponse> TransferUser(TransferUserRequest request, ServerCallContext context)
|
||||
{
|
||||
return Task.FromResult(new TransferUserResponse());
|
||||
DarkUserMemoryDatabase seededDb = _seeder.LoadFromFiles();
|
||||
long userId = _store.SeedUserFromDatabase(request.Uuid, seededDb);
|
||||
|
||||
TransferUserResponse response = new()
|
||||
{
|
||||
UserId = userId,
|
||||
Signature = $"sig_{userId}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public override Task<TransferUserByAppleResponse> TransferUserByApple(TransferUserByAppleRequest request, ServerCallContext context)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"MariesWonderland": "Debug"
|
||||
}
|
||||
},
|
||||
"Server": {
|
||||
|
||||
Reference in New Issue
Block a user