mirror of
https://github.com/BillyCool/MariesWonderland.git
synced 2026-03-22 06:52:24 +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;
|
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)
|
public bool TryGet(long userId, out DarkUserMemoryDatabase db)
|
||||||
=> _users.TryGetValue(userId, out db!);
|
=> _users.TryGetValue(userId, out db!);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public static class ServiceExtensions
|
|||||||
var masterDb = MasterDataLoader.Load(masterDataPath);
|
var masterDb = MasterDataLoader.Load(masterDataPath);
|
||||||
services.AddSingleton(masterDb);
|
services.AddSingleton(masterDb);
|
||||||
services.AddSingleton<UserDataStore>();
|
services.AddSingleton<UserDataStore>();
|
||||||
|
services.AddSingleton<UserDataSeeder>();
|
||||||
|
|
||||||
return services;
|
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.Configuration;
|
||||||
using MariesWonderland.Data;
|
using MariesWonderland.Data;
|
||||||
using MariesWonderland.Extensions;
|
using MariesWonderland.Extensions;
|
||||||
|
using MariesWonderland.Interceptors;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.Extensions.Options;
|
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.AddServerOptions(builder.Configuration);
|
||||||
builder.Services.AddDataStores(builder.Configuration);
|
builder.Services.AddDataStores(builder.Configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ using MariesWonderland.Proto.User;
|
|||||||
|
|
||||||
namespace MariesWonderland.Services;
|
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 UserDataStore _store = store;
|
||||||
|
private readonly UserDataSeeder _seeder = seeder;
|
||||||
|
|
||||||
public override Task<GetAndroidArgsResponse> GetAndroidArgs(GetAndroidArgsRequest request, ServerCallContext context)
|
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)
|
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)
|
public override Task<TransferUserByAppleResponse> TransferUserByApple(TransferUserByAppleRequest request, ServerCallContext context)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"MariesWonderland": "Debug"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Server": {
|
"Server": {
|
||||||
|
|||||||
Reference in New Issue
Block a user