Stream forwarding
This commit is contained in:
@ -14,7 +14,10 @@ public class Account
|
|||||||
public long Balance { get; init; }
|
public long Balance { get; init; }
|
||||||
|
|
||||||
[JsonProperty("tos")]
|
[JsonProperty("tos")]
|
||||||
public AccountTos Tos { get; init; }
|
public AccountTos Tos { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("forwards")]
|
||||||
|
public List<ForwardDest> Forwards { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountEndpoint
|
public class AccountEndpoint
|
||||||
@ -52,3 +55,12 @@ public class AccountTos
|
|||||||
[JsonProperty("link")]
|
[JsonProperty("link")]
|
||||||
public Uri Link { get; init; } = null!;
|
public Uri Link { get; init; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ForwardDest
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public Guid Id { get;init; }
|
||||||
|
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; init; } = null!;
|
||||||
|
}
|
12
NostrStreamer/ApiModel/NewForwardRequest.cs
Normal file
12
NostrStreamer/ApiModel/NewForwardRequest.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NostrStreamer.ApiModel;
|
||||||
|
|
||||||
|
public class NewForwardRequest
|
||||||
|
{
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("target")]
|
||||||
|
public string Target { get; init; } = null!;
|
||||||
|
}
|
@ -46,6 +46,8 @@ public class Config
|
|||||||
public List<EdgeLocation> Edges { get; init; } = new();
|
public List<EdgeLocation> Edges { get; init; } = new();
|
||||||
|
|
||||||
public TwitchApi Twitch { get; init; } = null!;
|
public TwitchApi Twitch { get; init; } = null!;
|
||||||
|
|
||||||
|
public string DataProtectionKeyPath { get; init; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TwitchApi
|
public class TwitchApi
|
||||||
|
@ -72,7 +72,12 @@ public class NostrController : Controller
|
|||||||
{
|
{
|
||||||
Accepted = user.TosAccepted >= _config.TosDate,
|
Accepted = user.TosAccepted >= _config.TosDate,
|
||||||
Link = new Uri(_config.ApiHost, "/tos")
|
Link = new Uri(_config.ApiHost, "/tos")
|
||||||
}
|
},
|
||||||
|
Forwards = user.Forwards.Select(a => new ForwardDest()
|
||||||
|
{
|
||||||
|
Id = a.Id,
|
||||||
|
Name = a.Name
|
||||||
|
}).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
return Content(JsonConvert.SerializeObject(account, NostrSerializer.Settings), "application/json");
|
return Content(JsonConvert.SerializeObject(account, NostrSerializer.Settings), "application/json");
|
||||||
@ -128,6 +133,34 @@ public class NostrController : Controller
|
|||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("account/forward")]
|
||||||
|
public async Task<IActionResult> AddForward([FromBody] NewForwardRequest req)
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
if (user == default)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _userService.AddForward(user.PubKey, req.Name, req.Target);
|
||||||
|
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("account/forward/{id:guid}")]
|
||||||
|
public async Task<IActionResult> DeleteForward([FromRoute] Guid id)
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
if (user == default)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _userService.RemoveForward(user.PubKey, id);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<User?> GetUser()
|
private async Task<User?> GetUser()
|
||||||
{
|
{
|
||||||
var pk = GetPubKey();
|
var pk = GetPubKey();
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Database.Configuration;
|
||||||
|
|
||||||
|
public class UserStreamForwardsConfiguration : IEntityTypeConfiguration<UserStreamForwards>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UserStreamForwards> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Name);
|
||||||
|
builder.Property(a => a.Target);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany(a => a.Forwards)
|
||||||
|
.HasForeignKey(a => a.UserPubkey)
|
||||||
|
.HasPrincipalKey(a => a.PubKey);
|
||||||
|
}
|
||||||
|
}
|
@ -29,4 +29,6 @@ public class StreamerContext : DbContext
|
|||||||
public DbSet<IngestEndpoint> Endpoints => Set<IngestEndpoint>();
|
public DbSet<IngestEndpoint> Endpoints => Set<IngestEndpoint>();
|
||||||
|
|
||||||
public DbSet<UserStreamRecording> Recordings => Set<UserStreamRecording>();
|
public DbSet<UserStreamRecording> Recordings => Set<UserStreamRecording>();
|
||||||
|
|
||||||
|
public DbSet<UserStreamForwards> Forwards => Set<UserStreamForwards>();
|
||||||
}
|
}
|
||||||
|
@ -56,4 +56,5 @@ public class User
|
|||||||
|
|
||||||
public List<Payment> Payments { get; init; } = new();
|
public List<Payment> Payments { get; init; } = new();
|
||||||
public List<UserStream> Streams { get; init; } = new();
|
public List<UserStream> Streams { get; init; } = new();
|
||||||
|
public List<UserStreamForwards> Forwards { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
13
NostrStreamer/Database/UserStreamForwards.cs
Normal file
13
NostrStreamer/Database/UserStreamForwards.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace NostrStreamer.Database;
|
||||||
|
|
||||||
|
public class UserStreamForwards
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string UserPubkey { get; init; } = null!;
|
||||||
|
public User User { get; init; } = null!;
|
||||||
|
|
||||||
|
public string Name { get; init; } = null!;
|
||||||
|
|
||||||
|
public string Target { get; init; } = null!;
|
||||||
|
}
|
356
NostrStreamer/Migrations/20231207193950_Forwards.Designer.cs
generated
Normal file
356
NostrStreamer/Migrations/20231207193950_Forwards.Designer.cs
generated
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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("20231207193950_Forwards")]
|
||||||
|
partial class Forwards
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("App")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Capabilities")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Cost")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Forward")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("App")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Endpoints");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.Payment", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("PaymentHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Invoice")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPaid")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Nostr")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PaymentHash");
|
||||||
|
|
||||||
|
b.HasIndex("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Payments");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("Balance")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ContentWarning")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Goal")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Image")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("StreamKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("TosAccepted")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<uint>("Version")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("xid")
|
||||||
|
.HasColumnName("xmin");
|
||||||
|
|
||||||
|
b.HasKey("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EdgeIp")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EndpointId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Ends")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Event")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ForwardClientId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSegment")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Starts")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("StreamId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Thumbnail")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EndpointId");
|
||||||
|
|
||||||
|
b.HasIndex("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Target")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("Forwards");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Relay")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Sig")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("StreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("ZapSplit")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StreamId", "PubKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Guests");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<double>("Duration")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Timestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("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.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()
|
||||||
|
.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");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
NostrStreamer/Migrations/20231207193950_Forwards.cs
Normal file
47
NostrStreamer/Migrations/20231207193950_Forwards.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NostrStreamer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Forwards : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Forwards",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserPubkey = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Target = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Forwards", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Forwards_Users_UserPubkey",
|
||||||
|
column: x => x.UserPubkey,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "PubKey",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Forwards_UserPubkey",
|
||||||
|
table: "Forwards",
|
||||||
|
column: "UserPubkey");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Forwards");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -187,6 +187,31 @@ namespace NostrStreamer.Migrations
|
|||||||
b.ToTable("Streams");
|
b.ToTable("Streams");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamForwards", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Target")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("Forwards");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -276,6 +301,17 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 =>
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||||
@ -300,6 +336,8 @@ namespace NostrStreamer.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Forwards");
|
||||||
|
|
||||||
b.Navigation("Payments");
|
b.Navigation("Payments");
|
||||||
|
|
||||||
b.Navigation("Streams");
|
b.Navigation("Streams");
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<PackageReference Include="LNURL" Version="0.0.30" />
|
<PackageReference Include="LNURL" Version="0.0.30" />
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||||
<PackageReference Include="MediaFormatLibrary.Lib" Version="1.0.0" />
|
<PackageReference Include="MediaFormatLibrary.Lib" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.19" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.19" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8">
|
||||||
|
@ -2,6 +2,7 @@ using System.Security.Claims;
|
|||||||
using MaxMind.GeoIP2;
|
using MaxMind.GeoIP2;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Nostr.Client.Client;
|
using Nostr.Client.Client;
|
||||||
using NostrStreamer.Database;
|
using NostrStreamer.Database;
|
||||||
@ -51,6 +52,9 @@ internal static class Program
|
|||||||
}, new[] {NostrAuth.Scheme});
|
}, new[] {NostrAuth.Scheme});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddDataProtection()
|
||||||
|
.PersistKeysToFileSystem(new DirectoryInfo(config.DataProtectionKeyPath));
|
||||||
|
|
||||||
// nostr services
|
// nostr services
|
||||||
services.AddSingleton<NostrMultiWebsocketClient>();
|
services.AddSingleton<NostrMultiWebsocketClient>();
|
||||||
services.AddSingleton<INostrClient>(s => s.GetRequiredService<NostrMultiWebsocketClient>());
|
services.AddSingleton<INostrClient>(s => s.GetRequiredService<NostrMultiWebsocketClient>());
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nostr.Client.Json;
|
using Nostr.Client.Json;
|
||||||
@ -13,6 +14,7 @@ public class NostrStreamManager : IStreamManager
|
|||||||
private readonly StreamEventBuilder _eventBuilder;
|
private readonly StreamEventBuilder _eventBuilder;
|
||||||
private readonly IDvrStore _dvrStore;
|
private readonly IDvrStore _dvrStore;
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
|
private readonly IDataProtectionProvider _dataProtectionProvider;
|
||||||
|
|
||||||
public NostrStreamManager(ILogger<NostrStreamManager> logger, StreamManagerContext context, IServiceProvider serviceProvider)
|
public NostrStreamManager(ILogger<NostrStreamManager> logger, StreamManagerContext context, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
@ -21,6 +23,7 @@ public class NostrStreamManager : IStreamManager
|
|||||||
_eventBuilder = serviceProvider.GetRequiredService<StreamEventBuilder>();
|
_eventBuilder = serviceProvider.GetRequiredService<StreamEventBuilder>();
|
||||||
_dvrStore = serviceProvider.GetRequiredService<IDvrStore>();
|
_dvrStore = serviceProvider.GetRequiredService<IDvrStore>();
|
||||||
_config = serviceProvider.GetRequiredService<Config>();
|
_config = serviceProvider.GetRequiredService<Config>();
|
||||||
|
_dataProtectionProvider = serviceProvider.GetRequiredService<IDataProtectionProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserStream GetStream()
|
public UserStream GetStream()
|
||||||
@ -44,10 +47,26 @@ public class NostrStreamManager : IStreamManager
|
|||||||
public Task<List<string>> OnForward()
|
public Task<List<string>> OnForward()
|
||||||
{
|
{
|
||||||
TestCanStream();
|
TestCanStream();
|
||||||
return Task.FromResult(new List<string>
|
var fwds = new List<string>
|
||||||
{
|
{
|
||||||
$"rtmp://127.0.0.1:1935/{_context.UserStream.Endpoint.App}/{_context.User.StreamKey}?vhost={_context.UserStream.Endpoint.Forward}"
|
$"rtmp://127.0.0.1:1935/{_context.UserStream.Endpoint.App}/{_context.User.StreamKey}?vhost={_context.UserStream.Endpoint.Forward}"
|
||||||
});
|
};
|
||||||
|
|
||||||
|
var dataProtector = _dataProtectionProvider.CreateProtector("forward-targets");
|
||||||
|
foreach (var f in _context.User.Forwards)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var target = dataProtector.Unprotect(f.Target);
|
||||||
|
fwds.Add(target);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to decrypt forward target {id} {msg}", f.Id, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(fwds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StreamStarted()
|
public async Task StreamStarted()
|
||||||
|
@ -27,6 +27,7 @@ public class StreamManagerFactory
|
|||||||
{
|
{
|
||||||
var user = await _db.Users
|
var user = await _db.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.Include(a => a.Forwards)
|
||||||
.SingleOrDefaultAsync(a => a.StreamKey.Equals(info.StreamKey));
|
.SingleOrDefaultAsync(a => a.StreamKey.Equals(info.StreamKey));
|
||||||
|
|
||||||
if (user == default) throw new Exception("No user found");
|
if (user == default) throw new Exception("No user found");
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Nostr.Client.Utils;
|
using Nostr.Client.Utils;
|
||||||
using NostrStreamer.ApiModel;
|
using NostrStreamer.ApiModel;
|
||||||
@ -11,11 +12,13 @@ public class UserService
|
|||||||
{
|
{
|
||||||
private readonly StreamerContext _db;
|
private readonly StreamerContext _db;
|
||||||
private readonly LndNode _lnd;
|
private readonly LndNode _lnd;
|
||||||
|
private readonly IDataProtectionProvider _dataProtector;
|
||||||
|
|
||||||
public UserService(StreamerContext db, LndNode lnd)
|
public UserService(StreamerContext db, LndNode lnd, IDataProtectionProvider dataProtector)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_lnd = lnd;
|
_lnd = lnd;
|
||||||
|
_dataProtector = dataProtector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -79,6 +82,7 @@ public class UserService
|
|||||||
public async Task<User?> GetUser(string pubkey)
|
public async Task<User?> GetUser(string pubkey)
|
||||||
{
|
{
|
||||||
return await _db.Users.AsNoTracking()
|
return await _db.Users.AsNoTracking()
|
||||||
|
.Include(a => a.Forwards)
|
||||||
.SingleOrDefaultAsync(a => a.PubKey.Equals(pubkey));
|
.SingleOrDefaultAsync(a => a.PubKey.Equals(pubkey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +94,25 @@ public class UserService
|
|||||||
if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated.");
|
if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddForward(string pubkey, string name, string dest)
|
||||||
|
{
|
||||||
|
var protector = _dataProtector.CreateProtector("forward-targets");
|
||||||
|
_db.Forwards.Add(new()
|
||||||
|
{
|
||||||
|
UserPubkey = pubkey,
|
||||||
|
Name = name,
|
||||||
|
Target = protector.Protect(dest)
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveForward(string pubkey, Guid id)
|
||||||
|
{
|
||||||
|
await _db.Forwards.Where(a => a.UserPubkey.Equals(pubkey) && a.Id == id)
|
||||||
|
.ExecuteDeleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateStreamInfo(string pubkey, PatchEvent req)
|
public async Task UpdateStreamInfo(string pubkey, PatchEvent req)
|
||||||
{
|
{
|
||||||
await _db.Users
|
await _db.Users
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"Twitch": {
|
"Twitch": {
|
||||||
"ClientId": "123",
|
"ClientId": "123",
|
||||||
"ClientSecret": "aaa"
|
"ClientSecret": "aaa"
|
||||||
}
|
},
|
||||||
|
"DataProtectionKeyPath": "./keys"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user