diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 5335654..05b4d45 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -9,8 +9,10 @@ + + all @@ -26,7 +28,7 @@ - + @@ -38,12 +40,6 @@ - - - ..\..\..\..\..\..\opt\homebrew\Cellar\dotnet\9.0.6\libexec\shared\Microsoft.AspNetCore.App\9.0.6\Microsoft.AspNetCore.dll - - - true @@ -74,4 +70,17 @@ + + <_ContentIncludedByDefault Remove="app\publish\appsettings.json" /> + <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Pass.deps.json" /> + <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Pass.runtimeconfig.json" /> + <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Pass.staticwebassets.endpoints.json" /> + <_ContentIncludedByDefault Remove="app\publish\Keys\Solian.json" /> + <_ContentIncludedByDefault Remove="app\publish\package-lock.json" /> + <_ContentIncludedByDefault Remove="app\publish\package.json" /> + + + + + diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 5c55491..013c140 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,36 +1,50 @@ +using System.Text.Json; using DysonNetwork.Pass; -using DysonNetwork.Pass.Account; -using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Startup; +using DysonNetwork.Shared.Etcd; using DysonNetwork.Shared.Startup; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using MagicOnion.Server; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; var builder = WebApplication.CreateBuilder(args); builder.ConfigureAppKestrel(); +builder.Services.AddHttpClient(); + +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + + options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); +}).AddDataAnnotationsLocalization(options => +{ + options.DataAnnotationLocalizerProvider = (type, factory) => + factory.Create(typeof(SharedResource)); +}); +builder.Services.AddRazorPages(); builder.Services.AddAppSwagger(); builder.Services.AddAppAuthentication(); builder.Services.AddAppRateLimiting(); + +builder.Services.AddMagicOnion(); +builder.Services.AddEtcdService(builder.Configuration); + builder.Services.AddAppBusinessServices(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); -builder.Services.AddControllers(); -builder.Services.AddMagicOnion(); builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("App"))); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - var app = builder.Build(); // Configure the HTTP request pipeline. +app.UseStaticFiles(); app.UseAuthorization(); app.ConfigureAppMiddleware(builder.Configuration); diff --git a/DysonNetwork.Pass/Properties/launchSettings.json b/DysonNetwork.Pass/Properties/launchSettings.json new file mode 100644 index 0000000..1149eea --- /dev/null +++ b/DysonNetwork.Pass/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5072", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "workingDirectory": "/Users/littlesheep/Documents/Projects/DysonNetwork/DysonNetwork.Pass" + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7098;http://localhost:5072", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "workingDirectory": "/Users/littlesheep/Documents/Projects/DysonNetwork/DysonNetwork.Pass" + } + } +} diff --git a/DysonNetwork.Pass/Publisher/PublisherService.cs b/DysonNetwork.Pass/Publisher/PublisherService.cs index de827a5..0747ad3 100644 --- a/DysonNetwork.Pass/Publisher/PublisherService.cs +++ b/DysonNetwork.Pass/Publisher/PublisherService.cs @@ -10,35 +10,28 @@ using NodaTime; namespace DysonNetwork.Pass.Publisher; -public class PublisherService : ServiceBase, IPublisherService +public class PublisherService(AppDatabase db) : ServiceBase, IPublisherService { - private readonly AppDatabase _db; - - public PublisherService(AppDatabase db) - { - _db = db; - } - public async Task GetPublisherByName(string name) { - return await _db.Publishers.FirstOrDefaultAsync(p => p.Name == name); + return await db.Publishers.FirstOrDefaultAsync(p => p.Name == name); } public async Task> GetUserPublishers(Guid accountId) { - var publisherIds = await _db.PublisherMembers + var publisherIds = await db.PublisherMembers .Where(m => m.AccountId == accountId) .Select(m => m.PublisherId) .ToListAsync(); - return await _db.Publishers + return await db.Publishers .Where(p => publisherIds.Contains(p.Id)) .ToListAsync(); } public async Task IsMemberWithRole(Guid publisherId, Guid accountId, PublisherMemberRole role) { - return await _db.PublisherMembers.AnyAsync(m => + return await db.PublisherMembers.AnyAsync(m => m.PublisherId == publisherId && m.AccountId == accountId && m.Role >= role); @@ -46,7 +39,7 @@ public class PublisherService : ServiceBase, IPublisherServic public async Task> GetPublisherFeatures(Guid publisherId) { - return await _db.PublisherFeatures + return await db.PublisherFeatures .Where(f => f.PublisherId == publisherId) .ToListAsync(); } diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index a82bcbc..dfee2bd 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -12,6 +12,7 @@ using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.RateLimiting; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 7c55d4a..4b38fcd 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -1,11 +1,130 @@ { - "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network_pass;Username=postgres;Password=password" - }, + "Debug": true, + "BaseUrl": "http://localhost:5071", "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } -} \ No newline at end of file + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=localhost;Port=5432;Database=dyson_network_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379", + "Etcd": "localhost:2379" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ + "http://localhost:5071", + "https://localhost:7099" + ], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "minio", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": [ + "127.0.0.1", + "::1" + ] +} diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 5e26542..b0c21f1 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -13,6 +13,7 @@ + diff --git a/DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs b/DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs index a787525..2b990ef 100644 --- a/DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs +++ b/DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs @@ -12,15 +12,16 @@ namespace DysonNetwork.Shared.Etcd { public static IServiceCollection AddEtcdService(this IServiceCollection services, IConfiguration configuration) { - var etcdConnectionString = configuration.GetConnectionString("Etcd"); - services.AddSingleton(new EtcdService(etcdConnectionString!)); + var connectionString = configuration["ConnectionStrings:Etcd"]; + if (connectionString is null) throw new ArgumentNullException(nameof(connectionString)); + services.AddSingleton(new EtcdService(connectionString!)); return services; } - public static IServiceCollection AddMagicOnionService(this IServiceCollection services) + public static IServiceCollection AddRemoteService(this IServiceCollection services) where TService : class, MagicOnion.IService { - services.AddSingleton(serviceProvider => + services.AddSingleton(serviceProvider => { var etcdService = serviceProvider.GetRequiredService(); var serviceName = typeof(TService).Name.TrimStart('I'); // Convention: IMyService -> MyService @@ -28,10 +29,8 @@ namespace DysonNetwork.Shared.Etcd // Synchronously wait for service discovery (or handle asynchronously if preferred) var endpoints = etcdService.DiscoverServicesAsync(serviceName).GetAwaiter().GetResult(); - if (!endpoints.Any()) - { + if (endpoints.Count == 0) throw new InvalidOperationException($"No endpoints found for MagicOnion service: {serviceName}"); - } // For simplicity, use the first discovered endpoint var endpoint = endpoints.First(); @@ -39,7 +38,7 @@ namespace DysonNetwork.Shared.Etcd var channel = GrpcChannel.ForAddress(endpoint); return MagicOnionClient.Create(channel); }); - + return services; } } diff --git a/DysonNetwork.Shared/Startup/ApplicationConfiguration.cs b/DysonNetwork.Shared/Startup/ApplicationConfiguration.cs index 86fbf3c..6f00720 100644 --- a/DysonNetwork.Shared/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Shared/Startup/ApplicationConfiguration.cs @@ -2,6 +2,8 @@ using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace DysonNetwork.Shared.Startup; @@ -30,7 +32,17 @@ public static class ApplicationConfiguration app.UseAuthorization(); app.MapControllers().RequireRateLimiting("fixed"); - app.MapStaticAssets().RequireRateLimiting("fixed"); + + try + { + app.MapStaticAssets().RequireRateLimiting("fixed"); + } + catch (InvalidOperationException) when (app.Environment.IsDevelopment()) + { + // Ignore missing static assets in development + app.Logger.LogWarning("Static assets not found. Skipping static assets mapping."); + } + app.MapRazorPages().RequireRateLimiting("fixed"); return app; diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index a01759d..a74a2d3 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -12,10 +12,7 @@ using Quartz; namespace DysonNetwork.Sphere; -public class AppDatabase( - DbContextOptions options, - IConfiguration configuration -) : DbContext(options) +public class AppDatabase(DbContextOptions options, IConfiguration configuration) : DbContext(options) { public DbSet Files { get; set; } public DbSet FileReferences { get; set; } diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 61f6e10..32623e6 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -25,6 +25,7 @@ + diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index b0e551e..9dcccb5 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Etcd; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Startup; using Microsoft.EntityFrameworkCore; @@ -14,15 +15,16 @@ builder.ConfigureAppKestrel(); // Add metrics and telemetry builder.Services.AddAppMetrics(); +// Add remote services +builder.Services.AddMagicOnion(); +builder.Services.AddEtcdService(builder.Configuration); + // Add application services builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); -// Add gRPC services -builder.Services.AddGrpc(); - // Configure MagicOnion client for IAccountService builder.Services.AddSingleton(provider => { @@ -86,8 +88,4 @@ var tusDiskStore = app.Services.GetRequiredService(); // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration, tusDiskStore); -// Remove direct gRPC service mappings for Pass services -// app.MapGrpcService(); -// app.MapGrpcService(); - app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Sphere/Properties/launchSettings.json b/DysonNetwork.Sphere/Properties/launchSettings.json index a6ab6aa..3af8f9e 100644 --- a/DysonNetwork.Sphere/Properties/launchSettings.json +++ b/DysonNetwork.Sphere/Properties/launchSettings.json @@ -8,7 +8,8 @@ "applicationUrl": "http://localhost:5071", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "workingDirectory": "/Users/littlesheep/Documents/Projects/DysonNetwork/DysonNetwork.Sphere" }, "https": { "commandName": "Project", @@ -17,7 +18,8 @@ "applicationUrl": "https://localhost:7099;http://localhost:5071", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "workingDirectory": "/Users/littlesheep/Documents/Projects/DysonNetwork/DysonNetwork.Sphere" } } } diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 5f62252..ab9eaf5 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -209,14 +209,18 @@ public static class ServiceCollectionExtensions services.AddScoped(); // Add MagicOnion services - services.AddMagicOnionService(); - services.AddMagicOnionService(); - services.AddMagicOnionService(); - services.AddMagicOnionService(); - services.AddMagicOnionService(); - services.AddMagicOnionService(); - services.AddMagicOnionService(); - + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + services.AddRemoteService(); + return services; } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs index 1cd797d..c2fd7dd 100644 --- a/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs +++ b/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs @@ -12,7 +12,7 @@ public class LastActiveInfo public Instant SeenAt { get; set; } } -public class LastActiveFlushHandler(DysonNetwork.Shared.Services.IAccountService accounts, DysonNetwork.Shared.Services.IAccountProfileService profiles) : IFlushHandler +public class LastActiveFlushHandler(IAccountService accounts) : IFlushHandler { public async Task FlushAsync(IReadOnlyList items) { diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 82c1088..dfc7257 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -10,7 +10,8 @@ "AllowedHosts": "*", "ConnectionStrings": { "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379" + "FastRetrieve": "localhost:6379", + "Etcd": "localhost:2379" }, "Authentication": { "Schemes": { diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index e8feba2..7f12dc1 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -10,6 +10,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -36,6 +37,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -56,8 +58,11 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -93,6 +98,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded <AssemblyExplorer> <Assembly Path="/opt/homebrew/Cellar/dotnet/9.0.6/libexec/packs/Microsoft.AspNetCore.App.Ref/9.0.6/ref/net9.0/Microsoft.AspNetCore.RateLimiting.dll" /> </AssemblyExplorer>