feat: stream-keys
This commit is contained in:
@ -4,9 +4,6 @@ namespace NostrStreamer.ApiModel;
|
|||||||
|
|
||||||
public class Account
|
public class Account
|
||||||
{
|
{
|
||||||
[JsonProperty("event")]
|
|
||||||
public PatchEvent? Event { get; init; }
|
|
||||||
|
|
||||||
[JsonProperty("endpoints")]
|
[JsonProperty("endpoints")]
|
||||||
public List<AccountEndpoint> Endpoints { get; init; } = new();
|
public List<AccountEndpoint> Endpoints { get; init; } = new();
|
||||||
|
|
||||||
|
8
NostrStreamer/ApiModel/CreateStreamKeyRequest.cs
Normal file
8
NostrStreamer/ApiModel/CreateStreamKeyRequest.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace NostrStreamer.ApiModel;
|
||||||
|
|
||||||
|
public class CreateStreamKeyRequest
|
||||||
|
{
|
||||||
|
public PatchEvent Event { get; init; } = null!;
|
||||||
|
|
||||||
|
public DateTime? Expires { get; init; }
|
||||||
|
}
|
@ -4,6 +4,9 @@ namespace NostrStreamer.ApiModel;
|
|||||||
|
|
||||||
public class PatchEvent
|
public class PatchEvent
|
||||||
{
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
[JsonProperty("title")]
|
[JsonProperty("title")]
|
||||||
public string Title { get; init; } = null!;
|
public string Title { get; init; } = null!;
|
||||||
|
|
||||||
|
@ -27,10 +27,12 @@ public class NostrController : Controller
|
|||||||
private readonly IClipService _clipService;
|
private readonly IClipService _clipService;
|
||||||
private readonly ILogger<NostrController> _logger;
|
private readonly ILogger<NostrController> _logger;
|
||||||
private readonly PushSender _pushSender;
|
private readonly PushSender _pushSender;
|
||||||
|
private readonly StreamEventBuilder _eventBuilder;
|
||||||
|
|
||||||
public NostrController(StreamerContext db, Config config, StreamManagerFactory streamManager,
|
public NostrController(StreamerContext db, Config config, StreamManagerFactory streamManager,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
IClipService clipService, ILogger<NostrController> logger, PushSender pushSender)
|
IClipService clipService, ILogger<NostrController> logger, PushSender pushSender,
|
||||||
|
StreamEventBuilder eventBuilder)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_config = config;
|
_config = config;
|
||||||
@ -39,6 +41,7 @@ public class NostrController : Controller
|
|||||||
_clipService = clipService;
|
_clipService = clipService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pushSender = pushSender;
|
_pushSender = pushSender;
|
||||||
|
_eventBuilder = eventBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("account")]
|
[HttpGet("account")]
|
||||||
@ -57,15 +60,6 @@ public class NostrController : Controller
|
|||||||
|
|
||||||
var account = new Account
|
var account = new Account
|
||||||
{
|
{
|
||||||
Event = new PatchEvent()
|
|
||||||
{
|
|
||||||
Title = user.Title ?? "",
|
|
||||||
Summary = user.Summary ?? "",
|
|
||||||
Image = user.Image ?? "",
|
|
||||||
ContentWarning = user.ContentWarning,
|
|
||||||
Tags = user.SplitTags(),
|
|
||||||
Goal = user.Goal
|
|
||||||
},
|
|
||||||
Endpoints = endpoints.Select(a => new AccountEndpoint
|
Endpoints = endpoints.Select(a => new AccountEndpoint
|
||||||
{
|
{
|
||||||
Name = a.Name,
|
Name = a.Name,
|
||||||
@ -100,9 +94,9 @@ public class NostrController : Controller
|
|||||||
var pubkey = GetPubKey();
|
var pubkey = GetPubKey();
|
||||||
if (string.IsNullOrEmpty(pubkey)) return Unauthorized();
|
if (string.IsNullOrEmpty(pubkey)) return Unauthorized();
|
||||||
|
|
||||||
await _userService.UpdateStreamInfo(pubkey, req);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _userService.UpdateStreamInfo(pubkey, req);
|
||||||
var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey);
|
var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey);
|
||||||
await streamManager.UpdateEvent();
|
await streamManager.UpdateEvent();
|
||||||
}
|
}
|
||||||
@ -404,6 +398,91 @@ public class NostrController : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("keys")]
|
||||||
|
public async Task<IActionResult> ListStreamKeys([FromQuery] int page = 0, [FromQuery] int pageSize = 100)
|
||||||
|
{
|
||||||
|
var userPubkey = GetPubKey();
|
||||||
|
if (string.IsNullOrEmpty(userPubkey))
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var keys = await _db.StreamKeys
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(a => a.UserStream)
|
||||||
|
.Where(a => a.UserPubkey == userPubkey)
|
||||||
|
.Skip(page * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(a =>
|
||||||
|
new
|
||||||
|
{
|
||||||
|
a.Id,
|
||||||
|
a.Created,
|
||||||
|
a.Key,
|
||||||
|
a.Expires,
|
||||||
|
Stream = a.UserStream.Event
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
items = keys,
|
||||||
|
page, pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
error = e.Message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("keys")]
|
||||||
|
public async Task<IActionResult> CreateStreamKey([FromBody] CreateStreamKeyRequest req)
|
||||||
|
{
|
||||||
|
var userPubkey = GetPubKey();
|
||||||
|
if (string.IsNullOrEmpty(userPubkey))
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newStream = new UserStream()
|
||||||
|
{
|
||||||
|
PubKey = userPubkey,
|
||||||
|
State = UserStreamState.Planned,
|
||||||
|
};
|
||||||
|
newStream.PatchStream(req.Event);
|
||||||
|
var ev = _eventBuilder.CreateStreamEvent(newStream);
|
||||||
|
newStream.Event = NostrJson.Serialize(ev) ?? "";
|
||||||
|
|
||||||
|
var newKey = new UserStreamKey()
|
||||||
|
{
|
||||||
|
Expires = req.Expires,
|
||||||
|
Key = Guid.NewGuid().ToString(),
|
||||||
|
StreamId = newStream.Id,
|
||||||
|
UserPubkey = userPubkey
|
||||||
|
};
|
||||||
|
_db.Streams.Add(newStream);
|
||||||
|
_db.StreamKeys.Add(newKey);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
newKey.Key,
|
||||||
|
newStream.Event
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
error = e.Message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<User?> GetUser()
|
private async Task<User?> GetUser()
|
||||||
{
|
{
|
||||||
var pk = GetPubKey();
|
var pk = GetPubKey();
|
||||||
|
@ -41,7 +41,7 @@ public class PlaylistController : Controller
|
|||||||
var streamManager = await _streamManagerFactory.ForStream(id);
|
var streamManager = await _streamManagerFactory.ForStream(id);
|
||||||
var userStream = streamManager.GetStream();
|
var userStream = streamManager.GetStream();
|
||||||
|
|
||||||
var path = $"/{userStream.Endpoint.App}/{variant}/{userStream.User.StreamKey}.m3u8";
|
var path = $"/{userStream.Endpoint.App}/{variant}/{userStream.Key}.m3u8";
|
||||||
var ub = new UriBuilder(_config.SrsHttpHost)
|
var ub = new UriBuilder(_config.SrsHttpHost)
|
||||||
{
|
{
|
||||||
Path = path,
|
Path = path,
|
||||||
@ -130,7 +130,7 @@ public class PlaylistController : Controller
|
|||||||
foreach (var variant in userStream.Endpoint.GetVariants().OrderBy(a => a.Bandwidth))
|
foreach (var variant in userStream.Endpoint.GetVariants().OrderBy(a => a.Bandwidth))
|
||||||
{
|
{
|
||||||
var stream = streams.FirstOrDefault(a =>
|
var stream = streams.FirstOrDefault(a =>
|
||||||
a.Name == userStream.User.StreamKey && a.App == $"{userStream.Endpoint.App}/{variant.SourceName}");
|
a.Name == userStream.Key && a.App == $"{userStream.Endpoint.App}/{variant.SourceName}");
|
||||||
|
|
||||||
var resArg = stream?.Video != default ? $"RESOLUTION={stream.Video?.Width}x{stream.Video?.Height}" :
|
var resArg = stream?.Video != default ? $"RESOLUTION={stream.Video?.Width}x{stream.Video?.Height}" :
|
||||||
variant.ToResolutionArg();
|
variant.ToResolutionArg();
|
||||||
@ -171,7 +171,7 @@ public class PlaylistController : Controller
|
|||||||
var streamManager = await _streamManagerFactory.ForStream(id);
|
var streamManager = await _streamManagerFactory.ForStream(id);
|
||||||
var userStream = streamManager.GetStream();
|
var userStream = streamManager.GetStream();
|
||||||
|
|
||||||
var path = $"/{userStream.Endpoint.App}/{variant}/{userStream.User.StreamKey}-{segment}";
|
var path = $"/{userStream.Endpoint.App}/{variant}/{userStream.Key}-{segment}";
|
||||||
await ProxyRequest(path);
|
await ProxyRequest(path);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -253,7 +253,7 @@ public class PlaylistController : Controller
|
|||||||
|
|
||||||
private async Task<string?> GetHlsCtx(UserStream stream)
|
private async Task<string?> GetHlsCtx(UserStream stream)
|
||||||
{
|
{
|
||||||
var path = $"/{stream.Endpoint.App}/source/{stream.User.StreamKey}.m3u8";
|
var path = $"/{stream.Endpoint.App}/source/{stream.Key}.m3u8";
|
||||||
var ub = new Uri(_config.SrsHttpHost, path);
|
var ub = new Uri(_config.SrsHttpHost, path);
|
||||||
var req = CreateProxyRequest(ub);
|
var req = CreateProxyRequest(ub);
|
||||||
using var rsp = await _client.SendAsync(req);
|
using var rsp = await _client.SendAsync(req);
|
||||||
|
@ -29,8 +29,8 @@ public class PodcastController(StreamerContext db, Config config) : Controller
|
|||||||
pod.LiveItem = new()
|
pod.LiveItem = new()
|
||||||
{
|
{
|
||||||
Guid = stream.Id,
|
Guid = stream.Id,
|
||||||
Title = stream.User.Title ?? "",
|
Title = stream.Title ?? "",
|
||||||
Description = stream.User.Summary,
|
Description = stream.Summary,
|
||||||
Status = stream.State.ToString().ToLower(),
|
Status = stream.State.ToString().ToLower(),
|
||||||
Start = stream.Starts,
|
Start = stream.Starts,
|
||||||
End = stream.Ends ?? new DateTime(),
|
End = stream.Ends ?? new DateTime(),
|
||||||
|
@ -8,8 +8,6 @@ public class UserStreamConfiguration : IEntityTypeConfiguration<UserStream>
|
|||||||
public void Configure(EntityTypeBuilder<UserStream> builder)
|
public void Configure(EntityTypeBuilder<UserStream> builder)
|
||||||
{
|
{
|
||||||
builder.HasKey(a => a.Id);
|
builder.HasKey(a => a.Id);
|
||||||
builder.Property(a => a.StreamId)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
builder.Property(a => a.Starts)
|
builder.Property(a => a.Starts)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -35,6 +33,13 @@ public class UserStreamConfiguration : IEntityTypeConfiguration<UserStream>
|
|||||||
|
|
||||||
builder.Property(a => a.AdmissionCost);
|
builder.Property(a => a.AdmissionCost);
|
||||||
|
|
||||||
|
builder.Property(a => a.Title);
|
||||||
|
builder.Property(a => a.Image);
|
||||||
|
builder.Property(a => a.Summary);
|
||||||
|
builder.Property(a => a.ContentWarning);
|
||||||
|
builder.Property(a => a.Tags);
|
||||||
|
builder.Property(a => a.Goal);
|
||||||
|
|
||||||
builder.HasOne(a => a.Endpoint)
|
builder.HasOne(a => a.Endpoint)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(a => a.EndpointId);
|
.HasForeignKey(a => a.EndpointId);
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Database.Configuration;
|
||||||
|
|
||||||
|
public class UserStreamKeyConfiguration : IEntityTypeConfiguration<UserStreamKey>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UserStreamKey> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
|
||||||
|
builder.Property(a => a.Key)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Created)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expires)
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.UserStream)
|
||||||
|
.WithOne(a => a.StreamKey)
|
||||||
|
.HasPrincipalKey<UserStream>(a => a.Id)
|
||||||
|
.HasForeignKey<UserStreamKey>(a => a.StreamId);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany(a => a.StreamKeys)
|
||||||
|
.HasForeignKey(a => a.UserPubkey)
|
||||||
|
.HasPrincipalKey(a => a.PubKey);
|
||||||
|
}
|
||||||
|
}
|
@ -37,4 +37,6 @@ public class StreamerContext : DbContext
|
|||||||
public DbSet<PushSubscription> PushSubscriptions => Set<PushSubscription>();
|
public DbSet<PushSubscription> PushSubscriptions => Set<PushSubscription>();
|
||||||
|
|
||||||
public DbSet<PushSubscriptionTarget> PushSubscriptionTargets => Set<PushSubscriptionTarget>();
|
public DbSet<PushSubscriptionTarget> PushSubscriptionTargets => Set<PushSubscriptionTarget>();
|
||||||
|
|
||||||
|
public DbSet<UserStreamKey> StreamKeys => Set<UserStreamKey>();
|
||||||
}
|
}
|
||||||
|
@ -67,4 +67,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();
|
public List<UserStreamForwards> Forwards { get; init; } = new();
|
||||||
}
|
public List<UserStreamKey> StreamKeys { get; init; } = new();
|
||||||
|
}
|
@ -7,14 +7,42 @@ public class UserStream
|
|||||||
public string PubKey { get; init; } = null!;
|
public string PubKey { get; init; } = null!;
|
||||||
public User User { get; init; } = null!;
|
public User User { get; init; } = null!;
|
||||||
|
|
||||||
public string StreamId { get; init; } = null!;
|
|
||||||
|
|
||||||
public DateTime Starts { get; init; } = DateTime.UtcNow;
|
public DateTime Starts { get; init; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public DateTime? Ends { get; set; }
|
public DateTime? Ends { get; set; }
|
||||||
|
|
||||||
public UserStreamState State { get; set; }
|
public UserStreamState State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream title
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream summary
|
||||||
|
/// </summary>
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream cover image
|
||||||
|
/// </summary>
|
||||||
|
public string? Image { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comma seperated tags
|
||||||
|
/// </summary>
|
||||||
|
public string? Tags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any content warning tag (NIP-36)
|
||||||
|
/// </summary>
|
||||||
|
public string? ContentWarning { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream goal
|
||||||
|
/// </summary>
|
||||||
|
public string? Goal { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Nostr Event for this stream
|
/// Nostr Event for this stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -25,7 +53,7 @@ public class UserStream
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Thumbnail { get; set; }
|
public string? Thumbnail { get; set; }
|
||||||
|
|
||||||
public Guid EndpointId { get; init; }
|
public Guid EndpointId { get; set; }
|
||||||
public IngestEndpoint Endpoint { get; init; } = null!;
|
public IngestEndpoint Endpoint { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,10 +86,15 @@ public class UserStream
|
|||||||
public List<UserStreamGuest> Guests { get; init; } = new();
|
public List<UserStreamGuest> Guests { get; init; } = new();
|
||||||
|
|
||||||
public List<UserStreamRecording> Recordings { get; init; } = new();
|
public List<UserStreamRecording> Recordings { get; init; } = new();
|
||||||
|
|
||||||
|
public UserStreamKey? StreamKey { get; init; }
|
||||||
|
|
||||||
|
public string Key => StreamKey?.Key ?? User.StreamKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UserStreamState
|
public enum UserStreamState
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
Planned = 1,
|
Planned = 1,
|
||||||
Live = 2,
|
Live = 2,
|
||||||
Ended = 3
|
Ended = 3
|
||||||
|
25
NostrStreamer/Database/UserStreamKey.cs
Normal file
25
NostrStreamer/Database/UserStreamKey.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace NostrStreamer.Database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Single use stream keys
|
||||||
|
/// </summary>
|
||||||
|
public class UserStreamKey
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string UserPubkey { get; init; } = null!;
|
||||||
|
public User User { get; init; } = null!;
|
||||||
|
|
||||||
|
public string Key { get; init; } = null!;
|
||||||
|
|
||||||
|
public DateTime Created { get; init; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expiry of the key when it can no longer be used
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? Expires { get; init; }
|
||||||
|
|
||||||
|
|
||||||
|
public Guid StreamId { get; init; }
|
||||||
|
public UserStream UserStream { get; init; } = null!;
|
||||||
|
}
|
@ -3,6 +3,7 @@ using Amazon.Runtime;
|
|||||||
using Amazon.S3;
|
using Amazon.S3;
|
||||||
using Igdb;
|
using Igdb;
|
||||||
using MaxMind.GeoIP2;
|
using MaxMind.GeoIP2;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nostr.Client.Identifiers;
|
using Nostr.Client.Identifiers;
|
||||||
using Nostr.Client.Json;
|
using Nostr.Client.Json;
|
||||||
@ -49,10 +50,11 @@ public static class Extensions
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] SplitTags(this User user)
|
public static string[] SplitTags(this UserStream stream)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(user.Tags) ?
|
return !string.IsNullOrEmpty(stream.Tags)
|
||||||
user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty<string>();
|
? stream.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
: Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (double lat, double lon)? GetLocation(this HttpContext ctx, IGeoIP2DatabaseReader db)
|
public static (double lat, double lon)? GetLocation(this HttpContext ctx, IGeoIP2DatabaseReader db)
|
||||||
@ -90,14 +92,16 @@ public static class Extensions
|
|||||||
var num1 = longitude * (Math.PI / 180.0);
|
var num1 = longitude * (Math.PI / 180.0);
|
||||||
var d2 = otherLatitude * (Math.PI / 180.0);
|
var d2 = otherLatitude * (Math.PI / 180.0);
|
||||||
var num2 = otherLongitude * (Math.PI / 180.0) - num1;
|
var num2 = otherLongitude * (Math.PI / 180.0) - num1;
|
||||||
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);
|
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) +
|
||||||
|
Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);
|
||||||
|
|
||||||
return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
|
return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetHost(this NostrEvent ev)
|
public static string GetHost(this NostrEvent ev)
|
||||||
{
|
{
|
||||||
var hostTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "p" && a.AdditionalData[2] == "host")?.AdditionalData[0];
|
var hostTag = ev.Tags!.FirstOrDefault(a => a.TagIdentifier == "p" && a.AdditionalData[2] == "host")
|
||||||
|
?.AdditionalData[0];
|
||||||
if (!string.IsNullOrEmpty(hostTag))
|
if (!string.IsNullOrEmpty(hostTag))
|
||||||
{
|
{
|
||||||
return hostTag;
|
return hostTag;
|
||||||
@ -105,7 +109,7 @@ public static class Extensions
|
|||||||
|
|
||||||
return ev.Pubkey!;
|
return ev.Pubkey!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NostrIdentifier ToIdentifier(this NostrEvent ev)
|
public static NostrIdentifier ToIdentifier(this NostrEvent ev)
|
||||||
{
|
{
|
||||||
if ((long)ev.Kind is >= 30_000 and < 40_000)
|
if ((long)ev.Kind is >= 30_000 and < 40_000)
|
||||||
@ -132,6 +136,33 @@ public static class Extensions
|
|||||||
Genres = a.Genres.Select(b => b.Name).ToList()
|
Genres = a.Genres.Select(b => b.Name).ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task CopyLastStreamDetails(this UserStream stream, StreamerContext db)
|
||||||
|
{
|
||||||
|
var lastStream = await db.Streams
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(a => a.PubKey == stream.PubKey)
|
||||||
|
.OrderByDescending(a => a.Starts)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
|
||||||
|
stream.Title = lastStream?.Title;
|
||||||
|
stream.Summary = lastStream?.Summary;
|
||||||
|
stream.Image = lastStream?.Image;
|
||||||
|
stream.ContentWarning = lastStream?.ContentWarning;
|
||||||
|
stream.Tags = lastStream?.Tags;
|
||||||
|
stream.Goal = lastStream?.Goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PatchStream(this UserStream stream, PatchEvent ev)
|
||||||
|
{
|
||||||
|
stream.Title = ev.Title;
|
||||||
|
stream.Summary = ev.Summary;
|
||||||
|
stream.Image = ev.Image;
|
||||||
|
stream.ContentWarning = ev.ContentWarning;
|
||||||
|
stream.Tags = ev.Tags.Length > 0 ? string.Join(',', ev.Tags) : null;
|
||||||
|
stream.Goal = ev.Goal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Variant
|
public class Variant
|
||||||
@ -161,7 +192,8 @@ public class Variant
|
|||||||
}
|
}
|
||||||
|
|
||||||
var strSplit = str.Split(":");
|
var strSplit = str.Split(":");
|
||||||
if (strSplit.Length != 3 || !int.TryParse(strSplit[1][..^1], out var h) || !int.TryParse(strSplit[2], out var b))
|
if (strSplit.Length != 3 || !int.TryParse(strSplit[1][..^1], out var h) ||
|
||||||
|
!int.TryParse(strSplit[2], out var b))
|
||||||
{
|
{
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
@ -203,4 +235,4 @@ public class Variant
|
|||||||
|
|
||||||
return $"BANDWIDTH={Bandwidth * 1000}";
|
return $"BANDWIDTH={Bandwidth * 1000}";
|
||||||
}
|
}
|
||||||
}
|
}
|
558
NostrStreamer/Migrations/20240821112332_StreamKeys.Designer.cs
generated
Normal file
558
NostrStreamer/Migrations/20240821112332_StreamKeys.Designer.cs
generated
Normal file
@ -0,0 +1,558 @@
|
|||||||
|
// <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("20240821112332_StreamKeys")]
|
||||||
|
partial class StreamKeys
|
||||||
|
{
|
||||||
|
/// <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<decimal>("Fee")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
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.PushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Endpoint")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUsed")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Pubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.PushSubscriptionTarget", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("SubscriberPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("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.StreamTickets", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("Token")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("StreamTickets");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<decimal?>("AdmissionCost")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
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<decimal>("Length")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal>("MilliSatsCollected")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
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.UserStreamClip", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("TakenByPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("Clips");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("Key")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("StreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StreamId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("StreamKeys");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.StreamTickets", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserStreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("NostrStreamer.Database.UserStreamKey", "StreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("StreamKeys")
|
||||||
|
.HasForeignKey("UserPubkey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("StreamKeys");
|
||||||
|
|
||||||
|
b.Navigation("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Guests");
|
||||||
|
|
||||||
|
b.Navigation("Recordings");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
NostrStreamer/Migrations/20240821112332_StreamKeys.cs
Normal file
61
NostrStreamer/Migrations/20240821112332_StreamKeys.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NostrStreamer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class StreamKeys : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "StreamKeys",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserPubkey = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Key = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
StreamId = table.Column<Guid>(type: "uuid", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_StreamKeys", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_StreamKeys_Streams_StreamId",
|
||||||
|
column: x => x.StreamId,
|
||||||
|
principalTable: "Streams",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_StreamKeys_Users_UserPubkey",
|
||||||
|
column: x => x.UserPubkey,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "PubKey",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StreamKeys_StreamId",
|
||||||
|
table: "StreamKeys",
|
||||||
|
column: "StreamId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StreamKeys_UserPubkey",
|
||||||
|
table: "StreamKeys",
|
||||||
|
column: "UserPubkey");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "StreamKeys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
557
NostrStreamer/Migrations/20240821132917_MigrateStreamInfo.Designer.cs
generated
Normal file
557
NostrStreamer/Migrations/20240821132917_MigrateStreamInfo.Designer.cs
generated
Normal file
@ -0,0 +1,557 @@
|
|||||||
|
// <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("20240821132917_MigrateStreamInfo")]
|
||||||
|
partial class MigrateStreamInfo
|
||||||
|
{
|
||||||
|
/// <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<decimal>("Fee")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
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.PushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Endpoint")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUsed")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Pubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.PushSubscriptionTarget", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("SubscriberPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("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.StreamTickets", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("Token")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("StreamTickets");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("PubKey")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("Balance")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("StreamKey")
|
||||||
|
.IsRequired()
|
||||||
|
.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<decimal?>("AdmissionCost")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("ContentWarning")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
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<string>("Goal")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Image")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSegment")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Length")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal>("MilliSatsCollected")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
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>("Summary")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Thumbnail")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EndpointId");
|
||||||
|
|
||||||
|
b.HasIndex("PubKey");
|
||||||
|
|
||||||
|
b.ToTable("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamClip", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("TakenByPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserStreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserStreamId");
|
||||||
|
|
||||||
|
b.ToTable("Clips");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("StreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StreamId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("StreamKeys");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.StreamTickets", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserStreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithOne("StreamKey")
|
||||||
|
.HasForeignKey("NostrStreamer.Database.UserStreamKey", "StreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("StreamKeys")
|
||||||
|
.HasForeignKey("UserPubkey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("StreamKeys");
|
||||||
|
|
||||||
|
b.Navigation("Streams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Guests");
|
||||||
|
|
||||||
|
b.Navigation("Recordings");
|
||||||
|
|
||||||
|
b.Navigation("StreamKey");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
NostrStreamer/Migrations/20240821132917_MigrateStreamInfo.cs
Normal file
149
NostrStreamer/Migrations/20240821132917_MigrateStreamInfo.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NostrStreamer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class MigrateStreamInfo : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ContentWarning",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Goal",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Image",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Tags",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Title",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StreamId",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ContentWarning",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Goal",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Image",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Tags",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Title",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ContentWarning",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Goal",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Image",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Tags",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Title",
|
||||||
|
table: "Streams");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ContentWarning",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Goal",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Image",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Tags",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Title",
|
||||||
|
table: "Users",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "StreamId",
|
||||||
|
table: "Streams",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -238,6 +238,9 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Property<decimal?>("AdmissionCost")
|
b.Property<decimal?>("AdmissionCost")
|
||||||
.HasColumnType("numeric");
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("ContentWarning")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("EdgeIp")
|
b.Property<string>("EdgeIp")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
@ -256,6 +259,12 @@ namespace NostrStreamer.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Goal")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Image")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<DateTime>("LastSegment")
|
b.Property<DateTime>("LastSegment")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
@ -275,13 +284,18 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Property<int>("State")
|
b.Property<int>("State")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<string>("StreamId")
|
b.Property<string>("Summary")
|
||||||
.IsRequired()
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("Thumbnail")
|
b.Property<string>("Thumbnail")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("EndpointId");
|
b.HasIndex("EndpointId");
|
||||||
@ -376,6 +390,39 @@ namespace NostrStreamer.Migrations
|
|||||||
b.ToTable("Guests");
|
b.ToTable("Guests");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("StreamId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("UserPubkey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StreamId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("UserPubkey");
|
||||||
|
|
||||||
|
b.ToTable("StreamKeys");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -476,6 +523,25 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Navigation("Stream");
|
b.Navigation("Stream");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NostrStreamer.Database.UserStream", "UserStream")
|
||||||
|
.WithOne("StreamKey")
|
||||||
|
.HasForeignKey("NostrStreamer.Database.UserStreamKey", "StreamId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NostrStreamer.Database.User", "User")
|
||||||
|
.WithMany("StreamKeys")
|
||||||
|
.HasForeignKey("UserPubkey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
|
||||||
|
b.Navigation("UserStream");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||||
@ -493,6 +559,8 @@ namespace NostrStreamer.Migrations
|
|||||||
|
|
||||||
b.Navigation("Payments");
|
b.Navigation("Payments");
|
||||||
|
|
||||||
|
b.Navigation("StreamKeys");
|
||||||
|
|
||||||
b.Navigation("Streams");
|
b.Navigation("Streams");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -501,6 +569,8 @@ namespace NostrStreamer.Migrations
|
|||||||
b.Navigation("Guests");
|
b.Navigation("Guests");
|
||||||
|
|
||||||
b.Navigation("Recordings");
|
b.Navigation("Recordings");
|
||||||
|
|
||||||
|
b.Navigation("StreamKey");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ public class LndInvoicesStream : BackgroundService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Subscribe invoices failed");
|
//_logger.LogError(ex, "Subscribe invoices failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
||||||
|
@ -22,7 +22,7 @@ public class StreamEventBuilder
|
|||||||
_nostrClient = nostrClient;
|
_nostrClient = nostrClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NostrEvent CreateStreamEvent(User user, UserStream stream)
|
public NostrEvent CreateStreamEvent(UserStream stream)
|
||||||
{
|
{
|
||||||
var status = stream.State switch
|
var status = stream.State switch
|
||||||
{
|
{
|
||||||
@ -35,11 +35,11 @@ public class StreamEventBuilder
|
|||||||
var tags = new List<NostrEventTag>
|
var tags = new List<NostrEventTag>
|
||||||
{
|
{
|
||||||
new("d", stream.Id.ToString()),
|
new("d", stream.Id.ToString()),
|
||||||
new("title", user.Title ?? ""),
|
new("title", stream.Title ?? ""),
|
||||||
new("summary", user.Summary ?? ""),
|
new("summary", stream.Summary ?? ""),
|
||||||
new("image", stream.Thumbnail ?? user.Image ?? ""),
|
new("image", stream.Thumbnail ?? stream.Image ?? ""),
|
||||||
new("status", status),
|
new("status", status),
|
||||||
new("p", user.PubKey, "", "host"),
|
new("p", stream.PubKey, "wss://relay.zap.stream", "host"),
|
||||||
new("relays", _config.Relays),
|
new("relays", _config.Relays),
|
||||||
new("starts", new DateTimeOffset(stream.Starts).ToUnixTimeSeconds().ToString()),
|
new("starts", new DateTimeOffset(stream.Starts).ToUnixTimeSeconds().ToString()),
|
||||||
new("service", new Uri(_config.ApiHost, "/api/nostr").ToString())
|
new("service", new Uri(_config.ApiHost, "/api/nostr").ToString())
|
||||||
@ -51,9 +51,9 @@ public class StreamEventBuilder
|
|||||||
tags.Add(new("streaming", new Uri(_config.DataHost, $"stream/{stream.Id}.m3u8").ToString()));
|
tags.Add(new("streaming", new Uri(_config.DataHost, $"stream/{stream.Id}.m3u8").ToString()));
|
||||||
tags.Add(new("current_participants", viewers.ToString()));
|
tags.Add(new("current_participants", viewers.ToString()));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(user.ContentWarning))
|
if (!string.IsNullOrEmpty(stream.ContentWarning))
|
||||||
{
|
{
|
||||||
tags.Add(new("content-warning", user.ContentWarning));
|
tags.Add(new("content-warning", stream.ContentWarning));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (status == "ended")
|
else if (status == "ended")
|
||||||
@ -70,14 +70,14 @@ public class StreamEventBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var tag in user.SplitTags())
|
foreach (var tag in stream.SplitTags())
|
||||||
{
|
{
|
||||||
tags.Add(new("t", tag));
|
tags.Add(new("t", tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(user.Goal))
|
if (!string.IsNullOrEmpty(stream.Goal))
|
||||||
{
|
{
|
||||||
tags.Add(new("goal", user.Goal));
|
tags.Add(new("goal", stream.Goal));
|
||||||
}
|
}
|
||||||
|
|
||||||
var ev = new NostrEvent
|
var ev = new NostrEvent
|
||||||
|
@ -57,7 +57,7 @@ public class NostrStreamManager : IStreamManager
|
|||||||
TestCanStream();
|
TestCanStream();
|
||||||
var fwds = 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.StreamKey}?vhost={_context.UserStream.Endpoint.Forward}"
|
||||||
};
|
};
|
||||||
|
|
||||||
var dataProtector = _dataProtectionProvider.CreateProtector("forward-targets");
|
var dataProtector = _dataProtectionProvider.CreateProtector("forward-targets");
|
||||||
@ -103,7 +103,6 @@ public class NostrStreamManager : IStreamManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StreamStopped()
|
public async Task StreamStopped()
|
||||||
@ -194,20 +193,27 @@ public class NostrStreamManager : IStreamManager
|
|||||||
{
|
{
|
||||||
//var matches = new Regex("\\.(\\d+)\\.[\\w]{2,4}$").Match(segment.AbsolutePath);
|
//var matches = new Regex("\\.(\\d+)\\.[\\w]{2,4}$").Match(segment.AbsolutePath);
|
||||||
|
|
||||||
if (_context.UserStream.Endpoint.Capabilities.Contains("dvr:source"))
|
try
|
||||||
{
|
{
|
||||||
var result = await _dvrStore.UploadRecording(_context.UserStream, segment);
|
if (_context.UserStream.Endpoint.Capabilities.Contains("dvr:source"))
|
||||||
_context.Db.Recordings.Add(new()
|
|
||||||
{
|
{
|
||||||
Id = result.Id,
|
var result = await _dvrStore.UploadRecording(_context.UserStream, segment);
|
||||||
UserStreamId = _context.UserStream.Id,
|
_context.Db.Recordings.Add(new()
|
||||||
Url = result.Result.ToString(),
|
{
|
||||||
Duration = result.Duration,
|
Id = result.Id,
|
||||||
Timestamp = DateTime
|
UserStreamId = _context.UserStream.Id,
|
||||||
.UtcNow //DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(matches.Groups[1].Value)).UtcDateTime
|
Url = result.Result.ToString(),
|
||||||
});
|
Duration = result.Duration,
|
||||||
|
Timestamp = DateTime
|
||||||
|
.UtcNow //DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(matches.Groups[1].Value)).UtcDateTime
|
||||||
|
});
|
||||||
|
|
||||||
await _context.Db.SaveChangesAsync();
|
await _context.Db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Failed to save recording segment {}, {}", segment, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _context.Db.Streams
|
await _context.Db.Streams
|
||||||
@ -242,7 +248,7 @@ public class NostrStreamManager : IStreamManager
|
|||||||
var existingEvent = _context.UserStream.GetEvent();
|
var existingEvent = _context.UserStream.GetEvent();
|
||||||
var oldViewers = existingEvent?.Tags?.FindFirstTagValue("current_participants");
|
var oldViewers = existingEvent?.Tags?.FindFirstTagValue("current_participants");
|
||||||
|
|
||||||
var newEvent = _eventBuilder.CreateStreamEvent(_context.User, _context.UserStream);
|
var newEvent = _eventBuilder.CreateStreamEvent(_context.UserStream);
|
||||||
var newViewers = newEvent?.Tags?.FindFirstTagValue("current_participants");
|
var newViewers = newEvent?.Tags?.FindFirstTagValue("current_participants");
|
||||||
|
|
||||||
if (newEvent != default && int.TryParse(oldViewers, out var a) && int.TryParse(newViewers, out var b) && a != b)
|
if (newEvent != default && int.TryParse(oldViewers, out var a) && int.TryParse(newViewers, out var b) && a != b)
|
||||||
@ -260,7 +266,7 @@ public class NostrStreamManager : IStreamManager
|
|||||||
DateTime? ends = state == UserStreamState.Ended ? DateTime.UtcNow : null;
|
DateTime? ends = state == UserStreamState.Ended ? DateTime.UtcNow : null;
|
||||||
_context.UserStream.State = state;
|
_context.UserStream.State = state;
|
||||||
_context.UserStream.Ends = ends;
|
_context.UserStream.Ends = ends;
|
||||||
var ev = _eventBuilder.CreateStreamEvent(_context.User, _context.UserStream);
|
var ev = _eventBuilder.CreateStreamEvent(_context.UserStream);
|
||||||
|
|
||||||
await _context.Db.Streams.Where(a => a.Id == _context.UserStream.Id)
|
await _context.Db.Streams.Where(a => a.Id == _context.UserStream.Id)
|
||||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.State, state)
|
.ExecuteUpdateAsync(o => o.SetProperty(v => v.State, state)
|
||||||
|
@ -9,4 +9,5 @@ public class StreamManagerContext
|
|||||||
public User User => UserStream.User;
|
public User User => UserStream.User;
|
||||||
public StreamInfo? StreamInfo { get; init; }
|
public StreamInfo? StreamInfo { get; init; }
|
||||||
public SrsApi EdgeApi { get; init; } = null!;
|
public SrsApi EdgeApi { get; init; } = null!;
|
||||||
|
public string StreamKey { get; init; } = null!;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,9 @@ public class StreamManagerFactory
|
|||||||
var user = await _db.Users
|
var user = await _db.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(a => a.Forwards)
|
.Include(a => a.Forwards)
|
||||||
.SingleOrDefaultAsync(a => a.StreamKey.Equals(info.StreamKey));
|
.Include(user => user.StreamKeys)
|
||||||
|
.SingleOrDefaultAsync(a =>
|
||||||
|
a.StreamKey.Equals(info.StreamKey) || a.StreamKeys.Any(b => b.Key == info.StreamKey));
|
||||||
|
|
||||||
if (user == default) throw new Exception("No user found");
|
if (user == default) throw new Exception("No user found");
|
||||||
|
|
||||||
@ -53,24 +55,28 @@ public class StreamManagerFactory
|
|||||||
throw new Exception("User account blocked");
|
throw new Exception("User account blocked");
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingLive = await _db.Streams
|
var singleUseKey = user.StreamKeys.FirstOrDefault(a => a.Key == info.StreamKey);
|
||||||
.SingleOrDefaultAsync(a => a.State == UserStreamState.Live && a.PubKey == user.PubKey);
|
|
||||||
|
var existingLive = singleUseKey != default
|
||||||
|
? await _db.Streams.SingleOrDefaultAsync(a => a.Id == singleUseKey.StreamId)
|
||||||
|
: await _db.Streams
|
||||||
|
.SingleOrDefaultAsync(a => a.State == UserStreamState.Live && a.PubKey == user.PubKey);
|
||||||
|
|
||||||
var stream = existingLive ?? new UserStream
|
var stream = existingLive ?? new UserStream
|
||||||
{
|
{
|
||||||
EndpointId = ep.Id,
|
EndpointId = ep.Id,
|
||||||
PubKey = user.PubKey,
|
PubKey = user.PubKey,
|
||||||
StreamId = "",
|
|
||||||
State = UserStreamState.Live,
|
State = UserStreamState.Live,
|
||||||
EdgeIp = info.EdgeIp,
|
EdgeIp = info.EdgeIp,
|
||||||
ForwardClientId = info.ClientId
|
ForwardClientId = info.ClientId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// add new stream
|
// add new stream
|
||||||
if (existingLive == default)
|
if (existingLive == default)
|
||||||
{
|
{
|
||||||
var ev = _eventBuilder.CreateStreamEvent(user, stream);
|
await stream.CopyLastStreamDetails(_db);
|
||||||
stream.Event = JsonConvert.SerializeObject(ev, NostrSerializer.Settings);
|
var ev = _eventBuilder.CreateStreamEvent(stream);
|
||||||
|
stream.Event = NostrJson.Serialize(ev) ?? "";
|
||||||
_db.Streams.Add(stream);
|
_db.Streams.Add(stream);
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
@ -85,18 +91,19 @@ public class StreamManagerFactory
|
|||||||
var ctx = new StreamManagerContext
|
var ctx = new StreamManagerContext
|
||||||
{
|
{
|
||||||
Db = _db,
|
Db = _db,
|
||||||
|
StreamKey = info.StreamKey,
|
||||||
UserStream = new()
|
UserStream = new()
|
||||||
{
|
{
|
||||||
Id = stream.Id,
|
Id = stream.Id,
|
||||||
PubKey = stream.PubKey,
|
PubKey = stream.PubKey,
|
||||||
StreamId = stream.StreamId,
|
|
||||||
State = stream.State,
|
State = stream.State,
|
||||||
EdgeIp = stream.EdgeIp,
|
EdgeIp = stream.EdgeIp,
|
||||||
ForwardClientId = stream.ForwardClientId,
|
ForwardClientId = stream.ForwardClientId,
|
||||||
Endpoint = ep,
|
Endpoint = ep,
|
||||||
User = user
|
User = user
|
||||||
},
|
},
|
||||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(),
|
||||||
|
new Uri($"http://{stream.EdgeIp}:1985"))
|
||||||
};
|
};
|
||||||
|
|
||||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||||
@ -108,6 +115,7 @@ public class StreamManagerFactory
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(a => a.User)
|
.Include(a => a.User)
|
||||||
.Include(a => a.Endpoint)
|
.Include(a => a.Endpoint)
|
||||||
|
.Include(a => a.StreamKey)
|
||||||
.FirstOrDefaultAsync(a => a.Id == id);
|
.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
|
||||||
if (stream == default) throw new Exception("No live stream");
|
if (stream == default) throw new Exception("No live stream");
|
||||||
@ -115,8 +123,10 @@ public class StreamManagerFactory
|
|||||||
var ctx = new StreamManagerContext
|
var ctx = new StreamManagerContext
|
||||||
{
|
{
|
||||||
Db = _db,
|
Db = _db,
|
||||||
|
StreamKey = stream.StreamKey?.Key ?? stream.User.StreamKey,
|
||||||
UserStream = stream,
|
UserStream = stream,
|
||||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(),
|
||||||
|
new Uri($"http://{stream.EdgeIp}:1985"))
|
||||||
};
|
};
|
||||||
|
|
||||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||||
@ -128,6 +138,7 @@ public class StreamManagerFactory
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(a => a.User)
|
.Include(a => a.User)
|
||||||
.Include(a => a.Endpoint)
|
.Include(a => a.Endpoint)
|
||||||
|
.Include(a => a.StreamKey)
|
||||||
.FirstOrDefaultAsync(a => a.PubKey.Equals(pubkey) && a.State == UserStreamState.Live);
|
.FirstOrDefaultAsync(a => a.PubKey.Equals(pubkey) && a.State == UserStreamState.Live);
|
||||||
|
|
||||||
if (stream == default) throw new Exception("No live stream");
|
if (stream == default) throw new Exception("No live stream");
|
||||||
@ -135,8 +146,10 @@ public class StreamManagerFactory
|
|||||||
var ctx = new StreamManagerContext
|
var ctx = new StreamManagerContext
|
||||||
{
|
{
|
||||||
Db = _db,
|
Db = _db,
|
||||||
|
StreamKey = stream.StreamKey?.Key ?? stream.User.StreamKey,
|
||||||
UserStream = stream,
|
UserStream = stream,
|
||||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(),
|
||||||
|
new Uri($"http://{stream.EdgeIp}:1985"))
|
||||||
};
|
};
|
||||||
|
|
||||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||||
@ -148,11 +161,13 @@ public class StreamManagerFactory
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(a => a.User)
|
.Include(a => a.User)
|
||||||
.Include(a => a.Endpoint)
|
.Include(a => a.Endpoint)
|
||||||
|
.Include(a => a.StreamKey)
|
||||||
.OrderByDescending(a => a.Starts)
|
.OrderByDescending(a => a.Starts)
|
||||||
.FirstOrDefaultAsync(a =>
|
.FirstOrDefaultAsync(a =>
|
||||||
a.User.StreamKey.Equals(info.StreamKey) &&
|
(a.StreamKey != default && a.StreamKey.Key == info.StreamKey) ||
|
||||||
a.Endpoint.App.Equals(info.App) &&
|
(a.User.StreamKey.Equals(info.StreamKey) &&
|
||||||
a.State == UserStreamState.Live);
|
a.Endpoint.App.Equals(info.App) &&
|
||||||
|
a.State == UserStreamState.Live));
|
||||||
|
|
||||||
if (stream == default)
|
if (stream == default)
|
||||||
{
|
{
|
||||||
@ -162,11 +177,13 @@ public class StreamManagerFactory
|
|||||||
var ctx = new StreamManagerContext
|
var ctx = new StreamManagerContext
|
||||||
{
|
{
|
||||||
Db = _db,
|
Db = _db,
|
||||||
|
StreamKey = info.StreamKey,
|
||||||
UserStream = stream,
|
UserStream = stream,
|
||||||
StreamInfo = info,
|
StreamInfo = info,
|
||||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(),
|
||||||
|
new Uri($"http://{stream.EdgeIp}:1985"))
|
||||||
};
|
};
|
||||||
|
|
||||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -254,8 +254,8 @@ public class UserService
|
|||||||
|
|
||||||
public async Task UpdateStreamInfo(string pubkey, PatchEvent req)
|
public async Task UpdateStreamInfo(string pubkey, PatchEvent req)
|
||||||
{
|
{
|
||||||
await _db.Users
|
await _db.Streams
|
||||||
.Where(a => a.PubKey == pubkey)
|
.Where(a => a.Id == req.Id && a.PubKey == pubkey)
|
||||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, req.Title)
|
.ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, req.Title)
|
||||||
.SetProperty(v => v.Summary, req.Summary)
|
.SetProperty(v => v.Summary, req.Summary)
|
||||||
.SetProperty(v => v.Image, req.Image)
|
.SetProperty(v => v.Image, req.Image)
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning",
|
"Microsoft.AspNetCore": "Warning",
|
||||||
"Microsoft.EntityFrameworkCore": "Warning",
|
"Microsoft.EntityFrameworkCore": "Warning",
|
||||||
"System.Net.Http.HttpClient": "Error"
|
"System.Net.Http.HttpClient": "Error",
|
||||||
|
"NostrStreamer.NostrAuthHandler": "Error",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
@ -35,7 +36,7 @@
|
|||||||
"PublicHost": "http://localhost:9000"
|
"PublicHost": "http://localhost:9000"
|
||||||
},
|
},
|
||||||
"SnortApi": "https://api.snort.social",
|
"SnortApi": "https://api.snort.social",
|
||||||
"GeoIpDatabase": "C:\\Users\\Kieran\\Downloads\\GeoLite2-City.mmdb",
|
"GeoIpDatabase": "/home/kieran/Downloads/GeoLite2-City.mmdb",
|
||||||
"Edges": [
|
"Edges": [
|
||||||
{
|
{
|
||||||
"Name": "US0",
|
"Name": "US0",
|
||||||
|
@ -118,6 +118,6 @@ vhost full.in.zap.stream {
|
|||||||
vhost __defaultVhost__ {
|
vhost __defaultVhost__ {
|
||||||
forward {
|
forward {
|
||||||
enabled on;
|
enabled on;
|
||||||
backend http://host.docker.internal:5295/api/srs;
|
backend http://172.17.0.1:5295/api/srs;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,10 +32,10 @@ vhost hls.zap.stream {
|
|||||||
|
|
||||||
http_hooks {
|
http_hooks {
|
||||||
enabled on;
|
enabled on;
|
||||||
on_publish http://host.docker.internal:5295/api/srs;
|
on_publish http://172.17.0.1:5295/api/srs;
|
||||||
on_unpublish http://host.docker.internal:5295/api/srs;
|
on_unpublish http://172.17.0.1:5295/api/srs;
|
||||||
on_hls http://host.docker.internal:5295/api/srs;
|
on_hls http://172.17.0.1:5295/api/srs;
|
||||||
on_dvr http://host.docker.internal:5295/api/srs;
|
on_dvr http://172.17.0.1:5295/api/srs;
|
||||||
}
|
}
|
||||||
|
|
||||||
dvr {
|
dvr {
|
||||||
|
Reference in New Issue
Block a user