From f70ef0bf97540bf018f68e3f664b1542bf6b1b71 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 20 Jul 2025 02:35:00 +0800 Subject: [PATCH] :bug: Fix compile errors --- DysonNetwork.Pass/Team/Team.cs | 7 +- .../Chat/Realtime/CloudflareService.cs | 308 ------------------ .../Chat/Realtime/IRealtimeService.cs | 11 +- .../Chat/Realtime/LiveKitService.cs | 5 +- .../Chat/Realtime/RealtimeStatusService.cs | 91 ------ .../Startup/ServiceCollectionExtensions.cs | 5 +- 6 files changed, 8 insertions(+), 419 deletions(-) delete mode 100644 DysonNetwork.Sphere/Chat/Realtime/CloudflareService.cs delete mode 100644 DysonNetwork.Sphere/Chat/Realtime/RealtimeStatusService.cs diff --git a/DysonNetwork.Pass/Team/Team.cs b/DysonNetwork.Pass/Team/Team.cs index 2947bdf..a6cf330 100644 --- a/DysonNetwork.Pass/Team/Team.cs +++ b/DysonNetwork.Pass/Team/Team.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -21,14 +22,10 @@ public class Team : ModelBase, IIdentifiedResource [MaxLength(256)] public string Nick { get; set; } = string.Empty; [MaxLength(4096)] public string? Bio { get; set; } - // Outdated fields, for backward compability - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } - [Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; } + [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } [JsonIgnore] public ICollection Members { get; set; } = new List(); [JsonIgnore] public ICollection Features { get; set; } = new List(); diff --git a/DysonNetwork.Sphere/Chat/Realtime/CloudflareService.cs b/DysonNetwork.Sphere/Chat/Realtime/CloudflareService.cs deleted file mode 100644 index edd9fda..0000000 --- a/DysonNetwork.Sphere/Chat/Realtime/CloudflareService.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using Microsoft.IdentityModel.Tokens; -using System.Text.Json.Serialization; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Chat.Realtime; - -public class CloudflareRealtimeService : IRealtimeService -{ - private readonly AppDatabase _db; - private readonly HttpClient _httpClient; - private readonly IConfiguration _configuration; - private RSA? _publicKey; - - public CloudflareRealtimeService( - AppDatabase db, - HttpClient httpClient, - IConfiguration configuration - ) - { - _db = db; - _httpClient = httpClient; - _configuration = configuration; - var apiKey = _configuration["Realtime:Cloudflare:ApiKey"]; - var apiSecret = _configuration["Realtime:Cloudflare:ApiSecret"]!; - var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{apiKey}:{apiSecret}")); - _httpClient.BaseAddress = new Uri("https://rtk.realtime.cloudflare.com/v2/"); - _httpClient.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials); - } - - public string ProviderName => "Cloudflare"; - - public async Task CreateSessionAsync(Guid roomId, Dictionary metadata) - { - var roomName = $"Room Call #{roomId.ToString().Replace("-", "")}"; - var requestBody = new - { - title = roomName, - preferred_region = _configuration["Realtime:Cloudflare:PreferredRegion"] - }; - - var content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync("meetings", content); - - response.EnsureSuccessStatusCode(); - - var responseContent = await response.Content.ReadAsStringAsync(); - var meetingResponse = JsonSerializer.Deserialize(responseContent); - if (meetingResponse is null) throw new Exception("Failed to create meeting with cloudflare"); - - return new RealtimeSessionConfig - { - SessionId = meetingResponse.Data.Id, - Parameters = new Dictionary - { - { "meetingId", meetingResponse.Data.Id } - } - }; - } - - public async Task EndSessionAsync(string sessionId, RealtimeSessionConfig config) - { - var requestBody = new - { - status = "INACTIVE" - }; - - var content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"); - var response = await _httpClient.PatchAsync($"sessions/{sessionId}", content); - - response.EnsureSuccessStatusCode(); - } - - public string GetUserToken(Account.Account account, string sessionId, bool isAdmin = false) - { - return GetUserTokenAsync(account, sessionId, isAdmin).GetAwaiter().GetResult(); - } - - public async Task GetUserTokenAsync(Account.Account account, string sessionId, bool isAdmin = false) - { - try - { - // First try to get the participant by their custom ID - var participantCheckResponse = await _httpClient - .GetAsync($"meetings/{sessionId}/participants/{account.Id}"); - - if (participantCheckResponse.IsSuccessStatusCode) - { - // Participant exists, get a new token - var tokenResponse = await _httpClient - .PostAsync($"meetings/{sessionId}/participants/{account.Id}/token", null); - tokenResponse.EnsureSuccessStatusCode(); - var tokenContent = await tokenResponse.Content.ReadAsStringAsync(); - var tokenData = JsonSerializer.Deserialize>(tokenContent); - if (tokenData == null || !tokenData.Success) - { - throw new Exception("Failed to get participant token"); - } - - return tokenData.Data?.Token ?? throw new Exception("Token is null"); - } - - // Participant doesn't exist, create a new one - var baseUrl = _configuration["BaseUrl"]; - var requestBody = new - { - name = "@" + account.Name, - picture = account.Profile.Picture is not null - ? $"{baseUrl}/api/files/{account.Profile.Picture.Id}" - : null, - preset_name = isAdmin ? "group_call_host" : "group_call_participant", - custom_participant_id = account.Id.ToString() - }; - - var content = new StringContent( - JsonSerializer.Serialize(requestBody), - Encoding.UTF8, - "application/json" - ); - - var createResponse = await _httpClient.PostAsync($"meetings/{sessionId}/participants", content); - createResponse.EnsureSuccessStatusCode(); - - var responseContent = await createResponse.Content.ReadAsStringAsync(); - var participantData = JsonSerializer.Deserialize>(responseContent); - if (participantData == null || !participantData.Success) - { - throw new Exception("Failed to create participant"); - } - - return participantData.Data?.Token ?? throw new Exception("Token is null"); - } - catch (Exception ex) - { - // Log the error or handle it appropriately - throw new Exception($"Failed to get or create participant: {ex.Message}", ex); - } - } - - public async Task ReceiveWebhook(string body, string authHeader) - { - if (string.IsNullOrEmpty(authHeader)) - { - throw new ArgumentException("Auth header is missing"); - } - - if (_publicKey == null) - { - await GetPublicKeyAsync(); - } - - var signature = authHeader.Replace("Signature ", ""); - var bodyBytes = Encoding.UTF8.GetBytes(body); - var signatureBytes = Convert.FromBase64String(signature); - - if (!(_publicKey?.VerifyData(bodyBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1) ?? - false)) - { - throw new SecurityTokenException("Webhook signature validation failed"); - } - - // Process the webhook event - var evt = JsonSerializer.Deserialize(body); - if (evt is null) return; - - switch (evt.Type) - { - case "meeting.ended": - var now = SystemClock.Instance.GetCurrentInstant(); - await _db.ChatRealtimeCall - .Where(c => c.SessionId == evt.Event.Meeting.Id) - .ExecuteUpdateAsync(s => s.SetProperty(p => p.EndedAt, now) - ); - break; - } - } - - private class WebhooksConfig - { - [JsonPropertyName("keys")] public List Keys { get; set; } = new List(); - } - - private class WebhookKey - { - [JsonPropertyName("publicKeyPem")] public string PublicKeyPem { get; set; } = string.Empty; - } - - private async Task GetPublicKeyAsync() - { - var response = await _httpClient.GetAsync("https://rtk.realtime.cloudflare.com/.well-known/webhooks.json"); - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - var webhooksConfig = JsonSerializer.Deserialize(content); - var publicKeyPem = webhooksConfig?.Keys.FirstOrDefault()?.PublicKeyPem; - - if (string.IsNullOrEmpty(publicKeyPem)) - { - throw new InvalidOperationException("Public key not found in webhooks configuration."); - } - - _publicKey = RSA.Create(); - _publicKey.ImportFromPem(publicKeyPem); - } - - private class CfMeetingResponse - { - [JsonPropertyName("data")] public CfMeetingData Data { get; set; } = new(); - } - - private class CfMeetingData - { - [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; - [JsonPropertyName("roomName")] public string RoomName { get; set; } = string.Empty; - [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; - [JsonPropertyName("status")] public string Status { get; set; } = string.Empty; - [JsonPropertyName("createdAt")] public DateTime CreatedAt { get; set; } - [JsonPropertyName("updatedAt")] public DateTime UpdatedAt { get; set; } - } - - private class CfParticipant - { - public string Id { get; set; } = string.Empty; - public string Token { get; set; } = string.Empty; - public string CustomParticipantId { get; set; } = string.Empty; - } - - public class CfResponse - { - [JsonPropertyName("success")] public bool Success { get; set; } - - [JsonPropertyName("data")] public T? Data { get; set; } - } - - public class CfTokenResponse - { - [JsonPropertyName("token")] public string Token { get; set; } = string.Empty; - } - - public class CfParticipantResponse - { - [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; - - [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - [JsonPropertyName("customUserId")] public string CustomUserId { get; set; } = string.Empty; - - [JsonPropertyName("presetName")] public string PresetName { get; set; } = string.Empty; - - [JsonPropertyName("isActive")] public bool IsActive { get; set; } - - [JsonPropertyName("token")] public string Token { get; set; } = string.Empty; - } - - public class CfWebhookEvent - { - [JsonPropertyName("id")] public string Id { get; set; } - - [JsonPropertyName("type")] public string Type { get; set; } - - [JsonPropertyName("webhookId")] public string WebhookId { get; set; } - - [JsonPropertyName("timestamp")] public DateTime Timestamp { get; set; } - - [JsonPropertyName("event")] public EventData Event { get; set; } - } - - public class EventData - { - [JsonPropertyName("meeting")] public MeetingData Meeting { get; set; } - - [JsonPropertyName("participant")] public ParticipantData Participant { get; set; } - } - - public class MeetingData - { - [JsonPropertyName("id")] public string Id { get; set; } - - [JsonPropertyName("roomName")] public string RoomName { get; set; } - - [JsonPropertyName("title")] public string Title { get; set; } - - [JsonPropertyName("status")] public string Status { get; set; } - - [JsonPropertyName("createdAt")] public DateTime CreatedAt { get; set; } - - [JsonPropertyName("updatedAt")] public DateTime UpdatedAt { get; set; } - } - - public class ParticipantData - { - [JsonPropertyName("id")] public string Id { get; set; } - - [JsonPropertyName("userId")] public string UserId { get; set; } - - [JsonPropertyName("customParticipantId")] - public string CustomParticipantId { get; set; } - - [JsonPropertyName("presetName")] public string PresetName { get; set; } - - [JsonPropertyName("joinedAt")] public DateTime JoinedAt { get; set; } - - [JsonPropertyName("leftAt")] public DateTime? LeftAt { get; set; } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs index 9b22229..1d08b18 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs @@ -31,7 +31,7 @@ public interface IRealtimeService Task EndSessionAsync(string sessionId, RealtimeSessionConfig config); /// - /// Gets a token for user to join the session (synchronous version for backward compatibility) + /// Gets a token for user to join the session /// /// The user identifier /// The session identifier @@ -39,15 +39,6 @@ public interface IRealtimeService /// User-specific token for the session string GetUserToken(Account account, string sessionId, bool isAdmin = false); - /// - /// Gets a token for user to join the session asynchronously - /// - /// The user identifier - /// The session identifier - /// The user is the admin of session - /// Task that resolves to the user-specific token for the session - Task GetUserTokenAsync(Account.Account account, string sessionId, bool isAdmin = false); - /// /// Processes incoming webhook requests from the realtime service provider /// diff --git a/DysonNetwork.Sphere/Chat/Realtime/LiveKitService.cs b/DysonNetwork.Sphere/Chat/Realtime/LiveKitService.cs index 812a955..84f56d0 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/LiveKitService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/LiveKitService.cs @@ -1,11 +1,8 @@ -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; using Livekit.Server.Sdk.Dotnet; using Microsoft.EntityFrameworkCore; using NodaTime; using System.Text.Json; using DysonNetwork.Shared.Cache; -using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; namespace DysonNetwork.Sphere.Chat.Realtime; @@ -111,7 +108,7 @@ public class LiveKitRealtimeService : IRealtimeService } /// - public string GetUserToken(Account.Account account, string sessionId, bool isAdmin = false) + public string GetUserToken(Account account, string sessionId, bool isAdmin = false) { var token = _accessToken.WithIdentity(account.Name) .WithName(account.Nick) diff --git a/DysonNetwork.Sphere/Chat/Realtime/RealtimeStatusService.cs b/DysonNetwork.Sphere/Chat/Realtime/RealtimeStatusService.cs deleted file mode 100644 index 2fdd815..0000000 --- a/DysonNetwork.Sphere/Chat/Realtime/RealtimeStatusService.cs +++ /dev/null @@ -1,91 +0,0 @@ -using DysonNetwork.Sphere.Connection; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Chat.Realtime; - -public class ParticipantInfoItem -{ - public string Identity { get; set; } = null!; - public string Name { get; set; } = null!; - public Guid? AccountId { get; set; } - public Dictionary Metadata { get; set; } = new(); - public DateTime JoinedAt { get; set; } -} - -public class RealtimeStatusService(AppDatabase db, WebSocketService ws, ILogger logger) -{ - // Broadcast participant update to all participants in a room - public async Task BroadcastParticipantUpdate(string roomName, List participantsInfo) - { - try - { - // Get the room ID from the session name - var roomInfo = await db.ChatRealtimeCall - .Where(c => c.SessionId == roomName && c.EndedAt == null) - .Select(c => new { c.RoomId, c.Id }) - .FirstOrDefaultAsync(); - - if (roomInfo == null) - { - logger.LogWarning("Could not find room info for session: {SessionName}", roomName); - return; - } - - // Get all room members who should receive this update - var roomMembers = await db.ChatMembers - .Where(m => m.ChatRoomId == roomInfo.RoomId && m.LeaveAt == null) - .Select(m => m.AccountId) - .ToListAsync(); - - // Get member profiles for participants who have account IDs - var accountIds = participantsInfo - .Where(p => p.AccountId.HasValue) - .Select(p => p.AccountId!.Value) - .ToList(); - - var memberProfiles = new Dictionary(); - if (accountIds.Count != 0) - { - memberProfiles = await db.ChatMembers - .Where(m => m.ChatRoomId == roomInfo.RoomId && accountIds.Contains(m.AccountId)) - .Include(m => m.Account) - .ThenInclude(m => m.Profile) - .ToDictionaryAsync(m => m.AccountId, m => m); - } - - // Convert to CallParticipant objects - var participants = participantsInfo.Select(p => new CallParticipant - { - Identity = p.Identity, - Name = p.Name, - AccountId = p.AccountId, - JoinedAt = p.JoinedAt, - Profile = p.AccountId.HasValue && memberProfiles.TryGetValue(p.AccountId.Value, out var profile) - ? profile - : null - }).ToList(); - - // Create the update packet with CallParticipant objects - var updatePacket = new WebSocketPacket - { - Type = WebSocketPacketType.CallParticipantsUpdate, - Data = new Dictionary - { - { "room_id", roomInfo.RoomId }, - { "call_id", roomInfo.Id }, - { "participants", participants } - } - }; - - // Send the update to all members - foreach (var accountId in roomMembers) - { - ws.SendPacketToAccount(accountId, updatePacket); - } - } - catch (Exception ex) - { - logger.LogError(ex, "Error broadcasting participant update for room {RoomName}", roomName); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 7f2f485..5e7bca3 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -14,6 +14,9 @@ using NodaTime.Serialization.SystemTextJson; using StackExchange.Redis; using System.Text.Json; using System.Threading.RateLimiting; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Services; +using DysonNetwork.Pass.Wallet; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.GeoIp; @@ -164,7 +167,7 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped();