diff --git a/NostrStreamer/ApiModel/Account.cs b/NostrStreamer/ApiModel/Account.cs index b20404e..1411343 100644 --- a/NostrStreamer/ApiModel/Account.cs +++ b/NostrStreamer/ApiModel/Account.cs @@ -1,18 +1,20 @@ using Newtonsoft.Json; -using Nostr.Client.Messages; namespace NostrStreamer.ApiModel; public class Account { [JsonProperty("event")] - public NostrEvent? Event { get; init; } + public PatchEvent? Event { get; init; } [JsonProperty("endpoints")] public List Endpoints { get; init; } = new(); [JsonProperty("balance")] public long Balance { get; init; } + + [JsonProperty("tos")] + public AccountTos Tos { get; init; } } public class AccountEndpoint @@ -42,15 +44,11 @@ public class EndpointCost public string Unit { get; init; } = null!; } -[Obsolete("Use EndpointCost")] -public class AccountQuota +public class AccountTos { - [JsonProperty("rate")] - public double Rate { get; init; } + [JsonProperty("accepted")] + public bool Accepted { get; init; } - [JsonProperty("unit")] - public string Unit { get; init; } = null!; - - [JsonProperty("remaining")] - public long Remaining { get; init; } + [JsonProperty("link")] + public Uri Link { get; init; } = null!; } \ No newline at end of file diff --git a/NostrStreamer/ApiModel/PatchAccount.cs b/NostrStreamer/ApiModel/PatchAccount.cs new file mode 100644 index 0000000..e2f6c90 --- /dev/null +++ b/NostrStreamer/ApiModel/PatchAccount.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace NostrStreamer.ApiModel; + +public class PatchAccount +{ + [JsonProperty("accept_tos")] + public bool AcceptTos { get; init; } +} diff --git a/NostrStreamer/Config.cs b/NostrStreamer/Config.cs index fdf7152..4e2a95a 100644 --- a/NostrStreamer/Config.cs +++ b/NostrStreamer/Config.cs @@ -38,6 +38,8 @@ public class Config public LndConfig Lnd { get; init; } = null!; public S3BlobConfig DvrStore { get; init; } = null!; + + public DateTime TosDate { get; init; } } public class LndConfig diff --git a/NostrStreamer/Controllers/NostrController.cs b/NostrStreamer/Controllers/NostrController.cs index c929c77..228bd67 100644 --- a/NostrStreamer/Controllers/NostrController.cs +++ b/NostrStreamer/Controllers/NostrController.cs @@ -43,16 +43,16 @@ public class NostrController : Controller .AsNoTracking() .ToListAsync(); - var latestEvent = await _db.Streams - .AsNoTracking() - .Where(a => a.User.PubKey == user.PubKey) - .OrderByDescending(a => a.Starts) - .Select(a => a.Event) - .FirstOrDefaultAsync(); - var account = new Account { - Event = !string.IsNullOrEmpty(latestEvent) ? JsonConvert.DeserializeObject(latestEvent, NostrSerializer.Settings) : null, + Event = new PatchEvent() + { + Title = user.Title ?? "", + Summary = user.Summary ?? "", + Image = user.Image ?? "", + ContentWarning = user.ContentWarning, + Tags = user.SplitTags() + }, Endpoints = endpoints.Select(a => new AccountEndpoint { Name = a.Name, @@ -65,7 +65,12 @@ public class NostrController : Controller Rate = a.Cost / 1000d } }).ToList(), - Balance = (long)Math.Floor(user.Balance / 1000m) + Balance = (long)Math.Floor(user.Balance / 1000m), + Tos = new() + { + Accepted = user.TosAccepted >= _config.TosDate, + Link = new Uri(_config.ApiHost, "/tos") + } }; return Content(JsonConvert.SerializeObject(account, NostrSerializer.Settings), "application/json"); @@ -77,8 +82,17 @@ public class NostrController : Controller var pubkey = GetPubKey(); if (string.IsNullOrEmpty(pubkey)) return Unauthorized(); - var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey); - await streamManager.PatchEvent(req.Title, req.Summary, req.Image, req.Tags, req.ContentWarning); + await _userService.UpdateStreamInfo(pubkey, req); + try + { + var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey); + await streamManager.UpdateEvent(); + } + catch + { + //ignore + } + return Accepted(); } @@ -95,6 +109,23 @@ public class NostrController : Controller }); } + [HttpPatch("account")] + public async Task PatchAccount([FromBody] PatchAccount patch) + { + var user = await GetUser(); + if (user == default) + { + return NotFound(); + } + + if (patch.AcceptTos) + { + await _userService.AcceptTos(user.PubKey); + } + + return Accepted(); + } + private async Task GetUser() { var pk = GetPubKey(); diff --git a/NostrStreamer/Database/Configuration/UserConfiguration.cs b/NostrStreamer/Database/Configuration/UserConfiguration.cs index dfe5a0c..fcd55bf 100644 --- a/NostrStreamer/Database/Configuration/UserConfiguration.cs +++ b/NostrStreamer/Database/Configuration/UserConfiguration.cs @@ -14,6 +14,9 @@ public class UserConfiguration : IEntityTypeConfiguration builder.Property(a => a.Balance) .IsRequired(); + builder.Property(a => a.TosAccepted) + .IsRequired(); + builder.Property(a => a.Version) .IsRowVersion(); diff --git a/NostrStreamer/Database/User.cs b/NostrStreamer/Database/User.cs index f6f6e90..775d6d5 100644 --- a/NostrStreamer/Database/User.cs +++ b/NostrStreamer/Database/User.cs @@ -38,6 +38,11 @@ public class User /// Any content warning tag (NIP-36) /// public string? ContentWarning { get; set; } + + /// + /// Date when user accepted TOS + /// + public DateTime? TosAccepted { get; init; } /// /// Concurrency token diff --git a/NostrStreamer/Extensions.cs b/NostrStreamer/Extensions.cs index cc38753..1b66d4e 100644 --- a/NostrStreamer/Extensions.cs +++ b/NostrStreamer/Extensions.cs @@ -20,7 +20,7 @@ public static class Extensions { return NostrPrivateKey.FromBech32(cfg.PrivateKey).DerivePublicKey().Hex; } - + public static NostrPrivateKey GetPrivateKey(this Config cfg) { return NostrPrivateKey.FromBech32(cfg.PrivateKey); @@ -32,7 +32,7 @@ public static class Extensions .Where(a => a.StartsWith("variant")) .Select(Variant.FromString).ToList(); } - + public static AmazonS3Client CreateClient(this S3BlobConfig c) { return new AmazonS3Client(new BasicAWSCredentials(c.AccessKey, c.SecretKey), @@ -44,6 +44,12 @@ public static class Extensions ForcePathStyle = true }); } + + public static string[] SplitTags(this User user) + { + return !string.IsNullOrEmpty(user.Tags) ? + user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty(); + } } public class Variant diff --git a/NostrStreamer/Migrations/20230801110903_TOS.Designer.cs b/NostrStreamer/Migrations/20230801110903_TOS.Designer.cs new file mode 100644 index 0000000..b723675 --- /dev/null +++ b/NostrStreamer/Migrations/20230801110903_TOS.Designer.cs @@ -0,0 +1,314 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NostrStreamer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NostrStreamer.Migrations +{ + [DbContext(typeof(StreamerContext))] + [Migration("20230801110903_TOS")] + partial class TOS + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("NostrStreamer.Database.IngestEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("App") + .IsRequired() + .HasColumnType("text"); + + b.Property>("Capabilities") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("Cost") + .HasColumnType("integer"); + + b.Property("Forward") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("App") + .IsUnique(); + + b.ToTable("Endpoints"); + }); + + modelBuilder.Entity("NostrStreamer.Database.Payment", b => + { + b.Property("PaymentHash") + .HasColumnType("text"); + + b.Property("Amount") + .HasColumnType("numeric(20,0)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Invoice") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsPaid") + .HasColumnType("boolean"); + + b.Property("Nostr") + .HasColumnType("text"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("PaymentHash"); + + b.HasIndex("PubKey"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Property("PubKey") + .HasColumnType("text"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("ContentWarning") + .HasColumnType("text"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("StreamKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summary") + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TosAccepted") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("PubKey"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EdgeIp") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndpointId") + .HasColumnType("uuid"); + + b.Property("Ends") + .HasColumnType("timestamp with time zone"); + + b.Property("Event") + .IsRequired() + .HasColumnType("text"); + + b.Property("ForwardClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recording") + .HasColumnType("text"); + + b.Property("Starts") + .HasColumnType("timestamp with time zone"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("StreamId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EndpointId"); + + b.HasIndex("PubKey"); + + b.ToTable("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Relay") + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("Sig") + .HasColumnType("text"); + + b.Property("StreamId") + .HasColumnType("uuid"); + + b.Property("ZapSplit") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("StreamId", "PubKey") + .IsUnique(); + + b.ToTable("Guests"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("double precision"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserStreamId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserStreamId"); + + b.ToTable("Recordings"); + }); + + modelBuilder.Entity("NostrStreamer.Database.Payment", b => + { + b.HasOne("NostrStreamer.Database.User", "User") + .WithMany("Payments") + .HasForeignKey("PubKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.HasOne("NostrStreamer.Database.IngestEndpoint", "Endpoint") + .WithMany() + .HasForeignKey("EndpointId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NostrStreamer.Database.User", "User") + .WithMany("Streams") + .HasForeignKey("PubKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Endpoint"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b => + { + b.HasOne("NostrStreamer.Database.UserStream", "Stream") + .WithMany("Guests") + .HasForeignKey("StreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Stream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b => + { + b.HasOne("NostrStreamer.Database.UserStream", "Stream") + .WithMany() + .HasForeignKey("UserStreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Stream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Navigation("Payments"); + + b.Navigation("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.Navigation("Guests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/NostrStreamer/Migrations/20230801110903_TOS.cs b/NostrStreamer/Migrations/20230801110903_TOS.cs new file mode 100644 index 0000000..7d21361 --- /dev/null +++ b/NostrStreamer/Migrations/20230801110903_TOS.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NostrStreamer.Migrations +{ + /// + public partial class TOS : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TosAccepted", + table: "Users", + type: "timestamp with time zone", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TosAccepted", + table: "Users"); + } + } +} diff --git a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs index d72d334..0497052 100644 --- a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs +++ b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs @@ -118,6 +118,10 @@ namespace NostrStreamer.Migrations b.Property("Title") .HasColumnType("text"); + b.Property("TosAccepted") + .IsRequired() + .HasColumnType("timestamp with time zone"); + b.Property("Version") .IsConcurrencyToken() .ValueGeneratedOnAddOrUpdate() @@ -282,7 +286,7 @@ namespace NostrStreamer.Migrations modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b => { b.HasOne("NostrStreamer.Database.UserStream", "Stream") - .WithMany("Recordings") + .WithMany() .HasForeignKey("UserStreamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -300,8 +304,6 @@ namespace NostrStreamer.Migrations modelBuilder.Entity("NostrStreamer.Database.UserStream", b => { b.Navigation("Guests"); - - b.Navigation("Recordings"); }); #pragma warning restore 612, 618 } diff --git a/NostrStreamer/NostrStreamer.csproj b/NostrStreamer/NostrStreamer.csproj index 1caa582..25eb93e 100644 --- a/NostrStreamer/NostrStreamer.csproj +++ b/NostrStreamer/NostrStreamer.csproj @@ -48,8 +48,4 @@ - - - - diff --git a/NostrStreamer/Pages/Tos.cshtml b/NostrStreamer/Pages/Tos.cshtml new file mode 100644 index 0000000..c1b513b --- /dev/null +++ b/NostrStreamer/Pages/Tos.cshtml @@ -0,0 +1,63 @@ +@page +@using NostrStreamer +@model NostrStreamer.Pages.Tos +@inject Config Config +@{ + Layout = null; +} + + + + + + api.zap.stream Terms of Service + + + +

api.zap.stream Terms of Service

+

+ Effective Date: @Config.TosDate.ToString("u")

+

+ Welcome to zap.stream! By using our streaming service, you agree to the following Terms of Service ("Terms"). + Please read them carefully. +

+
    +
  1. + User Accounts: You are responsible for maintaining the security of your account and any activities that occur + under it. +
  2. +
  3. + User Conduct: You agree not to violate any laws, infringe upon others' rights, or engage in fraudulent or + harmful activities while using our service. +
  4. +
  5. + Prohibited Content: You may not stream or upload copyrighted content unless you have obtained the necessary + rights or permissions. +
  6. +
  7. + Termination: We reserve the right to suspend or terminate your access to the service if you violate these + Terms. +
  8. +
  9. + Disclaimer: The zap.stream service is provided "as is" without any warranties. +
  10. +
  11. + Limitation of Liability: We shall not be liable for any damages arising from your use of the service. +
  12. +
  13. + Governing Law: These Terms shall be governed by and construed in accordance with the applicable local laws, + regulations, and jurisdiction where the service is accessed and used. +
  14. +
  15. + Modifications: We may update these Terms from time to time, and your continued use of the service constitutes + acceptance of the modified Terms. +
  16. +
  17. + Entire Agreement: These Terms constitute the entire agreement between you and zap.stream. +
  18. +
+

If you have any questions or concerns, please contact us at info@zap.stream.

+

By using zap.stream, you agree to abide by these Terms of Service. Enjoy your streaming experience!

+ + + diff --git a/NostrStreamer/Pages/Tos.cshtml.cs b/NostrStreamer/Pages/Tos.cshtml.cs new file mode 100644 index 0000000..441fc0b --- /dev/null +++ b/NostrStreamer/Pages/Tos.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace NostrStreamer.Pages; + +public class Tos : PageModel +{ + public void OnGet() + { + + } +} diff --git a/NostrStreamer/Program.cs b/NostrStreamer/Program.cs index 6e33e15..86daa4a 100644 --- a/NostrStreamer/Program.cs +++ b/NostrStreamer/Program.cs @@ -23,6 +23,7 @@ internal static class Program services.AddCors(); services.AddMemoryCache(); services.AddHttpClient(); + services.AddRazorPages(); services.AddControllers().AddNewtonsoftJson(); services.AddSingleton(config); @@ -75,6 +76,7 @@ internal static class Program app.UseCors(o => o.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); app.UseAuthorization(); + app.MapRazorPages(); app.MapControllers(); await app.RunAsync(); diff --git a/NostrStreamer/Services/StreamEventBuilder.cs b/NostrStreamer/Services/StreamEventBuilder.cs index ea52673..df0eee6 100644 --- a/NostrStreamer/Services/StreamEventBuilder.cs +++ b/NostrStreamer/Services/StreamEventBuilder.cs @@ -61,8 +61,7 @@ public class StreamEventBuilder tags.Add(new("recording", new Uri(_config.DataHost, $"recording/{stream.Id}.m3u8").ToString())); } - foreach (var tag in !string.IsNullOrEmpty(user.Tags) ? - user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty()) + foreach (var tag in user.SplitTags()) { tags.Add(new("t", tag)); } diff --git a/NostrStreamer/Services/StreamManager/IStreamManager.cs b/NostrStreamer/Services/StreamManager/IStreamManager.cs index 7477581..5945486 100644 --- a/NostrStreamer/Services/StreamManager/IStreamManager.cs +++ b/NostrStreamer/Services/StreamManager/IStreamManager.cs @@ -9,6 +9,12 @@ public interface IStreamManager ///
/// UserStream GetStream(); + + /// + /// Test if streaming is allowed for this user, otherwise throw + /// + /// Throws if cant stream + void TestCanStream(); /// /// Stream ingress check on srs-edge @@ -35,17 +41,6 @@ public interface IStreamManager /// Task ConsumeQuota(double duration); - /// - /// Update stream details - /// - /// - /// - /// - /// - /// - /// - Task PatchEvent(string? title, string? summary, string? image, string[]? tags, string? contentWarning); - /// /// Update viewer count /// diff --git a/NostrStreamer/Services/StreamManager/NostrStreamManager.cs b/NostrStreamer/Services/StreamManager/NostrStreamManager.cs index 1712725..e237235 100644 --- a/NostrStreamer/Services/StreamManager/NostrStreamManager.cs +++ b/NostrStreamer/Services/StreamManager/NostrStreamManager.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Nostr.Client.Json; @@ -14,15 +13,16 @@ public class NostrStreamManager : IStreamManager private readonly StreamEventBuilder _eventBuilder; private readonly IDvrStore _dvrStore; private readonly ThumbnailService _thumbnailService; + private readonly Config _config; - public NostrStreamManager(ILogger logger, StreamManagerContext context, - StreamEventBuilder eventBuilder, IDvrStore dvrStore, ThumbnailService thumbnailService) + public NostrStreamManager(ILogger logger, StreamManagerContext context, IServiceProvider serviceProvider) { _logger = logger; _context = context; - _eventBuilder = eventBuilder; - _dvrStore = dvrStore; - _thumbnailService = thumbnailService; + _eventBuilder = serviceProvider.GetRequiredService(); + _dvrStore = serviceProvider.GetRequiredService(); + _thumbnailService = serviceProvider.GetRequiredService(); + _config = serviceProvider.GetRequiredService(); } public UserStream GetStream() @@ -30,13 +30,22 @@ public class NostrStreamManager : IStreamManager return _context.UserStream; } - public Task> OnForward() + public void TestCanStream() { if (_context.User.Balance <= 0) { throw new LowBalanceException("User balance empty"); } + if (_context.User.TosAccepted == null || _context.User.TosAccepted < _config.TosDate) + { + throw new Exception("TOS not accepted"); + } + } + + public Task> OnForward() + { + TestCanStream(); return Task.FromResult(new List { $"rtmp://127.0.0.1:1935/{_context.UserStream.Endpoint.App}/{_context.User.StreamKey}?vhost={_context.UserStream.Endpoint.Forward}" @@ -46,12 +55,7 @@ public class NostrStreamManager : IStreamManager public async Task StreamStarted() { _logger.LogInformation("Stream started for: {pubkey}", _context.User.PubKey); - - if (_context.User.Balance <= 0) - { - throw new Exception("User balance empty"); - } - + TestCanStream(); await UpdateStreamState(UserStreamState.Live); #pragma warning disable CS4014 @@ -95,31 +99,6 @@ public class NostrStreamManager : IStreamManager } } - public async Task PatchEvent(string? title, string? summary, string? image, string[]? tags, string? contentWarning) - { - var user = _context.User; - - await _context.Db.Users - .Where(a => a.PubKey == _context.User.PubKey) - .ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, title) - .SetProperty(v => v.Summary, summary) - .SetProperty(v => v.Image, image) - .SetProperty(v => v.Tags, tags != null ? string.Join(",", tags) : null) - .SetProperty(v => v.ContentWarning, contentWarning)); - - user.Title = title; - user.Summary = summary; - user.Image = image; - user.Tags = tags != null ? string.Join(",", tags) : null; - user.ContentWarning = contentWarning; - - var ev = _eventBuilder.CreateStreamEvent(user, _context.UserStream); - await _context.Db.Streams.Where(a => a.Id == _context.UserStream.Id) - .ExecuteUpdateAsync(o => o.SetProperty(v => v.Event, JsonConvert.SerializeObject(ev, NostrSerializer.Settings))); - - _eventBuilder.BroadcastEvent(ev); - } - public async Task AddGuest(string pubkey, string role, decimal zapSplit) { _context.Db.Guests.Add(new() diff --git a/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs b/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs index b00102d..30f5faa 100644 --- a/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs +++ b/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs @@ -2,7 +2,6 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Nostr.Client.Json; using NostrStreamer.Database; -using NostrStreamer.Services.Dvr; namespace NostrStreamer.Services.StreamManager; @@ -12,18 +11,16 @@ public class StreamManagerFactory private readonly ILoggerFactory _loggerFactory; private readonly StreamEventBuilder _eventBuilder; private readonly IServiceProvider _serviceProvider; - private readonly IDvrStore _dvrStore; - private readonly ThumbnailService _thumbnailService; + private readonly Config _config; public StreamManagerFactory(StreamerContext db, ILoggerFactory loggerFactory, StreamEventBuilder eventBuilder, - IServiceProvider serviceProvider, IDvrStore dvrStore, ThumbnailService thumbnailService) + IServiceProvider serviceProvider, Config config) { _db = db; _loggerFactory = loggerFactory; _eventBuilder = eventBuilder; _serviceProvider = serviceProvider; - _dvrStore = dvrStore; - _thumbnailService = thumbnailService; + _config = config; } public async Task CreateStream(StreamInfo info) @@ -47,7 +44,12 @@ public class StreamManagerFactory if (user.Balance <= 0) { - throw new Exception("Cannot start stream with empty balance"); + throw new LowBalanceException("Cannot start stream with empty balance"); + } + + if (user.TosAccepted == null || user.TosAccepted < _config.TosDate) + { + throw new Exception("TOS not accepted"); } var stream = new UserStream @@ -82,7 +84,7 @@ public class StreamManagerFactory EdgeApi = new SrsApi(_serviceProvider.GetRequiredService(), new Uri($"http://{stream.EdgeIp}:1985")) }; - return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _eventBuilder, _dvrStore, _thumbnailService); + return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _serviceProvider); } public async Task ForStream(Guid id) @@ -102,7 +104,7 @@ public class StreamManagerFactory EdgeApi = new SrsApi(_serviceProvider.GetRequiredService(), new Uri($"http://{stream.EdgeIp}:1985")) }; - return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _eventBuilder, _dvrStore, _thumbnailService); + return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _serviceProvider); } public async Task ForCurrentStream(string pubkey) @@ -122,7 +124,7 @@ public class StreamManagerFactory EdgeApi = new SrsApi(_serviceProvider.GetRequiredService(), new Uri($"http://{stream.EdgeIp}:1985")) }; - return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _eventBuilder, _dvrStore, _thumbnailService); + return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _serviceProvider); } public async Task ForStream(StreamInfo info) @@ -150,6 +152,6 @@ public class StreamManagerFactory EdgeApi = new SrsApi(_serviceProvider.GetRequiredService(), new Uri($"http://{stream.EdgeIp}:1985")) }; - return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _eventBuilder, _dvrStore, _thumbnailService); + return new NostrStreamManager(_loggerFactory.CreateLogger(), ctx, _serviceProvider); } } diff --git a/NostrStreamer/Services/UserService.cs b/NostrStreamer/Services/UserService.cs index b5402b8..be21dad 100644 --- a/NostrStreamer/Services/UserService.cs +++ b/NostrStreamer/Services/UserService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Nostr.Client.Utils; +using NostrStreamer.ApiModel; using NostrStreamer.Database; namespace NostrStreamer.Services; @@ -69,4 +70,23 @@ public class UserService return await _db.Users.AsNoTracking() .SingleOrDefaultAsync(a => a.PubKey.Equals(pubkey)); } + + public async Task AcceptTos(string pubkey) + { + var change = await _db.Users.Where(a => a.PubKey.Equals(pubkey)) + .ExecuteUpdateAsync(o => o.SetProperty(v => v.TosAccepted, DateTime.UtcNow)); + + if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated."); + } + + public async Task UpdateStreamInfo(string pubkey, PatchEvent req) + { + await _db.Users + .Where(a => a.PubKey == pubkey) + .ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, req.Title) + .SetProperty(v => v.Summary, req.Summary) + .SetProperty(v => v.Image, req.Image) + .SetProperty(v => v.Tags, req.Tags.Length > 0 ? string.Join(",", req.Tags) : null) + .SetProperty(v => v.ContentWarning, req.ContentWarning)); + } } diff --git a/NostrStreamer/appsettings.json b/NostrStreamer/appsettings.json index 28aae52..f29d132 100644 --- a/NostrStreamer/appsettings.json +++ b/NostrStreamer/appsettings.json @@ -17,6 +17,7 @@ "SrsApiHost": "http://localhost:9002", "DataHost": "http://localhost:5295/api/playlist/", "ApiHost": "http://localhost:5295", + "TosDate": "2023-07-06", "Relays": [ "ws://localhost:8081" ],