From 56feac7f9d1e2a65228a7bb7ed0d8456ff4b5ca4 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 26 Aug 2024 13:33:41 +0300 Subject: [PATCH] Account flags --- NostrStreamer/ApiModel/PatchAccount.cs | 8 +- NostrStreamer/Controllers/AdminController.cs | 38 +- NostrStreamer/Controllers/NostrController.cs | 8 +- .../Configuration/UserConfiguration.cs | 2 + NostrStreamer/Database/User.cs | 30 +- .../20240826103113_AccountFlags.Designer.cs | 475 ++++++++++++++++++ .../Migrations/20240826103113_AccountFlags.cs | 40 ++ .../StreamerContextModelSnapshot.cs | 6 + NostrStreamer/NostrAuth.cs | 27 +- .../StreamManager/StreamManagerFactory.cs | 5 + NostrStreamer/Services/UserService.cs | 17 +- 11 files changed, 631 insertions(+), 25 deletions(-) create mode 100644 NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs create mode 100644 NostrStreamer/Migrations/20240826103113_AccountFlags.cs diff --git a/NostrStreamer/ApiModel/PatchAccount.cs b/NostrStreamer/ApiModel/PatchAccount.cs index e2f6c90..bf79a26 100644 --- a/NostrStreamer/ApiModel/PatchAccount.cs +++ b/NostrStreamer/ApiModel/PatchAccount.cs @@ -5,5 +5,11 @@ namespace NostrStreamer.ApiModel; public class PatchAccount { [JsonProperty("accept_tos")] - public bool AcceptTos { get; init; } + public bool? AcceptTos { get; init; } + + [JsonProperty("blocked")] + public bool? Blocked { get; init; } + + [JsonProperty("admin")] + public bool? Admin { get; init; } } diff --git a/NostrStreamer/Controllers/AdminController.cs b/NostrStreamer/Controllers/AdminController.cs index ba3979d..24485ae 100644 --- a/NostrStreamer/Controllers/AdminController.cs +++ b/NostrStreamer/Controllers/AdminController.cs @@ -1,24 +1,56 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NostrStreamer.ApiModel; +using NostrStreamer.Services; +using NostrStreamer.Services.Dvr; using NostrStreamer.Services.StreamManager; namespace NostrStreamer.Controllers; +[Authorize(AuthenticationSchemes = NostrAuth.Scheme, Roles = NostrAuth.RoleAdmin)] [Route("/api/admin")] public class AdminController : Controller { private readonly ILogger _logger; private readonly StreamManagerFactory _streamManagerFactory; - - public AdminController(ILogger logger, StreamManagerFactory streamManagerFactory) + private readonly IDvrStore _dvrStore; + private readonly UserService _userService; + + public AdminController(ILogger logger, StreamManagerFactory streamManagerFactory, IDvrStore dvrStore, + UserService userService) { _logger = logger; _streamManagerFactory = streamManagerFactory; + _dvrStore = dvrStore; + _userService = userService; } - + [HttpPatch("stream/{id:guid}")] public async Task PublishEvent([FromRoute] Guid id) { var stream = await _streamManagerFactory.ForStream(id); await stream.UpdateEvent(); } + + [HttpDelete("stream/{id:guid}")] + public async Task DeleteEvent([FromRoute] Guid id) + { + var mgr = await _streamManagerFactory.ForStream(id); + var stream = mgr.GetStream(); + await _dvrStore.DeleteRecordings(stream); + } + + [HttpPatch("account/{pubkey}")] + public async Task UpdateAccount([FromRoute] string pubkey, [FromBody] PatchAccount req) + { + if (req.Blocked.HasValue) + { + await _userService.SetBlocked(pubkey, req.Blocked.Value); + } + + if (req.Admin.HasValue) + { + await _userService.SetAdmin(pubkey, req.Admin.Value); + } + } } diff --git a/NostrStreamer/Controllers/NostrController.cs b/NostrStreamer/Controllers/NostrController.cs index 075c4be..869ad5c 100644 --- a/NostrStreamer/Controllers/NostrController.cs +++ b/NostrStreamer/Controllers/NostrController.cs @@ -136,7 +136,7 @@ public class NostrController : Controller return NotFound(); } - if (patch.AcceptTos) + if (patch.AcceptTos.HasValue && patch.AcceptTos.Value) { await _userService.AcceptTos(user.PubKey); } @@ -267,7 +267,7 @@ public class NostrController : Controller var existing = await _db.PushSubscriptions.FirstOrDefaultAsync(a => a.Key == sub.Key); if (existing != default) { - return Json(new { id = existing.Id }); + return Json(new {id = existing.Id}); } var newId = Guid.NewGuid(); @@ -298,7 +298,7 @@ public class NostrController : Controller var sub = await _db.PushSubscriptionTargets .Join(_db.PushSubscriptions, a => a.SubscriberPubkey, b => b.Pubkey, - (a, b) => new { a.SubscriberPubkey, a.TargetPubkey, b.Auth }) + (a, b) => new {a.SubscriberPubkey, a.TargetPubkey, b.Auth}) .Where(a => a.SubscriberPubkey == userPubkey && a.Auth == auth) .Select(a => a.TargetPubkey) .ToListAsync(); @@ -415,4 +415,4 @@ public class NostrController : Controller var claim = HttpContext.User.Claims.FirstOrDefault(a => a.Type == ClaimTypes.Name); return claim!.Value; } -} \ No newline at end of file +} diff --git a/NostrStreamer/Database/Configuration/UserConfiguration.cs b/NostrStreamer/Database/Configuration/UserConfiguration.cs index 5898059..e9c8f12 100644 --- a/NostrStreamer/Database/Configuration/UserConfiguration.cs +++ b/NostrStreamer/Database/Configuration/UserConfiguration.cs @@ -22,5 +22,7 @@ public class UserConfiguration : IEntityTypeConfiguration builder.Property(a => a.Tags); builder.Property(a => a.ContentWarning); builder.Property(a => a.Goal); + builder.Property(a => a.IsAdmin); + builder.Property(a => a.IsBlocked); } } diff --git a/NostrStreamer/Database/User.cs b/NostrStreamer/Database/User.cs index 5bd0566..c368579 100644 --- a/NostrStreamer/Database/User.cs +++ b/NostrStreamer/Database/User.cs @@ -3,47 +3,47 @@ namespace NostrStreamer.Database; public class User { public string PubKey { get; init; } = null!; - + /// /// Stream key /// public string StreamKey { get; init; } = null!; - + /// /// Milli sats balance /// public long Balance { get; set; } - + /// /// Stream title /// public string? Title { get; set; } - + /// /// Stream summary /// public string? Summary { get; set; } - + /// /// Stream cover image /// public string? Image { get; set; } - + /// /// Comma seperated tags /// public string? Tags { get; set; } - + /// /// Any content warning tag (NIP-36) /// public string? ContentWarning { get; set; } - + /// /// Date when user accepted TOS /// public DateTime? TosAccepted { get; init; } - + /// /// Stream goal /// @@ -53,7 +53,17 @@ public class User /// Concurrency token /// public uint Version { get; set; } - + + /// + /// User is service admin + /// + public bool IsAdmin { get; set; } + + /// + /// User is blocked and cannot stream + /// + public bool IsBlocked { get; set; } + public List Payments { get; init; } = new(); public List Streams { get; init; } = new(); public List Forwards { get; init; } = new(); diff --git a/NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs b/NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs new file mode 100644 index 0000000..4da8f3d --- /dev/null +++ b/NostrStreamer/Migrations/20240826103113_AccountFlags.Designer.cs @@ -0,0 +1,475 @@ +// +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("20240826103113_AccountFlags")] + partial class AccountFlags + { + /// + 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("Fee") + .HasColumnType("numeric(20,0)"); + + b.Property("Invoice") + .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.PushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUsed") + .HasColumnType("timestamp with time zone"); + + b.Property("Pubkey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("NostrStreamer.Database.PushSubscriptionTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("SubscriberPubkey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TargetPubkey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TargetPubkey"); + + b.HasIndex("SubscriberPubkey", "TargetPubkey") + .IsUnique(); + + b.ToTable("PushSubscriptionTargets"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Property("PubKey") + .HasColumnType("text"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("ContentWarning") + .HasColumnType("text"); + + b.Property("Goal") + .HasColumnType("text"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("IsBlocked") + .HasColumnType("boolean"); + + b.Property("StreamKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summary") + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TosAccepted") + .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("LastSegment") + .HasColumnType("timestamp with time zone"); + + b.Property("Length") + .HasColumnType("numeric"); + + b.Property("MilliSatsCollected") + .HasColumnType("numeric"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Starts") + .HasColumnType("timestamp with time zone"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("StreamId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Thumbnail") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EndpointId"); + + b.HasIndex("PubKey"); + + b.ToTable("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamClip", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("TakenByPubkey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserStreamId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserStreamId"); + + b.ToTable("Clips"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Target") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserPubkey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserPubkey"); + + b.ToTable("Forwards"); + }); + + 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.UserStreamClip", b => + { + b.HasOne("NostrStreamer.Database.UserStream", "UserStream") + .WithMany() + .HasForeignKey("UserStreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("UserStream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b => + { + b.HasOne("NostrStreamer.Database.User", "User") + .WithMany("Forwards") + .HasForeignKey("UserPubkey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("Recordings") + .HasForeignKey("UserStreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Stream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Navigation("Forwards"); + + b.Navigation("Payments"); + + b.Navigation("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.Navigation("Guests"); + + b.Navigation("Recordings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/NostrStreamer/Migrations/20240826103113_AccountFlags.cs b/NostrStreamer/Migrations/20240826103113_AccountFlags.cs new file mode 100644 index 0000000..dd09707 --- /dev/null +++ b/NostrStreamer/Migrations/20240826103113_AccountFlags.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NostrStreamer.Migrations +{ + /// + public partial class AccountFlags : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsAdmin", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsBlocked", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsAdmin", + table: "Users"); + + migrationBuilder.DropColumn( + name: "IsBlocked", + table: "Users"); + } + } +} diff --git a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs index 878b14c..ae483ce 100644 --- a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs +++ b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs @@ -174,6 +174,12 @@ namespace NostrStreamer.Migrations b.Property("Image") .HasColumnType("text"); + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("IsBlocked") + .HasColumnType("boolean"); + b.Property("StreamKey") .IsRequired() .HasColumnType("text"); diff --git a/NostrStreamer/NostrAuth.cs b/NostrStreamer/NostrAuth.cs index fbd2ae4..f4026e8 100644 --- a/NostrStreamer/NostrAuth.cs +++ b/NostrStreamer/NostrAuth.cs @@ -2,16 +2,19 @@ using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Nostr.Client.Json; using Nostr.Client.Messages; +using NostrStreamer.Database; namespace NostrStreamer; public static class NostrAuth { public const string Scheme = "Nostr"; + public const string RoleAdmin = "admin"; } public class NostrAuthOptions : AuthenticationSchemeOptions @@ -20,9 +23,12 @@ public class NostrAuthOptions : AuthenticationSchemeOptions public class NostrAuthHandler : AuthenticationHandler { - public NostrAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : - base(options, logger, encoder, clock) + private readonly StreamerContext _db; + public NostrAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, + StreamerContext db) + : base(options, logger, encoder, clock) { + _db = db; } protected override async Task HandleAuthenticateAsync() @@ -69,14 +75,14 @@ public class NostrAuthHandler : AuthenticationHandler var urlTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "u"); var methodTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "method"); - if (string.IsNullOrEmpty(urlTag?.AdditionalData[0] as string) || - !new Uri((urlTag.AdditionalData[0] as string)!).AbsolutePath.Equals(Request.Path, StringComparison.InvariantCultureIgnoreCase)) + if (string.IsNullOrEmpty(urlTag?.AdditionalData[0]) || + !new Uri(urlTag.AdditionalData[0]).AbsolutePath.Equals(Request.Path, StringComparison.InvariantCultureIgnoreCase)) { return AuthenticateResult.Fail("Invalid nostr event, url tag invalid"); } - if (string.IsNullOrEmpty(methodTag?.AdditionalData[0] as string) || - !((methodTag.AdditionalData[0] as string)?.Equals(Request.Method, StringComparison.InvariantCultureIgnoreCase) ?? false)) + if (string.IsNullOrEmpty(methodTag?.AdditionalData[0]) || + !methodTag.AdditionalData[0].Equals(Request.Method, StringComparison.InvariantCultureIgnoreCase)) { return AuthenticateResult.Fail("Invalid nostr event, method tag invalid"); } @@ -86,6 +92,15 @@ public class NostrAuthHandler : AuthenticationHandler new Claim(ClaimTypes.Name, ev.Pubkey!) }); + var user = await _db.Users + .AsNoTracking() + .SingleOrDefaultAsync(a => a.PubKey == ev.Pubkey!); + + if (user is {IsAdmin: true}) + { + principal.AddClaim(new Claim(ClaimTypes.Role, NostrAuth.RoleAdmin)); + } + return AuthenticateResult.Success(new(new ClaimsPrincipal(new[] {principal}), Scheme.Name)); } } diff --git a/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs b/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs index e096856..56974a8 100644 --- a/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs +++ b/NostrStreamer/Services/StreamManager/StreamManagerFactory.cs @@ -48,6 +48,11 @@ public class StreamManagerFactory throw new Exception("TOS not accepted"); } + if (user.IsBlocked) + { + throw new Exception("User account blocked"); + } + var existingLive = await _db.Streams .SingleOrDefaultAsync(a => a.State == UserStreamState.Live && a.PubKey == user.PubKey); diff --git a/NostrStreamer/Services/UserService.cs b/NostrStreamer/Services/UserService.cs index 9f60543..8969d3a 100644 --- a/NostrStreamer/Services/UserService.cs +++ b/NostrStreamer/Services/UserService.cs @@ -105,6 +105,7 @@ public class UserService await _db.Users .Where(a => a.PubKey == pubkey) .ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - pr.MinimumAmount.MilliSatoshi)); + _db.Payments.Add(new() { PubKey = pubkey, @@ -134,6 +135,7 @@ public class UserService await _db.Users .Where(a => a.PubKey == pubkey) .ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - result.FeeSat)); + return (result.FeeMsat, result.PaymentPreimage); } @@ -145,6 +147,7 @@ public class UserService await _db.Users .Where(a => a.PubKey == pubkey) .ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance + pr.MinimumAmount.MilliSatoshi)); + throw; } } @@ -218,6 +221,18 @@ public class UserService if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated."); } + public async Task SetBlocked(string pubkey, bool val) + { + await _db.Users.Where(a => a.PubKey.Equals(pubkey)) + .ExecuteUpdateAsync(o => o.SetProperty(v => v.IsBlocked, val)); + } + + public async Task SetAdmin(string pubkey, bool val) + { + await _db.Users.Where(a => a.PubKey.Equals(pubkey)) + .ExecuteUpdateAsync(o => o.SetProperty(v => v.IsAdmin, val)); + } + public async Task AddForward(string pubkey, string name, string dest) { var protector = _dataProtector.CreateProtector("forward-targets"); @@ -248,4 +263,4 @@ public class UserService .SetProperty(v => v.ContentWarning, req.ContentWarning) .SetProperty(v => v.Goal, req.Goal)); } -} \ No newline at end of file +}