diff --git a/DysonNetwork.Develop/AppDatabase.cs b/DysonNetwork.Develop/AppDatabase.cs new file mode 100644 index 0000000..205ffbc --- /dev/null +++ b/DysonNetwork.Develop/AppDatabase.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Develop; + +public class AppDatabase( + DbContextOptions options, + IConfiguration configuration +) : DbContext(options) +{ + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql( + configuration.GetConnectionString("App"), + opt => opt + .ConfigureDataSource(optSource => optSource.EnableDynamicJson()) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + .UseNodaTime() + ).UseSnakeCaseNamingConvention(); + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } +} diff --git a/DysonNetwork.Develop/Dockerfile b/DysonNetwork.Develop/Dockerfile new file mode 100644 index 0000000..fc5d356 --- /dev/null +++ b/DysonNetwork.Develop/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["DysonNetwork.Develop/DysonNetwork.Develop.csproj", "DysonNetwork.Develop/"] +RUN dotnet restore "DysonNetwork.Develop/DysonNetwork.Develop.csproj" +COPY . . +WORKDIR "/src/DysonNetwork.Develop" +RUN dotnet build "./DysonNetwork.Develop.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./DysonNetwork.Develop.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DysonNetwork.Develop.dll"] diff --git a/DysonNetwork.Develop/DysonNetwork.Develop.csproj b/DysonNetwork.Develop/DysonNetwork.Develop.csproj new file mode 100644 index 0000000..1577bf3 --- /dev/null +++ b/DysonNetwork.Develop/DysonNetwork.Develop.csproj @@ -0,0 +1,37 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + .dockerignore + + + + + + + + diff --git a/DysonNetwork.Sphere/Developer/CustomApp.cs b/DysonNetwork.Develop/Identity/CustomApp.cs similarity index 98% rename from DysonNetwork.Sphere/Developer/CustomApp.cs rename to DysonNetwork.Develop/Identity/CustomApp.cs index 425e646..6b03fa1 100644 --- a/DysonNetwork.Sphere/Developer/CustomApp.cs +++ b/DysonNetwork.Develop/Identity/CustomApp.cs @@ -4,7 +4,7 @@ using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; using NodaTime; -namespace DysonNetwork.Sphere.Developer; +namespace DysonNetwork.Develop.Identity; public enum CustomAppStatus { diff --git a/DysonNetwork.Sphere/Developer/CustomAppController.cs b/DysonNetwork.Develop/Identity/CustomAppController.cs similarity index 98% rename from DysonNetwork.Sphere/Developer/CustomAppController.cs rename to DysonNetwork.Develop/Identity/CustomAppController.cs index e9d5eb0..b6ecd38 100644 --- a/DysonNetwork.Sphere/Developer/CustomAppController.cs +++ b/DysonNetwork.Develop/Identity/CustomAppController.cs @@ -1,10 +1,9 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace DysonNetwork.Sphere.Developer; +namespace DysonNetwork.Develop.Identity; [ApiController] [Route("/api/developers/{pubName}/apps")] diff --git a/DysonNetwork.Sphere/Developer/CustomAppService.cs b/DysonNetwork.Develop/Identity/CustomAppService.cs similarity index 98% rename from DysonNetwork.Sphere/Developer/CustomAppService.cs rename to DysonNetwork.Develop/Identity/CustomAppService.cs index d2fc0a3..b3932d3 100644 --- a/DysonNetwork.Sphere/Developer/CustomAppService.cs +++ b/DysonNetwork.Develop/Identity/CustomAppService.cs @@ -1,8 +1,7 @@ using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; -using Microsoft.EntityFrameworkCore; -namespace DysonNetwork.Sphere.Developer; +namespace DysonNetwork.Develop.Identity; public class CustomAppService( AppDatabase db, diff --git a/DysonNetwork.Sphere/Developer/DeveloperController.cs b/DysonNetwork.Develop/Identity/DeveloperController.cs similarity index 97% rename from DysonNetwork.Sphere/Developer/DeveloperController.cs rename to DysonNetwork.Develop/Identity/DeveloperController.cs index 79e43c5..2a0da50 100644 --- a/DysonNetwork.Sphere/Developer/DeveloperController.cs +++ b/DysonNetwork.Develop/Identity/DeveloperController.cs @@ -1,18 +1,16 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Sphere.Developer; +namespace DysonNetwork.Develop.Identity; [ApiController] [Route("/api/developers")] public class DeveloperController( AppDatabase db, - PublisherService ps, + PublisherService.PublisherServiceClient ps, ActionLogService.ActionLogServiceClient als ) : ControllerBase diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs new file mode 100644 index 0000000..53f486c --- /dev/null +++ b/DysonNetwork.Develop/Program.cs @@ -0,0 +1,29 @@ +using DysonNetwork.Develop; +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Http; +using DysonNetwork.Shared.Registry; +using DysonNetwork.Develop.Startup; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.ConfigureAppKestrel(builder.Configuration); + +builder.Services.AddRegistryService(builder.Configuration); +builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppAuthentication(); +builder.Services.AddAppSwagger(); +builder.Services.AddDysonAuth(); +builder.Services.AddPublisherService(); + +var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); +} + +app.ConfigureAppMiddleware(builder.Configuration); + +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Develop/Properties/launchSettings.json b/DysonNetwork.Develop/Properties/launchSettings.json new file mode 100644 index 0000000..32476e3 --- /dev/null +++ b/DysonNetwork.Develop/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5156", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7192;http://localhost:5156", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DysonNetwork.Develop/Startup/ApplicationConfiguration.cs b/DysonNetwork.Develop/Startup/ApplicationConfiguration.cs new file mode 100644 index 0000000..b189d3c --- /dev/null +++ b/DysonNetwork.Develop/Startup/ApplicationConfiguration.cs @@ -0,0 +1,52 @@ +using System.Net; +using DysonNetwork.Shared.Auth; +using Microsoft.AspNetCore.HttpOverrides; +using Prometheus; + +namespace DysonNetwork.Develop.Startup; + +public static class ApplicationConfiguration +{ + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + { + app.MapMetrics(); + app.MapOpenApi(); + + app.UseSwagger(); + app.UseSwaggerUI(); + + app.UseRequestLocalization(); + + ConfigureForwardedHeaders(app, configuration); + + app.UseAuthentication(); + app.UseAuthorization(); + app.UseMiddleware(); + + app.MapControllers(); + + return app; + } + + private static void ConfigureForwardedHeaders(WebApplication app, IConfiguration configuration) + { + var knownProxiesSection = configuration.GetSection("KnownProxies"); + var forwardedHeadersOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }; + + if (knownProxiesSection.Exists()) + { + var proxyAddresses = knownProxiesSection.Get(); + if (proxyAddresses != null) + foreach (var proxy in proxyAddresses) + if (IPAddress.TryParse(proxy, out var ipAddress)) + forwardedHeadersOptions.KnownProxies.Add(ipAddress); + } + else + { + forwardedHeadersOptions.KnownProxies.Add(IPAddress.Any); + forwardedHeadersOptions.KnownProxies.Add(IPAddress.IPv6Any); + } + + app.UseForwardedHeaders(forwardedHeadersOptions); + } +} diff --git a/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f924c79 --- /dev/null +++ b/DysonNetwork.Develop/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,68 @@ +using System.Globalization; +using Microsoft.OpenApi.Models; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; +using System.Text.Json; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Develop.Startup; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddLocalization(); + + services.AddDbContext(); + services.AddSingleton(SystemClock.Instance); + services.AddHttpContextAccessor(); + services.AddSingleton(); + + services.AddHttpClient(); + + services.AddControllers().AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + }); + + services.AddGrpc(options => { options.EnableDetailedErrors = true; }); + + services.Configure(options => + { + var supportedCultures = new[] + { + new CultureInfo("en-US"), + new CultureInfo("zh-Hans"), + }; + + options.SupportedCultures = supportedCultures; + options.SupportedUICultures = supportedCultures; + }); + + return services; + } + + public static IServiceCollection AddAppAuthentication(this IServiceCollection services) + { + services.AddCors(); + services.AddAuthorization(); + return services; + } + + public static IServiceCollection AddAppSwagger(this IServiceCollection services) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Develop API", + }); + }); + services.AddOpenApi(); + return services; + } +} diff --git a/DysonNetwork.Develop/appsettings.json b/DysonNetwork.Develop/appsettings.json new file mode 100644 index 0000000..5aac70a --- /dev/null +++ b/DysonNetwork.Develop/appsettings.json @@ -0,0 +1,30 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "SiteUrl": "https://solian.app", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=localhost;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379", + "Etcd": "etcd.orb.local:2379" + }, + "KnownProxies": [ + "127.0.0.1", + "::1" + ], + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Develop", + "Url": "https://localhost:7099", + "ClientCert": "../Certificates/client.crt", + "ClientKey": "../Certificates/client.key" + } +} diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index 2ca966a..c562fa5 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -123,4 +123,16 @@ public static class GrpcClientHelper return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } -} \ No newline at end of file + + public static async Task CreatePublisherServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Sphere"); + return new PublisherService.PublisherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/publisher.proto b/DysonNetwork.Shared/Proto/publisher.proto new file mode 100644 index 0000000..5b75656 --- /dev/null +++ b/DysonNetwork.Shared/Proto/publisher.proto @@ -0,0 +1,108 @@ +syntax = "proto3"; + +package proto; + +option csharp_namespace = "DysonNetwork.Shared.Proto"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "file.proto"; + +enum PublisherType { + PUBLISHER_TYPE_UNSPECIFIED = 0; + INDIVIDUAL = 1; + ORGANIZATIONAL = 2; +} + +enum PublisherMemberRole { + PUBLISHER_MEMBER_ROLE_UNSPECIFIED = 0; + OWNER = 100; + MANAGER = 75; + EDITOR = 50; + VIEWER = 25; +} + +message PublisherFeature { + string id = 1; + string flag = 2; + google.protobuf.Timestamp expired_at = 3; + string publisher_id = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; +} + +message PublisherMember { + string publisher_id = 1; + string account_id = 2; + PublisherMemberRole role = 3; + google.protobuf.Timestamp joined_at = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; +} + +message Publisher { + string id = 1; + PublisherType type = 2; + string name = 3; + string nick = 4; + google.protobuf.StringValue bio = 5; + CloudFile picture = 8; + CloudFile background = 9; + optional bytes verification_mark = 10; + string account_id = 11; + string realm_id = 12; + google.protobuf.Timestamp created_at = 13; + google.protobuf.Timestamp updated_at = 14; +} + +message GetPublisherRequest { + string name = 1; +} + +message GetPublisherResponse { + Publisher publisher = 1; +} + +message ListPublishersRequest { + string account_id = 1; // filter by owner/member account + string realm_id = 2; // filter by realm + int32 page_size = 3; + string page_token = 4; + string order_by = 5; +} + +message ListPublishersResponse { + repeated Publisher publishers = 1; + string next_page_token = 2; + int32 total_size = 3; +} + +message ListPublisherMembersRequest { + string publisher_id = 1; +} + +message ListPublisherMembersResponse { + repeated PublisherMember members = 1; +} + +message SetPublisherFeatureFlagRequest { + string publisher_id = 1; + string flag = 2; +} + +message HasPublisherFeatureRequest { + string publisher_id = 1; + string flag = 2; +} + +message HasPublisherFeatureResponse { + bool enabled = 1; +} + +service PublisherService { + rpc GetPublisher(GetPublisherRequest) returns (GetPublisherResponse); + rpc ListPublishers(ListPublishersRequest) returns (ListPublishersResponse); + rpc ListPublisherMembers(ListPublisherMembersRequest) returns (ListPublisherMembersResponse); + rpc SetPublisherFeatureFlag(SetPublisherFeatureFlagRequest) returns (google.protobuf.StringValue); // returns optional message + rpc HasPublisherFeature(HasPublisherFeatureRequest) returns (HasPublisherFeatureResponse); +} diff --git a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index 4ba8d39..985b762 100644 --- a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -92,4 +92,23 @@ public static class ServiceInjectionHelper return services; } + + public static IServiceCollection AddPublisherService(this IServiceCollection services) + { + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); + + return services; + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs index 5e8eec7..103eb29 100644 --- a/DysonNetwork.Sphere/Activity/ActivityService.cs +++ b/DysonNetwork.Sphere/Activity/ActivityService.cs @@ -10,7 +10,7 @@ namespace DysonNetwork.Sphere.Activity; public class ActivityService( AppDatabase db, - PublisherService pub, + Publisher.PublisherService pub, PostService ps, DiscoveryService ds, AccountService.AccountServiceClient accounts diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index 619a826..dc97f94 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -1,7 +1,6 @@ using System.Linq.Expressions; using System.Reflection; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Realm; @@ -61,9 +60,6 @@ public class AppDatabase( public DbSet StickerPacks { get; set; } public DbSet StickerPackOwnerships { get; set; } - public DbSet CustomApps { get; set; } - public DbSet CustomAppSecrets { get; set; } - public DbSet WebArticles { get; set; } public DbSet WebFeeds { get; set; } @@ -103,16 +99,6 @@ public class AppDatabase( .HasIndex(p => p.SearchVector) .HasMethod("GIN"); - modelBuilder.Entity() - .HasIndex(s => s.Secret) - .IsUnique(); - - modelBuilder.Entity() - .HasMany(c => c.Secrets) - .WithOne(s => s.App) - .HasForeignKey(s => s.AppId) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() .HasOne(p => p.RepliedPost) .WithMany() diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 5dd98c2..c00b5c2 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -36,6 +36,7 @@ + diff --git a/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.Designer.cs b/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.Designer.cs index b59b1b8..b7934f5 100644 --- a/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.Designer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -397,20 +396,12 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("character varying(4096)") .HasColumnName("description"); - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) .HasColumnType("character varying(1024)") .HasColumnName("name"); - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - b.Property("Picture") .HasColumnType("jsonb") .HasColumnName("picture"); diff --git a/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.cs b/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.cs index 03f39bb..8726790 100644 --- a/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.cs +++ b/DysonNetwork.Sphere/Migrations/20250717135738_InitialMigration.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore.Migrations; using NodaTime; @@ -248,8 +247,6 @@ namespace DysonNetwork.Sphere.Migrations picture = table.Column(type: "jsonb", nullable: true), background = table.Column(type: "jsonb", nullable: true), verification = table.Column(type: "jsonb", nullable: true), - oauth_config = table.Column(type: "jsonb", nullable: true), - links = table.Column(type: "jsonb", nullable: true), publisher_id = table.Column(type: "uuid", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false), updated_at = table.Column(type: "timestamp with time zone", nullable: false), diff --git a/DysonNetwork.Sphere/Migrations/20250731065135_AddCheckInBackdated.Designer.cs b/DysonNetwork.Sphere/Migrations/20250731065135_AddCheckInBackdated.Designer.cs index 29b2a3b..a8ff363 100644 --- a/DysonNetwork.Sphere/Migrations/20250731065135_AddCheckInBackdated.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250731065135_AddCheckInBackdated.Designer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -397,20 +396,12 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("character varying(4096)") .HasColumnName("description"); - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) .HasColumnType("character varying(1024)") .HasColumnName("name"); - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - b.Property("Picture") .HasColumnType("jsonb") .HasColumnName("picture"); diff --git a/DysonNetwork.Sphere/Migrations/20250802095248_AddPoll.Designer.cs b/DysonNetwork.Sphere/Migrations/20250802095248_AddPoll.Designer.cs index e89aa1f..e5748f5 100644 --- a/DysonNetwork.Sphere/Migrations/20250802095248_AddPoll.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250802095248_AddPoll.Designer.cs @@ -5,7 +5,6 @@ using System.Text.Json; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Poll; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore; @@ -399,20 +398,12 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("character varying(4096)") .HasColumnName("description"); - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) .HasColumnType("character varying(1024)") .HasColumnName("name"); - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - b.Property("Picture") .HasColumnType("jsonb") .HasColumnName("picture"); diff --git a/DysonNetwork.Sphere/Migrations/20250805183629_AddStickerOwnerships.Designer.cs b/DysonNetwork.Sphere/Migrations/20250805183629_AddStickerOwnerships.Designer.cs index 092bf95..b80197e 100644 --- a/DysonNetwork.Sphere/Migrations/20250805183629_AddStickerOwnerships.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250805183629_AddStickerOwnerships.Designer.cs @@ -5,7 +5,6 @@ using System.Text.Json; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Poll; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore; @@ -399,20 +398,12 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("character varying(4096)") .HasColumnName("description"); - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) .HasColumnType("character varying(1024)") .HasColumnName("name"); - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - b.Property("Picture") .HasColumnType("jsonb") .HasColumnName("picture"); diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs index c96acba..f6b53eb 100644 --- a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs @@ -5,7 +5,6 @@ using System.Text.Json; using DysonNetwork.Shared.Data; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Poll; using DysonNetwork.Sphere.WebReader; using Microsoft.EntityFrameworkCore; @@ -396,20 +395,12 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("character varying(4096)") .HasColumnName("description"); - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) .HasColumnType("character varying(1024)") .HasColumnName("name"); - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - b.Property("Picture") .HasColumnType("jsonb") .HasColumnName("picture"); diff --git a/DysonNetwork.Sphere/PageData/PostPageData.cs b/DysonNetwork.Sphere/PageData/PostPageData.cs index af10475..3592a32 100644 --- a/DysonNetwork.Sphere/PageData/PostPageData.cs +++ b/DysonNetwork.Sphere/PageData/PostPageData.cs @@ -11,7 +11,7 @@ namespace DysonNetwork.Sphere.PageData; public class PostPageData( AppDatabase db, AccountService.AccountServiceClient accounts, - PublisherService pub, + Publisher.PublisherService pub, PostService ps, IConfiguration configuration ) diff --git a/DysonNetwork.Sphere/Poll/PollController.cs b/DysonNetwork.Sphere/Poll/PollController.cs index 6eca5f1..95c5890 100644 --- a/DysonNetwork.Sphere/Poll/PollController.cs +++ b/DysonNetwork.Sphere/Poll/PollController.cs @@ -11,7 +11,7 @@ namespace DysonNetwork.Sphere.Poll; [ApiController] [Route("/api/polls")] -public class PollController(AppDatabase db, PollService polls, PublisherService pub) : ControllerBase +public class PollController(AppDatabase db, PollService polls, Publisher.PublisherService pub) : ControllerBase { [HttpGet("{id:guid}")] public async Task> GetPoll(Guid id) @@ -85,7 +85,7 @@ public class PollController(AppDatabase db, PollService polls, PublisherService .FirstOrDefaultAsync(p => p.Id == id); if (poll is null) return NotFound("Poll not found"); - if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, PublisherMemberRole.Viewer)) + if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Viewer)) return StatusCode(403, "You need to be a viewer to view this poll's feedback."); var answerQuery = db.PollAnswers @@ -186,7 +186,7 @@ public class PollController(AppDatabase db, PollService polls, PublisherService var publisher = await pub.GetPublisherByName(pubName); if (publisher is null) return BadRequest("Publisher was not found."); - if (!await pub.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to create polls as this publisher."); var poll = new Poll @@ -231,7 +231,7 @@ public class PollController(AppDatabase db, PollService polls, PublisherService if (poll == null) return NotFound("Poll not found"); // Check if user is an editor of the publisher that owns the poll - if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need to be at least an editor to update this poll."); // Update properties if they are provided in the request @@ -294,7 +294,7 @@ public class PollController(AppDatabase db, PollService polls, PublisherService if (poll == null) return NotFound("Poll not found"); // Check if user is an editor of the publisher that owns the poll - if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need to be at least an editor to delete this poll."); // Delete all answers for this poll diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index be9a4c8..7a1db37 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using PublisherService = DysonNetwork.Sphere.Publisher.PublisherService; namespace DysonNetwork.Sphere.Post; @@ -301,13 +302,13 @@ public class PostController( { // Use the first personal publisher publisher = await db.Publishers.FirstOrDefaultAsync(e => - e.AccountId == accountId && e.Type == PublisherType.Individual); + e.AccountId == accountId && e.Type == Publisher.PublisherType.Individual); } else { publisher = await pub.GetPublisherByName(pubName); if (publisher is null) return BadRequest("Publisher was not found."); - if (!await pub.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to post as this publisher."); } @@ -473,14 +474,14 @@ public class PostController( if (post is null) return NotFound(); var accountId = Guid.Parse(currentUser.Id); - if (!await pub.IsMemberWithRole(post.Publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(post.Publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to edit this publisher's post."); if (pubName is not null) { var publisher = await pub.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await pub.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to transfer this post to this publisher."); post.PublisherId = publisher.Id; post.Publisher = publisher; @@ -552,7 +553,8 @@ public class PostController( .FirstOrDefaultAsync(); if (post is null) return NotFound(); - if (!await pub.IsMemberWithRole(post.Publisher.Id, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(post.Publisher.Id, Guid.Parse(currentUser.Id), + Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to delete the publisher's post."); await ps.DeletePostAsync(post); diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index d0b25d9..1842bec 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -161,7 +161,7 @@ public partial class PostService( { var sender = post.Publisher; using var scope = factory.CreateScope(); - var pub = scope.ServiceProvider.GetRequiredService(); + var pub = scope.ServiceProvider.GetRequiredService(); var nty = scope.ServiceProvider.GetRequiredService(); var accounts = scope.ServiceProvider.GetRequiredService(); try @@ -455,7 +455,7 @@ public partial class PostService( _ = Task.Run(async () => { using var scope = factory.CreateScope(); - var pub = scope.ServiceProvider.GetRequiredService(); + var pub = scope.ServiceProvider.GetRequiredService(); var nty = scope.ServiceProvider.GetRequiredService(); var accounts = scope.ServiceProvider.GetRequiredService(); try diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Sphere/Publisher/Publisher.cs index 0acaadd..ec6a7a3 100644 --- a/DysonNetwork.Sphere/Publisher/Publisher.cs +++ b/DysonNetwork.Sphere/Publisher/Publisher.cs @@ -5,6 +5,7 @@ using DysonNetwork.Shared.Data; using DysonNetwork.Sphere.Post; using Microsoft.EntityFrameworkCore; using NodaTime; +using NodaTime.Serialization.Protobuf; using VerificationMark = DysonNetwork.Shared.Data.VerificationMark; using Account = DysonNetwork.Pass.Account.Account; @@ -49,6 +50,49 @@ public class Publisher : ModelBase, IIdentifiedResource [NotMapped] public Account? Account { get; set; } public string ResourceIdentifier => $"publisher:{Id}"; + + public Shared.Proto.Publisher ToProto(AppDatabase db) + { + var p = new Shared.Proto.Publisher() + { + Id = Id.ToString(), + Type = Type == PublisherType.Individual + ? Shared.Proto.PublisherType.Individual + : Shared.Proto.PublisherType.Organizational, + Name = Name, + Nick = Nick, + Bio = Bio, + AccountId = AccountId?.ToString() ?? string.Empty, + RealmId = RealmId?.ToString() ?? string.Empty, + CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), + UpdatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) + }; + if (Picture is not null) + { + p.Picture = new Shared.Proto.CloudFile + { + Id = Picture.Id, + Name = Picture.Name, + MimeType = Picture.MimeType, + Hash = Picture.Hash, + Size = Picture.Size, + }; + } + + if (Background is not null) + { + p.Background = new Shared.Proto.CloudFile + { + Id = Background.Id, + Name = Background.Name, + MimeType = Background.MimeType, + Hash = Background.Hash, + Size = Background.Size, + }; + } + + return p; + } } public enum PublisherMemberRole @@ -68,6 +112,25 @@ public class PublisherMember : ModelBase public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer; public Instant? JoinedAt { get; set; } + + + public Shared.Proto.PublisherMember ToProto() + { + return new Shared.Proto.PublisherMember() + { + PublisherId = PublisherId.ToString(), + AccountId = AccountId.ToString(), + Role = Role switch + { + PublisherMemberRole.Owner => Shared.Proto.PublisherMemberRole.Owner, + PublisherMemberRole.Manager => Shared.Proto.PublisherMemberRole.Manager, + PublisherMemberRole.Editor => Shared.Proto.PublisherMemberRole.Editor, + PublisherMemberRole.Viewer => Shared.Proto.PublisherMemberRole.Viewer, + _ => throw new ArgumentOutOfRangeException(nameof(Role), Role, null) + }, + JoinedAt = JoinedAt?.ToTimestamp() + }; + } } public enum PublisherSubscriptionStatus diff --git a/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs b/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs new file mode 100644 index 0000000..262a498 --- /dev/null +++ b/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs @@ -0,0 +1,68 @@ +using DysonNetwork.Shared.Proto; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Sphere.Publisher; + +public class PublisherServiceGrpc(PublisherService service, AppDatabase db) + : Shared.Proto.PublisherService.PublisherServiceBase +{ + public override async Task GetPublisher(GetPublisherRequest request, + ServerCallContext context) + { + var p = await service.GetPublisherByName(request.Name); + if (p is null) throw new RpcException(new Status(StatusCode.NotFound, "publisher not found")); + return new GetPublisherResponse { Publisher = p.ToProto(db) }; + } + + public override async Task ListPublishers(ListPublishersRequest request, + ServerCallContext context) + { + IQueryable query = db.Publishers.AsQueryable(); + if (!string.IsNullOrWhiteSpace(request.AccountId) && Guid.TryParse(request.AccountId, out var aid)) + { + var ids = await db.PublisherMembers.Where(m => m.AccountId == aid).Select(m => m.PublisherId).ToListAsync(); + query = query.Where(p => ids.Contains(p.Id)); + } + + if (!string.IsNullOrWhiteSpace(request.RealmId) && Guid.TryParse(request.RealmId, out var rid)) + { + query = query.Where(p => p.RealmId == rid); + } + + var list = await query.Take(request.PageSize > 0 ? request.PageSize : 100).ToListAsync(); + var resp = new ListPublishersResponse(); + resp.Publishers.AddRange(list.Select(p => p.ToProto(db))); + resp.TotalSize = list.Count; + return resp; + } + + public override async Task ListPublisherMembers(ListPublisherMembersRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.PublisherId, out var pid)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid publisher_id")); + var members = await service.GetPublisherMembers(pid); + var resp = new ListPublisherMembersResponse(); + resp.Members.AddRange(members.Select(m => m.ToProto())); + return resp; + } + + public override async Task SetPublisherFeatureFlag( + SetPublisherFeatureFlagRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.PublisherId, out var pid)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid publisher_id")); + await service.SetFeatureFlag(pid, request.Flag); + return new Google.Protobuf.WellKnownTypes.StringValue { Value = request.Flag }; + } + + public override async Task HasPublisherFeature(HasPublisherFeatureRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.PublisherId, out var pid)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid publisher_id")); + var enabled = await service.HasFeature(pid, request.Flag); + return new HasPublisherFeatureResponse { Enabled = enabled }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs index 169eb9c..aa0a4f6 100644 --- a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs @@ -1,6 +1,7 @@ using System.Net; using DysonNetwork.Shared.Auth; using DysonNetwork.Sphere.Connection; +using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.HttpOverrides; using Prometheus; @@ -29,6 +30,7 @@ public static class ApplicationConfiguration // Map gRPC services app.MapGrpcService(); + app.MapGrpcService(); return app; } diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 4448298..cdb318f 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -17,7 +17,6 @@ using System.Threading.RateLimiting; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.GeoIp; using DysonNetwork.Sphere.WebReader; -using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Poll; using DysonNetwork.Sphere.Translation; @@ -169,7 +168,6 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); var translationProvider = configuration["Translation:Provider"]?.ToLower(); diff --git a/DysonNetwork.Sphere/Sticker/StickerController.cs b/DysonNetwork.Sphere/Sticker/StickerController.cs index 30271ac..11e1970 100644 --- a/DysonNetwork.Sphere/Sticker/StickerController.cs +++ b/DysonNetwork.Sphere/Sticker/StickerController.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -16,7 +15,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi private async Task _CheckStickerPackPermissions( Guid packId, Account currentUser, - PublisherMemberRole requiredRole + Publisher.PublisherMemberRole requiredRole ) { var pack = await db.StickerPacks @@ -149,7 +148,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi .FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); - if (member.Role < PublisherMemberRole.Editor) + if (member.Role < Publisher.PublisherMemberRole.Editor) return StatusCode(403, "You need to be at least an editor to update sticker packs"); if (request.Name is not null) @@ -181,7 +180,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi .FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); - if (member.Role < PublisherMemberRole.Editor) + if (member.Role < Publisher.PublisherMemberRole.Editor) return StatusCode(403, "You need to be an editor to delete sticker packs"); await st.DeleteStickerPackAsync(pack); @@ -242,7 +241,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); + var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; @@ -278,7 +277,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); + var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; @@ -309,7 +308,7 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi if (request.ImageId is null) return BadRequest("Image is required."); - var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); + var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; diff --git a/DysonNetwork.Sphere/WebReader/WebFeedController.cs b/DysonNetwork.Sphere/WebReader/WebFeedController.cs index 047e7fa..ff22075 100644 --- a/DysonNetwork.Sphere/WebReader/WebFeedController.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedController.cs @@ -9,7 +9,7 @@ namespace DysonNetwork.Sphere.WebReader; [Authorize] [ApiController] [Route("/api/publishers/{pubName}/feeds")] -public class WebFeedController(WebFeedService webFeed, PublisherService ps) : ControllerBase +public class WebFeedController(WebFeedService webFeed, Publisher.PublisherService ps) : ControllerBase { public record WebFeedRequest( [MaxLength(8192)] string? Url, @@ -53,7 +53,7 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co if (publisher is null) return NotFound(); var accountId = Guid.Parse(currentUser.Id); - if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to create a web feed"); var feed = await webFeed.CreateWebFeedAsync(publisher, request); @@ -70,7 +70,7 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co if (publisher is null) return NotFound(); var accountId = Guid.Parse(currentUser.Id); - if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to update a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); @@ -91,7 +91,7 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co if (publisher is null) return NotFound(); var accountId = Guid.Parse(currentUser.Id); - if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to delete a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); @@ -114,7 +114,7 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co if (publisher is null) return NotFound(); var accountId = Guid.Parse(currentUser.Id); - if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to scrape a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 0ce0170..5e3c276 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Sphere", "DysonNetwork.Sphere\DysonNetwork.Sphere.csproj", "{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A444D180-5B51-49C3-A35D-AA55832BBC66}" @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "Dyson EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "DysonNetwork.Gateway\DysonNetwork.Gateway.csproj", "{19EB0086-4049-4B78-91C4-EAC37130A006}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,5 +50,9 @@ Global {19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.Build.0 = Debug|Any CPU {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.ActiveCfg = Release|Any CPU {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.Build.0 = Release|Any CPU + {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal