using Google.Protobuf.WellKnownTypes; using Grpc.Core; using MariesWonderland.Data; using MariesWonderland.Extensions; using MariesWonderland.Models.Entities; using MariesWonderland.Proto.Data; using MariesWonderland.Proto.User; namespace MariesWonderland.Services; 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 GetAndroidArgs(GetAndroidArgsRequest request, ServerCallContext context) { return Task.FromResult(new GetAndroidArgsResponse { ApiKey = "1234567890", Nonce = "Mama" }); } public override Task Auth(AuthUserRequest request, ServerCallContext context) { var (userId, isNew) = _store.RegisterOrGetUser(request.Uuid); var userDb = _store.GetOrCreate(userId); var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var deviceRecord = userDb.EntitySUserDevice.FirstOrDefault(d => d.UserId == userId); if (deviceRecord == null) { deviceRecord = new EntitySUserDevice { UserId = userId }; userDb.EntitySUserDevice.Add(deviceRecord); } deviceRecord.Uuid = request.Uuid; deviceRecord.AdvertisingId = request.AdvertisingId; deviceRecord.IsTrackingEnabled = request.IsTrackingEnabled; deviceRecord.IdentifierForVendor = request.DeviceInherent?.IdentifierForVendor ?? ""; deviceRecord.DeviceToken = request.DeviceInherent?.DeviceToken ?? ""; deviceRecord.MacAddress = request.DeviceInherent?.MacAddress ?? ""; deviceRecord.RegistrationId = request.DeviceInherent?.RegistrationId ?? ""; deviceRecord.LastAuthAt = nowMs; if (isNew) deviceRecord.RegisteredAt = nowMs; var session = _store.CreateSession(userId, TimeSpan.FromHours(24)); var diffData = UserDataDiffBuilder.FullDiff(userDb); var response = new AuthUserResponse { SessionKey = session.SessionKey, ExpireDatetime = Timestamp.FromDateTime(session.ExpiresAt), Signature = request.Signature, UserId = userId }; foreach (var (k, v) in diffData) response.DiffUserData[k] = v; return Task.FromResult(response); } public override Task CheckTransferSetting(Empty request, ServerCallContext context) { return Task.FromResult(new CheckTransferSettingResponse()); } public override Task GameStart(Empty request, ServerCallContext context) { long userId = context.GetUserId(); DarkUserMemoryDatabase userDb = _store.GetOrCreate(userId); Dictionary before = UserDataDiffBuilder.Snapshot(userDb); EntityIUser user = userDb.EntityIUser.FirstOrDefault(u => u.UserId == userId) ?? AddEntity(userDb.EntityIUser, new EntityIUser { UserId = userId }); user.GameStartDatetime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); GameStartResponse response = new(); foreach (var (k, v) in UserDataDiffBuilder.Delta(before, userDb)) response.DiffUserData[k] = v; return Task.FromResult(response); } public override Task GetBackupToken(GetBackupTokenRequest request, ServerCallContext context) { return Task.FromResult(new GetBackupTokenResponse { BackupToken = "1234567890" }); } public override Task GetBirthYearMonth(Empty request, ServerCallContext context) { return Task.FromResult(new GetBirthYearMonthResponse()); } public override Task GetChargeMoney(Empty request, ServerCallContext context) { return Task.FromResult(new GetChargeMoneyResponse()); } public override Task GetUserGamePlayNote(GetUserGamePlayNoteRequest request, ServerCallContext context) { return Task.FromResult(new GetUserGamePlayNoteResponse()); } public override Task GetUserProfile(GetUserProfileRequest request, ServerCallContext context) { return Task.FromResult(new GetUserProfileResponse()); } public override Task RegisterUser(RegisterUserRequest request, ServerCallContext context) { // RegisterUser is the very first API called on a fresh install. It registers the device UUID // and assigns a permanent userId (random 19-digit number). Subsequent launches call Auth instead. var (userId, _) = _store.RegisterOrGetUser(request.Uuid); DarkUserMemoryDatabase userDb = _store.GetOrCreate(userId); Dictionary diffData = UserDataDiffBuilder.FullDiff(userDb); RegisterUserResponse response = new() { UserId = userId, Signature = $"sig_{userId}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}" }; foreach (var (k, v) in diffData) response.DiffUserData[k] = v; return Task.FromResult(response); } public override Task SetAppleAccount(SetAppleAccountRequest request, ServerCallContext context) { return Task.FromResult(new SetAppleAccountResponse()); } public override Task SetBirthYearMonth(SetBirthYearMonthRequest request, ServerCallContext context) { return Task.FromResult(new SetBirthYearMonthResponse()); } public override Task SetFacebookAccount(SetFacebookAccountRequest request, ServerCallContext context) { return Task.FromResult(new SetFacebookAccountResponse()); } public override Task SetUserFavoriteCostumeId(SetUserFavoriteCostumeIdRequest request, ServerCallContext context) { return Task.FromResult(new SetUserFavoriteCostumeIdResponse()); } public override Task SetUserMessage(SetUserMessageRequest request, ServerCallContext context) { return Task.FromResult(new SetUserMessageResponse()); } public override Task SetUserName(SetUserNameRequest request, ServerCallContext context) { long userId = context.GetUserId(); DarkUserMemoryDatabase userDb = _store.GetOrCreate(userId); Dictionary before = UserDataDiffBuilder.Snapshot(userDb); EntityIUserProfile profile = userDb.EntityIUserProfile.FirstOrDefault(p => p.UserId == userId) ?? AddEntity(userDb.EntityIUserProfile, new EntityIUserProfile { UserId = userId }); profile.Name = request.Name; profile.NameUpdateDatetime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); SetUserNameResponse response = new(); foreach (var (k, v) in UserDataDiffBuilder.Delta(before, userDb)) response.DiffUserData[k] = v; return Task.FromResult(response); } /// Adds an entity to a list and returns it (convenience for inline new-entity seeding). private static T AddEntity(List list, T entity) { list.Add(entity); return entity; } public override Task SetUserSetting(SetUserSettingRequest request, ServerCallContext context) { return Task.FromResult(new SetUserSettingResponse()); } public override Task TransferUser(TransferUserRequest request, ServerCallContext context) { 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 TransferUserByApple(TransferUserByAppleRequest request, ServerCallContext context) { return Task.FromResult(new TransferUserByAppleResponse()); } public override Task TransferUserByFacebook(TransferUserByFacebookRequest request, ServerCallContext context) { return Task.FromResult(new TransferUserByFacebookResponse()); } public override Task UnsetFacebookAccount(Empty request, ServerCallContext context) { return Task.FromResult(new UnsetFacebookAccountResponse()); } }